Group
Extension

Dist-Zilla-Plugin-Depak/lib/Dist/Zilla/Plugin/Depak.pm

package Dist::Zilla::Plugin::Depak;

our $DATE = '2017-07-14'; # DATE
our $VERSION = '0.21'; # VERSION

use 5.010001;
use strict;
use warnings;

use App::lcpan::Call qw(call_lcpan_script);
use Data::Dmp;
use Dist::Zilla::File::InMemory;
use ExtUtils::MakeMaker;
use File::Path qw(make_path);
use File::Slurper qw(read_binary write_binary);
use File::Temp qw(tempfile tempdir);
use File::Which;
use IPC::System::Options qw(system);
use JSON::MaybeXS;
use List::Util qw(first);

use Moose;
with (
    'Dist::Zilla::Role::FileFinderUser' => {
        default_finders => [':ExecFiles', ':InstallModules'],
    },
    'Dist::Zilla::Role::FileGatherer',
    'Dist::Zilla::Role::FileMunger',
    #'Dist::Zilla::Role::MetaProvider',
    'Dist::Zilla::Role::PERLANCAR::WriteModules',
);

has include_script => (is => 'rw');
has exclude_script => (is => 'rw');
has put_hook_at_the_end => (is => 'rw');

use namespace::autoclean;

sub mvp_multivalue_args { qw(include_script exclude_script) }

sub munge_files {
    #use experimental 'smartmatch';

    my $self = shift;


    $self->log_debug(["Initializing _mods and _dists"]);
    $self->{_mods} //= {};

    # let's check whether there are other FileMunger plugins and warn it. due to
    # the nature of this plugin (combines several module files of potentially
    # different encodings into a single perl file, and thus using the 'bytes'
    # encoding to combine them all) it might conflict with the other file
    # mungers, for example PodWeaver which does text munging and uses text
    # encoding. so, it's best to put this plugin as the last file munger.
    my $found_me;
    my @other_file_mungers;
    for my $p (@{ $self->zilla->plugins }) {
        if (!$found_me) {
            if ($p eq $self) { $found_me++ }
            next;
        }
        if ($p->does("Dist::Zilla::Role::FileMunger")) {
            push @other_file_mungers, $p->plugin_name;
        }
    }
    $self->log(["There are other FileMunger plugins (%s) after this plugin, ".
                    "this is not recommended because it might cause encoding problems",
                \@other_file_mungers]) if @other_file_mungers;

    my @scripts0 = grep { $_->name =~ m!^(bin|scripts?)! } @{ $self->found_files };
    my @scripts;
    if ($self->include_script) {
        for my $item (@{ $self->include_script }) {
            my $file = first { $item eq $_->name } @scripts0;
            $self->log_fatal(["included '%s' not in list of available scripts", $item])
                unless $file;
            push @scripts, $file;
        }
    } else {
        @scripts = @scripts0;
    }
    if ($self->exclude_script) {
        for my $item (@{ $self->exclude_script }) {
            @scripts = grep { $_->name ne $item } @scripts;
        }
    }
    $self->munge_script($_) for @scripts;

    my @modules  = grep { $_->name =~ m!^(lib)! } @{ $self->found_files };
    $self->munge_module($_) for @modules;

    # we usually do this in metadata(), but for some reason it is now executed
    # before munge_files() so _mods is still empty
    $self->log_debug(["Setting dist metadata x_Dist_Zilla_Plugin_Depak"]);
    $self->zilla->distmeta->{x_Dist_Zilla_Plugin_Depak} = {
        packed_modules => $self->{_mods},
        packed_dists   => $self->{_dists},
    };
}

sub munge_script {
    my ($self, $file) = @_;

    $self->log_fatal(["Can't find depak in PATH"]) unless which("depak");

    # we use the depak CLI instead of App::depak because we want to use
    # --config-profile.
    my $profile = $file->name;
    $profile =~ s!.+[\\/]!!;

    # since we're dealing with CLI, we need actual files. even a modified
    # DZF:OnDisk might not have the actual file located in $file->name, so we
    # write to tempfile first.

    my $source;
    {
        my ($fh, $filename) = tempfile();
        $source = $filename;
        write_binary($filename, $file->content);
    }

    my $target;
    {
        my ($fh, $filename) = tempfile();
        $target = $filename;
    }

    $self->write_modules_to_dir;
    my $mods_tempdir = $self->written_modules_dir;

    # the --json output is so that we can read the list of included modules
    my @depak_cmd = (
        "depak",
        "--include-dir", $mods_tempdir,
        "-i", $source, "-o", $target, "--overwrite",
        "--json",
    );

    if (-f "depak.conf") {
        push @depak_cmd, (
            "--config-profile", $profile,
            "--config-path", "depak.conf",
        );
    }

    if ($self->put_hook_at_the_end) {
        push @depak_cmd, "--put-hook-at-the-end";
    }

    $self->log_debug(["Depak-ing %s: %s", $file->{name}, \@depak_cmd]);
    my $stdout;
    system({die=>1, log=>1, shell=>0, capture_stdout=>\$stdout}, @depak_cmd);

    my $depak_res = JSON::MaybeXS::decode_json($stdout);
    $self->log_fatal(["depak failed: %s", $depak_res])
        unless $depak_res->[0] == 200;

    my $content = read_binary($target);

    $self->log_debug(["depak output: %s (%s, %d bytes)",
                      $file->{name}, $target, length($content)]);

    #$self->log_debug(["depak result: %s", $depak_res]);

    my $im = $depak_res->[3]{'func.included_modules'};
    for (keys %$im) { $self->{_mods}{$_} = $im->{$_} }

    # re-add the file instead of changing the content, so we can re-set the
    # encoding to 'bytes'
    my $newfile = Dist::Zilla::File::InMemory->new(
        encoding=>'bytes', name=>$file->{name}, content => $content);
    $self->zilla->prune_file($file);
    $self->add_file($newfile);
}

