major remake. featuring globbing in filenames, header scanning, automatic dependency...
[rofl0r-rcb.git] / rcb.pl
blob1d6b33d5f8693ef7202b15bfab5067e698e855f3
1 #!/usr/bin/env perl
3 use strict;
4 use warnings;
5 use File::Basename;
6 use Cwd 'abs_path';
7 #use Data::Dump qw(dump);
9 sub syntax {
10 die "syntax: $0 [--force --verbose] mainfile.c [-lc -lm -lncurses]\n--force will force a complete rebuild despite object file presence.";
13 sub expandarr {
14 my $res = "";
15 while(@_) {
16 my $x = shift;
17 chomp($x);
18 $res .= "$x ";
20 return $res;
23 sub expandhash {
24 my $res = "";
25 my $h = shift;
26 for my $x(keys %$h) {
27 chomp($x);
28 $res .= "$x ";
30 return $res;
33 sub name_wo_ext {
34 my $x = shift;
35 my $l = length($x);
36 $l-- while($l && substr($x, $l, 1) ne ".");
37 return substr($x, 0, $l + 1) if($l);
38 return "";
41 my $colors = {
42 "default" => 98,
43 "white" => 97,
44 "cyan" => 96,
45 "magenta" => 95,
46 "blue" => 94,
47 "yellow" => 93,
48 "green" => 92,
49 "red" => 91,
50 "gray" => 90,
51 "end" => 0
53 my $colstr = "\033[%dm";
55 my %hdep;
56 my @adep;
58 sub printc {
59 my $color = shift;
60 printf $colstr, $colors->{$color};
61 for my $x(@_) {
62 print $x;
64 printf $colstr, $colors->{"end"};
67 sub scandep_doit {
68 my ($self, $nf) = @_;
69 my $np = dirname($nf);
70 my $nb = basename($nf);
71 if(!defined($hdep{$nf})) {
72 if(! -e $nf) {
73 printc("red", "failed to find dependency $nf referenced from $self!\n");
74 die unless $nf =~ /\.h$/;
75 } else {
76 scanfile($np, $nb);
81 sub scandep {
82 my ($self, $path, $tf) = @_;
83 my $absolute = substr($tf, 0, 1) eq "/";
84 my $nf = $absolute ? $tf : abs_path($path . "/" . $tf);
85 die "problem processing $self, $path, $tf" if(!defined($nf));
86 if($nf =~ /\*/) {
87 my @deps = glob($nf);
88 for my $d(@deps) {
89 scandep_doit($self, $d);
91 } else {
92 scandep_doit($self, $nf);
96 sub scanfile {
97 my ($path, $file) = @_;
98 my $fp;
99 my $self = $path . "/" . $file;
100 my $tf = "";
102 $hdep{$self} = 1;
103 open($fp, "<", $self) or die "could not open file $self: $!";
104 while(<$fp>) {
105 my $line = $_;
106 if ($line =~ /^\/\/RcB: (\w{3,6}) \"(.+?)\"/) {
107 my $command = $1;
108 my $arg = $2;
109 if($command eq "DEP") {
110 $tf = $arg;
111 scandep($self, $path, $tf);
113 } elsif($line =~ /^\s*#\s*include\s+\"([\w\.\/_\-]+?)\"/) {
114 $tf = $1;
115 scandep($self, $path, $tf);
116 } else {
118 $tf = "x";
121 close $fp;
122 push @adep, $self if $file =~ /[\w_-]+\.[c]{1}$/; #only add .c files to deps...
125 my $forcerebuild = 0;
126 my $verbose = 0;
127 my $mainfile = undef;
128 argscan:
129 my $arg1 = shift @ARGV or syntax;
130 if($arg1 eq "--force") {
131 $forcerebuild = 1;
132 goto argscan;
133 } elsif($arg1 eq "--verbose") {
134 $verbose = 1;
135 goto argscan;
136 } else {
137 $mainfile = $arg1;
139 $mainfile = shift unless defined($mainfile);
140 syntax unless defined($mainfile);
142 my $cc;
143 if (defined($ENV{CC})) {
144 $cc = $ENV{CC};
145 } else {
146 $cc = "cc";
147 printc "blue", "[RcB] \$CC not set, defaulting to cc\n";
149 my $cflags = defined($ENV{CFLAGS}) ? $ENV{CFLAGS} : "";
150 my $nm;
151 if (defined($ENV{NM})) {
152 $nm = $ENV{NM};
153 } else {
154 $nm = "nm";
155 printc "blue", "[RcB] \$NM not set, defaulting to nm\n";
158 sub compile {
159 my ($cmdline) = @_;
160 printc "magenta", "[CC] ", $cmdline, "\n";
161 my $reslt = `$cmdline 2>&1`;
162 print $reslt;
163 return $reslt;
166 my $link = expandarr(@ARGV);
168 my $cnd = name_wo_ext($mainfile);
169 my $cndo = $cnd . "o";
170 my $bin = $cnd . "out";
172 my $cfgn = name_wo_ext($mainfile) . "rcb";
173 my $haveconfig = (-e $cfgn);
174 if($haveconfig) {
175 printc "blue", "[RcB] config file found. trying single compile.\n";
176 @adep = `cat $cfgn`;
177 my $cs = expandarr(@adep);
178 my $res = compile("$cc $cflags $cs $link -o $bin");
179 if($res =~ /undefined reference to/) {
180 printc "red", "[RcB] undefined reference[s] found, switching to scan mode\n";
181 } else {
182 print $res;
183 if($?) {
184 printc "red", "[RcB] error. exiting.\n";
185 } else {
186 printc "green", "[RcB] success. $bin created.\n";
188 exit $?;
192 printc "blue", "[RcB] scanning deps...";
194 scanfile dirname(abs_path($mainfile)), basename($mainfile);
196 printc "green", "done\n";
198 my %obj;
199 printc "blue", "[RcB] compiling main file...\n";
200 my $op = compile("$cc $cflags -c $mainfile -o $cndo");
201 exit 1 if($op =~ /error:/g);
202 $obj{$cndo} = 1;
203 my %sym;
205 my $i = 0;
206 my $success = 0;
207 my $run = 0;
208 my $relink = 1;
209 my $rebuildflag = 0;
210 my $objfail = 0;
212 my @glob_missym;
213 my @missym;
214 my %rebuilt;
215 printc "blue", "[RcB] resolving linker deps...\n";
216 while(!$success) {
217 my @opa;
218 if($i + 1 >= @adep) { #last element of the array is the already build mainfile
219 $run++;
220 $i = 0;
222 if(!$i) {
223 @glob_missym = @missym, last unless $relink;
224 # trying to link
225 my @missym_old = @missym;
226 @missym = ();
227 my $ex = expandhash(\%obj);
228 printc "blue", "[RcB] trying to link ...\n";
229 my $cmd = "$cc $cflags $ex $link -o $bin";
230 printc "cyan", "[LD] ", $cmd, "\n";
231 @opa = `$cmd 2>&1`;
232 for(@opa) {
233 if(/undefined reference to [\'\`\"]{1}([\w\._]+)[\'\`\"]{1}/) {
234 my $temp = $1;
235 print if $verbose;
236 push @missym, $temp;
237 } elsif(
238 /architecture of input file [\'\`\"]{1}([\w\.\/_]+)[\'\`\"]{1} is incompatible with/ ||
239 /fatal error: ([\w\.\/_]+): unsupported ELF machine number/
241 $cnd = $1;
242 $i = delete $obj{$cnd};
243 printc "red", "[RcB] incompatible object file $cnd, rebuilding...\n";
244 print;
245 $cnd =~ s/\.o/\.c/;
246 $rebuildflag = 1;
247 $objfail = 1;
248 @missym = @missym_old;
249 goto rebuild;
250 } elsif(
251 /collect2: ld returned 1 exit status/ ||
252 /In function [\'\`\"]{1}[\w_]+[\'\`\"]{1}:/ ||
253 /more undefined references to/
255 } else {
256 printc "red", "[RcB] FATAL: unexpected linker output!\n";
257 print;
258 exit 1;
261 if(!scalar(@missym)) {
262 for(@opa) {print;}
263 $success = 1;
264 last;
266 $relink = 0;
268 $cnd = $adep[$i];
269 goto skip unless defined $cnd;
270 $rebuildflag = 0;
271 rebuild:
272 chomp($cnd);
273 $cndo = name_wo_ext($cnd) . "o";
274 if(($forcerebuild || $rebuildflag || ! -e $cndo) && !defined($rebuilt{$cndo})) {
275 my $op = compile("$cc $cflags -c $cnd -o $cndo");
276 exit 1 if($op =~ /error:/);
277 $rebuilt{$cndo} = 1;
279 @opa = `$nm -g $cndo 2>&1`;
280 my %symhash;
281 my $matched = 0;
282 for(@opa) {
283 if(/[\da-fA-F]{8,16}\s+[TW]{1}\s+([\w_]+)/) {
284 my $symname = $1;
285 $symhash{$symname} = 1;
286 $matched = 1;
287 } elsif (/File format not recognized/) {
288 printc "red", "[RcB] nm doesn't recognize the format of $cndo, rebuilding...\n";
289 $rebuildflag = 1;
290 goto rebuild;
293 if($matched){
294 $sym{$cndo} = \%symhash;
295 my $good = 0;
296 for(@missym) {
297 if(defined($symhash{$_})) {
298 $obj{$cndo} = $i;
299 $adep[$i] = undef;
300 $relink = 1;
301 if($objfail) {
302 $objfail = 0;
303 $i = -1;
305 last;
309 skip:
310 $i++;
313 if(!$success) {
314 printc "red", "[RcB] failed to resolve the following symbols, check your DEP tags\n";
315 for(@glob_missym) {
316 print "$_\n";
318 } else {
319 printc "green", "[RcB] success. $bin created.\n";
320 printc "blue", "saving required dependencies to $cfgn\n";
321 my $fh;
322 open($fh, ">", $cfgn);
323 for(keys %obj) {
324 print { $fh } name_wo_ext($_), "c\n";
326 close($fh);