]> Dogcows Code - chaz/graphql-client/blob - maint/fatpack.pl
avoid fatpacking JSON::Path::Evaluator
[chaz/graphql-client] / maint / fatpack.pl
1 #!/usr/bin/env perl
2
3 =head1 NAME
4
5 maint/fatpack.pl - Generate a fatpack version of graphql
6
7 =head1 SYNOPSIS
8
9 maint/fatpack.pl --dist-dir DIRPATH [--clean]
10
11 =cut
12
13 use 5.010001;
14 use strict;
15 use warnings;
16
17 use CPAN::Meta;
18 use Capture::Tiny qw(capture_stdout);
19 use Config;
20 use File::pushd;
21 use Getopt::Long;
22 use MetaCPAN::API;
23 use Module::CoreList;
24 use Path::Tiny 0.089;
25
26 my $core_version = '5.010001';
27 my $plenv_version = '5.10.1';
28 my %blacklist_modules = map { $_ => 1 } (
29 'perl',
30 'Text::Table::ASV', # brought in by Text::Table::Any but not actually required
31 'Unicode::GCString', # optional XS module
32 'JSON::Path::Evaluator', # requires perl 5.16
33 );
34 my @extra_modules = (
35 'Proc::Find::Parents', # used by Term::Detect::Software on some platforms
36 );
37
38 my $clean = 0;
39 my $distdir;
40 GetOptions(
41 'clean!' => \$clean,
42 'dist=s' => \$distdir,
43 ) or die "Invalid options.\n";
44 $distdir && -d $distdir or die "Use --dist to specify path to a distribution directory.\n";
45
46 my $mcpan = MetaCPAN::API->new;
47
48 run($distdir, $clean);
49 exit;
50
51 sub install_modules {
52 my $path = path(shift);
53 my @modules = @_;
54 run_command('cpanm', '-n', "-L$path", @modules);
55 }
56
57 sub run {
58 my $distdir = path(shift);
59 my $clean = shift;
60
61 my $builddir = path('.build');
62 my $fatlibdir = path('fatlib');
63
64 if ($clean) {
65 print STDERR "Cleaning...\n";
66 $builddir->remove_tree({safe => 0});
67 $fatlibdir->remove_tree({safe => 0});
68 }
69
70 $builddir->mkpath;
71
72 my @modules = required_modules($distdir, $builddir->child('deps.txt'));
73 install_modules($builddir->child('local'), @modules);
74 pack_modules($builddir->child('local'), @modules);
75
76 clean_fatlib($fatlibdir);
77
78 # consolidate all modules into a new directory for packing
79 my $moduledir = $builddir->child('modules');
80 $moduledir->remove_tree({safe => 0});
81 $moduledir->mkpath;
82 system(qw{cp -r}, $fatlibdir, $distdir->child('lib'), "$moduledir/");
83
84 $moduledir->child('fatlib/Test')->remove_tree({safe => 0}); # don't need Test:: modules
85
86 my $fatpack = do {
87 my $cd_builddir = pushd($moduledir);
88
89 system('perlstrip', '--cache', '-v', find_modules('.'));
90 `fatpack file`;
91 };
92
93 generate_script($distdir->child('bin/graphql'), $fatpack, 'graphql');
94 }
95
96 sub required_modules {
97 my $path = path(shift);
98 my $cache_filepath = shift;
99
100 print STDERR "Determining required modules...\n";
101
102 my $cachefile = $cache_filepath && path($cache_filepath);
103 if (my $contents = eval { $cachefile->slurp_utf8 }) {
104 chomp $contents;
105 return split(/\n/, $contents);
106 }
107
108 my $meta = CPAN::Meta->load_file($path->child('META.json'));
109
110 my $requires = CPAN::Meta::Requirements->new;
111
112 for my $type (qw{requires recommends suggests}) {
113 my $reqs = $meta->effective_prereqs->requirements_for('runtime', $type);
114 for my $module ($reqs->required_modules) {
115 next if $blacklist_modules{$module};
116
117 my $core = $Module::CoreList::version{$core_version}{$module};
118 print STDERR "skipping core: $module $core\n" if $core;
119 next if $core && $reqs->accepts_module($module, $core);
120
121 $requires->add_string_requirement($module => $reqs->requirements_for_module($module));
122 dependencies_for_module($requires, $module);
123 }
124 }
125 $requires->clear_requirement($_) for qw(Module::CoreList ExtUtils::MakeMaker Carp);
126 my @deps = $requires->required_modules;
127
128 push @deps, @extra_modules;
129
130 $cachefile->spew_utf8([map { "$_\n" } @deps]) if $cachefile;
131
132 return @deps;
133 }
134
135 sub dependencies_for_dist {
136 my $requires = shift;
137 my $name = shift;
138
139 state %dists;
140 return if $dists{$name}++;
141 print STDERR "Finding dependencies for dist $name\n";
142
143 my $dist = $mcpan->release(distribution => $name);
144
145 my $reqs = CPAN::Meta::Requirements->new;
146
147 foreach my $dep (@{$dist->{dependency}}) {
148 next if $dep->{phase} ne 'runtime';
149 next if $dep->{relationship} ne 'requires'; # && $dep->{relationship} ne 'recommends';
150
151 my $module = $dep->{module};
152 next if $blacklist_modules{$module};
153
154 $reqs->add_minimum($dep->{module} => $dep->{version});
155 my $core = $Module::CoreList::version{$core_version}{$module};
156 print STDERR "skipping core: $module $core\n" if $core;
157 next if $core && $reqs->accepts_module($module, $core);
158
159 $requires->add_string_requirement($module => $reqs->requirements_for_module($module));
160 dependencies_for_module($requires, $dep->{module});
161 }
162 }
163
164 sub dependencies_for_module {
165 my $requires = shift;
166 my $name = shift;
167
168 state %modules;
169 return if $modules{$name}++;
170 print STDERR "Finding dependencies for module $name\n";
171
172 my $module = $mcpan->module($name);
173 dependencies_for_dist($requires, $module->{distribution});
174 }
175
176 sub clean_fatlib {
177 my $path = path(shift);
178 $path->child($Config{archname})->remove_tree({safe => 0});
179 $path->child('POD2')->remove_tree({safe => 0});
180 $path->visit(sub {
181 local $_ = shift;
182 if (/\.p(od|l)$/ || /\.sample$/) {
183 print "rm $_\n";
184 $_->remove;
185 }
186 }, {recurse => 1});
187 }
188
189 sub find_modules {
190 my $path = path(shift);
191 my @pm_filepaths;
192 $path->visit(sub {
193 local $_ = shift;
194 push @pm_filepaths, $_ if /\.pm$/;
195 }, {recurse => 1});
196 return @pm_filepaths;
197 }
198
199 sub pack_modules {
200 my ($path, @modules) = @_;
201
202 my @filepaths = map { my $s = $_; $s =~ s!::!/!g; "$s.pm" } @modules;
203
204 my $stdout = capture_stdout {
205 local $ENV{PERL5LIB} = $path->child('lib/perl5')->absolute;
206 system('fatpack', 'packlists-for', @filepaths);
207 };
208
209 my @packlists = split(/\n/, $stdout);
210 for my $packlist (@packlists) {
211 warn "Packing $packlist\n";
212 }
213
214 system('fatpack', 'tree', map { path($_)->absolute } @packlists);
215 }
216
217 sub generate_script {
218 my ($input_filepath, $fatpack, $output_filepath) = @_;
219
220 open(my $in, '<', $input_filepath) or die "open failed: $!";
221 open(my $out, '>', "$output_filepath.tmp") or die "open failed: $!";
222
223 while (<$in>) {
224 s|^#!\h*perl|#!/usr/bin/env perl|;
225 s|^# FATPACK.*|$fatpack|;
226 print $out $_;
227 }
228
229 unlink($output_filepath);
230 rename("$output_filepath.tmp", $output_filepath);
231
232 path($output_filepath)->chmod(0755);
233
234 print STDERR "Wrote fatpacked script: $output_filepath\n";
235 }
236
237 sub run_command {
238 local $ENV{PLENV_VERSION} = $plenv_version;
239 system('plenv', 'exec', @_);
240 }
241
This page took 0.051433 seconds and 4 git commands to generate.