package Dist::Build; $Dist::Build::VERSION = '0.019'; use strict; use warnings; use Exporter 5.57 'import'; our @EXPORT = qw/Build Build_PL/; use Carp qw/croak/; use CPAN::Meta; use ExtUtils::Config; use ExtUtils::Helpers 0.007 qw/split_like_shell detildefy make_executable/; use ExtUtils::Manifest 'maniread'; use ExtUtils::InstallPaths; use File::Spec::Functions qw/catfile catdir abs2rel /; use Getopt::Long 2.36 qw/GetOptionsFromArray/; use Parse::CPAN::Meta; use version (); use ExtUtils::Builder::Planner 0.016; use ExtUtils::Builder::Util qw/get_perl unix_to_native_path/; use Dist::Build::Serializer; my $json_backend = Parse::CPAN::Meta->json_backend; my $json = $json_backend->new->canonical->pretty->utf8; my $serializer = 'Dist::Build::Serializer'; sub load_json { my $filename = shift; open my $fh, '<:raw', $filename; my $content = do { local $/; <$fh> }; return $json->decode($content); } sub save_json { my ($filename, $content) = @_; open my $fh, '>:raw', $filename; print $fh $json->encode($content); return; } my @options = qw/install_base=s install_path=s% installdirs=s destdir=s prefix=s config=s% uninst:1 verbose:1 dry_run:1 pureperl_only|pureperl-only:1 create_packlist=i jobs=i allow_mb_mismatch:1/; sub get_config { my ($meta_name, @arguments) = @_; my %options; GetOptionsFromArray($_, \%options, @options) or die "Could not parse arguments" for @arguments; $options{$_} = detildefy($options{$_}) for grep { exists $options{$_} } qw/install_base destdir prefix/; if ($options{install_path}) { $_ = detildefy($_) for values %{ $options{install_path} }; } $options{config} = ExtUtils::Config->new($options{config}); $options{install_paths} = ExtUtils::InstallPaths->new(%options, dist_name => $meta_name); return %options; } sub Build_PL { my ($args, $env) = @_; my $meta = CPAN::Meta->load_file('META.json', { lazy_validation => 0 }); my @env = defined $env->{PERL_MB_OPT} ? split_like_shell($env->{PERL_MB_OPT}) : (); my %options = get_config($meta->name, [ @{$args} ], [ @env ]); my $planner = ExtUtils::Builder::Planner->new; $planner->create_phony('code', 'config'); $planner->create_phony('manify', 'config'); $planner->create_phony('dynamic'); $planner->create_phony('pure_all', 'code', 'manify', 'dynamic'); $planner->create_phony('build', 'pure_all'); $planner->add_delegate('meta', sub { $meta }); $planner->add_delegate('distribution', sub { $meta->name }); $planner->add_delegate('version', sub { version->new($meta->version) }); (my $main_module = $meta->name) =~ s/-/::/g; $planner->add_delegate('main_module', sub { $main_module }); $planner->add_delegate('release_status', sub { $meta->release_status }); $planner->add_delegate('perl_path', sub { get_perl(config => $options{config}, %options) }); for my $variable (qw/config install_paths verbose uninst jobs pureperl_only/) { $planner->add_delegate($variable, sub { $options{$variable} }); } $planner->add_delegate('is_os', sub { my ($self, @wanted) = @_; return not not grep { $_ eq $^O } @wanted }); $planner->add_delegate('is_os_type', sub { my ($self, $wanted) = @_; require Perl::OSType; return Perl::OSType::is_os_type($wanted); }); $planner->add_delegate('new_planner', sub { my $inner = ExtUtils::Builder::Planner->new; $inner->add_delegate('config', sub { $options{config} }); return $inner; }); my @meta_fragments; $planner->add_delegate('add_meta', sub { my (undef, @fragments) = @_; push @meta_fragments, @fragments; }); my $core = $planner->new_scope; $core->load_extension('Dist::Build::Core'); my @blibs = map { catfile('blib', $_) } qw/lib arch bindoc libdoc script bin/; $core->mkdir($_) for @blibs; $core->create_phony('config', @blibs); $core->lib_dir('lib'); $core->script_dir('script'); $core->add_seen(unix_to_native_path($_)) for sort keys %{ maniread() }; $core->tap_harness('test', dependencies => [ 'pure_all' ], test_dir => 't'); $core->install('install', dependencies => [ 'pure_all' ], install_map => $options{install_paths}->install_map); for my $file (glob 'planner/*.pl') { $planner->new_scope->run_dsl($file); } $core->autoclean; my $plan = $planner->materialize; mkdir '_build' if not -d '_build'; save_json(catfile(qw/_build graph/), $serializer->serialize_plan($plan)); save_json(catfile(qw/_build params/), [ $args, \@env ]); if (@meta_fragments) { require CPAN::Meta::Merge; my $merger = CPAN::Meta::Merge->new(default_version => '2'); my $metahash = $merger->merge($meta, @meta_fragments); $metahash->{dynamic_config} = 0; $meta = CPAN::Meta->create($metahash, { lazy_validation => 0 }); } $meta->save('MYMETA.json'); printf "Creating new 'Build' script for '%s' version '%s'\n", $meta->name, $meta->version; my $dir = $meta->name eq 'Dist-Build' ? 'lib' : 'inc'; open my $fh, '>:utf8', 'Build'; print $fh "#!perl\nuse lib '$dir';\nuse Dist::Build;\nBuild(\\\@ARGV, \\\%ENV);\n"; close $fh; make_executable('Build'); return; } sub Build { my ($args, $env) = @_; my $meta = CPAN::Meta->load_file('MYMETA.json', { lazy_validation => 0 }); my ($bpl, $mbopts) = @{ load_json(catfile(qw/_build params/)) }; my %options = get_config($meta->name, $bpl, $mbopts, $args); my $action = @{$args} ? shift @{$args} : 'build'; my $preplan = load_json(catfile(qw/_build graph/)); my $plan = $serializer->deserialize_plan($preplan, %options); return $plan->run($action); } 1; # ABSTRACT: A modern module builder, author tools not included! __END__ =pod =encoding UTF-8 =head1 NAME Dist::Build - A modern module builder, author tools not included! =head1 VERSION version 0.019 =head1 DESCRIPTION C is a Build.PL implementation. Unlike L it is extensible, unlike L it uses a build graph internally which makes it easy to combine different customizations. It's typically extended by adding a C<.pl> script in C. E.g. load_extension("Dist::Build::ShareDir"); dist_sharedir('share', 'Foo-Bar'); load_extension("Dist::Build::XS"); load_extension("Dist::Build::XS::Alien"); add_xs( alien => 'foo', extra_sources => [ glob 'src/*.c' ], ); =head1 PLUGINS =over 4 =item * L This plugin enables one to compile XS modules. It has a range of options, and a series of extensions that add to its capabilities. =over 4 =item * L This is used to link to an L library. =item * L This wraps L to detect headers, libraries and features. =item * L This can be used to import headers and flags as exported by L. =item * L This adds flags for a given library as configured in its pkgconfig file. =item * L This integrates L into the C command. =back =item * L This allows one to install sharedirs =item * L This allows one to export headers and flags, to be imported by L =item * L This allows one to dynamically evaluate dependencies. =item * L This module contains all commands used for the base actions of the module. =back =head1 DELEGATES By default, the following delegates are defined on your L: =over 4 =item * meta A L object representing the C file. =item * distribution The name of the distribution =item * version The version of the distribution =item * main_module The main module of the distribution. =item * release_status The release status of the distribution (e.g. C<'stable'>). =item * perl_path The path to the perl executable. =item * config The L object for this build =item * install_paths The L object for this build. =item * is_os(@os_names) This returns true if the current operating system matches any of the listed ones. =item * is_os_type($os_type) This returns true if the type of the OS matches C<$os_type>. Legal values are C, C and C. =item * verbose The value of the C command line argument. =item * uninst The value of the C command line argument. =item * jobs The value of the C command line argument. =item * pureperl_only The value of the C command line argument. =back =head1 AUTHOR Leon Timmermans =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2024 by Leon Timmermans. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut