Skip to content

Instantly share code, notes, and snippets.

@FurryHead
Created July 31, 2011 03:24
Show Gist options
  • Save FurryHead/1116342 to your computer and use it in GitHub Desktop.
Save FurryHead/1116342 to your computer and use it in GitHub Desktop.
Makefile generator in Perl
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
########The script defaults#########
my $make_outfile = "PerlMakefile";
my $exe_outfile = "p.out";
my $compiler = "gcc";
my $src_dir = "./src";
my $obj_dir = "./obj";
my $ext = "c";
my $lflags = "";
my $cflags = "";
my $rm = "rm -f";
####################################
my %deps;
sub get_deps {
my ($fname) = @_;
if ($fname !~ /(\/|\\)/) {
$fname = $src_dir."/".$fname;
}
open(CFILE, "<$fname") || die("Error: No such file '$fname' ");
foreach my $line (<CFILE>) {
chomp $line;
if ($line =~ /^# *include *"(.+)"/i) {
$line =~ s/^# *include *"(.+).h"/$1/i;
$line = $src_dir."/".$line;
(my $fobj = $fname) =~ s/((\.$ext)|(\.h))/\.o/i;
$fobj =~ s/$src_dir/$obj_dir/i;
if (not $deps{$fobj}) {
$deps{$fobj} = ();
}
${$deps{$fobj}}{$line.".".$ext} = 1 if (-f $line.".".$ext);
${$deps{$fobj}}{$line.".h"} = 1 if (-f $line.".h");
${$deps{$fobj}}{$fname} = 1;
&get_deps($line.".".$ext) if (-f $line.".".$ext && not ${$deps{$fobj}}{$line.".".$ext});
&get_deps($line.".h") if (-f $line.".h" && not ${$deps{$fobj}}{$line.".h"});
}
}
close(CFILE);
}
sub print_help {
print <<EOF
FurryHead's Makefile generator.
Usage: $0 [-h] [-p] [-e <exe_name>] [-m <makefile_name>] [-s <src_dir>] [-o <obj_dir>] [-t <ext>] [-k <compiler>] [-l "extra link flags"] [-c "extra compile flags"]
-e Specifies the (project) executable output file name. Defaults to '${exe_outfile}'
-m Specifies the output Makefile name. Defaults to '${make_outfile}'
-s Specifies the directory containing the source files. Defaults to '${src_dir}'
-o Specifies the directory containing the object files. Defaults to '${obj_dir}'
-t The file extension to scan for deps, for example "cxx". Defaults to '${ext}'
-k The compiler to use, i.e. "g++" or "gcc". Defaults to '${compiler}'
-c Anything you'd normally put in CFLAGS= (compile flags/options). Defaults to '${cflags}'
-l Anything you'd normally put in LFLAGS= (link flags/options). Defaults to '${lflags}'
-r The command to execute for removing files (make clean). Defaults to '${rm}'
-p (Linux only) Adds total progress updates to compile steps. (I.e. "3 of 4 remaining")
-h Prints this help.
The generator defaults can be set at the top of the script file ($0)
EOF
;
}
my @original_args = @ARGV;
my %args;
getopts("hpm:o:s:e:t:k:c:l:r:", \%args) || &print_help && exit;
&print_help && exit if $args{h};
my $starttime = scalar time;
$make_outfile = $args{m} if ($args{m});
$exe_outfile = $args{e} if $args{e};
$src_dir = $args{s} if $args{s};
$obj_dir = $args{o} if $args{o};
$ext = $args{t} if $args{t};
$compiler = $args{k} if $args{k};
$cflags = $args{c} if $args{c};
$lflags = $args{l} if $args{l};
$rm = $args{r} if $args{r};
my $progress = ($args{p} ? 1 : 0);
print "Gathering dependencies...\n";
my $time = time;
opendir(SDIR, $src_dir) || die("No such directory: $src_dir");
while (my $f = readdir(SDIR)) {
next unless ($f =~ /\.(${ext}|[h])$/);
&get_deps($src_dir."/".$f);
}
closedir(SDIR);
print "Finished in " . (time - $time) . " seconds.\n";
my @objs = keys(%deps);
print "Generating Makefile ($make_outfile)...\n";
$time = time;
open MAKEFILE, ">$make_outfile";
print MAKEFILE <<EOF
CC=$compiler
OUT=$exe_outfile
RM=$rm
EOF
;
if ($progress) {
print MAKEFILE <<EOF
# I don't really understand this myself,
# I copied it off a post on stackoverflow.com
# But it lets me display how many steps in the build remain
ifndef BUILD
sixteen := x x x x x x x x x x x x x x x x
MAX := \$(foreach x,\$(sixteen),\$(sixteen))
T := \$(shell \$(MAKE) -nrRf \$(firstword \$(MAKEFILE_LIST)) \$(MAKECMDGOALS) BUILD="COUNTTHIS" | grep -c "COUNTTHIS")
N := \$(wordlist 1,\$T,\$(MAX))
counter = \$(words \$N)\$(eval N := \$(wordlist 2,\$(words \$N),\$N))
BUILD = \$(counter)/\$(T)
endif
EOF
;
}
print MAKEFILE <<EOF
OBJS=@{objs}
CFLAGS=$cflags
LFLAGS=$lflags
all : \$(OUT)
\techo "Finished."
\$(OUT) : \$(OBJS)
EOF
;
if ($progress) {
print MAKEFILE "\techo \"(\$(BUILD)) Linking \$(OUT)...\"";
} else {
print MAKEFILE "\techo \"Linking \$(OUT)...\"";
}
print MAKEFILE "\n\t\$(CC) \$(LFLAGS) \$(OBJS) -o \$(OUT)\n\n";
foreach my $obj (sort(@objs)) {
my %written;
my @f;
foreach my $z (sort(keys(%{$deps{$obj}}))) {
(my $o = $z) =~ s/\.(h|$ext)$//i;
my $ob = $o.".o";
$ob =~ s/$src_dir/$obj_dir/i;
if ($ob ne $obj && !$written{$ob} && -f $o.".h" && -f $o.".$ext" && !$written{$z}) {
$written{$ob} = 1;
$written{$o.".$ext"} = 1;
$written{$o.".h"} = 1;
push(@f, $ob);
} else {
if (!$written{$z}) {
$written{$z} = 1;
push(@f, $z);
}
}
}
print MAKEFILE "$obj :";
@f = sort(@f);
#these two loops are to ensure the source files
#get listed first (and object files last),
#otherwise make/gcc complains
foreach my $t (@f) {
next if ($t =~ /\.o$/);
print MAKEFILE " $t";
}
foreach my $t (@f) {
next if ($t !~ /\.o$/);
print MAKEFILE " $t";
}
print MAKEFILE "\n";
if ($progress) {
print MAKEFILE "\techo \"(\$(BUILD)) Compiling \$< to \$@...\"\n";
} else {
print MAKEFILE "\techo \"Compiling \$< to \$@...\"\n";
}
print MAKEFILE "\t\$(CC) \$(CFLAGS) -c \$< -o \$@\n";
print MAKEFILE "\n";
}
print MAKEFILE <<EOF
clean:
\t\$(RM) \$(OUT)
\t\$(RM) \$(OBJS)
.SILENT:
EOF
;
close MAKEFILE;
print "Finished in " . (time - $time) . " seconds.\n";
print "Makefile generation complete. Total time taken: " . (time - $starttime) . " seconds\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment