[t/spec] Minor improvements to the series tests.
[pugs.git] / util / build_pugs.pl
blobfe7f71568432edc7faa56b025065f3304c2dc88e
1 #!/usr/bin/perl
3 use strict;
4 use warnings;
5 use Cwd qw(cwd);
6 use File::Copy qw(copy);
7 use File::Path qw(mkpath rmtree);
8 use File::Find qw(find);
9 use File::Basename qw(dirname);
10 use List::Util qw(max min);
12 our %BuildPrefs;
13 use Config;
14 use FindBin;
15 BEGIN { chdir $FindBin::RealBin; chdir '..'; };
16 use lib 'inc';
17 use PugsBuild::Config;
19 help() if ($ARGV[0] || '--help') =~ /^--?h(?:elp)?/i;
20 build(classify_options(@ARGV));
21 exit 0;
23 sub help {
24 print <<".";
25 $0 - build a pugs executable
27 This script calls GHC to build a pugs exectuable, optionally inlining
28 precompiled modules in a second pass.
30 Primary configuration settings are read from the file `config.yml` in
31 the build root. You may override these settings using the PUGS_BUILD_OPTS
32 environment variable.
34 Current settings:
36 print PugsBuild::Config->pretty_print;
38 exit 0;
41 my $run_setup;
42 my $want_profiling = 0;
43 my $AR_EXE;
45 sub build {
46 my($opts) = @_;
47 my $thispugs = { @{ $opts->{GEN_PRELUDE} } }->{'--pugs'} or # laugh at me now.
48 die "$0: no pugs passed in _+GEN_PRELUDE segment";
50 if ( grep '--precompile-prelude', @{ $opts->{GEN_PRELUDE} } ) {
51 $PugsBuild::Config::Conf->{'precompile_prelude'}
52 = { @{ $opts->{GEN_PRELUDE} } }->{'--precompile-prelude'}
55 print "Build configuration:\n" . PugsBuild::Config->pretty_print;
57 my ($version, $ghc, $ghc_pkg, $ghc_version, $setup, @args) = @{$opts->{GHC}};
59 $want_profiling = grep { /^-prof$/ } @args;
60 @args = grep { !/^-prof$/ } @args;
62 # Set heap options via environment here; Win32 needs it instead
63 # of setting on GHC flags line.
64 my @rts_args;
65 foreach my $arg (@args) {
66 $_ = $arg;
67 push @rts_args, $_ if s/^\+RTS$// .. s/^-RTS$//;
69 $ENV{GHCRTS} = join(' ', ($ENV{GHCRTS} ? $ENV{GHC_RTS} : ()), @rts_args);
71 if ($Config{osname} eq 'cygwin') {
72 my $cygwin_path = `cygpath -w /`;
73 my $cygpath = sub {
74 my $path = shift;
75 #warn "<> processing $path...\n";
76 my $retval = `cygpath -m $path`;
77 chomp $retval;
78 #warn "<> Now it is $retval...\n";
79 return $retval;
82 unshift @args, '-I/usr/lib/perl5/5.8/cygwin/CORE',
83 "-optc-IC:\\ghc\\ghc-$ghc_version\\include\\mingw",
84 "-optc-ID:\\ghc\\ghc-$ghc_version\\include\\mingw",
85 '-optc-I/usr/include',
86 '-optl-I/usr/include/cygwin';
87 for my $arg (@args) {
88 $arg =~ s{(-optc-[IL]|-optl|-optl-L|-I|-L)(/\S+)}{$1 . $cygpath->($2)}eg;
92 write_buildinfo($version, $ghc, $ghc_pkg, $ghc_version, @args);
94 my $pwd = cwd();
96 print "*** Building dependencies. Please wait...\n\n";
98 # Instead of --user, use our own package-conf storage position.
100 my $runcompiler = File::Spec->rel2abs("$pwd/util/runcompiler$Config{_exe}");
101 my $prefix = File::Spec->rel2abs("$pwd/third-party/installed");
102 my $hc_pkg = File::Spec->rel2abs("$pwd/util/ghc-pkg-wrapper$Config{_exe}");
104 if ($Config{osname} eq 'cygwin') {
105 # NB. We're exploiting for's aliasing of variables.
106 foreach my $path ($runcompiler, $prefix, $hc_pkg) {
107 $path = `cygpath -m $path`; chomp $path;
111 mkdir $prefix unless -d $prefix;
113 # On Win32, a very broken heuristics in Cabal forced us to fake a
114 # gcc-lib\ld.exe under pugs path.
115 my ($ghc_inst_path, $ghc_bin_path, $hsc2hs);
116 if ($^O eq 'MSWin32') {
117 foreach my $args (@{$opts->{SETUP}}) {
118 $args =~ /^--with-hsc2hs=((.*[\\\/]).*)/ or next;
119 $hsc2hs = $1;
120 $ghc_inst_path = $ghc_bin_path = $2;
121 $ghc_inst_path =~ s{[/\\]bin[/\\]?$}{};
122 $ENV{PATH} = "$ENV{PATH};$ghc_inst_path;$ghc_bin_path";
124 if (!-e "gcc-lib/ld.exe" and -e "$ghc_inst_path/gcc-lib/ld.exe") {
125 mkdir "gcc-lib";
126 copy("$ghc_inst_path/gcc-lib/ld.exe" => "gcc-lib/ld.exe");
129 die "Cannot obtain gcc-lib/ld.exe" unless -e "gcc-lib/ld.exe";
130 warn "GHC installation path: $ghc_inst_path\n";
131 warn "GHC bin path: $ghc_bin_path\n";
132 warn "Runcompile: $runcompiler\n";
134 else {
135 foreach my $args (@{$opts->{SETUP}}) {
136 $args =~ /^--with-hsc2hs=((.*[\\\/]).*)/ or next;
137 $hsc2hs = $1;
138 $ghc_bin_path = $2;
142 $hsc2hs ||= $ENV{HSC2HS};
143 $AR_EXE = $Config{full_ar} || File::Spec->catfile($ghc_bin_path, "ar$Config{_exe}");
145 my @configure_args = (
146 ($want_profiling ? '--enable-library-profiling' : ()),
147 '--with-compiler=' . $runcompiler,
148 '--with-hc-pkg=' . $hc_pkg,
149 '--with-hsc2hs=' . $hsc2hs,
150 '--prefix=' . $prefix
153 =begin Judy
155 # Judy library
156 chdir "third-party/judy/Judy-1.0.3";
157 copy('src/Judy.h', '../../HsJudy');
159 if ($^O eq 'MSWin32') {
160 chdir 'src';
161 $ENV{CC} = "$ghc_inst_path\\gcc";
162 $ENV{COPT} = "-I$ghc_inst_path\\include\\mingw -I$ghc_inst_path\\gcc-lib\\include " .
163 "-B$ghc_inst_path\\gcc-lib";
164 warn "\nCC = $ENV{CC}\nCOPT = $ENV{COPT}\n";
165 system("nmake /F Makefile.win32 /NOLOGO");
166 chdir '..';
167 } else {
168 #if (!-e "src/obj/.libs/libJudy.a") {
169 my $make = $Config{make};
171 # Judy at this moment wants GNU make.
172 $make = 'gmake' unless `$make --version` =~ /GNU/;
174 system("./configure") unless -e "src/Makefile";
175 #system("$make clean");
176 chdir 'src';
177 system("$make");
178 chdir '..';
179 #mkdir("../../installed") if !-d "../../installed";
181 #copy('src/obj/.libs/libJudy.a', '../../installed') unless -e '../../installed/libJudy.a';
182 #copy('src/obj/.libs/libJudy.a', '../../HsJudy') unless -e '../../HsJudy/libJudy.a';
185 chdir "../../..";
187 =cut
189 foreach my $module (qw< HsSyck hsregex >) {
190 if ( my ($archive_dir) = (
191 glob("third-party/installed/*/$module-*"),
192 glob("third-party/installed/*/pugs-$module-*"),
193 )) {
194 my $_a = ($want_profiling ? '_p.a' : '.a');
195 my $oldest_a_file = max(
196 map {-M $_} (
197 glob("$archive_dir/*$_a"),
198 glob("$archive_dir/*/*$_a"),
199 glob("$archive_dir/*/*/*$_a"),
203 my $newest_hs_file;
204 my $wanted = sub {
205 return unless /\.hsc?$/ or /\.cabal$/;
206 $newest_hs_file = -M $_ if !$newest_hs_file or -M $_ < $newest_hs_file;
208 find $wanted, "third-party/$module";
210 if ($newest_hs_file and $oldest_a_file and $newest_hs_file >= $oldest_a_file) {
211 # We are safe - no rebuild needed, but expose anyway
212 print "*** Skipping building the '$module' dependency.\n\n";
213 system($hc_pkg, expose => "pugs-$module");
214 next;
218 chdir "third-party/$module";
220 warn join ' ', ("../../Setup$Config{_exe}", 'configure', @configure_args), $/;
221 if (-e '.setup-config') {
222 system("../../Setup$Config{_exe}", 'configure', @configure_args);
223 system("../../Setup$Config{_exe}", 'unregister');
224 system("../../Setup$Config{_exe}", 'clean');
227 system("../../Setup$Config{_exe}", 'configure', @configure_args);
229 print "*** Building the '$module' dependency. Please wait...\n\n";
231 system("../../Setup$Config{_exe}", 'build');
232 system("../../Setup$Config{_exe}", 'install');
233 chdir $pwd;
235 my ($archive_dir) = (
236 glob("third-party/installed/*/pugs-$module-*"),
237 glob("third-party/installed/*/$module-*"),
238 glob("third-party/installed/pugs-$module-*"),
239 glob("third-party/installed/$module-*"),
240 ) or die "Installation failed for $module";
242 foreach my $a_file (
243 glob("$archive_dir/*.a"),
244 glob("$archive_dir/*/*.a"),
245 glob("$archive_dir/*/*/*.a"),
247 system($AR_EXE, s => $a_file) unless $^O eq 'MSWin32';
250 system($hc_pkg, expose => "pugs-$module");
253 =begin Judy
255 # Embedding Judy object files in HsJudy
256 my ($archive_dir) = (
257 glob("third-party/installed/*/pugs-HsJudy-*"),
258 glob("third-party/installed/*/HsJudy-*"),
259 glob("third-party/installed/pugs-HsJudy-*"),
260 glob("third-party/installed/HsJudy-*"),
263 my @archive_files = (
264 glob("$archive_dir/*.a"),
265 glob("$archive_dir/*/*.a"),
266 glob("$archive_dir/*/*/*.a"),
269 my @o_files = map { glob("third-party/judy/Judy-1.0.3/src/$_/*.o"), }
270 qw( Judy1 JudyHS JudyCommon JudyL JudySL );
272 print "Embedding @o_files into @archive_files\n";
273 system($AR_EXE, "-r", $_, @o_files) for @archive_files;
275 if ($Config{ranlib} ne ':') {
276 system(split(/ /,$Config{ranlib}), $_) for @archive_files;
279 =cut
281 print "*** Finished building dependencies.\n\n";
283 $run_setup = sub { system($setup, @_) };
284 $run_setup->('configure', '--user', @configure_args, grep { !/^--.*=$/ } @{$opts->{SETUP}});
286 build_lib($version, $ghc, @args);
288 # Cabal wants to copy LICENSE/GPL-3 to .../LICENSE/GPL-3
289 mkdir "third-party/installed/share/doc/Pugs-$version";
290 mkdir "third-party/installed/share/doc/Pugs-$version/LICENSE";
291 $run_setup->('install');
293 if ($Config{ranlib} ne ':') {
294 system(split(/ /,$Config{ranlib}), $_)
295 for glob("third-party/installed/lib/Pugs-$version/*.a");
298 build_exe($version, $runcompiler, $ghc_version, @args);
300 if ($want_profiling) {
301 $want_profiling = 0;
302 build_exe($version, $runcompiler, $ghc_version, @args);
305 # determine if prelude wants be precompiled first,
306 # then does so if it needs to be
307 return unless PugsBuild::Config->lookup('precompile_prelude');
309 my $ppc_yml = "blib6/lib/Prelude.pm.yml";
311 my @depends = qw(
312 src/perl6/Prelude.pm
313 src/Pugs/Prelude.hs
314 src/Pugs/AST/Internals/Instances.hs
315 src/DrIFT/YAML.hs
316 util/build_pugs.pl
319 if ((!-s $ppc_yml) or grep { -e $_ and (-M $ppc_yml > -M $_) } @depends) {
320 # can't assume blib6/lib exists: the user may be running
321 # `make unoptimised` which doesn't create it.
322 mkpath(dirname($ppc_yml));
324 # also, remove all .pm.yml files there too.
325 unlink($_) for glob(File::Spec->catfile(dirname($ppc_yml), "*.pm.yml"));
327 # finally regenerate the prelude.
328 run($^X, qw<util/gen_prelude.pl -v -i src/perl6/Prelude.pm>,
329 (map { ('-i' => $_) } @{ PugsBuild::Config->lookup('precompile_modules') }),
330 '-p', $thispugs, '--output', $ppc_yml);
334 sub gzip_file {
335 my ($in, $out) = @_;
336 require Compress::Zlib;
337 open my $ifh, "<", $in or die "open: $in: $!";
338 open my $ofh, ">", $out or die "open: $out: $!";
339 binmode $ofh;
340 my $gz = Compress::Zlib::gzopen($ofh, "wb") or
341 die "gzopen: $Compress::Zlib::gzerrno";
342 while (<$ifh>) {
343 $gz->gzwrite($_) or die "gzwrite: $Compress::Zlib::gzerrno";
345 $gz->gzclose;
346 unlink $in;
350 sub build_lib {
351 my $version = shift;
352 my $ghc = shift;
354 my @a_file = File::Spec->rel2abs("dist/build/libHSPugs-$version.a");
355 push @a_file, File::Spec->rel2abs("dist/build/libHSPugs-${version}_p.a") if $want_profiling;
357 # Add GHC to PATH
358 local $ENV{PATH} = dirname($ghc) . $Config{path_sep} . $ENV{PATH};
360 run($^X, qw<util/version_h.pl>);
362 mkdir "dist/build" unless -d "dist/build";
364 # Remove all -boot files since GHC 6.4 doesn't track them.
365 # This is not needed for GHC 6.5 which doesn't produce them anyway.
366 # Also, remove Version.o and Version.hi so -v will report correctly.
367 my $wanted = sub {
368 return unless $_ =~ /-boot$/ or $_ =~ /Version\.\w+$/;
369 unlink $_;
371 find $wanted, "dist/build";
373 unlink $_ for @a_file;
374 $run_setup->('build');
375 (-e or die "Build failed for '$_': $?") for @a_file;
377 my $fixup = sub {
378 my $module = shift; # eg. "Data.Yaml.Syck"
379 my $pathname = $module;
380 $pathname =~ s!\.!/!g;
381 $pathname .= '_stub.o';
382 my $basename = $pathname;
383 $basename =~ s!.*/!!;
385 # XXX - work around Cabal bug --
386 # we have to locate "Syck_stub.o" and copy it into
387 # dist/build/src/Data/Yaml/.
388 my @candidates;
389 my $target = File::Spec->canonpath(
390 File::Spec->catfile(qw< dist build src >, $pathname)
392 my $wanted = sub {
393 return unless $_ eq $basename;
394 push @candidates, $File::Find::name;
396 find $wanted, "dist";
398 if (@candidates > 1) {
399 # This is harmless -- so we don't do anything.
400 # warn "*** Found more than one '$basename' -- using the first one. \n";
402 elsif (@candidates == 0) {
403 warn "*** Wasn't able to find '$basename' (this may be a problem)...\n";
404 return;
407 unless( File::Spec->canonpath($candidates[0]) eq $target ) {
408 mkpath(($target =~ m!(.*[/\\])!)[0]); # create dir for target
409 copy($candidates[0] => $target)
410 or die "Copy '$candidates[0]' => '$target' failed: $!";
413 for (@a_file) {
414 print "==> $AR_EXE r $_ $target\n";
415 system($AR_EXE, r => $_, $target);
419 $fixup->('Pugs.Embed.Perl5') if grep /^-DPUGS_HAVE_PERL5$/, @_;
420 $fixup->('Pugs.Embed.Parrot') if grep /^-DPUGS_HAVE_PARROT$/, @_;
422 foreach my $a_ext (grep { /\.a$/ and !/^-/ } @_) {
423 # Do some very sneaky things -- linking other .a with us!
424 my $basename = $a_ext;
425 $basename =~ s!.*/!!;
426 my $dir = "dist/tmp-$basename";
427 mkdir $dir;
428 chdir $dir;
429 system($AR_EXE, x => $a_ext);
430 for (@a_file) {
431 print "==> $AR_EXE r $_ @{[glob('*')]}\n";
432 system($AR_EXE, r => $_, glob("*"));
434 unlink(glob("*"));
435 chdir '..';
436 chdir '..';
437 rmdir $dir;
440 # Run ranlib.
441 if ($Config{ranlib} ne ':') {
442 system(split(/ /,$Config{ranlib}), $_) for @a_file;
446 sub packages {
447 map { +("-package" => $_) } @_;
450 sub build_exe {
451 my $version = shift;
452 my $ghc = shift;
453 my $ghc_version = shift;
455 my @pkgs = '-hide-all-packages';
457 my $push_pkgs = sub { push @pkgs, packages(@_) };
459 #my @o = qw( src/pcre/pcre.o src/syck/bytecode.o src/syck/emitter.o src/syck/gram.o src/syck/handler.o src/syck/implicit.o src/syck/node.o src/syck/syck.o src/syck/syck_st.o src/syck/token.o src/syck/yaml2byte.o src/cbits/fpstring.o );
460 #push @o, 'src/UnicodeC.o' if grep /WITH_UNICODEC/, @_;
461 #system $ghc, '--make', @_, @o, '-o' => 'pugs', 'src/Main.hs';
463 $push_pkgs->(qw{
464 stm network mtl filepath base HsSyck
465 containers bytestring random process directory
466 time array pretty parsec template-haskell
467 pugs-hsregex Pugs
470 if ($^O =~ /(?:MSWin32|mingw|msys|cygwin)/) {
471 $push_pkgs->('Win32');
473 else {
474 $push_pkgs->('unix');
476 $push_pkgs->('readline') if grep /^-DPUGS_HAVE_READLINE$/, @_;
477 $push_pkgs->(qw(plugins haskell-src)) if grep /^-DPUGS_HAVE_HSPLUGINS$/, @_;
478 my @libs = "-lHSPugs-$version" . ($want_profiling ? '_p' : '');
479 push @libs, grep /^-opt/, @_;
480 push @libs, grep /^-[lL]/, @_;
481 push @libs, grep /\.(?:a|o(?:bj)?|\Q$Config{so}\E)$/, @_;
482 push @libs, grep /^-auto/, @_;
484 # XXX - Hack to work around Cabal's semibroken profiling lib support!
485 my $out = "pugs$Config{_exe}";
487 if ($want_profiling) {
488 $out = "pugs-prof$Config{_exe}";
489 push @libs, '-prof';
490 push @pkgs, glob('third-party/HsSyck/dist/build/syck/*.o'), qw( third-party/hsregex/dist/build/pcre/pcre.o );
492 else {
493 push @libs, grep /^-threaded/, @_;
496 # Force relinking of Main.hs to avoid stale dependencies in .hi
497 unlink 'src/Main.o';
498 unlink 'src/Main.hi';
500 @_ = ('--make', @pkgs, qw(-optl-Lthird-party/installed -o ), "$out.new", qw( src/Main.hs ), @libs);
501 #@_ = (@pkgs, qw(-idist/build -Ldist/build -idist/build/src -Ldist/build/src -o pugs src/Main.hs), @libs);
502 print "*** Building: ", join(' ', $ghc, @_), $/;
503 system $ghc, @_;
505 die "Build failed: $?" unless -e "$out.new";
507 if (-e $out) {
508 unlink $out or die "Cannot remove $out: $!";
511 rename "$out.new" => $out;
514 sub write_buildinfo {
515 my ($version, $ghc, $ghc_pkg, $ghc_version, @args) = @_;
517 open IN, "< Pugs.cabal.in" or die $!;
518 open OUT, "> Pugs.cabal" or die $!;
520 my $depends = '';
521 my $add_dep = sub { $depends .= ", $_" for @_ };
522 my $cond_dep = sub {
523 my $arg = shift;
524 $add_dep->(@_) if grep /^-D\Q$arg\E$/, @args;
527 if ($^O =~ /(?:MSWin32|mingw|msys|cygwin)/) {
528 $add_dep->('Win32 -any');
530 else {
531 $add_dep->('unix -any');
534 $cond_dep->('PUGS_HAVE_HSPLUGINS', 'plugins -any', 'haskell-src -any');
535 $cond_dep->('PUGS_HAVE_READLINE', 'readline -any');
537 my $perl5_c = '';
538 if (grep /^-DPUGS_HAVE_PERL5$/, @args) {
539 $perl5_c = 'src/perl5/p5embed.c';
542 my $parrot_c = '';
543 if (grep /^-DPUGS_HAVE_PARROT$/, @args) {
544 $parrot_c = 'src/pge/parrotembed.c';
547 # Remove -Wl flags in Perl5 embedding.
548 @args = grep { !/^-W/ } @args;
550 # Remove -threaded if we are building profiled.
551 @args = grep { !/^-threaded/ } @args if $want_profiling;
553 my @include_dirs = grep { -d $_ }
554 map File::Spec->canonpath(substr($_, 2)),
555 grep /^-I/, @args;
556 my @lib_dirs = grep { -d $_ }
557 map File::Spec->canonpath(substr($_, 2)),
558 grep /^-L/, @args;
559 my @libs = map substr($_, 2), grep /^-l/, @args;
560 #push @libs, grep /\.(?:a|o(?:bj)?)$/, @args;
562 my $has_new_cabal = (`$ghc_pkg describe Cabal` =~ /version: 1\.[1-9]/i);
564 while (<IN>) {
565 # Adjust the dependency line based on Cabal version
566 s/HsSyck -any, //;
567 s/pugs-hsregex -any, //;
568 s/__OPTIONS__/@args/;
569 s/__VERSION__/$version/;
570 s/__DEPENDS__/$depends/;
571 s/__PERL5_C__/$perl5_c/;
572 s/__PARROT_C__/$parrot_c/;
573 s/__INCLUDE_DIRS__/@include_dirs/;
574 s/__LIBS__/@libs/;
575 s/__LIB_DIRS__/@lib_dirs/;
576 print OUT $_;
579 close IN;
580 close OUT;
583 sub classify_options {
584 my($kind, %opts);
585 for (@_) {
586 # we can't use +SEGMENT and -SEGMENT since that interferes with GHC.
587 $kind = $1, next if /^_\+(.*)/; # _+SEGMENT start
588 undef $kind, next if $_ eq "_-$kind"; # _-SEGMENT end
590 s/^__(.*)__$/PugsBuild::Config->lookup($1)/e;
592 die "don't know where this option belongs: $_" unless $kind;
593 push @{ $opts{$kind} }, $_;
595 \%opts;
598 sub run {
599 print ((join " ", @_) . "\n");
600 system @_ and die (sprintf "system: [%s]: $!", join " ", @_);
603 sub copy_all {
604 my ($src, $dest) = @_;
605 mkpath($dest);
606 local *DIR;
607 opendir(DIR, $src) or die $!;
608 my @nodes = readdir(DIR);
609 foreach my $node (sort @nodes) {
610 next if $node =~ /^(\.|\.\.|\.svn|src)$/;
611 my $src_path = "$src/$node";
612 my $dest_path = "$dest/$node";
613 if (-f $src_path) {
614 copy($src_path, $dest_path);
616 if (-d $src_path) {
617 copy_all($src_path, $dest_path);