Mattia Barbon recently implemented the capability to group multiple dynamic PMCs into a single library. It took me a while, but the correct way of using it finally percolated through my thick skull. One remaining problem is that the build process is very platform-dependent. This patch doesn't fix that, but it does eliminate the gmake dependency. Another problem is that you have to specifically write Makefile rules to build your group of dynamic PMCs into a library, and that is very difficult to do portably.
This patch introduces something that feels suspiciously like libtool, despite the fact that libtool has never been very kind to me. But for now I am targeting this only at the dynamic PMC generation problem; this solution could be expanded to ease porting of other parts of the build procedure, but I think other people are already working on that. The patch adds an additional target to config/gen/makefiles.pl: instead of just converting config/gen/makefiles/dynclasses.in to dynclasses/Makefile, it also converts config/gen/makefiles/dynclasses.pl.in to dynclasses/build.pl, and changes that Makefile to call build.pl to do all the real work. It is thus able to pick up config/init/data.pl's notions of all of the ${cc}, ${ld}, etc. definitions, but leaves the description of which PMCs to build with the original Makefile (which probably isn't the greatest place, but I'm trying to change as little as possible.) My guess is that this will not immediately cause dynamic PMCs to start working on the platforms where they do not currently work, but it should make it easier to get them to work. It also implements a new pmclass attribute in .pmc files (only meaningful for dynamic PMCs): C<group GROUPNAME>, which will get automatically picked up by the new dynclasses/build.pl to generate a single shared library out of all PMCs with the same group tag. So to implement two new dynamic PMCs 'mylangPmc1' and 'mylangPmc2', you would: * Implement the .pmc files, and include 'group mylang' in their pmclass lines * Add mylangPmc1 and mylangPmc2 to config/gen/makefiles/dynclasses.in * Re-run Configure.pl That is the same procedure as is currently used to implement independent dynamic PMCs right now, except for the addition of the 'group mylang' tag. I am not committing this patch directly because I know that other people are currently actively working on the dynamic PMC stuff and the build system, and I didn't want to step on anyone's toes. Note that build.pl is NOT a general build tool, although it covers everything needed for the dynclasses/ directory. At the moment, it doesn't even bother to do dependency analysis for the grouped PMCs, although it does for all of the rest. Still, this patch gets stuff working that currently doesn't exist, and doesn't break anything that currently works AFAIK. I fully expect (and hope) that it will be replaced by something more general someday. But I'd rather not wait for that day, having first-hand experience with how much "fun" it is to get partial linking of dynamic libraries working on multiple platforms.
Index: config/gen/makefiles.pl =================================================================== RCS file: /cvs/public/parrot/config/gen/makefiles.pl,v retrieving revision 1.34 diff -u -r1.34 makefiles.pl --- config/gen/makefiles.pl 19 Jun 2004 09:33:09 -0000 1.34 +++ config/gen/makefiles.pl 5 Sep 2004 22:28:23 -0000 @@ -81,6 +81,8 @@ commentType => '#', replace_slashes => 1); genfile('config/gen/makefiles/dynclasses.in', 'dynclasses/Makefile', commentType => '#', replace_slashes => 1); + genfile('config/gen/makefiles/dynclasses.pl.in', 'dynclasses/build.pl', + commentType => '#', replace_slashes => 0); genfile('config/gen/makefiles/dynoplibs.in', 'dynoplibs/Makefile', commentType => '#', replace_slashes => 1); genfile('config/gen/makefiles/parrot_compiler.in', 'languages/parrot_compiler/Makefile', Index: classes/pmc2c2.pl =================================================================== RCS file: /cvs/public/parrot/classes/pmc2c2.pl,v retrieving revision 1.16 diff -u -r1.16 pmc2c2.pl --- classes/pmc2c2.pl 22 Aug 2004 09:15:51 -0000 1.16 +++ classes/pmc2c2.pl 5 Sep 2004 22:28:24 -0000 @@ -135,12 +135,6 @@ Used with C<abstract>: No C<class_init> code is generated. -=item C<dynpmc> - -The class is a dynamic class. These have a special C<class_init> -routine suitable for dynamic loading at runtime. See the F<dynclasses> -directory for an example. - =item C<const_too> Classes with this flag get 2 vtables and 2 enums, one pair with @@ -164,6 +158,18 @@ library ref +=item C<dynpmc> + +The class is a dynamic class. These have a special C<class_init> +routine suitable for dynamic loading at runtime. See the F<dynclasses> +directory for an example. + +=item C<group GROUP> + +The class is part of a group of interrelated PMCs that should be +compiled together into a single shared library of the given name. Only +valid for dynamic PMCs. + =back =item 3. @@ -318,7 +324,7 @@ my $c = shift; $$c =~ s/^(.*?^\s*)pmclass ([\w]*)//ms; my ($pre, $classname) = ($1, $2); - my %has_value = ( does => 1, extends => 1 ); + my %has_value = ( does => 1, extends => 1, group => 1 ); my %flags; # look through the pmc declaration header for flags such as noinit Index: config/init/data.pl =================================================================== RCS file: /cvs/public/parrot/config/init/data.pl,v retrieving revision 1.31 diff -u -r1.31 data.pl --- config/init/data.pl 10 Jul 2004 07:13:43 -0000 1.31 +++ config/init/data.pl 5 Sep 2004 22:28:24 -0000 @@ -32,6 +32,7 @@ package Configure::Data; use Config; use Data::Dumper; + use FindBin; # see build_dir # We need a Glossary somewhere! @@ -40,6 +41,8 @@ optimize => $optimize ? $Config{optimize} : '', verbose => $verbose, + build_dir => $FindBin::Bin, + # Compiler -- used to turn .c files into object files. # (Usually cc or cl, or something like that.) cc => $Config{cc}, Index: config/gen/makefiles/dynclasses.in =================================================================== RCS file: /cvs/public/parrot/config/gen/makefiles/dynclasses.in,v retrieving revision 1.5 diff -u -r1.5 dynclasses.in --- config/gen/makefiles/dynclasses.in 25 Apr 2004 10:47:41 -0000 1.5 +++ config/gen/makefiles/dynclasses.in 5 Sep 2004 22:28:24 -0000 @@ -1,41 +1,27 @@ -# -# sample Makefile -# -LD = ${ld} -LD_SHARED = ${ld_shared} PERL = ${perl} RM_F = ${rm_f} SO = ${so} -CFLAGS = ${ccflags} ${cc_debug} ${ccwarn} ${cc_hasjit} ${cg_flag} ${gc_flag} - # add your dynamic pmcs here -all: foo$(SO) subproxy$(SO) \ -tclobject$(SO) tclstring$(SO) tclint$(SO) tclfloat$(SO) \ -tcllist$(SO) tclarray$(SO) - -.SUFFIXES: .pmc .c $(SO) - -# preserve .c if needed -.PRECIOUS: %.c - -%.c : %.pmc - $(PERL) ..${slash}classes${slash}pmc2c2.pl --dump $< - $(PERL) ..${slash}classes${slash}pmc2c2.pl --c $< - -%$(SO) : %.c - $(LD) $(CFLAGS) $(LD_SHARED) $(LD_SHARED_FLAGS) $(LDFLAGS) \ - ${cc_o_out}$@ \ - -I..${slash}include -I..${slash}classes \ - -L..${slash}blib${slash}lib -lparrot $< - $(PERL) -MFile::Copy=cp -e ${PQ}cp q|$@|, q|../runtime/parrot/dynext/$@|${PQ} +PMCS = foo subproxy \ +tclobject tclstring tclint tclfloat \ +tcllist tclarray \ +match matchrange + +BUILD = ${perl} build.pl + +all : + $(BUILD) generate $(PMCS) + $(BUILD) compile $(PMCS) + $(BUILD) linklibs $(PMCS) + $(BUILD) copy --destination=../runtime/parrot/dynext $(PMCS) clean : - $(RM_F) *.c *.h *$(SO) *.dump + $(RM_F) *.c *.h *$(SO) *.dump lib-* realclean: clean - $(RM_F) Makefile + $(RM_F) Makefile build.pl distclean: realclean Index: config/gen/makefiles/dynclasses.pl.in =================================================================== RCS file: config/gen/makefiles/dynclasses.pl.in diff -N config/gen/makefiles/dynclasses.pl.in --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ config/gen/makefiles/dynclasses.pl.in 6 Sep 2004 23:05:50 -0000 @@ -0,0 +1,188 @@ +use strict; +use File::Copy qw(copy move); + +# Config stuff +our $CC = "${cc} -c"; +our $LD = "${ld}"; +our $LD_SHARED = "${ld_shared}"; +our $LD_SHARED_FLAGS = "${ld_shared_flags}"; +our $LDFLAGS = "${ldflags}"; +our $PERL = "${perl}"; +our $SO = "${so}"; +our $O = "${o}"; +our $CFLAGS = "${ccflags} ${cc_debug} ${ccwarn} ${cc_hasjit} ${cg_flag} ${gc_flag}"; +our $PMC2C = "$PERL ${build_dir}${slash}classes${slash}pmc2c2.pl"; + +# Actual commands +sub compile_shared_cmd { + my ($target, $source) = @_; + "$LD $CFLAGS $LD_SHARED $LD_SHARED_FLAGS $LDFLAGS " . + "${cc_o_out}" . $target . " " . + "-I${build_dir}${slash}include -I${build_dir}${slash}classes " . + "-L${build_dir}${slash}blib${slash}lib -lparrot " . + $source; +}; + +sub compile_cmd { + my ($target, $source) = @_; + "$CC $CFLAGS " . + "${cc_o_out}" . $target . " " . + "-I${build_dir}${slash}include -I${build_dir}${slash}classes " . + $source; +}; + +sub partial_link_cmd { + my ($target, @sources) = @_; + "$LD $CFLAGS $LD_SHARED $LD_SHARED_FLAGS $LDFLAGS ". + "${cc_o_out}" . $target . " " . + join(" ", @sources); +} + +our $NOW = time; + +################### MAIN PROGRAM ################ + +my ($mode, @pmcs) = @ARGV; + +if ($mode eq 'generate') { + # Convert X.pmc -> X.dump and X.c and also create any lib-GROUP.c files + + generate_dump($_) foreach (@pmcs); + generate_c($_) foreach (@pmcs); + + my ($group_files, $pmc_group) = gather_groups(@pmcs); + + while (my ($group, $pmcs) = each %$group_files) { + my $pmcfiles = join(" ", map { "$_.pmc" } @$pmcs); + run("$PMC2C --library $group --c $pmcfiles") + or die "pmc2c library creation failed ($?)\n"; + } +} elsif ($mode eq 'compile') { + my ($group_files, $pmc_group) = gather_groups(@pmcs); + + my @grouped_pmcs = grep { exists $pmc_group->{$_} } @pmcs; + my @ungrouped_pmcs = grep { ! exists $pmc_group->{$_} } @pmcs; + + # Convert X.c -> X.so for all non-grouped X.c + compile_shared($_) foreach (@ungrouped_pmcs); + + # Convert X.c -> X.o for all grouped X.c + compile($_) foreach (@grouped_pmcs); + + # lib-GROUP.c + for my $group (keys %$group_files) { + compile("$group", "lib-$group") + or die "compile $group.c failed ($?)\n"; + } +} elsif ($mode eq 'linklibs') { + my ($group_files, $pmc_group) = gather_groups(@pmcs); + + # Convert lib-GROUP.so + A.so + B.so ... -> GROUP.so + while (my ($group, $pmcs) = each %$group_files) { + partial_link($group, "lib-$group", @$pmcs) + or die "partial link of $group failed ($?)\n"; + } +} elsif ($mode eq 'copy') { + # Copy *.so -> destination, where destination is the first + # argument, given as --destination=DIRECTORY + shift(@pmcs) =~ /--destination=(.*)/ + or die "copy command requires destination"; + my $dest = $1; + + my ($group_files, $pmc_group) = gather_groups(@pmcs); + + foreach (@pmcs, keys %$group_files) { + copy("$_$SO", $dest) or die "Copy $_$SO failed ($?)\n"; + } +} else { + die "invalid command '$mode'\nmust be one of generate, compile, linklibs, or copy\n"; +} + +sub run { + print join(" ", @_), "\n"; + return system(@_) == 0; +} + +sub gather_groups { + my %group_files; + my %pmc_group; + for my $pmc (@_) { + our $class; + require "$pmc.dump"; + my $group = $class->{flags}{group} + or next; + ($group) = keys %$group; + $pmc_group{$pmc} = $group; + push @{ $group_files{$group} }, $pmc; + } + + return (\%group_files, \%pmc_group); +} + +sub modtime { + my $ago = (-M shift); + if (defined $ago) { + return $NOW - $ago; + } else { + return; + } +} + +sub needs_build { + my ($target, @sources) = @_; + my $target_mod = modtime($target) + or return 1; + for my $source (@sources) { + return 1 if modtime($source) >= $target_mod; + } + return 0; +} + +sub generate_dump { + my ($pmc) = @_; + + if (needs_build("$pmc.dump", "$pmc.pmc")) { + run("$PMC2C --dump $pmc.pmc") + or die "pmc2c dump failed ($?)\n"; + } else { + print "$pmc.dump is up to date\n"; + } +} + +sub generate_c { + my ($pmc) = @_; + + if (needs_build("$pmc.c", "$pmc.pmc")) { + run("$PMC2C --c $pmc.pmc") + or die "pmc2c code generation failed ($?)\n"; + } +} + +sub compile { + my ($src_stem, $dest_stem) = @_; + $dest_stem ||= $src_stem; + if (needs_build("$dest_stem$O", "$src_stem.c")) { + run(compile_cmd("$dest_stem$O", "$src_stem.c")) + or die "compile $src_stem.c failed ($?)\n"; + } +} + +sub compile_shared { + my ($src_stem, $dest_stem) = @_; + $dest_stem ||= $src_stem; + if (needs_build("$dest_stem$SO", "$src_stem.c")) { + run(compile_shared_cmd("$dest_stem$SO", "$src_stem.c")) + or die "compile $src_stem.c failed ($?)\n"; + } else { + print "$dest_stem$SO is up to date\n"; + } +} + +sub partial_link { + my ($group, @stems) = @_; + my @sources = map { "$_$O" } @stems; + if (needs_build("$group$SO", @sources)) { + run(partial_link_cmd("$group$SO", @sources)) + or die "partial link $group$SO failed ($?)\n"; + } +}