ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/App-Staticperl/mkbundle
Revision: 1.43
Committed: Mon Aug 7 04:36:34 2023 UTC (10 months, 3 weeks ago) by root
Branch: MAIN
Changes since 1.42: +14 -9 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #!/opt/bin/perl
2
3 #############################################################################
4 # cannot load modules till after the tracer BEGIN block
5
6 our $VERBOSE = 1;
7 our $STRIP = "pod"; # none, pod or ppi
8 our $COMPRESS = "lzf";
9 our $KEEPNL = 0;
10 our $UNISTRIP = 1; # always on, try to strip unicore swash data
11 our $PERL = 0;
12 our $APP;
13 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 our $EXTRA_CFLAGS = "";
20 our $EXTRA_LDFLAGS = "";
21 our $EXTRA_LIBS = "";
22
23 our $OPTIMISE_SIZE = 0; # optimise for raw file size instead of for compression?
24
25 our $CACHE;
26 our $CACHEVER = 2; # do not change unless you know what you are doing
27
28 my $PREFIX = "bundle";
29 my $PACKAGE = "static";
30
31 my %pm;
32 my %pmbin;
33 my @libs;
34 my @static_ext;
35 my $extralibs;
36 my @staticlibs;
37 my @incext;
38
39 @ARGV
40 or die "$0: use 'staticperl help' (or read the sources of staticperl)\n";
41
42 # remove "." from @INC - staticperl.sh does it for us, but be on the safe side
43 BEGIN { @INC = grep !/^\.$/, @INC }
44
45 $|=1;
46
47 our ($TRACER_W, $TRACER_R);
48
49 sub find_incdir($) {
50 for (@INC) {
51 next if ref;
52 return $_ if -e "$_/$_[0]";
53 }
54
55 undef
56 }
57
58 sub find_inc($) {
59 my $dir = find_incdir $_[0];
60
61 return "$dir/$_[0]"
62 if defined $dir;
63
64 undef
65 }
66
67 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 my $pkg = "pkg000000";
79
80 unshift @INC, sub {
81 my $dir = find_incdir $_[1]
82 or return;
83
84 syswrite $W_TRACER, "-\n$dir\n$_[1]\n";
85
86 open my $fh, "<:raw:perlio", "$dir/$_[1]"
87 or warn "ERROR: $dir/$_[1]: $!\n";
88
89 $fh
90 };
91
92 while (<$R_TRACER>) {
93 if (/use (.*)$/) {
94 my $mod = $1;
95 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 eval $eval;
107 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
116 syswrite $W_TRACER, "\n";
117 }
118
119 exit 0;
120 }
121 }
122
123 # module loading is now safe
124
125 sub trace_parse {
126 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
133 print "+ found potential dependency $name\n"
134 if $VERBOSE >= 3;
135 }
136 }
137
138 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 sub trace_eval {
147 print "tracing eval $_[0]\n"
148 if $VERBOSE >= 2;
149
150 syswrite $TRACER_W, "eval $_[0]\n";
151 trace_parse;
152 }
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 use Config;
164 use Digest::MD5;
165
166 sub cache($$$) {
167 my ($variant, $src, $filter) = @_;
168
169 if (length $CACHE and 2048 <= length $src and defined $variant) {
170 my $file = "$CACHE/" . Digest::MD5::md5_hex "$CACHEVER\x00$variant\x00$src";
171
172 if (open my $fh, "<:raw:perlio", $file) {
173 print "using cache for $file\n"
174 if $VERBOSE >= 7;
175
176 local $/;
177 return <$fh>;
178 }
179
180 $src = $filter->($src);
181
182 print "creating cache entry $file\n"
183 if $VERBOSE >= 8;
184
185 if (open my $fh, ">:raw:perlio", "$file~") {
186 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 sub dump_string {
199 my ($fh, $data) = @_;
200
201 if (length $data) {
202 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 }
225 } else {
226 print $fh " \"\"\n";
227 }
228 }
229
230 #############################################################################
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
297 #############################################################################
298
299 sub cmd_boot {
300 $pm{"!boot"} = $_[0];
301 }
302
303 sub cmd_add {
304 $_[0] =~ /^(.*?)(?:\s+(\S+))?$/
305 or die "$_[0]: cannot parse";
306
307 my $file = $1;
308 my $as = defined $2 ? $2 : $1;
309
310 $pm{$as} = $file;
311 $pmbin{$as} = 1 if $_[1];
312 }
313
314 sub cmd_staticlib {
315 push @staticlibs, $_
316 for split /\s+/, $_[0];
317 }
318
319 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 for grep /$pattern/ && /\.(pl|pm)$/, @$files;
333 }
334 }
335
336 sub parse_argv;
337
338 sub cmd_file {
339 open my $fh, "<", $_[0]
340 or die "$_[0]: $!\n";
341
342 local @ARGV;
343
344 while (<$fh>) {
345 chomp;
346 next unless /\S/;
347 next if /^\s*#/;
348
349 s/^\s*-*/--/;
350 my ($cmd, $args) = split / /, $_, 2;
351
352 push @ARGV, $cmd;
353 push @ARGV, $args if defined $args;
354 }
355
356 parse_argv;
357 }
358
359 use Getopt::Long;
360
361 sub parse_argv {
362 GetOptions
363 "perl" => \$PERL,
364 "app=s" => \$APP,
365
366 "verbose|v" => sub { ++$VERBOSE },
367 "quiet|q" => sub { --$VERBOSE },
368
369 "strip=s" => \$STRIP,
370 "keepnl" => \$KEEPNL,
371 "compress=s" => \$COMPRESS,
372 "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
392 "<>" => sub { cmd_file $_[0] },
393 or exit 1;
394 }
395
396 Getopt::Long::Configure ("bundling", "no_auto_abbrev", "no_ignore_case");
397
398 parse_argv;
399
400 die "cannot specify both --app and --perl\n"
401 if $PERL and defined $APP;
402
403 die "--compress must be either none or lzf\n"
404 unless $COMPRESS =~ /^(?:none|lzf)\z/;
405
406 # required for @INC loading, unfortunately
407 trace_module "PerlIO::scalar";
408
409 #############################################################################
410 # apply include/exclude
411
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
424 print "applying include $glob - protected ", (scalar @match), " files.\n"
425 if $VERBOSE >= 5;
426 } else {
427 # exclude
428 delete @pm{@match};
429
430 print "applying exclude $glob - removed ", (scalar @match), " files.\n"
431 if $VERBOSE >= 5;
432 }
433 }
434
435 my @pmi = keys %pmi;
436 @pm{@pmi} = delete @pmi{@pmi};
437 }
438
439 #############################################################################
440 # scan for AutoLoader, static archives and other dependencies
441
442 sub scan_al {
443 my ($auto, $autodir) = @_;
444
445 my $ix = "$autodir/autosplit.ix";
446
447 print "processing autoload index for '$auto'\n"
448 if $VERBOSE >= 6;
449
450 $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 $pm{$al} = $inc;
465 print "found autoload function '$al'\n"
466 if $VERBOSE >= 6;
467
468 } elsif (/^\s*package\s+([^[:space:];]+)\s*;?\s*$/) {
469 ($package = $1) =~ s/::/\//g;
470 } elsif (/^\s*(?:#|1?\s*;?\s*$)/) {
471 # nop
472 } else {
473 warn "WARNING: $ix: unparsable line, please report: $_";
474 }
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 if (defined $autodir && -d $autodir) {
484 # 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 print "found extralibs for $pm\n"
491 if $VERBOSE >= 6;
492
493 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 print "found static archive for $pm\n"
504 if $VERBOSE >= 3;
505
506 push @libs, "$autodir/$base$Config{_a}";
507 push @static_ext, $pm;
508 }
509
510 # dynamic object
511 if (-f "$autodir/$base.$Config{dlext}") {
512 if ($ALLOW_DYNAMIC) {
513 my $as = "!$auto/$base.$Config{dlext}";
514 $pm{$as} = "$autodir/$base.$Config{dlext}";
515 $pmbin{$as} = 1;
516
517 $HAVE_DYNAMIC = 1;
518
519 print "+ added dynamic object $as\n"
520 if $VERBOSE >= 3;
521 } else {
522 die "ERROR: found shared object '$autodir/$base.$Config{dlext}' but --allow-dynamic not given, aborting.\n"
523 }
524 }
525
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 s/ .*$//; # newer-style .packlists might contain key=value pairs
533
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 }
554 }
555 }
556
557 #############################################################################
558
559 print "processing bundle files (try more -v power if you get bored waiting here)...\n"
560 if $VERBOSE >= 1;
561
562 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 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 or die "ERROR: $pm: path too long (only 128 octets supported)\n";
585
586 my $src = ref $path
587 ? $$path
588 : do {
589 open my $pm, "<:raw:perlio", $path
590 or die "$path: $!";
591
592 local $/;
593
594 <$pm>
595 };
596
597 my $size = length $src;
598
599 unless ($pmbin{$pm}) { # only do this unless the file is binary
600 if ($pm =~ /^auto\/POSIX\/[^\/]+\.al$/) {
601 if ($src =~ /^ unimpl \"/m) {
602 print "$pm: skipping (raises runtime error only).\n"
603 if $VERBOSE >= 3;
604 next;
605 }
606 }
607
608 $src = cache "$STRIP,$UNISTRIP,$OPTIMISE_SIZE,$COMPRESS", $src, sub {
609 if ($UNISTRIP && $pm =~ /^unicore\/.*\.pl$/) {
610 print "applying unicore stripping $pm\n"
611 if $VERBOSE >= 6;
612
613 # 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
633 s/#.*\n/\n/mg;
634 s/\s+\n/\n/mg;
635 }
636
637 "$pre$data$post"
638 }smex;
639 }
640
641 if ($STRIP =~ /ppi/i) {
642 require PPI;
643
644 # PPI (quite correctly) treats pod in __DATA__ as data, not pod, so
645 # we don't have to work around Opcode.pm, as with Pod::Strip
646
647 if (my $ppi = PPI::Document->new (\$src)) {
648 for my $node (
649 @{ $ppi->find (PPI::Token::Comment::) },
650 @{ $ppi->find (PPI::Token::Pod::) }
651 ) {
652 if ($KEEPNL) {
653 $node->{content} =~ s/[^\n]//g;
654 $node->insert_after (PPI::Token::Whitespace->new ("\n")) if length $node->{content};
655 }
656
657 $node->delete;
658 }
659
660 # prune END stuff
661 for (my $last = $ppi->last_element; $last; ) {
662 my $prev = $last->previous_token;
663
664 if ($last->isa (PPI::Token::Whitespace::)) {
665 $last->delete;
666 } elsif ($last->isa (PPI::Statement::End::)) {
667 $last->delete;
668 last;
669 } elsif ($last->isa (PPI::Token::Pod::)) {
670 $last->delete;
671 } elsif ($last->isa (PPI::Token::Comment::)) {
672 $last->delete;
673 } else {
674 last;
675 }
676
677 $last = $prev;
678 }
679
680 # prune some but not all insignificant whitespace
681 for my $ws (@{ $ppi->find (PPI::Token::Whitespace::) }) {
682 my $prev = $ws->previous_token;
683 my $next = $ws->next_token;
684
685 if (!$prev || !$next) {
686 $ws->delete;
687 } else {
688 if ($next->isa (PPI::Token::Whitespace::)) {
689 # push this whitespace data into the next node
690 $next->{content} = "$ws->{content}$next->{content}";
691 $ws->{content} = "";
692 } elsif (
693 (
694 $next->isa (PPI::Token::Operator::) && $next->{content} =~ /^(?:,|=|!|!=|==|=>)$/ # no ., because of digits. == float
695 or $prev->isa (PPI::Token::Operator::) && $prev->{content} =~ /^(?:,|=|\.|!|!=|==|=>)$/
696 or $prev->isa (PPI::Token::Structure::)
697 or ($OPTIMISE_SIZE &&
698 ($prev->isa (PPI::Token::Word::)
699 && (PPI::Token::Symbol:: eq ref $next
700 || $next->isa (PPI::Structure::Block::)
701 || $next->isa (PPI::Structure::List::)
702 || $next->isa (PPI::Structure::Condition::)))
703 )
704 )
705 # perl has some idiotic warning about nonexisting operators (Reverse %s operator)
706 # also catch "= ~"
707 && !(
708 $prev->isa (PPI::Token::Operator::) && $prev->{content} eq "="
709 && $next->isa (PPI::Token::Operator::) && $next->{content} =~ /[+\-\~]/
710 )
711 ) {
712 if ($KEEPNL) {
713 $ws->{content} =~ s/[^\n]//g;
714 } else {
715 $ws->{content} = '';
716 }
717 } else {
718 if ($KEEPNL) {
719 $ws->{content} =~ s/[^\n]//g;
720 $ws->{content} ||= ' '; # keep at least one space
721 } else {
722 $ws->{content} = ' ';
723 }
724 }
725 }
726 }
727
728 # prune whitespace around blocks
729 if ($OPTIMISE_SIZE) {
730 # these usually decrease size, but decrease compressability more
731 for my $struct (PPI::Structure::Block::, PPI::Structure::Condition::) {
732 for my $node (@{ $ppi->find ($struct) }) {
733 my $n1 = $node->first_token;
734 my $n2 = $n1->previous_token;
735 $n1->delete if $n1->isa (PPI::Token::Whitespace::);
736 $n2->delete if $n2 && $n2->isa (PPI::Token::Whitespace::);
737 my $n1 = $node->last_token;
738 my $n2 = $n1->next_token;
739 $n1->delete if $n1->isa (PPI::Token::Whitespace::);
740 $n2->delete if $n2 && $n2->isa (PPI::Token::Whitespace::);
741 }
742 }
743
744 for my $node (@{ $ppi->find (PPI::Structure::List::) }) {
745 my $n1 = $node->first_token;
746 $n1->delete if $n1->isa (PPI::Token::Whitespace::);
747 my $n1 = $node->last_token;
748 $n1->delete if $n1->isa (PPI::Token::Whitespace::);
749 }
750 }
751
752 # reformat qw() lists which often have lots of whitespace
753 for my $node (@{ $ppi->find (PPI::Token::QuoteLike::Words::) }) {
754 if ($node->{content} =~ /^qw(.)(.*)(.)$/s) {
755 my ($a, $qw, $b) = ($1, $2, $3);
756 $qw =~ s/^\s+//;
757 $qw =~ s/\s+$//;
758 $qw =~ s/\s+/ /g;
759 $node->{content} = "qw$a$qw$b";
760 }
761 }
762
763 $src = $ppi->serialize;
764 } else {
765 warn "WARNING: $pm{$pm}: PPI failed to parse this file\n";
766 }
767 } elsif ($STRIP =~ /pod/i && $pm ne "Opcode.pm") { # opcode parses its own pod
768 require Pod::Strip;
769
770 my $stripper = Pod::Strip->new;
771
772 my $out;
773 $stripper->output_string (\$out);
774 $stripper->parse_string_document ($src)
775 or die;
776 $src = $out;
777 }
778
779 if ($VERIFY && $pm =~ /\.pm$/ && $pm ne "Opcode.pm") {
780 if (open my $fh, "-|") {
781 <$fh>;
782 } else {
783 eval "#line 1 \"$pm\"\n$src" or warn "\n\n\n$pm\n\n$src\n$@\n\n\n";
784 exit 0;
785 }
786 }
787
788 $src = $compress->($src);
789
790 $src
791 };
792
793 # if ($pm eq "Opcode.pm") {
794 # open my $fh, ">x" or die; print $fh $src;#d#
795 # exit 1;
796 # }
797 }
798
799 print "adding $pm (original size $size, stored size ", length $src, ")\n"
800 if $VERBOSE >= 2;
801
802 push @index, ((length $pm) << 25) | length $data;
803 $data .= $pm . $src;
804 }
805
806 length $data < 2**25
807 or die "ERROR: bundle too large (only 32MB supported)\n";
808
809 my $varpfx = "bundle";
810
811 #############################################################################
812 # output
813
814 print "generating $PREFIX.h... "
815 if $VERBOSE >= 1;
816
817 {
818 open my $fh, ">", "$PREFIX.h"
819 or die "$PREFIX.h: $!\n";
820
821 print $fh <<EOF;
822 /* do not edit, automatically created by staticperl */
823
824 #include <EXTERN.h>
825 #include <perl.h>
826 #include <XSUB.h>
827
828 /* public API */
829 EXTERN_C PerlInterpreter *staticperl;
830 EXTERN_C void staticperl_xs_init (pTHX);
831 EXTERN_C void staticperl_init (XSINIT_t xs_init); /* argument can be 0 */
832 EXTERN_C void staticperl_cleanup (void);
833
834 EOF
835 }
836
837 print "\n"
838 if $VERBOSE >= 1;
839
840 #############################################################################
841 # output
842
843 print "generating $PREFIX.c... "
844 if $VERBOSE >= 1;
845
846 open my $fh, ">", "$PREFIX.c"
847 or die "$PREFIX.c: $!\n";
848
849 print $fh <<EOF;
850 /* do not edit, automatically created by staticperl */
851
852 #include "bundle.h"
853
854 /* public API */
855 PerlInterpreter *staticperl;
856
857 EOF
858
859 #############################################################################
860 # lzf decompressor
861
862 if ($COMPRESS eq "lzf") {
863 print $fh <<'EOF';
864 /* stripped down/perlified version of lzf_d.c from liblzf-3.7 */
865
866 #if (__i386 || __amd64) && __GNUC__ >= 3
867 # define lzf_movsb(dst, src, len) \
868 asm ("rep movsb" \
869 : "=D" (dst), "=S" (src), "=c" (len) \
870 : "0" (dst), "1" (src), "2" (len));
871 #endif
872
873 static unsigned int
874 lzf_decompress (const void *const in_data, unsigned int in_len,
875 void *out_data, unsigned int out_len)
876 {
877 U8 const *ip = (const U8 *)in_data;
878 U8 *op = (U8 *)out_data;
879 U8 const *const in_end = ip + in_len;
880 U8 *const out_end = op + out_len;
881
882 do
883 {
884 unsigned int ctrl = *ip++;
885
886 if (ctrl < (1 << 5)) /* literal run */
887 {
888 ctrl++;
889
890 if (op + ctrl > out_end)
891 return 0;
892
893 #ifdef lzf_movsb
894 lzf_movsb (op, ip, ctrl);
895 #else
896 while (ctrl--)
897 *op++ = *ip++;
898 #endif
899 }
900 else /* back reference */
901 {
902 unsigned int len = ctrl >> 5;
903
904 U8 *ref = op - ((ctrl & 0x1f) << 8) - 1;
905
906 if (len == 7)
907 len += *ip++;
908
909 ref -= *ip++;
910
911 if (op + len + 2 > out_end)
912 return 0;
913
914 if (ref < (U8 *)out_data)
915 return 0;
916
917 len += 2;
918 #ifdef lzf_movsb
919 lzf_movsb (op, ref, len);
920 #else
921 do
922 *op++ = *ref++;
923 while (--len);
924 #endif
925 }
926 }
927 while (ip < in_end);
928
929 return op - (U8 *)out_data;
930 }
931
932 static SV *
933 static_to_sv (const char *ptr, STRLEN len)
934 {
935 SV *res;
936 const U8 *p = (const U8 *)ptr;
937
938 if (len == 0) /* empty */
939 res = newSVpvn ("", 0);
940 else if (*p == 0) /* not compressed */
941 res = newSVpvn (p + 1, len - 1);
942 else /* lzf compressed, with UTF-8-encoded original size in front */
943 {
944 STRLEN ulenlen;
945 UV ulen = utf8n_to_uvchr (p, len, &ulenlen, 0);
946
947 p += ulenlen;
948 len -= ulenlen;
949
950 res = NEWSV (0, ulen);
951 sv_upgrade (res, SVt_PV);
952 SvPOK_only (res);
953 lzf_decompress (p, len, SvPVX (res), ulen);
954 SvCUR_set (res, ulen);
955 }
956
957 return res;
958 }
959
960 EOF
961 } else {
962 print $fh <<EOF;
963
964 #define static_to_sv(ptr,len) newSVpvn (ptr, len)
965
966 EOF
967 }
968
969 #############################################################################
970 # bundle data
971
972 my $count = @index;
973
974 print $fh <<EOF;
975 #include "bundle.h"
976
977 /* bundle data */
978
979 static const U32 $varpfx\_count = $count;
980 static const U32 $varpfx\_index [$count + 1] = {
981 EOF
982
983 my $col;
984 for (@index) {
985 printf $fh "0x%08x,", $_;
986 print $fh "\n" unless ++$col % 10;
987
988 }
989 printf $fh "0x%08x\n};\n", (length $data);
990
991 print $fh "static const char $varpfx\_data [] =\n";
992 dump_string $fh, $data;
993
994 print $fh ";\n\n";
995
996 #############################################################################
997 # bootstrap
998
999 # boot file for staticperl
1000 # this file will be eval'ed at initialisation time
1001
1002 # lines marked with "^D" are only used when $HAVE_DYNAMIC
1003 my $bootstrap = '
1004 BEGIN {
1005 package ' . $PACKAGE . ';
1006
1007 # the path prefix to use when putting files into %INC
1008 our $inc_prefix;
1009
1010 # the @INC hook to use when we have PerlIO::scalar available
1011 my $perlio_inc = sub {
1012 my $data = find "$_[1]"
1013 or return;
1014
1015 $INC{$_[1]} = "$inc_prefix$_[1]";
1016
1017 open my $fh, "<", \$data;
1018 $fh
1019 };
1020
1021 D if (defined &PerlIO::scalar::bootstrap) {
1022 # PerlIO::scalar statically compiled in
1023 PerlIO::scalar->bootstrap;
1024 @INC = $perlio_inc;
1025 D } else {
1026 D # PerlIO::scalar not available, use slower method
1027 D @INC = sub {
1028 D # always check if PerlIO::scalar might now be available
1029 D if (defined &PerlIO::scalar::bootstrap) {
1030 D # switch to the faster perlio_inc hook
1031 D @INC = map { $_ == $_[0] ? $perlio_inc : $_ } @INC;
1032 D goto &$perlio_inc;
1033 D }
1034 D
1035 D my $data = find "$_[1]"
1036 D or return;
1037 D
1038 D $INC{$_[1]} = "$inc_prefix$_[1]";
1039 D
1040 D sub {
1041 D $data =~ /\G([^\n]*\n?)/g
1042 D or return;
1043 D
1044 D $_ = $1;
1045 D 1
1046 D }
1047 D };
1048 D }
1049 }
1050 ';
1051
1052 $bootstrap .= "require '!boot';"
1053 if exists $pm{"!boot"};
1054
1055 if ($HAVE_DYNAMIC) {
1056 $bootstrap =~ s/^D/ /mg;
1057 } else {
1058 $bootstrap =~ s/^D.*$//mg;
1059 }
1060
1061 $bootstrap =~ s/#.*$//mg;
1062 $bootstrap =~ s/\s+/ /g;
1063 $bootstrap =~ s/(\W) /$1/g;
1064 $bootstrap =~ s/ (\W)/$1/g;
1065
1066 print $fh "const char bootstrap [] = ";
1067 dump_string $fh, $bootstrap;
1068 print $fh ";\n\n";
1069
1070 print $fh <<EOF;
1071 /* search all bundles for the given file, using binary search */
1072 XS(find)
1073 {
1074 dXSARGS;
1075
1076 if (items != 1)
1077 Perl_croak (aTHX_ "Usage: $PACKAGE\::find (\$path)");
1078
1079 {
1080 STRLEN namelen;
1081 char *name = SvPV (ST (0), namelen);
1082 SV *res = 0;
1083
1084 int l = 0, r = $varpfx\_count;
1085
1086 while (l <= r)
1087 {
1088 int m = (l + r) >> 1;
1089 U32 idx = $varpfx\_index [m];
1090 int comp = namelen - (idx >> 25);
1091
1092 if (!comp)
1093 {
1094 int ofs = idx & 0x1FFFFFFU;
1095 comp = memcmp (name, $varpfx\_data + ofs, namelen);
1096
1097 if (!comp)
1098 {
1099 /* found */
1100 int ofs2 = $varpfx\_index [m + 1] & 0x1FFFFFFU;
1101
1102 ofs += namelen;
1103 res = static_to_sv ($varpfx\_data + ofs, ofs2 - ofs);
1104 goto found;
1105 }
1106 }
1107
1108 if (comp < 0)
1109 r = m - 1;
1110 else
1111 l = m + 1;
1112 }
1113
1114 XSRETURN (0);
1115
1116 found:
1117 ST (0) = sv_2mortal (res);
1118 }
1119
1120 XSRETURN (1);
1121 }
1122
1123 /* list all files in the bundle */
1124 XS(list)
1125 {
1126 dXSARGS;
1127
1128 if (items != 0)
1129 Perl_croak (aTHX_ "Usage: $PACKAGE\::list");
1130
1131 {
1132 int i;
1133
1134 EXTEND (SP, $varpfx\_count);
1135
1136 for (i = 0; i < $varpfx\_count; ++i)
1137 {
1138 U32 idx = $varpfx\_index [i];
1139
1140 PUSHs (sv_2mortal (newSVpvn ($varpfx\_data + (idx & 0x1FFFFFFU), idx >> 25)));
1141 }
1142 }
1143
1144 XSRETURN ($varpfx\_count);
1145 }
1146
1147 #ifdef STATICPERL_BUNDLE_INCLUDE
1148 #include STATICPERL_BUNDLE_INCLUDE
1149 #endif
1150
1151 EOF
1152
1153 #############################################################################
1154 # xs_init
1155
1156 print $fh <<EOF;
1157 void
1158 staticperl_xs_init (pTHX)
1159 {
1160 EOF
1161
1162 @static_ext = sort @static_ext;
1163
1164 # prototypes
1165 for (@static_ext) {
1166 s/\.pm$//;
1167 (my $cname = $_) =~ s/\//__/g;
1168 print $fh " EXTERN_C void boot_$cname (pTHX_ CV* cv);\n";
1169 }
1170
1171 print $fh <<EOF;
1172 char *file = __FILE__;
1173 dXSUB_SYS;
1174
1175 newXSproto ("$PACKAGE\::find", find, file, "\$");
1176 newXSproto ("$PACKAGE\::list", list, file, "");
1177
1178 #ifdef STATICPERL_BUNDLE_XS_INIT
1179 STATICPERL_BUNDLE_XS_INIT;
1180 #endif
1181 EOF
1182
1183 # calls
1184 for (@static_ext) {
1185 s/\.pm$//;
1186
1187 (my $cname = $_) =~ s/\//__/g;
1188 (my $pname = $_) =~ s/\//::/g;
1189
1190 my $bootstrap = $pname eq "DynaLoader" ? "boot_DynaLoader" : "bootstrap";
1191
1192 print $fh " newXS (\"$pname\::$bootstrap\", boot_$cname, file);\n";
1193 }
1194
1195 print $fh <<EOF;
1196 Safefree (PL_origfilename);
1197 PL_origfilename = savepv (PL_origargv [0]);
1198 sv_setpv (GvSV (gv_fetchpvs ("0", GV_ADD|GV_NOTQUAL, SVt_PV)), PL_origfilename);
1199
1200 #ifdef _WIN32
1201 /* windows perls usually trail behind unix perls 8-10 years in exporting symbols */
1202
1203 if (!PL_preambleav)
1204 PL_preambleav = newAV ();
1205
1206 av_unshift (PL_preambleav, 1);
1207 av_store (PL_preambleav, 0, newSVpv (bootstrap, sizeof (bootstrap) - 1));
1208 #else
1209 Perl_av_create_and_unshift_one (&PL_preambleav, newSVpv (bootstrap, sizeof (bootstrap) - 1));
1210 #endif
1211
1212 if (PL_oldname)
1213 ((XSINIT_t)PL_oldname)(aTHX);
1214 }
1215 EOF
1216
1217 #############################################################################
1218 # optional perl_init/perl_destroy
1219
1220 if ($IGNORE_ENV) {
1221 $IGNORE_ENV = <<EOF;
1222 unsetenv ("PERL_UNICODE");
1223 unsetenv ("PERL_HASH_SEED_DEBUG");
1224 unsetenv ("PERL_DESTRUCT_LEVEL");
1225 unsetenv ("PERL_SIGNALS");
1226 unsetenv ("PERL_DEBUG_MSTATS");
1227 unsetenv ("PERL5OPT");
1228 unsetenv ("PERLIO_DEBUG");
1229 unsetenv ("PERLIO");
1230 unsetenv ("PERL_HASH_SEED");
1231 EOF
1232 } else {
1233 $IGNORE_ENV = "";
1234 }
1235
1236 if ($APP) {
1237 print $fh <<EOF;
1238
1239 int
1240 main (int argc, char *argv [])
1241 {
1242 extern char **environ;
1243 int i, exitstatus;
1244 char **args = malloc ((argc + 3) * sizeof (const char *));
1245
1246 args [0] = argv [0];
1247 args [1] = "-e";
1248 args [2] = "0";
1249 args [3] = "--";
1250
1251 for (i = 1; i < argc; ++i)
1252 args [i + 3] = argv [i];
1253
1254 $IGNORE_ENV
1255 PERL_SYS_INIT3 (&argc, &argv, &environ);
1256 staticperl = perl_alloc ();
1257 perl_construct (staticperl);
1258
1259 PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1260
1261 exitstatus = perl_parse (staticperl, staticperl_xs_init, argc + 3, args, environ);
1262 if (!exitstatus)
1263 perl_run (staticperl);
1264
1265 exitstatus = perl_destruct (staticperl);
1266 perl_free (staticperl);
1267 PERL_SYS_TERM ();
1268 /*free (args); no point doing it this late */
1269
1270 return exitstatus;
1271 }
1272 EOF
1273 } elsif ($PERL) {
1274 print $fh <<EOF;
1275
1276 int
1277 main (int argc, char *argv [])
1278 {
1279 extern char **environ;
1280 int exitstatus;
1281
1282 $IGNORE_ENV
1283 PERL_SYS_INIT3 (&argc, &argv, &environ);
1284 staticperl = perl_alloc ();
1285 perl_construct (staticperl);
1286
1287 PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1288
1289 exitstatus = perl_parse (staticperl, staticperl_xs_init, argc, argv, environ);
1290 if (!exitstatus)
1291 perl_run (staticperl);
1292
1293 exitstatus = perl_destruct (staticperl);
1294 perl_free (staticperl);
1295 PERL_SYS_TERM ();
1296
1297 return exitstatus;
1298 }
1299 EOF
1300 } else {
1301 print $fh <<EOF;
1302
1303 EXTERN_C void
1304 staticperl_init (XSINIT_t xs_init)
1305 {
1306 static char *args[] = {
1307 "staticperl",
1308 "-e",
1309 "0"
1310 };
1311
1312 extern char **environ;
1313 int argc = sizeof (args) / sizeof (args [0]);
1314 char **argv = args;
1315
1316 $IGNORE_ENV
1317 PERL_SYS_INIT3 (&argc, &argv, &environ);
1318 staticperl = perl_alloc ();
1319 perl_construct (staticperl);
1320 PL_origalen = 1;
1321 PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
1322 PL_oldname = (char *)xs_init;
1323 perl_parse (staticperl, staticperl_xs_init, argc, argv, environ);
1324
1325 perl_run (staticperl);
1326 }
1327
1328 EXTERN_C void
1329 staticperl_cleanup (void)
1330 {
1331 perl_destruct (staticperl);
1332 perl_free (staticperl);
1333 staticperl = 0;
1334 PERL_SYS_TERM ();
1335 }
1336 EOF
1337 }
1338
1339 close $fh;
1340
1341 print -s "$PREFIX.c", " octets (", (length $data) , " data octets).\n\n"
1342 if $VERBOSE >= 1;
1343
1344 #############################################################################
1345 # libs, cflags
1346
1347 my $ccopts;
1348
1349 {
1350 print "generating $PREFIX.ccopts... "
1351 if $VERBOSE >= 1;
1352
1353 $ccopts = "$Config{ccflags} $Config{optimize} $Config{cppflags} -I$Config{archlibexp}/CORE $EXTRA_CFLAGS";
1354 $ccopts =~ s/([\(\)])/\\$1/g;
1355
1356 open my $fh, ">$PREFIX.ccopts"
1357 or die "$PREFIX.ccopts: $!";
1358 print $fh $ccopts;
1359
1360 print "$ccopts\n\n"
1361 if $VERBOSE >= 1;
1362 }
1363
1364 my $ldopts;
1365
1366 {
1367 print "generating $PREFIX.ldopts... ";
1368
1369 $ldopts = $STATIC ? "-static " : "";
1370
1371 $ldopts .= "$Config{ccdlflags} $Config{ldflags} $EXTRA_LDFLAGS @libs $Config{archlibexp}/CORE/$Config{libperl} $Config{perllibs} $EXTRA_LIBS";
1372
1373 my %seen;
1374 $ldopts .= " $_" for reverse grep !$seen{$_}++, reverse +($extralibs =~ /(\S+)/g);
1375
1376 for (@staticlibs) {
1377 $ldopts =~ s/(^|\s) (-l\Q$_\E) ($|\s)/$1-Wl,-Bstatic $2 -Wl,-Bdynamic$3/gx;
1378 }
1379
1380 $ldopts =~ s/([\(\)])/\\$1/g;
1381
1382 open my $fh, ">$PREFIX.ldopts"
1383 or die "$PREFIX.ldopts: $!";
1384 print $fh $ldopts;
1385
1386 print "$ldopts\n\n"
1387 if $VERBOSE >= 1;
1388 }
1389
1390 if ($PERL or defined $APP) {
1391 $APP = "perl" unless defined $APP;
1392
1393 my $build = "$Config{cc} $ccopts -o \Q$APP\E$Config{_exe} bundle.c $ldopts";
1394
1395 print "build $APP...\n"
1396 if $VERBOSE >= 1;
1397
1398 print "$build\n"
1399 if $VERBOSE >= 2;
1400
1401 system $build;
1402
1403 unlink "$PREFIX.$_"
1404 for qw(ccopts ldopts c h);
1405
1406 print "\n"
1407 if $VERBOSE >= 1;
1408 }
1409