contrib / buildsystems / engine.plon commit contrib/buildsystems: also handle -lexpat (6e50021)
   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 V=1 2>$ErrsFile` if !@makedry;
  86# test for an empty Errors file and remove it
  87unlink $ErrsFile if -f -z $ErrsFile;
  88
  89if (defined $make_out) {
  90    open OUT, ">" . $make_out;
  91    print OUT @makedry;
  92    close OUT;
  93}
  94
  95# Parse the make output into usable info
  96parseMakeOutput();
  97
  98# Finally, ask the generator to start generating..
  99Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure);
 100
 101# main flow ends here
 102# -------------------------------------------------------------------------------------------------
 103
 104
 105# 1) path: /foo/bar/baz        2) path: /foo/bar/baz   3) path: /foo/bar/baz
 106#    base: /foo/bar/baz/temp      base: /foo/bar          base: /tmp
 107#    rel:  ..                     rel:  baz               rel:  ../foo/bar/baz
 108sub makeOutRel2Git
 109{
 110    my ($path, $base) = @_;
 111    my $rel;
 112    if ("$path" eq "$base") {
 113        return ".";
 114    } elsif ($base =~ /^$path/) {
 115        # case 1
 116        my $tmp = $base;
 117        $tmp =~ s/^$path//;
 118        foreach (split('/', $tmp)) {
 119            $rel .= "../" if ("$_" ne "");
 120        }
 121    } elsif ($path =~ /^$base/) {
 122        # case 2
 123        $rel = $path;
 124        $rel =~ s/^$base//;
 125        $rel = "./$rel";
 126    } else {
 127        my $tmp = $base;
 128        foreach (split('/', $tmp)) {
 129            $rel .= "../" if ("$_" ne "");
 130        }
 131        $rel .= $path;
 132    }
 133    $rel =~ s/\/\//\//g; # simplify
 134    $rel =~ s/\/$//;     # don't end with /
 135    return $rel;
 136}
 137
 138sub parseMakeOutput
 139{
 140    print "Parsing GNU Make output to figure out build structure...\n";
 141    my $line = 0;
 142    while (my $text = shift @makedry) {
 143        my $ate_next;
 144        do {
 145            $ate_next = 0;
 146            $line++;
 147            chomp $text;
 148            chop $text if ($text =~ /\r$/);
 149            if ($text =~ /\\$/) {
 150                $text =~ s/\\$//;
 151                $text .= shift @makedry;
 152                $ate_next = 1;
 153            }
 154        } while($ate_next);
 155
 156        if ($text =~ /^test /) {
 157            # options to test (eg -o) may be mistaken for linker options
 158            next;
 159        }
 160
 161        if ($text =~ /^(mkdir|msgfmt) /) {
 162            # options to the Portable Object translations
 163            # the line "mkdir ... && msgfmt ..." contains no linker options
 164            next;
 165        }
 166
 167        if($text =~ / -c /) {
 168            # compilation
 169            handleCompileLine($text, $line);
 170
 171        } elsif ($text =~ / -o /) {
 172            # linking executable
 173            handleLinkLine($text, $line);
 174
 175        } elsif ($text =~ /\.o / && $text =~ /\.a /) {
 176            # libifying
 177            handleLibLine($text, $line);
 178#
 179#        } elsif ($text =~ /^cp /) {
 180#            # copy file around
 181#
 182#        } elsif ($text =~ /^rm -f /) {
 183#            # shell command
 184#
 185#        } elsif ($text =~ /^make[ \[]/) {
 186#            # make output
 187#
 188#        } elsif ($text =~ /^echo /) {
 189#            # echo to file
 190#
 191#        } elsif ($text =~ /^if /) {
 192#            # shell conditional
 193#
 194#        } elsif ($text =~ /^tclsh /) {
 195#            # translation stuff
 196#
 197#        } elsif ($text =~ /^umask /) {
 198#            # handling boilerplates
 199#
 200#        } elsif ($text =~ /\$\(\:\)/) {
 201#            # ignore
 202#
 203#        } elsif ($text =~ /^FLAGS=/) {
 204#            # flags check for dependencies
 205#
 206#        } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
 207#            # perl commands for copying files
 208#
 209#        } elsif ($text =~ /generate-cmdlist\.sh/) {
 210#            # command for generating list of commands
 211#
 212#        } elsif ($text =~ /new locations or Tcl/) {
 213#            # command for detecting Tcl/Tk changes
 214#
 215#        } elsif ($text =~ /mkdir -p/) {
 216#            # command creating path
 217#
 218#        } elsif ($text =~ /: no custom templates yet/) {
 219#            # whatever
 220#
 221#        } else {
 222#            print "Unhandled (line: $line): $text\n";
 223        }
 224    }
 225
 226#    use Data::Dumper;
 227#    print "Parsed build structure:\n";
 228#    print Dumper(%build_structure);
 229}
 230
 231# variables for the compilation part of each step
 232my (@defines, @incpaths, @cflags, @sources);
 233
 234sub clearCompileStep
 235{
 236    @defines = ();
 237    @incpaths = ();
 238    @cflags = ();
 239    @sources = ();
 240}
 241
 242sub removeDuplicates
 243{
 244    my (%dupHash, $entry);
 245    %dupHash = map { $_, 1 } @defines;
 246    @defines = keys %dupHash;
 247
 248    %dupHash = map { $_, 1 } @incpaths;
 249    @incpaths = keys %dupHash;
 250
 251    %dupHash = map { $_, 1 } @cflags;
 252    @cflags = keys %dupHash;
 253}
 254
 255sub handleCompileLine
 256{
 257    my ($line, $lineno) = @_;
 258    my @parts = shellwords($line);
 259    my $sourcefile;
 260    shift(@parts); # ignore cmd
 261    while (my $part = shift @parts) {
 262        if ("$part" eq "-o") {
 263            # ignore object file
 264            shift @parts;
 265        } elsif ("$part" eq "-c") {
 266            # ignore compile flag
 267        } elsif ("$part" eq "-c") {
 268        } elsif ($part =~ /^.?-I/) {
 269            push(@incpaths, $part);
 270        } elsif ($part =~ /^.?-D/) {
 271            push(@defines, $part);
 272        } elsif ($part =~ /^-/) {
 273            push(@cflags, $part);
 274        } elsif ($part =~ /\.(c|cc|cpp)$/) {
 275            $sourcefile = $part;
 276        } else {
 277            die "Unhandled compiler option @ line $lineno: $part";
 278        }
 279    }
 280    @{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags;
 281    @{$compile_options{"${sourcefile}_DEFINES"}} = @defines;
 282    @{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths;
 283    clearCompileStep();
 284}
 285
 286sub handleLibLine
 287{
 288    my ($line, $lineno) = @_;
 289    my (@objfiles, @lflags, $libout, $part);
 290    # kill cmd and rm 'prefix'
 291    $line =~ s/^rm -f .* && .* rcs //;
 292    my @parts = shellwords($line);
 293    while ($part = shift @parts) {
 294        if ($part =~ /^-/) {
 295            push(@lflags, $part);
 296        } elsif ($part =~ /\.(o|obj)$/) {
 297            push(@objfiles, $part);
 298        } elsif ($part =~ /\.(a|lib)$/) {
 299            $libout = $part;
 300            $libout =~ s/\.a$//;
 301        } else {
 302            die "Unhandled lib option @ line $lineno: $part";
 303        }
 304    }
 305#    print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
 306#    exit(1);
 307    foreach (@objfiles) {
 308        my $sourcefile = $_;
 309        $sourcefile =~ s/\.o$/.c/;
 310        push(@sources, $sourcefile);
 311        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
 312        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
 313        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
 314    }
 315    removeDuplicates();
 316
 317    push(@{$build_structure{"LIBS"}}, $libout);
 318    @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
 319                                             "_OBJECTS");
 320    @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
 321    @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
 322    @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
 323    @{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags;
 324    @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
 325    @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
 326    clearCompileStep();
 327}
 328
 329sub handleLinkLine
 330{
 331    my ($line, $lineno) = @_;
 332    my (@objfiles, @lflags, @libs, $appout, $part);
 333    my @parts = shellwords($line);
 334    shift(@parts); # ignore cmd
 335    while ($part = shift @parts) {
 336        if ($part =~ /^-IGNORE/) {
 337            push(@lflags, $part);
 338        } elsif ($part =~ /^-[GRIMDO]/) {
 339            # eat compiler flags
 340        } elsif ("$part" eq "-o") {
 341            $appout = shift @parts;
 342        } elsif ("$part" eq "-lz") {
 343            push(@libs, "zlib.lib");
 344        } elsif ("$part" eq "-lcrypto") {
 345            push(@libs, "libeay32.lib");
 346        } elsif ("$part" eq "-lssl") {
 347            push(@libs, "ssleay32.lib");
 348        } elsif ("$part" eq "-lcurl") {
 349            push(@libs, "libcurl.lib");
 350        } elsif ("$part" eq "-lexpat") {
 351            push(@libs, "expat.lib");
 352        } elsif ("$part" eq "-liconv") {
 353            push(@libs, "libiconv.lib");
 354        } elsif ($part =~ /^-/) {
 355            push(@lflags, $part);
 356        } elsif ($part =~ /\.(a|lib)$/) {
 357            $part =~ s/\.a$/.lib/;
 358            push(@libs, $part);
 359        } elsif ($part eq 'invalidcontinue.obj') {
 360            # ignore - known to MSVC
 361        } elsif ($part =~ /\.o$/) {
 362            push(@objfiles, $part);
 363        } elsif ($part =~ /\.obj$/) {
 364            # do nothing, 'make' should not be producing .obj, only .o files
 365        } else {
 366            die "Unhandled link option @ line $lineno: $part";
 367        }
 368    }
 369#    print "AppOut: '$appout'\nLFlags: @lflags\nLibs  : @libs\nOfiles: @objfiles\n";
 370#    exit(1);
 371    foreach (@objfiles) {
 372        my $sourcefile = $_;
 373        $sourcefile =~ s/\.o$/.c/;
 374        push(@sources, $sourcefile);
 375        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
 376        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
 377        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
 378    }
 379    removeDuplicates();
 380
 381    removeDuplicates();
 382    push(@{$build_structure{"APPS"}}, $appout);
 383    @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
 384                                             "_SOURCES", "_OBJECTS", "_LIBS");
 385    @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
 386    @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
 387    @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
 388    @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
 389    @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
 390    @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
 391    @{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
 392    clearCompileStep();
 393}