contrib / buildsystems / engine.plon commit Merge branch 'ds/feature-macros' (f4f8dfe)
   1#!/usr/bin/perl -w
   2######################################################################
   3# Do not call this script directly!
   4#
   5# The generate script ensures that @INC is correct before the engine
   6# is executed.
   7#
   8# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
   9######################################################################
  10use strict;
  11use File::Basename;
  12use File::Spec;
  13use Cwd;
  14use Generators;
  15use Text::ParseWords;
  16
  17my (%build_structure, %compile_options, @makedry);
  18my $out_dir = getcwd();
  19my $git_dir = $out_dir;
  20$git_dir =~ s=\\=/=g;
  21$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne "");
  22die "Couldn't find Git repo" if ("$git_dir" eq "");
  23
  24my @gens = Generators::available();
  25my $gen = "Vcproj";
  26
  27sub showUsage
  28{
  29    my $genlist = join(', ', @gens);
  30    print << "EOM";
  31generate usage:
  32  -g <GENERATOR>  --gen <GENERATOR> Specify the buildsystem generator    (default: $gen)
  33                                    Available: $genlist
  34  -o <PATH>       --out <PATH>      Specify output directory generation  (default: .)
  35                  --make-out <PATH> Write the output of GNU Make into a file
  36  -i <FILE>       --in <FILE>       Specify input file, instead of running GNU Make
  37  -h,-?           --help            This help
  38EOM
  39    exit 0;
  40}
  41
  42# Parse command-line options
  43my $make_out;
  44while (@ARGV) {
  45    my $arg = shift @ARGV;
  46    if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") {
  47        showUsage();
  48        exit(0);
  49    } elsif("$arg" eq "--out" || "$arg" eq "-o") {
  50        $out_dir = shift @ARGV;
  51    } elsif("$arg" eq "--make-out") {
  52        $make_out = shift @ARGV;
  53    } elsif("$arg" eq "--gen" || "$arg" eq "-g") {
  54        $gen = shift @ARGV;
  55    } elsif("$arg" eq "--in" || "$arg" eq "-i") {
  56        my $infile = shift @ARGV;
  57        open(F, "<$infile") || die "Couldn't open file $infile";
  58        @makedry = <F>;
  59        close(F);
  60    } else {
  61        die "Unknown option: " . $arg;
  62    }
  63}
  64
  65# NOT using File::Spec->rel2abs($path, $base) here, as
  66# it fails badly for me in the msysgit environment
  67$git_dir = File::Spec->rel2abs($git_dir);
  68$out_dir = File::Spec->rel2abs($out_dir);
  69my $rel_dir = makeOutRel2Git($git_dir, $out_dir);
  70
  71# Print some information so the user feels informed
  72print << "EOM";
  73-----
  74Generator: $gen
  75Git dir:   $git_dir
  76Out dir:   $out_dir
  77-----
  78Running GNU Make to figure out build structure...
  79EOM
  80
  81# Pipe a make --dry-run into a variable, if not already loaded from file
  82# Capture the make dry stderr to file for review (will be empty for a release build).
  83
  84my $ErrsFile = "msvc-build-makedryerrors.txt";
  85@makedry = `make -C $git_dir -n MSVC=1 SKIP_VCPKG=1 V=1 2>$ErrsFile`
  86if !@makedry;
  87# test for an empty Errors file and remove it
  88unlink $ErrsFile if -f -z $ErrsFile;
  89
  90if (defined $make_out) {
  91    open OUT, ">" . $make_out;
  92    print OUT @makedry;
  93    close OUT;
  94}
  95
  96# Parse the make output into usable info
  97parseMakeOutput();
  98
  99# Finally, ask the generator to start generating..
 100Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure);
 101
 102# main flow ends here
 103# -------------------------------------------------------------------------------------------------
 104
 105
 106# 1) path: /foo/bar/baz        2) path: /foo/bar/baz   3) path: /foo/bar/baz
 107#    base: /foo/bar/baz/temp      base: /foo/bar          base: /tmp
 108#    rel:  ..                     rel:  baz               rel:  ../foo/bar/baz
 109sub makeOutRel2Git
 110{
 111    my ($path, $base) = @_;
 112    my $rel;
 113    if ("$path" eq "$base") {
 114        return ".";
 115    } elsif ($base =~ /^$path/) {
 116        # case 1
 117        my $tmp = $base;
 118        $tmp =~ s/^$path//;
 119        foreach (split('/', $tmp)) {
 120            $rel .= "../" if ("$_" ne "");
 121        }
 122    } elsif ($path =~ /^$base/) {
 123        # case 2
 124        $rel = $path;
 125        $rel =~ s/^$base//;
 126        $rel = "./$rel";
 127    } else {
 128        my $tmp = $base;
 129        foreach (split('/', $tmp)) {
 130            $rel .= "../" if ("$_" ne "");
 131        }
 132        $rel .= $path;
 133    }
 134    $rel =~ s/\/\//\//g; # simplify
 135    $rel =~ s/\/$//;     # don't end with /
 136    return $rel;
 137}
 138
 139sub parseMakeOutput
 140{
 141    print "Parsing GNU Make output to figure out build structure...\n";
 142    my $line = 0;
 143    while (my $text = shift @makedry) {
 144        my $ate_next;
 145        do {
 146            $ate_next = 0;
 147            $line++;
 148            chomp $text;
 149            chop $text if ($text =~ /\r$/);
 150            if ($text =~ /\\$/) {
 151                $text =~ s/\\$//;
 152                $text .= shift @makedry;
 153                $ate_next = 1;
 154            }
 155        } while($ate_next);
 156
 157        if ($text =~ /^test /) {
 158            # options to test (eg -o) may be mistaken for linker options
 159            next;
 160        }
 161
 162        if ($text =~ /^(mkdir|msgfmt) /) {
 163            # options to the Portable Object translations
 164            # the line "mkdir ... && msgfmt ..." contains no linker options
 165            next;
 166        }
 167
 168        if($text =~ / -c /) {
 169            # compilation
 170            handleCompileLine($text, $line);
 171
 172        } elsif ($text =~ / -o /) {
 173            # linking executable
 174            handleLinkLine($text, $line);
 175
 176        } elsif ($text =~ /\.o / && $text =~ /\.a /) {
 177            # libifying
 178            handleLibLine($text, $line);
 179#
 180#        } elsif ($text =~ /^cp /) {
 181#            # copy file around
 182#
 183#        } elsif ($text =~ /^rm -f /) {
 184#            # shell command
 185#
 186#        } elsif ($text =~ /^make[ \[]/) {
 187#            # make output
 188#
 189#        } elsif ($text =~ /^echo /) {
 190#            # echo to file
 191#
 192#        } elsif ($text =~ /^if /) {
 193#            # shell conditional
 194#
 195#        } elsif ($text =~ /^tclsh /) {
 196#            # translation stuff
 197#
 198#        } elsif ($text =~ /^umask /) {
 199#            # handling boilerplates
 200#
 201#        } elsif ($text =~ /\$\(\:\)/) {
 202#            # ignore
 203#
 204#        } elsif ($text =~ /^FLAGS=/) {
 205#            # flags check for dependencies
 206#
 207#        } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
 208#            # perl commands for copying files
 209#
 210#        } elsif ($text =~ /generate-cmdlist\.sh/) {
 211#            # command for generating list of commands
 212#
 213#        } elsif ($text =~ /new locations or Tcl/) {
 214#            # command for detecting Tcl/Tk changes
 215#
 216#        } elsif ($text =~ /mkdir -p/) {
 217#            # command creating path
 218#
 219#        } elsif ($text =~ /: no custom templates yet/) {
 220#            # whatever
 221#
 222#        } else {
 223#            print "Unhandled (line: $line): $text\n";
 224        }
 225    }
 226
 227#    use Data::Dumper;
 228#    print "Parsed build structure:\n";
 229#    print Dumper(%build_structure);
 230}
 231
 232# variables for the compilation part of each step
 233my (@defines, @incpaths, @cflags, @sources);
 234
 235sub clearCompileStep
 236{
 237    @defines = ();
 238    @incpaths = ();
 239    @cflags = ();
 240    @sources = ();
 241}
 242
 243sub removeDuplicates
 244{
 245    my (%dupHash, $entry);
 246    %dupHash = map { $_, 1 } @defines;
 247    @defines = keys %dupHash;
 248
 249    %dupHash = map { $_, 1 } @incpaths;
 250    @incpaths = keys %dupHash;
 251
 252    %dupHash = map { $_, 1 } @cflags;
 253    @cflags = keys %dupHash;
 254}
 255
 256sub handleCompileLine
 257{
 258    my ($line, $lineno) = @_;
 259    my @parts = shellwords($line);
 260    my $sourcefile;
 261    shift(@parts); # ignore cmd
 262    while (my $part = shift @parts) {
 263        if ("$part" eq "-o") {
 264            # ignore object file
 265            shift @parts;
 266        } elsif ("$part" eq "-c") {
 267            # ignore compile flag
 268        } elsif ("$part" eq "-c") {
 269        } elsif ($part =~ /^.?-I/) {
 270            push(@incpaths, $part);
 271        } elsif ($part =~ /^.?-D/) {
 272            push(@defines, $part);
 273        } elsif ($part =~ /^-/) {
 274            push(@cflags, $part);
 275        } elsif ($part =~ /\.(c|cc|cpp)$/) {
 276            $sourcefile = $part;
 277        } else {
 278            die "Unhandled compiler option @ line $lineno: $part";
 279        }
 280    }
 281    @{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags;
 282    @{$compile_options{"${sourcefile}_DEFINES"}} = @defines;
 283    @{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths;
 284    clearCompileStep();
 285}
 286
 287sub handleLibLine
 288{
 289    my ($line, $lineno) = @_;
 290    my (@objfiles, @lflags, $libout, $part);
 291    # kill cmd and rm 'prefix'
 292    $line =~ s/^rm -f .* && .* rcs //;
 293    my @parts = shellwords($line);
 294    while ($part = shift @parts) {
 295        if ($part =~ /^-/) {
 296            push(@lflags, $part);
 297        } elsif ($part =~ /\.(o|obj)$/) {
 298            push(@objfiles, $part);
 299        } elsif ($part =~ /\.(a|lib)$/) {
 300            $libout = $part;
 301            $libout =~ s/\.a$//;
 302        } else {
 303            die "Unhandled lib option @ line $lineno: $part";
 304        }
 305    }
 306#    print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
 307#    exit(1);
 308    foreach (@objfiles) {
 309        my $sourcefile = $_;
 310        $sourcefile =~ s/\.o$/.c/;
 311        push(@sources, $sourcefile);
 312        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
 313        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
 314        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
 315    }
 316    removeDuplicates();
 317
 318    push(@{$build_structure{"LIBS"}}, $libout);
 319    @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
 320                                             "_OBJECTS");
 321    @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
 322    @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
 323    @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
 324    @{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags;
 325    @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
 326    @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
 327    clearCompileStep();
 328}
 329
 330sub handleLinkLine
 331{
 332    my ($line, $lineno) = @_;
 333    my (@objfiles, @lflags, @libs, $appout, $part);
 334    my @parts = shellwords($line);
 335    shift(@parts); # ignore cmd
 336    while ($part = shift @parts) {
 337        if ($part =~ /^-IGNORE/) {
 338            push(@lflags, $part);
 339        } elsif ($part =~ /^-[GRIMDO]/) {
 340            # eat compiler flags
 341        } elsif ("$part" eq "-o") {
 342            $appout = shift @parts;
 343        } elsif ("$part" eq "-lz") {
 344            push(@libs, "zlib.lib");
 345        } elsif ("$part" eq "-lcrypto") {
 346            push(@libs, "libeay32.lib");
 347        } elsif ("$part" eq "-lssl") {
 348            push(@libs, "ssleay32.lib");
 349        } elsif ("$part" eq "-lcurl") {
 350            push(@libs, "libcurl.lib");
 351        } elsif ("$part" eq "-lexpat") {
 352            push(@libs, "expat.lib");
 353        } elsif ("$part" eq "-liconv") {
 354            push(@libs, "libiconv.lib");
 355        } elsif ($part =~ /^[-\/]/) {
 356            push(@lflags, $part);
 357        } elsif ($part =~ /\.(a|lib)$/) {
 358            $part =~ s/\.a$/.lib/;
 359            push(@libs, $part);
 360        } elsif ($part eq 'invalidcontinue.obj') {
 361            # ignore - known to MSVC
 362        } elsif ($part =~ /\.o$/) {
 363            push(@objfiles, $part);
 364        } elsif ($part =~ /\.obj$/) {
 365            # do nothing, 'make' should not be producing .obj, only .o files
 366        } else {
 367            die "Unhandled link option @ line $lineno: $part";
 368        }
 369    }
 370#    print "AppOut: '$appout'\nLFlags: @lflags\nLibs  : @libs\nOfiles: @objfiles\n";
 371#    exit(1);
 372    foreach (@objfiles) {
 373        my $sourcefile = $_;
 374        $sourcefile =~ s/\.o$/.c/;
 375        push(@sources, $sourcefile);
 376        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
 377        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
 378        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
 379    }
 380    removeDuplicates();
 381
 382    removeDuplicates();
 383    push(@{$build_structure{"APPS"}}, $appout);
 384    @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
 385                                             "_SOURCES", "_OBJECTS", "_LIBS");
 386    @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
 387    @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
 388    @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
 389    @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
 390    @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
 391    @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
 392    @{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
 393    clearCompileStep();
 394}