xref: /openbsd-src/gnu/usr.bin/perl/make_ext.pl (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1#!./miniperl
2use strict;
3use warnings;
4use Config;
5BEGIN {
6    if ($^O eq 'MSWin32') {
7	unshift @INC, ('../cpan/Cwd', '../cpan/Cwd/lib');
8	require File::Spec::Functions;
9	require FindExt;
10    }
11    else {
12	unshift @INC, 'cpan/Cwd';
13    }
14}
15use Cwd;
16
17my $is_Win32 = $^O eq 'MSWin32';
18my $is_VMS = $^O eq 'VMS';
19my $is_Unix = !$is_Win32 && !$is_VMS;
20
21# To clarify, this isn't the entire suite of modules considered "toolchain"
22# It's not even all modules needed to build ext/
23# It's just the source paths of the (minimum complete set of) modules in ext/
24# needed to build the nonxs modules
25# After which, all nonxs modules are in lib, which was always sufficient to
26# allow miniperl to build everything else.
27
28# This list cannot get any longer without overflowing the length limit for
29# environment variables on VMS
30my @toolchain = qw(cpan/AutoLoader/lib
31		   cpan/Cwd cpan/Cwd/lib
32		   cpan/ExtUtils-Command/lib
33		   dist/ExtUtils-Install/lib
34		   cpan/ExtUtils-MakeMaker/lib
35		   cpan/ExtUtils-Manifest/lib
36		   cpan/File-Path/lib
37		   );
38
39# Used only in ExtUtils::Liblist::Kid::_win32_ext()
40push @toolchain, 'cpan/Text-ParseWords/lib' if $is_Win32;
41
42my @ext_dirs = qw(cpan dist ext);
43my $ext_dirs_re = '(?:' . join('|', @ext_dirs) . ')';
44
45# This script acts as a simple interface for building extensions.
46
47# It's actually a cut and shut of the Unix version ext/utils/makeext and the
48# Windows version win32/build_ext.pl hence the two invocation styles.
49
50# On Unix, it primarily used by the perl Makefile one extention at a time:
51#
52# d_dummy $(dynamic_ext): miniperl preplibrary FORCE
53# 	@$(RUN) ./miniperl make_ext.pl --target=dynamic $@ MAKE=$(MAKE) LIBPERL_A=$(LIBPERL)
54#
55# On Windows or VMS,
56# If '--static' is specified, static extensions will be built.
57# If '--dynamic' is specified, dynamic extensions will be built.
58# If '--nonxs' is specified, nonxs extensions will be built.
59# If '--dynaloader' is specificied, DynaLoader will be built.
60# If '--all' is specified, all extensions will be built.
61#
62#    make_ext.pl "MAKE=make [-make_opts]" --dir=directory [--target=target] [--static|--dynamic|--all] +ext2 !ext1
63#
64# E.g.
65#
66#     make_ext.pl "MAKE=nmake -nologo" --dir=..\ext
67#
68#     make_ext.pl "MAKE=nmake -nologo" --dir=..\ext --target=clean
69#
70#     make_ext.pl MAKE=dmake --dir=..\ext
71#
72#     make_ext.pl MAKE=dmake --dir=..\ext --target=clean
73#
74# Will skip building extensions which are marked with an '!' char.
75# Mostly because they still not ported to specified platform.
76#
77# If any extensions are listed with a '+' char then only those
78# extensions will be built, but only if they arent countermanded
79# by an '!ext' and are appropriate to the type of building being done.
80
81# It may be deleted in a later release of perl so try to
82# avoid using it for other purposes.
83
84my (%excl, %incl, %opts, @extspec, @pass_through);
85
86foreach (@ARGV) {
87    if (/^!(.*)$/) {
88	$excl{$1} = 1;
89    } elsif (/^\+(.*)$/) {
90	$incl{$1} = 1;
91    } elsif (/^--([\w\-]+)$/) {
92	$opts{$1} = 1;
93    } elsif (/^--([\w\-]+)=(.*)$/) {
94	push @{$opts{$1}}, $2;
95    } elsif (/=/) {
96	push @pass_through, $_;
97    } elsif (length) {
98	push @extspec, $_;
99    }
100}
101
102my $static = $opts{static} || $opts{all};
103my $dynamic = $opts{dynamic} || $opts{all};
104my $nonxs = $opts{nonxs} || $opts{all};
105my $dynaloader = $opts{dynaloader} || $opts{all};
106
107# The Perl Makefile.SH will expand all extensions to
108#	lib/auto/X/X.a  (or lib/auto/X/Y/Y.a if nested)
109# A user wishing to run make_ext might use
110#	X (or X/Y or X::Y if nested)
111
112# canonise into X/Y form (pname)
113
114foreach (@extspec) {
115    if (s{^lib/auto/}{}) {
116	# Remove lib/auto prefix and /*.* suffix
117	s{/[^/]+\.[^/]+$}{};
118    } elsif (s{^$ext_dirs_re/}{}) {
119	# Remove ext/ prefix and /pm_to_blib suffix
120	s{/pm_to_blib$}{};
121	# Targets are given as files on disk, but the extension spec is still
122	# written using /s for each ::
123	tr!-!/!;
124    } elsif (s{::}{\/}g) {
125	# Convert :: to /
126    } else {
127	s/\..*o//;
128    }
129}
130
131my $makecmd  = shift @pass_through; # Should be something like MAKE=make
132unshift @pass_through, 'PERL_CORE=1';
133
134my @dirs  = @{$opts{dir} || \@ext_dirs};
135my $target   = $opts{target}[0];
136$target = 'all' unless defined $target;
137
138# Previously, $make was taken from config.sh.  However, the user might
139# instead be running a possibly incompatible make.  This might happen if
140# the user types "gmake" instead of a plain "make", for example.  The
141# correct current value of MAKE will come through from the main perl
142# makefile as MAKE=/whatever/make in $makecmd.  We'll be cautious in
143# case third party users of this script (are there any?) don't have the
144# MAKE=$(MAKE) argument, which was added after 5.004_03.
145unless(defined $makecmd and $makecmd =~ /^MAKE=(.*)$/) {
146    die "$0:  WARNING:  Please include MAKE=\$(MAKE) in \@ARGV\n";
147}
148
149# This isn't going to cope with anything fancy, such as spaces inside command
150# names, but neither did what it replaced. Once there is a use case that needs
151# it, please supply patches. Until then, I'm sticking to KISS
152my @make = split ' ', $1 || $Config{make} || $ENV{MAKE};
153# Using an array of 0 or 1 elements makes the subsequent code simpler.
154my @run = $Config{run};
155@run = () if not defined $run[0] or $run[0] eq '';
156
157
158if ($target eq '') {
159    die "make_ext: no make target specified (eg all or clean)\n";
160} elsif ($target !~ /(?:^all|clean)$/) {
161    # for the time being we are strict about what make_ext is used for
162    die "$0: unknown make target '$target'\n";
163}
164
165if (!@extspec and !$static and !$dynamic and !$nonxs and !$dynaloader)  {
166    die "$0: no extension specified\n";
167}
168
169my $perl;
170my %extra_passthrough;
171
172if ($is_Win32) {
173    (my $here = getcwd()) =~ s{/}{\\}g;
174    $perl = $^X;
175    if ($perl =~ m#^\.\.#) {
176	$perl = "$here\\$perl";
177    }
178    (my $topdir = $perl) =~ s/\\[^\\]+$//;
179    # miniperl needs to find perlglob and pl2bat
180    $ENV{PATH} = "$topdir;$topdir\\win32\\bin;$ENV{PATH}";
181    my $pl2bat = "$topdir\\win32\\bin\\pl2bat";
182    unless (-f "$pl2bat.bat") {
183	my @args = ($perl, "-I$topdir\\lib", ("$pl2bat.pl") x 2);
184	print "@args\n";
185	system(@args) unless defined $::Cross::platform;
186    }
187
188    my $build = getcwd();
189    print "In $build";
190    foreach my $dir (@dirs) {
191	chdir($dir) or die "Cannot cd to $dir: $!\n";
192	(my $ext = getcwd()) =~ s{/}{\\}g;
193	FindExt::scan_ext($ext);
194	FindExt::set_static_extensions(split ' ', $Config{static_ext});
195	chdir $build
196	    or die "Couldn't chdir to '$build': $!"; # restore our start directory
197    }
198
199    my @ext;
200    push @ext, FindExt::static_ext() if $static;
201    push @ext, FindExt::dynamic_ext() if $dynamic;
202    push @ext, FindExt::nonxs_ext() if $nonxs;
203    push @ext, 'DynaLoader' if $dynaloader;
204
205    foreach (sort @ext) {
206	if (%incl and !exists $incl{$_}) {
207	    #warn "Skipping extension $_, not in inclusion list\n";
208	    next;
209	}
210	if (exists $excl{$_}) {
211	    warn "Skipping extension $_, not ported to current platform";
212	    next;
213	}
214	push @extspec, $_;
215	if($_ eq 'DynaLoader' and $target !~ /clean$/) {
216	    # No, we don't know why nmake can't work out the dependency chain
217	    push @{$extra_passthrough{$_}}, 'DynaLoader.c';
218	} elsif(FindExt::is_static($_)) {
219	    push @{$extra_passthrough{$_}}, 'LINKTYPE=static';
220	}
221    }
222
223    chdir '..'
224	or die "Couldn't chdir to build directory: $!"; # now in the Perl build
225}
226elsif ($is_VMS) {
227    $perl = $^X;
228    push @extspec, (split ' ', $Config{static_ext}) if $static;
229    push @extspec, (split ' ', $Config{dynamic_ext}) if $dynamic;
230    push @extspec, (split ' ', $Config{nonxs_ext}) if $nonxs;
231    push @extspec, 'DynaLoader' if $dynaloader;
232}
233
234{
235    # Cwd needs to be built before Encode recurses into subdirectories.
236    # This seems to be the simplest way to ensure this ordering:
237    my (@first, @other);
238    foreach (@extspec) {
239	if ($_ eq 'Cwd') {
240	    push @first, $_;
241	} else {
242	    push @other, $_;
243	}
244    }
245    @extspec = (@first, @other);
246}
247
248if ($Config{osname} eq 'catamount' and @extspec) {
249    # Snowball's chance of building extensions.
250    die "This is $Config{osname}, not building $extspec[0], sorry.\n";
251}
252
253foreach my $spec (@extspec)  {
254    my $mname = $spec;
255    $mname =~ s!/!::!g;
256    my $ext_pathname;
257
258    # Try new style ext/Data-Dumper/ first
259    my $copy = $spec;
260    $copy =~ tr!/!-!;
261    foreach my $dir (@ext_dirs) {
262	if (-d "$dir/$copy") {
263	    $ext_pathname = "$dir/$copy";
264	    last;
265	}
266    }
267
268    if (!defined $ext_pathname) {
269	if (-d "ext/$spec") {
270	    # Old style ext/Data/Dumper/
271	    $ext_pathname = "ext/$spec";
272	} else {
273	    warn "Can't find extension $spec in any of @ext_dirs";
274	    next;
275	}
276    }
277
278    print "\tMaking $mname ($target)\n";
279
280    build_extension($ext_pathname, $perl, $mname,
281		    [@pass_through, @{$extra_passthrough{$spec} || []}]);
282}
283
284sub build_extension {
285    my ($ext_dir, $perl, $mname, $pass_through) = @_;
286
287    unless (chdir "$ext_dir") {
288	warn "Cannot cd to $ext_dir: $!";
289	return;
290    }
291
292    my $up = $ext_dir;
293    $up =~ s![^/]+!..!g;
294
295    $perl ||= "$up/miniperl";
296    my $return_dir = $up;
297    my $lib_dir = "$up/lib";
298    # $lib_dir must be last, as we're copying files into it, and in a parallel
299    # make there's a race condition if one process tries to open a module that
300    # another process has half-written.
301    my @new_inc = ((map {"$up/$_"} @toolchain), $lib_dir);
302    if ($is_Win32) {
303	@new_inc = map {File::Spec::Functions::rel2abs($_)} @new_inc;
304    }
305    $ENV{PERL5LIB} = join $Config{path_sep}, @new_inc;
306    $ENV{PERL_CORE} = 1;
307    # warn $ENV{PERL5LIB};
308
309    my $makefile;
310    if ($is_VMS) {
311	$makefile = 'descrip.mms';
312	if ($target =~ /clean$/
313	    && !-f $makefile
314	    && -f "${makefile}_old") {
315	    $makefile = "${makefile}_old";
316	}
317    } else {
318	$makefile = 'Makefile';
319    }
320
321    if (!-f $makefile) {
322	if (!-f 'Makefile.PL') {
323	    print "\nCreating Makefile.PL in $ext_dir for $mname\n";
324	    # We need to cope well with various possible layouts
325	    my @dirs = split /::/, $mname;
326	    my $leaf = pop @dirs;
327	    my $leafname = "$leaf.pm";
328	    my $pathname = join '/', @dirs, $leafname;
329	    my @locations = ($leafname, $pathname, "lib/$pathname");
330	    my $fromname;
331	    foreach (@locations) {
332		if (-f $_) {
333		    $fromname = $_;
334		    last;
335		}
336	    }
337
338	    unless ($fromname) {
339		die "For $mname tried @locations in in $ext_dir but can't find source";
340	    }
341            my $pod_name;
342	    ($pod_name = $fromname) =~ s/\.pm\z/.pod/;
343	    $pod_name = $fromname unless -e $pod_name;
344	    open my $fh, '>', 'Makefile.PL'
345		or die "Can't open Makefile.PL for writing: $!";
346	    print $fh <<"EOM";
347#-*- buffer-read-only: t -*-
348
349# This Makefile.PL was written by $0.
350# It will be deleted automatically by make realclean
351
352use strict;
353use ExtUtils::MakeMaker;
354
355WriteMakefile(
356    NAME          => '$mname',
357    VERSION_FROM  => '$fromname',
358    ABSTRACT_FROM => '$pod_name',
359    realclean     => {FILES => 'Makefile.PL'},
360);
361
362# ex: set ro:
363EOM
364	    close $fh or die "Can't close Makefile.PL: $!";
365	}
366	print "\nRunning Makefile.PL in $ext_dir\n";
367
368	# Presumably this can be simplified
369	my @cross;
370	if (defined $::Cross::platform) {
371	    # Inherited from win32/buildext.pl
372	    @cross = "-MCross=$::Cross::platform";
373	} elsif ($opts{cross}) {
374	    # Inherited from make_ext.pl
375	    @cross = '-MCross';
376	}
377
378	my @args = (@cross, 'Makefile.PL');
379	if ($is_VMS) {
380	    my $libd = VMS::Filespec::vmspath($lib_dir);
381	    push @args, "INST_LIB=$libd", "INST_ARCHLIB=$libd";
382	} else {
383	    push @args, 'INSTALLDIRS=perl', 'INSTALLMAN1DIR=none',
384		'INSTALLMAN3DIR=none';
385	}
386	push @args, @$pass_through;
387	_quote_args(\@args) if $is_VMS;
388	print join(' ', @run, $perl, @args), "\n";
389	my $code = system @run, $perl, @args;
390	warn "$code from $ext_dir\'s Makefile.PL" if $code;
391
392	# Right. The reason for this little hack is that we're sitting inside
393	# a program run by ./miniperl, but there are tasks we need to perform
394	# when the 'realclean', 'distclean' or 'veryclean' targets are run.
395	# Unfortunately, they can be run *after* 'clean', which deletes
396	# ./miniperl
397	# So we do our best to leave a set of instructions identical to what
398	# we would do if we are run directly as 'realclean' etc
399	# Whilst we're perfect, unfortunately the targets we call are not, as
400	# some of them rely on a $(PERL) for their own distclean targets.
401	# But this always used to be a problem with the old /bin/sh version of
402	# this.
403	if ($is_Unix) {
404	    my $suffix = '.sh';
405	    foreach my $clean_target ('realclean', 'veryclean') {
406		my $file = "$return_dir/$clean_target$suffix";
407		open my $fh, '>>', $file or die "open $file: $!";
408		# Quite possible that we're being run in parallel here.
409		# Can't use Fcntl this early to get the LOCK_EX
410		flock $fh, 2 or warn "flock $file: $!";
411		print $fh <<"EOS";
412cd $ext_dir
413if test ! -f Makefile -a -f Makefile.old; then
414    echo "Note: Using Makefile.old"
415    make -f Makefile.old $clean_target MAKE='@make' @pass_through
416else
417    if test ! -f Makefile ; then
418	echo "Warning: No Makefile!"
419    fi
420    make $clean_target MAKE='@make' @pass_through
421fi
422cd $return_dir
423EOS
424		close $fh or die "close $file: $!";
425	    }
426	}
427    }
428
429    if (not -f $makefile) {
430	print "Warning: No Makefile!\n";
431    }
432
433    if ($is_VMS) {
434	_macroify_passthrough($pass_through);
435	unshift @$pass_through, "/DESCRIPTION=$makefile";
436    }
437
438    if (!$target or $target !~ /clean$/) {
439	# Give makefile an opportunity to rewrite itself.
440	# reassure users that life goes on...
441	my @args = ('config', @$pass_through);
442	_quote_args(\@args) if $is_VMS;
443	system(@run, @make, @args) and print "@run @make @args failed, continuing anyway...\n";
444    }
445    my @targ = ($target, @$pass_through);
446    _quote_args(\@targ) if $is_VMS;
447    print "Making $target in $ext_dir\n@run @make @targ\n";
448    my $code = system(@run, @make, @targ);
449    die "Unsuccessful make($ext_dir): code=$code" if $code != 0;
450
451    chdir $return_dir || die "Cannot cd to $return_dir: $!";
452}
453
454sub _quote_args {
455    my $args = shift; # must be array reference
456
457    # Do not quote qualifiers that begin with '/'.
458    map { if (!/^\//) {
459          $_ =~ s/\"/""/g;     # escape C<"> by doubling
460          $_ = q(").$_.q(");
461        }
462    } @{$args}
463    ;
464}
465
466sub _macroify_passthrough {
467    my $passthrough = shift;
468    _quote_args($passthrough);
469    my $macro = '/MACRO=(' . join(',',@$passthrough) . ')';
470    @$passthrough = ();
471    @$passthrough[0] = $macro;
472}
473