sub munge_module {
    my ($self, $file) = @_;

    my $munged;
    my $content = $file->content;
    my @pod;

    if ($content =~ /^#\s*(PACKED_MODULES|PACKED_CONTENTS_POD)\s*$/m) {
        $munged++;
        $self->{_mods} //= {};
        $content =~ s/(^#\s*PACKED_MODULES\s*$)/
            "our \%PACKED_MODULES = %{ " . dmp($self->{_mods}) . " }; $1"/em;
        push @pod, "Modules packed into this distribution:\n\n=over\n\n",
            (map {"=item * $_\n\n"} sort keys %{$self->{_mods}}),
            "\n=back\n\n";
    }

    if ($content =~ /^#\s*(PACKED_DISTS|PACKED_CONTENTS_POD)\s*$/m) {
        $munged++;
        unless ($self->{_dists}) {
            if (!keys %{ $self->{_mods} }) {
                $self->{_dists} = {};
            } else {
                my $res = call_lcpan_script(
                    argv => ["mod2dist", keys %{$self->{_mods}}],
                );
                $self->log_fatal(["Can't lcpan mod2dist: %s - %res", $res->[0], $res->[1]])
                    unless $res->[0] == 200;
                if (ref $res->[2]) {
                    for (values %{$res->[2]}) {
                        $self->{_dists}{$_} = 0;
                    }
                } else {
                    # single result
                    $self->{_dists}{$res->[2]} = 0;
                }
            }
        }
        $content =~ s/(^#\s*PACKED_DISTS\s*$)/
            "our \@PACKED_DISTS = \@{" . dmp([sort keys %{$self->{_dists}}]) . "}; $1"/em;
        push @pod, "Distributions packed into this distribution:\n\n=over\n\n",
            (map {"=item * $_\n\n"} sort keys %{$self->{_dists}}),
            "\n=back\n\n";
    }

    if ($content =~ /^#\s*PACKED_CONTENTS_POD\s*$/m) {
        $munged++;
        $content =~ s/(^#\s*PACKED_CONTENTS_POD\s*$)/
            join("", @pod)/em;
        push @pod, "Distributions packed inside this script:\n\n=over\n\n",
            (map {"=item * $_\n\n"} sort keys %{$self->{_dists}}),
            "\n=back\n\n";
    }

    if ($munged) {
        $self->log_debug(["Setting %PACKED_MODULES / \@PACKED_DISTS / PACKED_CONTENTS_POD in %s", $file->{name}]);
        $file->content($content);
    }
}

sub gather_files {
}

sub metadata {
    my $self = shift;

    # weird, why is metadata() now executed before munge_files()? so let's use
    # distmeta directly.
}

__PACKAGE__->meta->make_immutable;
1;
# ABSTRACT: Pack dependencies onto scripts during build using 'depak'

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Plugin::Depak - Pack dependencies onto scripts during build using 'depak'

=head1 VERSION

This document describes version 0.21 of Dist::Zilla::Plugin::Depak (from Perl distribution Dist-Zilla-Plugin-Depak), released on 2017-07-14.

=head1 SYNOPSIS

In F<dist.ini>:

 [Depak]
 ;;; the default is to include all scripts, but use below to include only some
 ;;; scripts
 ;include_script=bin/script1
 ;include_script=bin/script2

In C<depak.conf> in dist top-level directory, put your L<depak> configuration.

During build, your scripts will be replaced with the packed version.

Also, you should also have a module named C<Something::Packed> (i.e. whose name
ends in C<::Packed>), which contains:

 # PACKED_MODULES
 # PACKED_DISTS

During build, these will be replaced with:

 our %PACKED_MODULES = (...); # PACKED_MODULES
 our @PACKED_DISTS = (...); # PACKED_DISTS

=head1 DESCRIPTION

This plugin will replace your scripts with the packed version (that is, scripts
that have their dependencies packed onto themselves). Packing will be done using
L<depak>.

If F<depak.conf> exists in your dist's top-level directory, it will be used as
the depak configuration.

In addition to replacing scripts with the packed version, it will also search
for directives C<# PACKED_MODULES> and C<# PACKED_DISTS> in module files and
replace them with C<%PACKED_MODULES> and C<@PACKED_DISTS>. The
C<%PACKED_MODULES> hash lists all the modules that are included in the one of
the scripts. This can be useful for tools that might need it. C<@PACKED_DISTS>
array lists all the dists that are included in one of the scripts. This also can
be useful for tools that might need it, like
L<Dist::Zilla::Plugin::PERLANCAR::CheckDepDists>.

There is also C<# PACKED_CONTENTS_POD> which you can put in your script. It will
be replaced with POD that list the packed modules/dists.

=for Pod::Coverage .+

=head1 CONFIGURATION

=head2 include_script = str+

Explicitly include only specified script. Can be specified multiple times. The
default, when no C<include_script> configuration is specified, is to include all
scripts in the distribution.

=head2 exclude_script = str+

Exclude a script. Can be specified multiple times.

=head2 put_hook_at_the_end => bool

Will be passed to C<depak>.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/Dist-Zilla-Plugin-Depak>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-Dist-Zilla-Plugin-Depak>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-Depak>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 SEE ALSO

L<depak>

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017, 2016, 2015 by perlancar@cpan.org.

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


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.