ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/App-Staticperl/mkbundle
Revision: 1.42
Committed: Mon Aug 7 03:04:13 2023 UTC (9 months, 1 week ago) by root
Branch: MAIN
Changes since 1.41: +42 -19 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     #############################################################################
4     # cannot load modules till after the tracer BEGIN block
5    
6 root 1.29 our $VERBOSE = 1;
7     our $STRIP = "pod"; # none, pod or ppi
8 root 1.41 our $COMPRESS = "lzf";
9 root 1.42 our $KEEPNL = 0;
10 root 1.29 our $UNISTRIP = 1; # always on, try to strip unicore swash data
11     our $PERL = 0;
12 root 1.9 our $APP;
13 root 1.29 our $VERIFY = 0;
14     our $STATIC = 0;
15     our $PACKLIST = 0;
16     our $IGNORE_ENV = 0;
17     our $ALLOW_DYNAMIC = 0;
18     our $HAVE_DYNAMIC; # maybe useful?
19 root 1.37 our $EXTRA_CFLAGS = "";
20     our $EXTRA_LDFLAGS = "";
21     our $EXTRA_LIBS = "";
22 root 1.1
23 root 1.11 our $OPTIMISE_SIZE = 0; # optimise for raw file size instead of for compression?
24    
25     our $CACHE;
26 root 1.39 our $CACHEVER = 2; # do not change unless you know what you are doing
27 root 1.11
28 root 1.1 my $PREFIX = "bundle";
29     my $PACKAGE = "static";
30    
31     my %pm;
32 root 1.6 my %pmbin;
33 root 1.1 my @libs;
34     my @static_ext;
35     my $extralibs;
36 root 1.10 my @staticlibs;
37     my @incext;
38 root 1.1
39     @ARGV
40     or die "$0: use 'staticperl help' (or read the sources of staticperl)\n";
41    
42 root 1.11 # remove "." from @INC - staticperl.sh does it for us, but be on the safe side
43     BEGIN { @INC = grep !/^\.$/, @INC }
44    
45 root 1.1 $|=1;
46    
47     our ($TRACER_W, $TRACER_R);
48    
49 root 1.12 sub find_incdir($) {
50 root 1.1 for (@INC) {
51     next if ref;
52     return $_ if -e "$_/$_[0]";
53     }
54    
55     undef
56     }
57    
58 root 1.12 sub find_inc($) {
59     my $dir = find_incdir $_[0];
60    
61     return "$dir/$_[0]"
62     if defined $dir;
63    
64     undef
65     }
66    
67 root 1.1 BEGIN {
68     # create a loader process to detect @INC requests before we load any modules
69     my ($W_TRACER, $R_TRACER); # used by tracer
70    
71     pipe $R_TRACER, $TRACER_W or die "pipe: $!";
72     pipe $TRACER_R, $W_TRACER or die "pipe: $!";
73    
74     unless (fork) {
75     close $TRACER_R;
76     close $TRACER_W;
77    
78 root 1.16 my $pkg = "pkg000000";
79    
80 root 1.1 unshift @INC, sub {
81 root 1.12 my $dir = find_incdir $_[1]
82 root 1.1 or return;
83    
84     syswrite $W_TRACER, "-\n$dir\n$_[1]\n";
85    
86 root 1.34 open my $fh, "<:raw:perlio", "$dir/$_[1]"
87 root 1.1 or warn "ERROR: $dir/$_[1]: $!\n";
88    
89     $fh
90     };
91    
92     while (<$R_TRACER>) {
93     if (/use (.*)$/) {
94     my $mod = $1;
95 root 1.25 my $eval;
96    
97     if ($mod =~ /^'.*'$/ or $mod =~ /^".*"$/) {
98     $eval = "require $mod";
99     } elsif ($mod =~ y%/.%%) {
100     $eval = "require q\x00$mod\x00";
101     } else {
102     my $pkg = ++$pkg;
103     $eval = "{ package $pkg; use $mod; }";
104     }
105    
106 root 1.17 eval $eval;
107 root 1.1 warn "ERROR: $@ (while loading '$mod')\n"
108     if $@;
109     } elsif (/eval (.*)$/) {
110     my $eval = $1;
111     eval $eval;
112     warn "ERROR: $@ (in '$eval')\n"
113     if $@;
114     }
115 root 1.12
116     syswrite $W_TRACER, "\n";
117 root 1.1 }
118    
119     exit 0;
120     }
121     }
122    
123     # module loading is now safe
124 root 1.5
125 root 1.12 sub trace_parse {
126 root 1.1 for (;;) {
127     <$TRACER_R> =~ /^-$/ or last;
128     my $dir = <$TRACER_R>; chomp $dir;
129     my $name = <$TRACER_R>; chomp $name;
130    
131     $pm{$name} = "$dir/$name";
132 root 1.12
133     print "+ found potential dependency $name\n"
134     if $VERBOSE >= 3;
135 root 1.1 }
136     }
137    
138 root 1.12 sub trace_module {
139     print "tracing module $_[0]\n"
140     if $VERBOSE >= 2;
141    
142     syswrite $TRACER_W, "use $_[0]\n";
143     trace_parse;
144     }
145    
146 root 1.1 sub trace_eval {
147 root 1.12 print "tracing eval $_[0]\n"
148     if $VERBOSE >= 2;
149    
150 root 1.1 syswrite $TRACER_W, "eval $_[0]\n";
151 root 1.12 trace_parse;
152 root 1.1 }
153    
154     sub trace_finish {
155     close $TRACER_W;
156     close $TRACER_R;
157     }
158    
159     #############################################################################
160     # now we can use modules
161    
162     use common::sense;
163 root 1.11 use Config;
164 root 1.1 use Digest::MD5;
165    
166 root 1.11 sub cache($$$) {
167     my ($variant, $src, $filter) = @_;
168    
169 root 1.12 if (length $CACHE and 2048 <= length $src and defined $variant) {
170 root 1.11 my $file = "$CACHE/" . Digest::MD5::md5_hex "$CACHEVER\x00$variant\x00$src";
171    
172 root 1.34 if (open my $fh, "<:raw:perlio", $file) {
173 root 1.12 print "using cache for $file\n"
174     if $VERBOSE >= 7;
175    
176 root 1.11 local $/;
177     return <$fh>;
178     }
179    
180     $src = $filter->($src);
181    
182 root 1.12 print "creating cache entry $file\n"
183     if $VERBOSE >= 8;
184    
185 root 1.34 if (open my $fh, ">:raw:perlio", "$file~") {
186 root 1.11 if ((syswrite $fh, $src) == length $src) {
187     close $fh;
188     rename "$file~", $file;
189     }
190     }
191    
192     return $src;
193     }
194    
195     $filter->($src)
196     }
197    
198 root 1.1 sub dump_string {
199     my ($fh, $data) = @_;
200    
201     if (length $data) {
202 root 1.29 if ($^O eq "MSWin32") {
203     # 16 bit system, strings can't be longer than 64k. seriously.
204     print $fh "{\n";
205     for (
206     my $ofs = 0;
207     length (my $substr = substr $data, $ofs, 20);
208     $ofs += 20
209     ) {
210     $substr = join ",", map ord, split //, $substr;
211     print $fh " $substr,\n";
212     }
213     print $fh " 0 }\n";
214     } else {
215     for (
216     my $ofs = 0;
217     length (my $substr = substr $data, $ofs, 80);
218     $ofs += 80
219     ) {
220     $substr =~ s/([^\x20-\x21\x23-\x5b\x5d-\x7e])/sprintf "\\%03o", ord $1/ge;
221     $substr =~ s/\?/\\?/g; # trigraphs...
222     print $fh " \"$substr\"\n";
223     }
224 root 1.1 }
225     } else {
226     print $fh " \"\"\n";
227     }
228     }
229    
230 root 1.11 #############################################################################
231    
232     sub glob2re {
233     for (quotemeta $_[0]) {
234     s/\\\*/\x00/g;
235     s/\x00\x00/.*/g;
236     s/\x00/[^\/]*/g;
237     s/\\\?/[^\/]/g;
238    
239     $_ = s/^\\\/// ? "^$_\$" : "(?:^|/)$_\$";
240    
241     s/(?: \[\^\/\] | \. ) \*\$$//x;
242    
243     return qr<$_>s
244     }
245     }
246    
247     our %INCSKIP = (
248     "unicore/TestProp.pl" => undef, # 3.5MB of insanity, apparently just some testcase
249     );
250    
251     sub get_dirtree {
252     my $root = shift;
253    
254     my @tree;
255     my $skip;
256    
257     my $scan; $scan = sub {
258     for (sort do {
259     opendir my $fh, $_[0]
260     or return;
261     readdir $fh
262     }) {
263     next if /^\./;
264    
265     my $path = "$_[0]/$_";
266    
267     if (-d "$path/.") {
268     $scan->($path);
269     } else {
270     $path = substr $path, $skip;
271     push @tree, $path
272     unless exists $INCSKIP{$path};
273     }
274     }
275     };
276    
277     $root =~ s/\/$//;
278     $skip = 1 + length $root;
279     $scan->($root);
280    
281     \@tree
282     }
283    
284     my $inctrees;
285    
286     sub get_inctrees {
287     unless ($inctrees) {
288     my %inctree;
289     $inctree{$_} ||= [$_, get_dirtree $_] # entries in @INC are often duplicates
290     for @INC;
291     $inctrees = [values %inctree];
292     }
293    
294     @$inctrees
295     }
296 root 1.1
297 root 1.11 #############################################################################
298 root 1.1
299     sub cmd_boot {
300 root 1.29 $pm{"!boot"} = $_[0];
301 root 1.1 }
302    
303     sub cmd_add {
304 root 1.21 $_[0] =~ /^(.*?)(?:\s+(\S+))?$/
305 root 1.1 or die "$_[0]: cannot parse";
306    
307     my $file = $1;
308 root 1.24 my $as = defined $2 ? $2 : $1;
309 root 1.1
310     $pm{$as} = $file;
311 root 1.6 $pmbin{$as} = 1 if $_[1];
312 root 1.1 }
313    
314 root 1.10 sub cmd_staticlib {
315     push @staticlibs, $_
316     for split /\s+/, $_[0];
317     }
318    
319 root 1.11 sub cmd_include {
320     push @incext, [$_[1], glob2re $_[0]];
321     }
322    
323     sub cmd_incglob {
324     my ($pattern) = @_;
325    
326     $pattern = glob2re $pattern;
327    
328     for (get_inctrees) {
329     my ($dir, $files) = @$_;
330    
331     $pm{$_} = "$dir/$_"
332 root 1.14 for grep /$pattern/ && /\.(pl|pm)$/, @$files;
333 root 1.11 }
334     }
335    
336 root 1.12 sub parse_argv;
337    
338 root 1.1 sub cmd_file {
339     open my $fh, "<", $_[0]
340     or die "$_[0]: $!\n";
341    
342 root 1.12 local @ARGV;
343    
344 root 1.1 while (<$fh>) {
345     chomp;
346 root 1.12 next unless /\S/;
347     next if /^\s*#/;
348    
349     s/^\s*-*/--/;
350 root 1.1 my ($cmd, $args) = split / /, $_, 2;
351    
352 root 1.12 push @ARGV, $cmd;
353     push @ARGV, $args if defined $args;
354 root 1.1 }
355 root 1.12
356     parse_argv;
357 root 1.1 }
358    
359     use Getopt::Long;
360    
361 root 1.12 sub parse_argv {
362     GetOptions
363 root 1.37 "perl" => \$PERL,
364     "app=s" => \$APP,
365 root 1.25
366 root 1.37 "verbose|v" => sub { ++$VERBOSE },
367     "quiet|q" => sub { --$VERBOSE },
368 root 1.25
369 root 1.37 "strip=s" => \$STRIP,
370 root 1.42 "keepnl" => \$KEEPNL,
371 root 1.41 "compress=s" => \$COMPRESS,
372 root 1.37 "cache=s" => \$CACHE, # internal option
373     "eval|e=s" => sub { trace_eval $_[1] },
374     "use|M=s" => sub { trace_module $_[1] },
375     "boot=s" => sub { cmd_boot $_[1] },
376     "add=s" => sub { cmd_add $_[1], 0 },
377     "addbin=s" => sub { cmd_add $_[1], 1 },
378     "incglob=s" => sub { cmd_incglob $_[1] },
379     "include|i=s" => sub { cmd_include $_[1], 1 },
380     "exclude|x=s" => sub { cmd_include $_[1], 0 },
381     "usepacklists!" => \$PACKLIST,
382    
383     "static!" => \$STATIC,
384     "staticlib=s" => sub { cmd_staticlib $_[1] },
385     "allow-dynamic!" => \$ALLOW_DYNAMIC,
386     "ignore-env" => \$IGNORE_ENV,
387    
388     "extra-cflags=s" => \$EXTRA_CFLAGS,
389     "extra-ldflags=s" => \$EXTRA_LDFLAGS,
390     "extra-libs=s" => \$EXTRA_LIBS,
391 root 1.25
392 root 1.37 "<>" => sub { cmd_file $_[0] },
393 root 1.12 or exit 1;
394     }
395    
396 root 1.1 Getopt::Long::Configure ("bundling", "no_auto_abbrev", "no_ignore_case");
397    
398 root 1.12 parse_argv;
399 root 1.1
400 root 1.9 die "cannot specify both --app and --perl\n"
401     if $PERL and defined $APP;
402    
403 root 1.41 die "--compress must be either none or lzf\n"
404     unless $COMPRESS =~ /^(?:none|lzf)\z/;
405    
406 root 1.11 # required for @INC loading, unfortunately
407     trace_module "PerlIO::scalar";
408    
409     #############################################################################
410 root 1.14 # apply include/exclude
411 root 1.11
412     {
413     my %pmi;
414    
415     for (@incext) {
416     my ($inc, $glob) = @$_;
417    
418     my @match = grep /$glob/, keys %pm;
419    
420     if ($inc) {
421     # include
422     @pmi{@match} = delete @pm{@match};
423 root 1.12
424     print "applying include $glob - protected ", (scalar @match), " files.\n"
425     if $VERBOSE >= 5;
426 root 1.11 } else {
427     # exclude
428     delete @pm{@match};
429 root 1.12
430 root 1.14 print "applying exclude $glob - removed ", (scalar @match), " files.\n"
431 root 1.12 if $VERBOSE >= 5;
432 root 1.11 }
433     }
434    
435     my @pmi = keys %pmi;
436     @pm{@pmi} = delete @pmi{@pmi};
437     }
438    
439     #############################################################################
440 root 1.14 # scan for AutoLoader, static archives and other dependencies
441 root 1.11
442     sub scan_al {
443     my ($auto, $autodir) = @_;
444    
445     my $ix = "$autodir/autosplit.ix";
446    
447 root 1.12 print "processing autoload index for '$auto'\n"
448     if $VERBOSE >= 6;
449    
450 root 1.11 $pm{"$auto/autosplit.ix"} = $ix;
451    
452     open my $fh, "<:perlio", $ix
453     or die "$ix: $!";
454    
455     my $package;
456    
457     while (<$fh>) {
458     if (/^\s*sub\s+ ([^[:space:];]+) \s* (?:\([^)]*\))? \s*;?\s*$/x) {
459     my $al = "auto/$package/$1.al";
460     my $inc = find_inc $al;
461    
462     defined $inc or die "$al: autoload file not found, but should be there.\n";
463    
464 root 1.12 $pm{$al} = $inc;
465     print "found autoload function '$al'\n"
466     if $VERBOSE >= 6;
467 root 1.11
468     } elsif (/^\s*package\s+([^[:space:];]+)\s*;?\s*$/) {
469     ($package = $1) =~ s/::/\//g;
470     } elsif (/^\s*(?:#|1?\s*;?\s*$)/) {
471     # nop
472     } else {
473 root 1.12 warn "WARNING: $ix: unparsable line, please report: $_";
474 root 1.11 }
475     }
476     }
477    
478     for my $pm (keys %pm) {
479     if ($pm =~ /^(.*)\.pm$/) {
480     my $auto = "auto/$1";
481     my $autodir = find_inc $auto;
482    
483 root 1.12 if (defined $autodir && -d $autodir) {
484 root 1.11 # AutoLoader
485     scan_al $auto, $autodir
486     if -f "$autodir/autosplit.ix";
487    
488     # extralibs.ld
489     if (open my $fh, "<:perlio", "$autodir/extralibs.ld") {
490 root 1.12 print "found extralibs for $pm\n"
491     if $VERBOSE >= 6;
492    
493 root 1.11 local $/;
494     $extralibs .= " " . <$fh>;
495     }
496    
497     $pm =~ /([^\/]+).pm$/ or die "$pm: unable to match last component";
498    
499     my $base = $1;
500    
501     # static ext
502     if (-f "$autodir/$base$Config{_a}") {
503 root 1.12 print "found static archive for $pm\n"
504     if $VERBOSE >= 3;
505    
506 root 1.11 push @libs, "$autodir/$base$Config{_a}";
507     push @static_ext, $pm;
508     }
509    
510     # dynamic object
511 root 1.28 if (-f "$autodir/$base.$Config{dlext}") {
512 root 1.29 if ($ALLOW_DYNAMIC) {
513     my $as = "!$auto/$base.$Config{dlext}";
514 root 1.28 $pm{$as} = "$autodir/$base.$Config{dlext}";
515     $pmbin{$as} = 1;
516    
517 root 1.29 $HAVE_DYNAMIC = 1;
518 root 1.28
519 root 1.29 print "+ added dynamic object $as\n"
520 root 1.28 if $VERBOSE >= 3;
521     } else {
522 root 1.29 die "ERROR: found shared object '$autodir/$base.$Config{dlext}' but --allow-dynamic not given, aborting.\n"
523 root 1.28 }
524     }
525 root 1.12
526     if ($PACKLIST && open my $fh, "<:perlio", "$autodir/.packlist") {
527     print "found .packlist for $pm\n"
528     if $VERBOSE >= 3;
529    
530     while (<$fh>) {
531     chomp;
532 root 1.14 s/ .*$//; # newer-style .packlists might contain key=value pairs
533 root 1.12
534     # only include certain files (.al, .ix, .pm, .pl)
535     if (/\.(pm|pl|al|ix)$/) {
536     for my $inc (@INC) {
537     # in addition, we only add files that are below some @INC path
538     $inc =~ s/\/*$/\//;
539    
540     if ($inc eq substr $_, 0, length $inc) {
541     my $base = substr $_, length $inc;
542     $pm{$base} = $_;
543    
544     print "+ added .packlist dependency $base\n"
545     if $VERBOSE >= 3;
546     }
547    
548     last;
549     }
550     }
551     }
552     }
553 root 1.11 }
554     }
555     }
556    
557     #############################################################################
558    
559 root 1.12 print "processing bundle files (try more -v power if you get bored waiting here)...\n"
560     if $VERBOSE >= 1;
561    
562 root 1.41 my $compress = sub { shift };
563    
564     if ($COMPRESS eq "lzf") {
565     require Compress::LZF;
566     $compress = sub { Compress::LZF::compress_best (shift) };
567     }
568    
569 root 1.1 my $data;
570     my @index;
571     my @order = sort {
572     length $a <=> length $b
573     or $a cmp $b
574     } keys %pm;
575    
576     # sorting by name - better compression, but needs more metadata
577     # sorting by length - faster lookup
578     # usually, the metadata overhead beats the loss through compression
579    
580     for my $pm (@order) {
581     my $path = $pm{$pm};
582    
583     128 > length $pm
584 root 1.11 or die "ERROR: $pm: path too long (only 128 octets supported)\n";
585 root 1.1
586     my $src = ref $path
587     ? $$path
588     : do {
589 root 1.34 open my $pm, "<:raw:perlio", $path
590 root 1.1 or die "$path: $!";
591    
592     local $/;
593    
594     <$pm>
595     };
596    
597 root 1.11 my $size = length $src;
598    
599 root 1.6 unless ($pmbin{$pm}) { # only do this unless the file is binary
600     if ($pm =~ /^auto\/POSIX\/[^\/]+\.al$/) {
601     if ($src =~ /^ unimpl \"/m) {
602 root 1.13 print "$pm: skipping (raises runtime error only).\n"
603 root 1.12 if $VERBOSE >= 3;
604 root 1.6 next;
605     }
606 root 1.1 }
607    
608 root 1.41 $src = cache "$STRIP,$UNISTRIP,$OPTIMISE_SIZE,$COMPRESS", $src, sub {
609 root 1.11 if ($UNISTRIP && $pm =~ /^unicore\/.*\.pl$/) {
610 root 1.12 print "applying unicore stripping $pm\n"
611     if $VERBOSE >= 6;
612    
613 root 1.11 # special stripping for unicore swashes and properties
614     # much more could be done by going binary
615     $src =~ s{
616     (^return\ <<'END';\n) (.*?\n) (END(?:\n|\Z))
617     }{
618     my ($pre, $data, $post) = ($1, $2, $3);
619    
620     for ($data) {
621     s/^([0-9a-fA-F]+)\t([0-9a-fA-F]+)\t/sprintf "%X\t%X", hex $1, hex $2/gem
622     if $OPTIMISE_SIZE;
623    
624     # s{
625     # ^([0-9a-fA-F]+)\t([0-9a-fA-F]*)\t
626     # }{
627     # # ww - smaller filesize, UU - compress better
628     # pack "C0UU",
629     # hex $1,
630     # length $2 ? (hex $2) - (hex $1) : 0
631     # }gemx;
632 root 1.6
633 root 1.11 s/#.*\n/\n/mg;
634     s/\s+\n/\n/mg;
635     }
636 root 1.1
637 root 1.11 "$pre$data$post"
638     }smex;
639 root 1.1 }
640    
641 root 1.11 if ($STRIP =~ /ppi/i) {
642     require PPI;
643    
644 root 1.42 # PPI (quite correctly) treeats pod in __DATA__ as data, not pod
645    
646 root 1.11 if (my $ppi = PPI::Document->new (\$src)) {
647     $ppi->prune ("PPI::Token::Comment");
648 root 1.42
649     for my $pod (@{ $ppi->find (PPI::Token::Pod::) }) {
650     # should somehow convert to whitespace token
651     if ($KEEPNL) {
652     $pod->{content} =~ s/[^\n]//g;
653     } else {
654     $pod->{content} = '';
655     }
656     }
657 root 1.11
658     # prune END stuff
659     for (my $last = $ppi->last_element; $last; ) {
660     my $prev = $last->previous_token;
661    
662     if ($last->isa (PPI::Token::Whitespace::)) {
663     $last->delete;
664     } elsif ($last->isa (PPI::Statement::End::)) {
665     $last->delete;
666     last;
667     } elsif ($last->isa (PPI::Token::Pod::)) {
668     $last->delete;
669     } else {
670     last;
671     }
672    
673     $last = $prev;
674     }
675    
676     # prune some but not all insignificant whitespace
677     for my $ws (@{ $ppi->find (PPI::Token::Whitespace::) }) {
678     my $prev = $ws->previous_token;
679     my $next = $ws->next_token;
680    
681     if (!$prev || !$next) {
682     $ws->delete;
683     } else {
684 root 1.40 if ($next->isa (PPI::Token::Whitespace::)) {
685 root 1.42 # push this whitespace data into the next node
686     $next->{content} = "$ws->{content}$next->{content}";
687     $ws->{content} = "";
688 root 1.40 } elsif (
689 root 1.42 (
690     $next->isa (PPI::Token::Operator::) && $next->{content} =~ /^(?:,|=|!|!=|==|=>)$/ # no ., because of digits. == float
691     or $prev->isa (PPI::Token::Operator::) && $prev->{content} =~ /^(?:,|=|\.|!|!=|==|=>)$/
692     or $prev->isa (PPI::Token::Structure::)
693     or ($OPTIMISE_SIZE &&
694     ($prev->isa (PPI::Token::Word::)
695     && (PPI::Token::Symbol:: eq ref $next
696     || $next->isa (PPI::Structure::Block::)
697     || $next->isa (PPI::Structure::List::)
698     || $next->isa (PPI::Structure::Condition::)))
699     )
700     )
701     # perl has some idiotic warning about nonexisting operators (Reverse %s operator)
702     && !(
703     $prev->isa (PPI::Token::Operator::) && $prev->{content} eq "="
704     && $next->isa (PPI::Token::Operator::) && $next->{content} =~ /[+\-]/
705     )
706 root 1.11 ) {
707 root 1.42 if ($KEEPNL) {
708     $ws->{content} =~ s/[^\n]//g;
709 root 1.39 } else {
710 root 1.42 $ws->{content} = '';
711 root 1.39 }
712 root 1.11 } else {
713 root 1.42 if ($KEEPNL) {
714     $ws->{content} =~ s/[^\n]//g;
715     $ws->{content} ||= ' '; # keep at least one space
716     } else {
717     $ws->{content} = ' ';
718     }
719 root 1.11 }
720     }
721     }
722 root 1.1
723 root 1.11 # prune whitespace around blocks
724     if ($OPTIMISE_SIZE) {
725     # these usually decrease size, but decrease compressability more
726     for my $struct (PPI::Structure::Block::, PPI::Structure::Condition::) {
727     for my $node (@{ $ppi->find ($struct) }) {
728     my $n1 = $node->first_token;
729     my $n2 = $n1->previous_token;
730     $n1->delete if $n1->isa (PPI::Token::Whitespace::);
731     $n2->delete if $n2 && $n2->isa (PPI::Token::Whitespace::);
732     my $n1 = $node->last_token;
733     my $n2 = $n1->next_token;
734     $n1->delete if $n1->isa (PPI::Token::Whitespace::);
735     $n2->delete if $n2 && $n2->isa (PPI::Token::Whitespace::);
736     }
737     }
738    
739     for my $node (@{ $ppi->find (PPI::Structure::List::) }) {
740     my $n1 = $node->first_token;
741     $n1->delete if $n1->isa (PPI::Token::Whitespace::);
742     my $n1 = $node->last_token;
743     $n1->delete if $n1->isa (PPI::Token::Whitespace::);
744     }
745 root 1.6 }
746 root 1.1
747 root 1.11 # reformat qw() lists which often have lots of whitespace
748     for my $node (@{ $ppi->find (PPI::Token::QuoteLike::Words::) }) {
749     if ($node->{content} =~ /^qw(.)(.*)(.)$/s) {
750     my ($a, $qw, $b) = ($1, $2, $3);
751     $qw =~ s/^\s+//;
752     $qw =~ s/\s+$//;
753     $qw =~ s/\s+/ /g;
754     $node->{content} = "qw$a$qw$b";
755     }
756 root 1.6 }
757 root 1.11
758     $src = $ppi->serialize;
759     } else {
760     warn "WARNING: $pm{$pm}: PPI failed to parse this file\n";
761 root 1.6 }
762 root 1.15 } elsif ($STRIP =~ /pod/i && $pm ne "Opcode.pm") { # opcode parses its own pod
763 root 1.11 require Pod::Strip;
764    
765     my $stripper = Pod::Strip->new;
766 root 1.6
767 root 1.11 my $out;
768     $stripper->output_string (\$out);
769     $stripper->parse_string_document ($src)
770     or die;
771     $src = $out;
772 root 1.1 }
773    
774 root 1.11 if ($VERIFY && $pm =~ /\.pm$/ && $pm ne "Opcode.pm") {
775     if (open my $fh, "-|") {
776     <$fh>;
777     } else {
778     eval "#line 1 \"$pm\"\n$src" or warn "\n\n\n$pm\n\n$src\n$@\n\n\n";
779     exit 0;
780 root 1.6 }
781 root 1.1 }
782    
783 root 1.41 $src = $compress->($src);
784    
785 root 1.11 $src
786     };
787 root 1.1
788 root 1.6 # if ($pm eq "Opcode.pm") {
789     # open my $fh, ">x" or die; print $fh $src;#d#
790     # exit 1;
791     # }
792 root 1.1 }
793    
794 root 1.12 print "adding $pm (original size $size, stored size ", length $src, ")\n"
795 root 1.1 if $VERBOSE >= 2;
796    
797     push @index, ((length $pm) << 25) | length $data;
798     $data .= $pm . $src;
799     }
800    
801     length $data < 2**25
802 root 1.12 or die "ERROR: bundle too large (only 32MB supported)\n";
803 root 1.1
804 root 1.26 my $varpfx = "bundle";
805 root 1.1
806     #############################################################################
807     # output
808    
809 root 1.12 print "generating $PREFIX.h... "
810     if $VERBOSE >= 1;
811 root 1.1
812     {
813     open my $fh, ">", "$PREFIX.h"
814     or die "$PREFIX.h: $!\n";
815    
816     print $fh <<EOF;
817 root 1.27 /* do not edit, automatically created by staticperl */
818 root 1.5
819 root 1.1 #include <EXTERN.h>
820     #include <perl.h>
821     #include <XSUB.h>
822    
823     /* public API */
824     EXTERN_C PerlInterpreter *staticperl;
825 root 1.5 EXTERN_C void staticperl_xs_init (pTHX);
826 root 1.18 EXTERN_C void staticperl_init (XSINIT_t xs_init); /* argument can be 0 */
827 root 1.1 EXTERN_C void staticperl_cleanup (void);
828 root 1.5
829 root 1.1 EOF
830     }
831    
832 root 1.12 print "\n"
833     if $VERBOSE >= 1;
834 root 1.1
835     #############################################################################
836     # output
837    
838 root 1.12 print "generating $PREFIX.c... "
839     if $VERBOSE >= 1;
840 root 1.1
841     open my $fh, ">", "$PREFIX.c"
842     or die "$PREFIX.c: $!\n";
843    
844     print $fh <<EOF;
845 root 1.27 /* do not edit, automatically created by staticperl */
846 root 1.1
847     #include "bundle.h"
848    
849     /* public API */
850     PerlInterpreter *staticperl;
851    
852     EOF
853    
854     #############################################################################
855 root 1.41 # lzf decompressor
856    
857     if ($COMPRESS eq "lzf") {
858     print $fh <<'EOF';
859     /* stripped down/perlified version of lzf_d.c from liblzf-3.7 */
860    
861     #if (__i386 || __amd64) && __GNUC__ >= 3
862     # define lzf_movsb(dst, src, len) \
863     asm ("rep movsb" \
864     : "=D" (dst), "=S" (src), "=c" (len) \
865     : "0" (dst), "1" (src), "2" (len));
866     #endif
867    
868     static unsigned int
869     lzf_decompress (const void *const in_data, unsigned int in_len,
870     void *out_data, unsigned int out_len)
871     {
872     U8 const *ip = (const U8 *)in_data;
873     U8 *op = (U8 *)out_data;
874     U8 const *const in_end = ip + in_len;
875     U8 *const out_end = op + out_len;
876    
877     do
878     {
879     unsigned int ctrl = *ip++;
880    
881     if (ctrl < (1 << 5)) /* literal run */
882     {
883     ctrl++;
884    
885     if (op + ctrl > out_end)
886     return 0;
887    
888     #ifdef lzf_movsb
889     lzf_movsb (op, ip, ctrl);
890     #else
891     while (ctrl--)
892     *op++ = *ip++;
893     #endif
894     }
895     else /* back reference */
896     {
897     unsigned int len = ctrl >> 5;
898    
899     U8 *ref = op - ((ctrl & 0x1f) << 8) - 1;
900    
901     if (len == 7)
902     len += *ip++;
903    
904     ref -= *ip++;
905    
906     if (op + len + 2 > out_end)
907     return 0;
908    
909     if (ref < (U8 *)out_data)
910     return 0;
911    
912     len += 2;
913     #ifdef lzf_movsb
914     lzf_movsb (op, ref, len);
915     #else
916     do
917     *op++ = *ref++;
918     while (--len);
919     #endif
920     }
921     }
922     while (ip < in_end);
923    
924     return op - (U8 *)out_data;
925     }
926    
927     static SV *
928     static_to_sv (const char *ptr, STRLEN len)
929     {
930     SV *res;
931     const U8 *p = (const U8 *)ptr;
932    
933     if (len == 0) /* empty */
934     res = newSVpvn ("", 0);
935     else if (*p == 0) /* not compressed */
936     res = newSVpvn (p + 1, len - 1);
937     else /* lzf compressed, with UTF-8-encoded original size in front */
938     {
939     STRLEN ulenlen;
940     UV ulen = utf8n_to_uvchr (p, len, &ulenlen, 0);
941    
942     p += ulenlen;
943     len -= ulenlen;
944    
945     res = NEWSV (0, ulen);
946     sv_upgrade (res, SVt_PV);
947     SvPOK_only (res);
948     lzf_decompress (p, len, SvPVX (res), ulen);
949     SvCUR_set (res, ulen);
950     }
951    
952     return res;
953     }
954    
955     EOF
956     } else {
957     print $fh <<EOF;
958    
959     #define static_to_sv(ptr,len) newSVpvn (ptr, len)
960    
961     EOF
962     }
963    
964     #############################################################################
965 root 1.1 # bundle data
966    
967     my $count = @index;
968    
969     print $fh <<EOF;
970     #include "bundle.h"
971    
972     /* bundle data */
973    
974     static const U32 $varpfx\_count = $count;
975     static const U32 $varpfx\_index [$count + 1] = {
976     EOF
977    
978     my $col;
979     for (@index) {
980     printf $fh "0x%08x,", $_;
981     print $fh "\n" unless ++$col % 10;
982    
983     }
984     printf $fh "0x%08x\n};\n", (length $data);
985    
986     print $fh "static const char $varpfx\_data [] =\n";
987     dump_string $fh, $data;
988    
989 root 1.12 print $fh ";\n\n";
990 root 1.1
991     #############################################################################
992     # bootstrap
993    
994     # boot file for staticperl
995     # this file will be eval'ed at initialisation time
996    
997 root 1.29 # lines marked with "^D" are only used when $HAVE_DYNAMIC
998 root 1.1 my $bootstrap = '
999     BEGIN {
1000     package ' . $PACKAGE . ';
1001    
1002 root 1.28 # the path prefix to use when putting files into %INC
1003     our $inc_prefix;
1004 root 1.1
1005 root 1.28 # the @INC hook to use when we have PerlIO::scalar available
1006     my $perlio_inc = sub {
1007 root 1.1 my $data = find "$_[1]"
1008     or return;
1009    
1010 root 1.28 $INC{$_[1]} = "$inc_prefix$_[1]";
1011 root 1.1
1012     open my $fh, "<", \$data;
1013     $fh
1014     };
1015 root 1.28
1016     D if (defined &PerlIO::scalar::bootstrap) {
1017     # PerlIO::scalar statically compiled in
1018     PerlIO::scalar->bootstrap;
1019     @INC = $perlio_inc;
1020     D } else {
1021     D # PerlIO::scalar not available, use slower method
1022     D @INC = sub {
1023     D # always check if PerlIO::scalar might now be available
1024     D if (defined &PerlIO::scalar::bootstrap) {
1025     D # switch to the faster perlio_inc hook
1026     D @INC = map { $_ == $_[0] ? $perlio_inc : $_ } @INC;
1027     D goto &$perlio_inc;
1028     D }
1029     D
1030     D my $data = find "$_[1]"
1031     D or return;
1032     D
1033     D $INC{$_[1]} = "$inc_prefix$_[1]";
1034     D
1035     D sub {
1036     D $data =~ /\G([^\n]*\n?)/g
1037     D or return;
1038     D
1039     D $_ = $1;
1040     D 1
1041     D }
1042     D };
1043     D }
1044 root 1.1 }
1045     ';
1046    
1047 root 1.29 $bootstrap .= "require '!boot';"
1048     if exists $pm{"!boot"};
1049 root 1.1
1050 root 1.29 if ($HAVE_DYNAMIC) {
1051 root 1.28 $bootstrap =~ s/^D/ /mg;
1052     } else {
1053     $bootstrap =~ s/^D.*$//mg;
1054     }
1055    
1056     $bootstrap =~ s/#.*$//mg;
1057 root 1.1 $bootstrap =~ s/\s+/ /g;
1058     $bootstrap =~ s/(\W) /$1/g;
1059     $bootstrap =~ s/ (\W)/$1/g;
1060    
1061     print $fh "const char bootstrap [] = ";
1062     dump_string $fh, $bootstrap;
1063     print $fh ";\n\n";
1064    
1065     print $fh <<EOF;
1066     /* search all bundles for the given file, using binary search */
1067     XS(find)
1068     {
1069     dXSARGS;
1070    
1071     if (items != 1)
1072     Perl_croak (aTHX_ "Usage: $PACKAGE\::find (\$path)");
1073    
1074     {
1075     STRLEN namelen;
1076     char *name = SvPV (ST (0), namelen);
1077     SV *res = 0;
1078    
1079     int l = 0, r = $varpfx\_count;
1080    
1081     while (l <= r)
1082     {
1083     int m = (l + r) >> 1;
1084     U32 idx = $varpfx\_index [m];
1085     int comp = namelen - (idx >> 25);
1086    
1087     if (!comp)
1088     {
1089     int ofs = idx & 0x1FFFFFFU;
1090     comp = memcmp (name, $varpfx\_data + ofs, namelen);
1091    
1092     if (!comp)
1093     {
1094     /* found */
1095     int ofs2 = $varpfx\_index [m + 1] & 0x1FFFFFFU;
1096    
1097     ofs += namelen;
1098 root 1.41 res = static_to_sv ($varpfx\_data + ofs, ofs2 - ofs);
1099 root 1.1 goto found;
1100     }
1101     }
1102    
1103     if (comp < 0)
1104     r = m - 1;
1105     else
1106     l = m + 1;
1107     }
1108    
1109     XSRETURN (0);
1110    
1111     found:
1112 root 1.28 ST (0) = sv_2mortal (res);
1113 root 1.1 }
1114    
1115     XSRETURN (1);
1116     }
1117    
1118     /* list all files in the bundle */
1119     XS(list)
1120     {
1121     dXSARGS;
1122    
1123     if (items != 0)
1124     Perl_croak (aTHX_ "Usage: $PACKAGE\::list");
1125    
1126     {
1127     int i;
1128    
1129     EXTEND (SP, $varpfx\_count);
1130    
1131     for (i = 0; i < $varpfx\_count; ++i)
1132     {
1133     U32 idx = $varpfx\_index [i];
1134    
1135 root 1.28 PUSHs (sv_2mortal (newSVpvn ($varpfx\_data + (idx & 0x1FFFFFFU), idx >> 25)));
1136 root 1.1 }
1137     }
1138    
1139     XSRETURN ($varpfx\_count);
1140     }
1141    
1142 root 1.35 #ifdef STATICPERL_BUNDLE_INCLUDE
1143     #include STATICPERL_BUNDLE_INCLUDE
1144     #endif
1145    
1146 root 1.1 EOF
1147    
1148     #############################################################################
1149     # xs_init
1150    
1151     print $fh <<EOF;
1152 root 1.5 void
1153     staticperl_xs_init (pTHX)
1154 root 1.1 {
1155     EOF
1156    
1157 root 1.28 @static_ext = sort @static_ext;
1158 root 1.1
1159     # prototypes
1160     for (@static_ext) {
1161     s/\.pm$//;
1162     (my $cname = $_) =~ s/\//__/g;
1163     print $fh " EXTERN_C void boot_$cname (pTHX_ CV* cv);\n";
1164     }
1165    
1166     print $fh <<EOF;
1167     char *file = __FILE__;
1168     dXSUB_SYS;
1169    
1170     newXSproto ("$PACKAGE\::find", find, file, "\$");
1171     newXSproto ("$PACKAGE\::list", list, file, "");
1172 root 1.35
1173     #ifdef STATICPERL_BUNDLE_XS_INIT
1174     STATICPERL_BUNDLE_XS_INIT;
1175     #endif
1176 root 1.1 EOF
1177    
1178     # calls
1179     for (@static_ext) {
1180     s/\.pm$//;
1181    
1182     (my $cname = $_) =~ s/\//__/g;
1183     (my $pname = $_) =~ s/\//::/g;
1184    
1185 root 1.28 my $bootstrap = $pname eq "DynaLoader" ? "boot_DynaLoader" : "bootstrap";
1186 root 1.1
1187     print $fh " newXS (\"$pname\::$bootstrap\", boot_$cname, file);\n";
1188     }
1189    
1190     print $fh <<EOF;
1191 root 1.36 Safefree (PL_origfilename);
1192     PL_origfilename = savepv (PL_origargv [0]);
1193     sv_setpv (GvSV (gv_fetchpvs ("0", GV_ADD|GV_NOTQUAL, SVt_PV)), PL_origfilename);
1194    
1195 root 1.29 #ifdef _WIN32
1196     /* windows perls usually trail behind unix perls 8-10 years in exporting symbols */
1197    
1198     if (!PL_preambleav)
1199     PL_preambleav = newAV ();
1200    
1201     av_unshift (PL_preambleav, 1);
1202     av_store (PL_preambleav, 0, newSVpv (bootstrap, sizeof (bootstrap) - 1));
1203     #else
1204 root 1.30 Perl_av_create_and_unshift_one (&PL_preambleav, newSVpv (bootstrap, sizeof (bootstrap) - 1));
1205 root 1.29 #endif
1206 root 1.18
1207     if (PL_oldname)
1208     ((XSINIT_t)PL_oldname)(aTHX);
1209 root 1.1 }
1210     EOF
1211    
1212     #############################################################################
1213     # optional perl_init/perl_destroy
1214    
1215 root 1.25 if ($IGNORE_ENV) {
1216     $IGNORE_ENV = <<EOF;
1217     unsetenv ("PERL_UNICODE");
1218     unsetenv ("PERL_HASH_SEED_DEBUG");
1219     unsetenv ("PERL_DESTRUCT_LEVEL");
1220     unsetenv ("PERL_SIGNALS");
1221     unsetenv ("PERL_DEBUG_MSTATS");
1222     unsetenv ("PERL5OPT");
1223     unsetenv ("PERLIO_DEBUG");
1224     unsetenv ("PERLIO");
1225     unsetenv ("PERL_HASH_SEED");
1226     EOF
1227     } else {
1228     $IGNORE_ENV = "";
1229     }
1230    
1231 root 1.9 if ($APP) {
1232     print $fh <<EOF;
1233    
1234     int
1235     main (int argc, char *argv [])
1236     {
1237     extern char **environ;
1238 root 1.17 int i, exitstatus;
1239     char **args = malloc ((argc + 3) * sizeof (const char *));
1240    
1241     args [0] = argv [0];
1242     args [1] = "-e";
1243     args [2] = "0";
1244     args [3] = "--";
1245 root 1.9
1246 root 1.17 for (i = 1; i < argc; ++i)
1247     args [i + 3] = argv [i];
1248 root 1.9
1249 root 1.25 $IGNORE_ENV
1250 root 1.9 PERL_SYS_INIT3 (&argc, &argv, &environ);
1251     staticperl = perl_alloc ();
1252     perl_construct (staticperl);
1253    
1254     PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1255    
1256 root 1.17 exitstatus = perl_parse (staticperl, staticperl_xs_init, argc + 3, args, environ);
1257 root 1.9 if (!exitstatus)
1258     perl_run (staticperl);
1259    
1260     exitstatus = perl_destruct (staticperl);
1261     perl_free (staticperl);
1262     PERL_SYS_TERM ();
1263 root 1.38 /*free (args); no point doing it this late */
1264 root 1.9
1265     return exitstatus;
1266     }
1267     EOF
1268     } elsif ($PERL) {
1269 root 1.1 print $fh <<EOF;
1270    
1271     int
1272     main (int argc, char *argv [])
1273     {
1274     extern char **environ;
1275     int exitstatus;
1276    
1277 root 1.25 $IGNORE_ENV
1278 root 1.1 PERL_SYS_INIT3 (&argc, &argv, &environ);
1279     staticperl = perl_alloc ();
1280     perl_construct (staticperl);
1281    
1282     PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1283    
1284 root 1.5 exitstatus = perl_parse (staticperl, staticperl_xs_init, argc, argv, environ);
1285 root 1.1 if (!exitstatus)
1286     perl_run (staticperl);
1287    
1288     exitstatus = perl_destruct (staticperl);
1289     perl_free (staticperl);
1290     PERL_SYS_TERM ();
1291    
1292     return exitstatus;
1293     }
1294     EOF
1295     } else {
1296     print $fh <<EOF;
1297    
1298     EXTERN_C void
1299 root 1.18 staticperl_init (XSINIT_t xs_init)
1300 root 1.1 {
1301 root 1.9 static char *args[] = {
1302     "staticperl",
1303     "-e",
1304     "0"
1305     };
1306    
1307 root 1.17 extern char **environ;
1308     int argc = sizeof (args) / sizeof (args [0]);
1309     char **argv = args;
1310    
1311 root 1.25 $IGNORE_ENV
1312 root 1.1 PERL_SYS_INIT3 (&argc, &argv, &environ);
1313     staticperl = perl_alloc ();
1314     perl_construct (staticperl);
1315     PL_origalen = 1;
1316     PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1317 root 1.18 PL_oldname = (char *)xs_init;
1318 root 1.5 perl_parse (staticperl, staticperl_xs_init, argc, argv, environ);
1319 root 1.1
1320     perl_run (staticperl);
1321     }
1322    
1323     EXTERN_C void
1324     staticperl_cleanup (void)
1325     {
1326     perl_destruct (staticperl);
1327     perl_free (staticperl);
1328     staticperl = 0;
1329     PERL_SYS_TERM ();
1330     }
1331     EOF
1332     }
1333    
1334 root 1.29 close $fh;
1335    
1336 root 1.12 print -s "$PREFIX.c", " octets (", (length $data) , " data octets).\n\n"
1337     if $VERBOSE >= 1;
1338 root 1.1
1339     #############################################################################
1340     # libs, cflags
1341    
1342 root 1.29 my $ccopts;
1343    
1344 root 1.1 {
1345 root 1.12 print "generating $PREFIX.ccopts... "
1346     if $VERBOSE >= 1;
1347 root 1.1
1348 root 1.37 $ccopts = "$Config{ccflags} $Config{optimize} $Config{cppflags} -I$Config{archlibexp}/CORE $EXTRA_CFLAGS";
1349 root 1.29 $ccopts =~ s/([\(\)])/\\$1/g;
1350 root 1.1
1351     open my $fh, ">$PREFIX.ccopts"
1352     or die "$PREFIX.ccopts: $!";
1353 root 1.29 print $fh $ccopts;
1354 root 1.12
1355 root 1.29 print "$ccopts\n\n"
1356 root 1.12 if $VERBOSE >= 1;
1357 root 1.1 }
1358    
1359 root 1.29 my $ldopts;
1360    
1361 root 1.1 {
1362     print "generating $PREFIX.ldopts... ";
1363    
1364 root 1.29 $ldopts = $STATIC ? "-static " : "";
1365 root 1.1
1366 root 1.37 $ldopts .= "$Config{ccdlflags} $Config{ldflags} $EXTRA_LDFLAGS @libs $Config{archlibexp}/CORE/$Config{libperl} $Config{perllibs} $EXTRA_LIBS";
1367 root 1.1
1368     my %seen;
1369 root 1.32 $ldopts .= " $_" for reverse grep !$seen{$_}++, reverse +($extralibs =~ /(\S+)/g);
1370 root 1.1
1371 root 1.10 for (@staticlibs) {
1372 root 1.29 $ldopts =~ s/(^|\s) (-l\Q$_\E) ($|\s)/$1-Wl,-Bstatic $2 -Wl,-Bdynamic$3/gx;
1373 root 1.10 }
1374    
1375 root 1.29 $ldopts =~ s/([\(\)])/\\$1/g;
1376 root 1.1
1377     open my $fh, ">$PREFIX.ldopts"
1378     or die "$PREFIX.ldopts: $!";
1379 root 1.29 print $fh $ldopts;
1380 root 1.12
1381 root 1.29 print "$ldopts\n\n"
1382 root 1.12 if $VERBOSE >= 1;
1383 root 1.1 }
1384    
1385 root 1.9 if ($PERL or defined $APP) {
1386     $APP = "perl" unless defined $APP;
1387    
1388 root 1.29 my $build = "$Config{cc} $ccopts -o \Q$APP\E$Config{_exe} bundle.c $ldopts";
1389    
1390     print "build $APP...\n"
1391 root 1.12 if $VERBOSE >= 1;
1392 root 1.9
1393 root 1.29 print "$build\n"
1394     if $VERBOSE >= 2;
1395    
1396     system $build;
1397 root 1.1
1398 root 1.33 unlink "$PREFIX.$_"
1399     for qw(ccopts ldopts c h);
1400 root 1.11
1401 root 1.12 print "\n"
1402     if $VERBOSE >= 1;
1403 root 1.1 }
1404