File Coverage

blib/lib/Win32/Packer.pm
Criterion Covered Total %
statement 18 20 90.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 25 27 92.5


line stmt bran cond sub pod time code
1             package Win32::Packer;
2              
3             our $VERSION = '0.01';
4              
5 1     1   48117 use 5.010;
  1         4  
6 1     1   4 use Carp;
  1         2  
  1         42  
7 1     1   255 use Log::Any;
  1         6998  
  1         5  
8 1     1   531 use Path::Tiny;
  1         12620  
  1         56  
9 1     1   497 use Module::ScanDeps;
  1         29821  
  1         65  
10 1     1   10 use Config;
  1         1  
  1         30  
11 1     1   137 use Win32::Ldd qw(pe_dependencies);
  0            
  0            
12              
13             use Win32::Packer::Helpers qw(mkpath to_bool to_array to_array_path
14             to_aoh_path assert_file assert_file_name
15             assert_aoh_path_file assert_dir
16             assert_subsystem assert_aoh_path_dir
17             assert_aoh_path c_string_quote
18             windows_directory to_loh_path);
19              
20             use Win32::Packer::WrapperCCode;
21             use Win32::Packer::LoadPLCode;
22             our ($wrapper_c_code, $load_pl_code);
23              
24             use Moo;
25             use namespace::autoclean;
26              
27             extends 'Win32::Packer::Base';
28              
29             has _OS => ( is => 'ro', # _OS is a hack to enable compiling the module on non-windows OSs
30             isa => sub { $_[0] =~ /^MSWin32/i or croak "Unsupported OS" },
31             default => sub { $^O } );
32             has extra_module => ( is => 'ro', coerce => \&to_array, default => sub { [] } );
33             has extra_inc => ( is => 'ro', coerce => \&to_array_path, default => sub { [] } );
34             has scripts => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] },
35             isa => sub { @{$_[0]} > 0 or croak "scripts argument missing" } );
36             has extra_exe => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] },
37             isa => \&assert_aoh_path );
38             has extra_dll => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] },
39             isa => \&assert_aoh_path_file );
40             has extra_dir => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] },
41             isa => \&assert_aoh_path_dir );
42             has extra_file => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] },
43             isa => \&assert_aoh_path_file );
44             has merge => ( is => 'ro', coerce => \&to_aoh_path, default => sub { [] } );
45             has license => ( is => 'ro', coerce => \&path, isa => \&assert_file );
46             has perl_exe => ( is => 'lazy', isa => \&assert_file,
47             default => sub { path($^X)->realpath } );
48             has strawberry => ( is => 'lazy', isa => \&assert_dir );
49             has windows => ( is => 'lazy', isa => \&assert_dir,
50             default => \&windows_directory );
51             has inc => ( is => 'lazy', coerce => \&to_array_path );
52             has scan_deps_opts => ( is => 'ro', default => sub { {} } );
53             has cache => ( is => 'ro', coerce => \&mkpath, isa => \&assert_dir) ;
54             has clean_cache => ( is => 'ro', coerce => \&to_bool );
55             has keep_work_dir => ( is => 'ro', coerce => \&to_bool, default => 0 );
56             has cc_exe => ( is => 'lazy', isa => \&assert_file, coerce => \&path );
57             has ld_exe => ( is => 'lazy', isa => \&assert_file, coerce => \&path );
58             has strawberry_c_bin => ( is => 'lazy', isa => \&assert_dir, coerce => \&path );
59             has cygpath => ( is => 'lazy', isa => \&assert_file, coerce => \&path );
60             has cygwin => ( is => 'lazy', isa => \&assert_dir, coerce => \&path );
61             has cygwin_bin => ( is => 'lazy', isa => \&assert_dir, coerce => \&path );
62             has search_path => ( is => 'ro', coerce => \&to_array_path, default => sub { [] } );
63             has windres_exe => ( is => 'lazy', isa => \&assert_file, coerce => \&path );
64             has app_subsystem => ( is => 'ro', default => 'console',
65             isa => \&assert_subsystem );
66              
67             has _pm_deps => ( is => 'lazy' );
68             has _pe_deps => ( is => 'lazy' );
69             has _wrapper_dir => ( is => 'lazy', isa => \&assert_dir );
70              
71             has _wrapper_c => ( is => 'lazy', isa => \&assert_file );
72             has _wrapper_o => ( is => 'lazy', isa => \&assert_file );
73              
74             has _load_pl => ( is => 'lazy', isa => \&assert_file );
75              
76             has _script_wrappers => ( is => 'lazy' );
77              
78             has _extra_exe_mod => ( is => 'lazy');
79              
80             has _extra_exe_resolved => ( is => 'lazy' );
81              
82             around new => sub {
83             my $orig = shift;
84             my $class = shift;
85             my $self = $class->$orig(@_);
86             $self->_clean_all;
87             $self;
88             };
89              
90             sub _clean_all {
91             my $self = shift;
92             if ($self->clean_cache) {
93             if (defined (my $cache = $self->cache)) {
94             $self->log->debug("deleting cache");
95             $cache->remove_tree({safe => 0, keep_root => 1});
96             }
97             else {
98             $self->warn("clean_cache is set but cache directory is not defined");
99             }
100             }
101              
102             $self->log->debug("cleaning work dir");
103             eval { $self->work_dir->remove_tree({safe => 0, keep_root => 1}); 1 }
104             or $self->log->warnf("Unable to remove old working dir completely: %s", $@);
105             }
106              
107             sub _build__extra_exe_mod {
108             my $self = shift;
109             my @mod;
110             for (@{$self->_extra_exe_resolved}) {
111             if (defined (my $subsystem = $_->{subsystem})) {
112             push @mod, { %$_, path => $self->_change_exe_subsystem($_, $subsystem) };
113             }
114             else {
115             push @mod, $_;
116             }
117             }
118             \@mod
119             }
120              
121             sub _build__extra_exe_resolved {
122             my $self = shift;
123             my @res;
124             for (@{$self->extra_exe}) {
125             my $path = $_->{path};
126             if ($path->is_file) {
127             push @res, $_;
128             }
129             else {
130             if ($_->{cygwin}) {
131             my $cygwin = $self->cygwin;
132             if (my ($path) = grep($_->is_file,
133             map $_->child($path), $cygwin, $cygwin->child('bin'))) {
134             $self->log->debug("Executable '$_->{path}' resolved to '$path'");
135             push @res, { %$_, path => $path };
136             next;
137             }
138             }
139             $self->_die("Could not resolve exe $path");
140             }
141             }
142             \@res
143             }
144              
145             sub _build_inc {
146             my $self = shift;
147             [ @{$self->extra_inc}, @INC ]
148             }
149              
150             sub _build_cygwin_bin {
151             my $self = shift;
152             $self->cygwin->child('bin');
153             }
154              
155             sub _build_cygwin {
156             my $self = shift;
157              
158             my $cygpath = $self->{cygpath} // path('cygpath');
159             my ($rc, $out, $err) = $self->_run_cmd($cygpath, -w => '/');
160             if ($rc) {
161             my $cygwin = $out;
162             chomp $cygwin;
163             return $cygwin if -d $cygwin;
164             }
165              
166             require Win32::TieRegistry;
167             my %reg;
168             Win32::TieRegistry->import(TiedHash => \%reg);
169              
170             for my $dir ( $reg{'HKEY_CURRENT_USER\\SOFTWARE\\Cygwin\\setup\\rootdir'},
171             $reg{'HKEY_LOCAL_MACHINE\\SOFTWARE\\Cygwin\\setup\\rootdir'},
172             $self->system_drive->child('Cygwin') ) {
173             defined $dir and -d $dir or next;
174             return $dir;
175             }
176              
177             croak "Cygwin directory not found";
178             }
179              
180             sub _build_cygpath {
181             my $self = shift;
182             $self->cygwin->child('bin/cygpath.exe');
183             }
184              
185             sub _build_strawberry {
186             my $self = shift;
187             my $p = $self->perl_exe->parent->parent->parent;
188             $self->log->trace("Strawberry dir: $p");
189             $p
190             }
191              
192             sub _build_strawberry_c_bin {
193             my $self = shift;
194             $self->strawberry->child('c/bin');
195             }
196              
197             sub _config2exe {
198             my ($self, $name) = @_;
199             my $base = $Config{$name};
200             $base =~ s/(?:\.exe)?$/.exe/i;
201             my $exe = path($base)->absolute($self->strawberry_c_bin);
202             $self->log->debugf("exe for command '%s' is '%s'", $name, $exe);
203             $exe
204             }
205              
206             sub _build_cc_exe { shift->_config2exe('cc') }
207             sub _build_ld_exe { shift->_config2exe('ld') }
208              
209             sub _build_windres_exe {
210             my $self = shift;
211             my $exe = $self->strawberry_c_bin->child('windres.exe');
212             $self->log->debugf("exe for command 'windres' is '%s'", $exe);
213             $exe;
214             }
215              
216             sub _build_work_dir {
217             my $self = shift;
218             my $keep = $self->keep_work_dir;
219             my $p = Path::Tiny->tempdir("Win32-Packer-XXXXXX", CLEANUP => !$keep )->realpath;
220             $self->log->debug("Work dir: $p");
221             $self->log->info("Would keep work dir '$p'") if $keep;
222             $p;
223             }
224              
225             sub _new_installer_maker {
226             my $self = shift;
227             my %opts = ((@_ & 1) ? (type => @_) : @_);
228              
229             my $type = delete $opts{type} // 'zip';
230             $type =~ s/-/_/g;
231             $type =~ /^(?:\w+)$/ or $self->_die("Wrong installer type '$type'");
232             my $backend = __PACKAGE__ . "::InstallerMaker::$type";
233             eval "require $backend; 1" or $self->_die("Unable to load backend '$backend': $@");
234             $self->log->debug("Package $backend loaded");
235              
236             for (qw(app_name app_version app_vendor app_id app_description app_keywords app_comments
237             icon log work_dir output_dir)) {
238             if (defined (my $v = $self->$_)) {
239             $opts{$_} //= $v
240             }
241             }
242              
243             $backend->new(%opts);
244             }
245              
246             sub installer_maker {
247             my $self = shift;
248              
249             my $installer = $self->_new_installer_maker(@_);
250              
251             $self->_install_scripts($installer);
252             $self->_install_load_pl($installer);
253             $self->_install_wrappers($installer);
254             $self->_install_extra_exe($installer);
255             $self->_install_extra_dir($installer);
256             $self->_install_extra_file($installer);
257             $self->_install_pm_deps($installer);
258             $self->_install_pe_deps($installer);
259             $self->_install_license($installer);
260              
261             $self->_install_merge($installer);
262              
263             $installer;
264             }
265              
266             sub make_installer {
267             my $self = shift;
268             my $installer = $self->installer_maker(@_);
269             $installer->run;
270             }
271              
272             sub _install_scripts {
273             my ($self, $installer) = @_;
274              
275             $self->log->info("Adding scripts");
276             my $lib = path('lib');
277             for (@{$self->scripts}) {
278             my $to = $lib->child($_->{basename}.'.pl');
279             $installer->add_file($_->{path}, $to);
280             }
281             }
282              
283             sub _install_merge {
284             my ($self, $installer) = @_;
285             $self->log->info("Merging extra data");
286             $installer->merge($_->{path}, $self->_common_file_opts($_))
287             for @{$self->merge};
288             }
289              
290             sub store {
291             my ($self, $fn) = @_;
292             $fn //= $self->work_dir->child('store', 'packer.sto');
293             path($fn)->absolute->parent->mkpath;
294              
295             $self->log->info("Saving Win32::Packer object into '$fn'");
296              
297             require Storable;
298             local $self->{log}; # may have code references
299             Storable::store($self, "$fn");
300              
301             $fn;
302             }
303              
304             sub retrieve {
305             my ($class, $fn, $log) = @_;
306              
307             require Storable;
308             my $self = Storable::retrieve($fn);
309              
310             if (defined $log) {
311             $self->log($log);
312             $self->log->info("Win32::Packer object retrieved from '$fn'");
313             $self->log->tracef("object: %s", $self);
314             }
315             $self;
316             }
317              
318             sub _install_load_pl {
319             my ($self, $installer) = @_;
320             $self->log->info("Adding load.pl");
321             $installer->add_file($self->_load_pl);
322             }
323              
324             sub _common_file_opts {
325             my ($self, $obj) = @_;
326             my @c;
327             for my $k (qw(shortcut shortcut_description shortcut_icon handles firewall_allow skip)) {
328             if (defined (my $v = $obj->{$k})) {
329             push @c, $k, $v;
330             }
331             }
332             @c
333             }
334              
335             sub _install_wrappers {
336             my ($self, $installer) = @_;
337             $self->log->info("Adding wrappers");
338             for (@{$self->_script_wrappers}) {
339             $installer->add_file($_->{path}, $_->{path}->basename,
340             $self->_common_file_opts($_));
341             }
342             }
343              
344             sub _install_extra_exe {
345             my ($self, $installer) = @_;
346             $self->log->info("Adding extra exe");
347             for (@{$self->_extra_exe_mod}) {
348             my $path = $_->{path};
349             my $to = $path->basename;
350             if (defined (my $subdir = $_->{subdir})) {
351             $to = $subdir->child($to)
352             }
353             $installer->add_file($path, $to,
354             $self->_common_file_opts($_));
355             }
356             }
357              
358             sub _install_extra_dir {
359             my ($self, $installer) = @_;
360             $self->log->info("Adding extra dir");
361             for (@{$self->extra_dir}) {
362             my $path = $_->{path};
363             my $to = $_->{subdir} // path($path->realpath->basename);
364             $installer->add_tree($path, $to, $self->_common_file_opts($_));
365             }
366             }
367              
368             sub _install_extra_file {
369             my ($self, $installer) = @_;
370             $self->log->info("Adding extra files");
371             for (@{$self->extra_file}) {
372             my $path = $_->{path};
373             my $to = $_->{subdir} // path($path->realpath->basename);
374             $installer->add_file($path, $to,
375             $self->_common_file_opts($_));
376             }
377             }
378              
379             sub _install_license {
380             my ($self, $installer) = @_;
381             if (defined (my $license = $self->license)) {
382             $self->log->info("Adding license file");
383             $installer->add_file($license, 'LICENSE.RTF', _is_license => 1);
384             }
385             }
386              
387             sub _install_pm_deps {
388             my ($self, $installer) = @_;
389             $self->log->info("Adding pm deps");
390             my $lib = path('lib');
391             for (values %{$self->_pm_deps}) {
392             my $path = $_->{file};
393             my $to = $lib->child($_->{key});
394             $installer->add_file($path, $to);
395             }
396             }
397              
398             sub _install_pe_deps {
399             my ($self, $installer) = @_;
400             $self->log->info("Adding pe deps");
401             my $pe_deps = $self->_pe_deps;
402             for my $pe (keys %$pe_deps) {
403             my $path = path($pe_deps->{$pe});
404             $installer->add_file($path, $pe);
405             }
406             }
407              
408             sub _module2pm {
409             my ($self, $mod) = @_;
410             $mod =~ s/::/\//g;
411             $mod =~ s{(\.\w+)?$}{$1 // '.pm'}ei;
412             $mod
413             }
414              
415             sub _merge_opts {
416             my ($self, $defs, %opts) = @_;
417             for my $k (keys %$defs) {
418             my $v = $opts{$k};
419             if (defined $v) {
420             ref $v eq 'ARRAY' and $opts{$k} = [@$v, @{$defs->{$k}}];
421             }
422             else {
423             $opts{$k} = $defs->{$k};
424             }
425             }
426              
427             $self->log->tracef("merged options: %s", \%opts);
428             %opts
429             }
430              
431             sub _build__pm_deps {
432             my $self = shift;
433              
434             $self->log->info("Calculating dependencies...");
435             $self->log->tracef("inc: %s, extra modules: %s, scripts: %s", $self->inc, $self->extra_module, $self->scripts);
436             my $rv = do {
437             local @Module::ScanDeps::IncludeLibs = @{$self->inc};
438              
439             my @pm_files = map {
440             Module::ScanDeps::_find_in_inc($self->_module2pm($_))
441             or $self->_die("module $_ not found")
442             } @{$self->extra_module};
443             $self->log->debugf("pm files: %s", \@pm_files);
444              
445             my @script_files = map $_->{path}->stringify, @{$self->scripts};
446             $self->log->debugf("script files: %s", \@script_files);
447              
448             my @more_args;
449             if (defined (my $cache = $self->cache)) {
450             push @more_args, cache_file => $cache->child('module_scan_deps.cache')->stringify
451             }
452              
453             Module::ScanDeps::scan_deps($self->_merge_opts($self->scan_deps_opts,
454             recurse => 1,
455             warn_missing => 1,
456             files => [@script_files, @pm_files],
457             @more_args));
458             };
459             $self->log->debugf("pm dependencies: %s", $rv);
460             $rv
461             }
462              
463             sub _push_pe_dependencies {
464             my ($self, $pe_deps, $dt, $subdir) = @_;
465             if ($dt->{resolved}) {
466             my $module = $dt->{module};
467             $module = $subdir->child($module)->stringify if defined $subdir;
468             my $resolved_module = path($dt->{resolved_module});
469              
470             unless ($module =~ /\.(?:exe|xs\.dll)$/i or
471             $self->windows->subsumes($resolved_module)) {
472             unless (defined $pe_deps->{$module}) {
473             $self->log->tracef("resolving DLL dependency %s to %s (subdir: %s)", $module, $resolved_module, $subdir);
474             $pe_deps->{$module} = $resolved_module
475             }
476             }
477             }
478              
479             if (defined (my $children = $dt->{children})) {
480             $self->_push_pe_dependencies($pe_deps, $_, $subdir) for @$children;
481             }
482             }
483              
484             my %xs_dll_search_path_method = map { my $name = $_;
485             $name =~ s/:/_/g;
486             $_ => "_${name}_xs_dll_search_path"
487             } map lc, qw(Wx);
488              
489             sub _scan_xs_dll_deps {
490             my ($self, $pe_deps) = @_;
491             $self->log->info("Looking for DLL dependencies for XS modules");
492              
493             for my $dep (values %{$self->_pm_deps}) {
494             if ($dep->{key} =~ m{\.xs\.dll$}i) {
495             $self->log->debugf("looking for '%s' ('%s') DLL dependencies", $dep->{used_by}[0], $dep->{key});
496             my @search_path = @{$self->search_path};
497             if (my ($name) = $dep->{used_by}[0] =~ m{(.*)\.pm$}i) {
498             $name =~ s|/|::|g;
499             do {
500             if (defined (my $method = $xs_dll_search_path_method{lc $name})) {
501             my @special = $self->$method;
502             $self->log->debugf("using special search path: %s", \@special);
503             push @search_path, @special;
504             }
505             } while ($name =~ s/::[^:]+$//);
506             }
507             my $file = path($dep->{file})->realpath;
508             my $dt = do {
509             local $ENV{PATH} = join(';', @search_path, $ENV{PATH}) if @search_path;
510             pe_dependencies($file)
511             };
512             $self->_push_pe_dependencies($pe_deps, $dt);
513             }
514             }
515             }
516              
517             sub _scan_exe_dll_deps {
518             my ($self, $pe_deps) = @_;
519              
520             $self->log->info("Looking for DLL dependencies for EXE and extra DLL files");
521              
522             my @exes = ( to_loh_path($self->perl_exe),
523             @{$self->extra_exe},
524             @{$self->extra_dll} );
525             for my $exe (@exes) {
526             unless ($exe->{scan_deps} // 1) {
527             $self->log->debug("Skipping dependency scanning for $exe->{path}");
528             next;
529             }
530             $self->log->debugf("looking for '%s' DLL dependencies", $exe);
531             my $path = $exe->{path};
532             my $subdir = $exe->{subdir};
533              
534             my @search_path = ($path->parent, @{$exe->{search_path}});
535             push @search_path, $self->cygwin_bin if $exe->{cygwin};
536             push @search_path, @{$self->search_path};
537              
538             my $dt = do {
539             local $ENV{PATH} = join(';', @search_path, $ENV{PATH});
540             # $self->log->tracef("PATH: %s", $ENV{PATH});
541             pe_dependencies($path)
542             };
543             $self->_push_pe_dependencies($pe_deps, $dt, $subdir);
544             }
545             }
546              
547             sub _build__pe_deps {
548             my $self = shift;
549             my $pe_deps = {};
550             $self->_scan_xs_dll_deps($pe_deps);
551             $self->_scan_exe_dll_deps($pe_deps);
552             $pe_deps
553             }
554              
555             sub _build__script_wrappers {
556             my $self = shift;
557             [ map {
558             my %h = ( path => $self->_make_wrapper_exe($_),
559             $self->_common_file_opts($_) );
560             \%h
561             } @{$self->{scripts}} ]
562             }
563              
564             sub _change_exe_subsystem {
565             my ($self, $exe, $subsystem) = @_;
566              
567             my $path = $exe->{path};
568             $self->log->trace("Changing '$path' subsystem to $subsystem");
569              
570             require Win32::Exe;
571             my $e = Win32::Exe->new("$path") // $self->_die("Unable to inspect '$path': $^E");
572              
573             if ($subsystem eq $e->get_subsystem) {
574             $self->log->debug("App '$path' has already subsystem $subsystem");
575             return $path
576             }
577              
578             if ($subsystem eq 'console') {
579             $e->set_subsystem_console
580             }
581             elsif ($subsystem eq 'windows') {
582             $e->set_subsystem_windows
583             }
584             else {
585             $self->_die("Unsupported Windows subsystem $subsystem");
586             }
587              
588             my $tmpdir = $self->work_dir->child('modexe');
589             if (defined (my $subdir = $exe->{subdir})) {
590             $tmpdir = $tmpdir->child($subdir)
591             }
592             $tmpdir->mkpath;
593              
594             my $mod = $tmpdir->child($path->basename);
595             $e->write("$mod");
596             $self->log->debug("App subsystem for '$path' changed to $subsystem ($mod)");
597             $mod;
598             }
599              
600             sub _dir_copy {
601             my ($self, $from, $to) = @_;
602             $self->log->debugf("copying directory '%s' to '%s'", $from, $to);
603              
604             $to->mkpath;
605             for my $c ($from->children) {
606             if ($c->is_dir) {
607             $self->_dir_copy($c, $to->child($c->basename));
608             }
609             elsif ($c->is_file) {
610             $self->log->debugf("copying '%s' to '%s'", $c, $to);
611             $c->copy($to);
612             }
613             else {
614             $self->log->warnf("unable to copy file system object '%s'", $from);
615             }
616             }
617             }
618              
619             sub _build__wrapper_dir { mkpath(shift->work_dir->child('wrapper'))->realpath }
620              
621             sub _build__wrapper_c {
622             my $p = shift->_wrapper_dir->child("wrapper.c");
623             $p->spew($wrapper_c_code);
624             $p
625             }
626              
627             sub _build__wrapper_o {
628             my ($self, $wrapper_c) = @_;
629             my $wrapper_o = $self->_wrapper_dir->child("wrapper.obj");
630             $self->_run_cmd($self->cc_exe, "-I$Config{archlibexp}/CORE", \$Config{ccflags}, '-c', $self->_wrapper_c, '-o', $wrapper_o)
631             or $self->_die("unable to compile '$wrapper_c'");
632             $wrapper_o
633             }
634              
635             sub _make_wrapper_manifest {
636             my ($self, $script) = @_;
637             if ($script->{require_administrator}) {
638             my $basename = $script->{basename};
639             my $manifest = $self->_wrapper_dir->child("$basename.manifest")->realpath;
640              
641             $self->log->debug("Creating wrapper manifest '$manifest' for setting 'requireAdministrator'");
642              
643             my $data = [ assembly => { xmlns => "urn:schemas-microsoft-com:asm.v1", manifestVersion => "1.0"},
644             [ assemblyIdentity => { version => "1.0.0.0",
645             processorArchitecture => "X86",
646             name => "hello",
647             type => "win32" }],
648             [ description => {}, "Hello World" ],
649             [ trustInfo => { xmlns => "urn:schemas-microsoft-com:asm.v2"},
650             [ security => {},
651             [ requestedPrivileges => {},
652             [ requestedExecutionLevel => { level => "requireAdministrator",
653             uiAccess => "false" } ]]]]];
654              
655             require XML::FromPerl;
656             my $doc = XML::FromPerl::xml_from_perl($data);
657             $doc->toFile($manifest, 2);
658             $self->log->debug("Wrapper manifest created at $manifest");
659             return $manifest;
660             }
661             else {
662             $self->log->trace("Skipping manifest creation for $script->{basename}");
663             }
664             return ();
665             }
666              
667             sub _make_wrapper_rco {
668             my ($self, $script) = @_;
669             my @lines;
670             if (defined (my $manifest = $self->_make_wrapper_manifest($script))) {
671             # push @lines, "1 Manifest ".c_string_quote($manifest->realpath->canonpath)."\n";
672             push @lines, "1 24 ".c_string_quote($manifest->realpath->canonpath)."\n";
673             }
674             if (defined (my $icon = $script->{icon} // $self->icon)) {
675             $icon->is_file or $self->_die("Icon not found at '$icon'");
676             push @lines, '2 ICON '.c_string_quote($icon->realpath->canonpath)."\n";
677             }
678             if (@lines) {
679             my $basename = $script->{basename};
680             my $wrapper_rc = $self->_wrapper_dir->child("$basename.rc");
681             my $wrapper_rco = $self->_wrapper_dir->child("$basename.rco");
682             $wrapper_rc->spew(join '', @lines);
683             $self->_run_cmd($self->windres_exe,
684             -J => 'rc', -i => "$wrapper_rc",
685             -O => 'coff', -o => "$wrapper_rco")
686             or $self->_die("unable to compile resource file '$wrapper_rc'");
687             return $wrapper_rco;
688             }
689             return ()
690             }
691              
692             sub _make_wrapper_exe {
693             my ($self, $script) = @_;
694             my $basename = $script->{basename};
695             my $wrapper_exe = $self->_wrapper_dir->child("$basename.exe");
696              
697             my @obj = ($self->_wrapper_o, $self->_make_wrapper_rco($script));
698              
699             my $app_subsystem = $script->{app_subsystem} // $self->app_subsystem;
700             $app_subsystem =~ /^(?:console|windows)$/ or $self->_die("Bad app type $app_subsystem");
701              
702             my @libpth = split /\s+/, $Config{libpth};
703             my $libperl = $Config{libperl};
704             $libperl =~ s/^lib//i; $libperl =~ s/\.a$//i;
705             $self->_run_cmd($self->ld_exe,
706             \$Config{ldflags},
707             "-m$app_subsystem",
708             @obj,
709             map("-L$_", @libpth),
710             "-l$libperl",
711             \$Config{perllibs},
712             -o => $wrapper_exe)
713             or $self->_die("unable to link '$wrapper_exe'");
714             $wrapper_exe
715             }
716              
717             sub _build__load_pl {
718             my $self = shift;
719             my $p = $self->_wrapper_dir->child("load.pl");
720             $p->spew($load_pl_code);
721             $self->log->debug("load.pl saved to $p");
722             $p
723             }
724              
725             # special search paths
726             sub _wx_xs_dll_search_path {
727             my $self = shift;
728              
729             my ($wxcfg) = eval { # get_configurations doesn't work right in scalar context!!!
730             require Alien::wxWidgets;
731             Alien::wxWidgets->get_configurations();
732             };
733              
734             unless (defined $wxcfg) {
735             $self->log->warnf('Unable to retrieve Alien::wxWidgets configuration: %s', $@);
736             return;
737             }
738              
739             my $wxkey = $wxcfg->{key};
740             unless (defined $wxkey) {
741             $self->log->warnf('"key" entry missing from Alien::wxWidgets configuration: %s', $wxcfg);
742             return;
743             }
744             my $perl_path = $self->strawberry->child('perl');
745             my @search_path;
746             for (qw(site/lib vendor/lib lib)) {
747             my $wxlib = $perl_path->child($_)->child('Alien/wxWidgets')->child($wxkey)->child('lib');
748             push @search_path, $wxlib->realpath if -d $wxlib;
749             }
750             $self->log->warnf("Wx search path is empty, DLLs will be missing") unless @search_path;
751             @search_path;
752             }
753              
754             1;
755              
756             __END__