|
#!/usr/bin/perl |
|
|
|
use strict; |
|
use warnings; |
|
|
|
# If any of these fail with Perl not finding the import try doing |
|
# |
|
# perl -MCPAN -e shell |
|
# > install File::Which |
|
# |
|
# ...where File::Which (eg) is the module that was not found |
|
|
|
use Getopt::Long; |
|
use Pod::Usage; |
|
use File::Find; |
|
use File::Which; # exports which() |
|
use File::Copy::Recursive qw(dircopy fcopy); |
|
use File::Spec; |
|
use File::HomeDir; |
|
use Time::Piece; |
|
use File::Path qw( rmtree ); |
|
use Cwd qw(abs_path getcwd); |
|
use POSIX qw/strftime/; |
|
|
|
my $platform = undef; |
|
my $install = undef; |
|
my $outdir = undef; |
|
my $ifw = File::Spec->catpath( File::HomeDir->my_home, 'Qt', 'QtIFW-1.4.0', 'bin', 'binarycreator' ); |
|
my $installer_name = "Plistinator"; |
|
my $domain = "au.com.smithsoft"; |
|
my $license = "LICENSE.txt"; |
|
my $verbose = undef; |
|
my $qt5 = File::Spec->catpath( File::HomeDir->my_home, 'build', 'qt5' ); |
|
my $build_version = $ENV{'VERSION_NO'} || '1.0.0'; |
|
my $build_date = $ENV{'BUILD_DATE'} || localtime->strftime("%Y-%m-%d@%H:%M"); |
|
my $help = 0; |
|
my $man = 0; |
|
## Parse options and print usage if there is a syntax error, |
|
## or if usage was explicitly requested. |
|
GetOptions( |
|
'build=s' => \$build_version, |
|
'date=s' => \$build_date, |
|
'platform=s' => \$platform, |
|
'install=s' => \$install, |
|
'outdir=s' => \$outdir, |
|
'ifw=s' => \$ifw, |
|
'package=s' => \$installer_name, |
|
'domain=s' => \$domain, |
|
'license=s' => \$license, |
|
'verbose|v' => \$verbose, |
|
'help|?' => \$help, |
|
'man' => \$man, |
|
) or pod2usage(2); |
|
pod2usage(1) if $help; |
|
pod2usage(-verbose => 2) if $man; |
|
|
|
|
|
my $product_name = $installer_name; |
|
if ( $^O =~ m/win/i ) |
|
{ |
|
$product_name = $product_name . ".exe"; |
|
} |
|
|
|
my $build_name = lc $installer_name; |
|
my $build_product = File::Spec->catdir( File::HomeDir->my_home, 'build', 'product' ); |
|
|
|
my $script_path = $0; |
|
$script_path = File::Spec->rel2abs( $script_path ) if ( ! File::Spec->file_name_is_absolute( $script_path )); |
|
my ( undef, $script_dir, undef ) = File::Spec->splitpath( $script_path ); |
|
$script_dir =~ s/\/$//; |
|
$script_dir =~ s/\\$//; |
|
my @dir_parts = File::Spec->splitdir($script_dir); |
|
pop @dir_parts; |
|
my $src_dir = File::Spec->catdir( @dir_parts ); |
|
|
|
|
|
my $app_binary; |
|
if ( $^O =~ m/win/i ) |
|
{ |
|
$app_binary = shift || File::Spec->catfile( $build_product, 'bin', $product_name ); |
|
} |
|
else |
|
{ |
|
$app_binary = shift || File::Spec->catfile( $build_product, "$build_name-release", 'src', $product_name ); |
|
} |
|
|
|
-e $app_binary or pod2usage("$0: Could not find app binary: $app_binary"); |
|
|
|
if ( ! -x $ifw ) |
|
{ |
|
my $msg = "$0: could not find QtIFW - use -ifw to specify it"; |
|
$ifw = which('binarycreator') or pod2usage($msg); |
|
} |
|
|
|
my ( $ivol, $idirs, $ifn ) = File::Spec->splitpath( $ifw ); |
|
my $arc = File::Spec->catpath( $ivol, $idirs, 'archivegen' ); |
|
|
|
$arc = "$arc.exe" if ( $^O eq 'MSWin32' ); |
|
if ( ! -x $arc ) |
|
{ |
|
$arc = which('archivegen') or pod2usage("Could not find archivegen program"); |
|
} |
|
|
|
$outdir = $build_product unless ( $outdir ); |
|
-d $outdir or pod2usage("$0: Could not find location to put installer: $outdir"); |
|
|
|
if ( $install ) |
|
{ |
|
-d $install or pod2usage("$0: Could not find installer def: $install"); |
|
} |
|
else |
|
{ |
|
print "No installer def specified via \"-install\" switch\n" if ( $verbose ); |
|
print " ...trying current directory: " if ( $verbose ); |
|
my $curdir = getcwd; |
|
$install = File::Spec->catdir( $curdir, 'installer' ); |
|
if ( -d $install ) |
|
{ |
|
print "found!\n" if ( $verbose ); |
|
} |
|
else |
|
{ |
|
print "no \"installer\" in $curdir\n" if ( $verbose ); |
|
print " ...trying relative to script \"$script_path\": " if ( $verbose ); |
|
my @installer_parts = @dir_parts; |
|
push @installer_parts, ( 'dist', 'installer' ); |
|
$install = File::Spec->catdir( @installer_parts ); |
|
if ( -d $install ) |
|
{ |
|
print "found!\n" if ( $verbose ); |
|
} |
|
else |
|
{ |
|
print "no \"installer\" found in $install\n" if ( $verbose ); |
|
pod2usage("Use -install switch or check install definitions\n"); |
|
} |
|
} |
|
} |
|
|
|
print "Processing installer\n"; |
|
print "Building installer from definitions in $install\n" if ( $verbose ); |
|
print "Using Qt Installer framework $ifw\n" if ( $verbose ); |
|
|
|
my $config = File::Spec->catfile($install, 'config', 'config.xml'); |
|
my $configs = File::Spec->catdir($install, 'config'); |
|
my $packages = File::Spec->catfile($install, 'packages'); |
|
-d $configs or die "Installer definition missing file: Expected $configs\n"; |
|
-d $packages or die "Installer definition missing dir: Expected $packages\n"; |
|
|
|
my $verbose_switch = ""; |
|
$verbose_switch = "-v " if ( $verbose ); |
|
|
|
my $tmp = File::Spec->catdir( $outdir, ".tmp" ); |
|
if ( -d $tmp ) |
|
{ |
|
print "\nRemoving existing temp dir\n" if ( $verbose ); |
|
rmtree( $tmp ); |
|
} |
|
mkdir $tmp or die "Could not create work dir in ./$tmp : $!\n"; |
|
|
|
my $tmp_configs = File::Spec->catdir($tmp, 'config'); |
|
my $tmp_packages = File::Spec->catdir($tmp, 'packages'); |
|
dircopy($configs, $tmp_configs) or die "Could not copy install definitions $configs to work dir $tmp_configs: $!\n"; |
|
dircopy($packages, $tmp_packages) or die "Could not copy install definitions $packages to work dir $tmp_packages: $!\n"; |
|
|
|
print "Created working directory in $tmp\n" if ( $verbose ); |
|
|
|
$config = File::Spec->catfile($tmp, 'config', 'config.xml'); |
|
$packages = File::Spec->catdir($tmp, 'packages'); |
|
|
|
my $bin_dest = undef; |
|
my $pkg_conf = undef; |
|
my $meta_dest = undef; |
|
my $packages_count = 0; |
|
my $pkg_srch = "$domain.$installer_name"; |
|
print " Checking installer definitions in \"$packages\" for $pkg_srch\n"; |
|
find( sub { |
|
return unless ( /^meta$/ ); |
|
return unless ( -d ); |
|
return unless ( $File::Find::dir =~ m/$pkg_srch/ ); |
|
print " Found package ".$File::Find::dir."\n" if ( $verbose ); |
|
$packages_count++; |
|
$meta_dest = $File::Find::name; |
|
$bin_dest = File::Spec->catdir( $File::Find::dir, 'data' ); |
|
$pkg_conf = File::Spec->catfile( $meta_dest, 'package.xml' ); |
|
}, $packages ); |
|
|
|
if ( $packages_count > 1 ) |
|
{ |
|
print "Found multiple packages - using $bin_dest\n"; |
|
print "Use -domain and -package args to specify which to use\n"; |
|
} |
|
|
|
if ( ! $bin_dest ) |
|
{ |
|
my $expected = File::Spec->catdir($install, 'packages', "$pkg_srch", 'data'); |
|
warn "May need to specify -domain and/or -package switches\n"; |
|
die "Could not find $expected\n"; |
|
} |
|
|
|
my ( undef, $bin_dir, $bin_fn ) = File::Spec->splitpath( $app_binary ); |
|
if ( $^O =~ m/win/i ) |
|
{ |
|
# windows eats C: letter in the path above so we should retrieve it again :/ |
|
$bin_dir = File::Spec->catdir($build_product, 'bin'); |
|
} |
|
|
|
my @libs = ( 'Widgets', 'Xml', 'Gui', 'Core' ); |
|
my $tmp_dist = File::Spec->catdir( $tmp, 'bin' ); |
|
my $tmp_share = File::Spec->catdir( $tmp, 'share' ); |
|
print "Packaging Qt and binaries for " . $^O . "\n"; |
|
-d $tmp_dist or mkdir $tmp_dist || die "Could not mkdir $tmp_dist: $!\n"; |
|
if ( $^O =~ m/nux/ ) |
|
{ |
|
-d $tmp_share or mkdir $tmp_share || die "Could not mkdir $tmp_share: $!\n"; |
|
|
|
my $plat_config = File::Spec->catfile($tmp, 'config', 'config-linux.xml'); |
|
fcopy( $plat_config, $config ) or die "Could not copy $plat_config -> $config: $!\n"; |
|
print " $plat_config -> $config\n" if ( $verbose ); |
|
|
|
for my $dst_dir ( 'platforms', 'imageformats' ) |
|
{ |
|
my $plugin_src_dir = File::Spec->catdir($bin_dir, 'plugins', $dst_dir); |
|
my $plugin_dst_dir = File::Spec->catdir($tmp_dist, $dst_dir); |
|
dircopy($plugin_src_dir, $plugin_dst_dir); |
|
print " $plugin_src_dir -> $plugin_dst_dir\n" if ( $verbose ); |
|
} |
|
my $xcb_plugin = File::Spec->catfile($bin_dir, 'plugins', 'platforms', 'libqxcb.so'); |
|
|
|
# libQt5Widgets.so.5 => /home/sez/build/qt5/lib/libQt5Widgets.so.5 (0x00007ff31d541000) |
|
# libQt5Xml.so.5 => /home/sez/build/qt5/lib/libQt5Xml.so.5 (0x00007ff31d304000) |
|
# libQt5Gui.so.5 => /home/sez/build/qt5/lib/libQt5Gui.so.5 (0x00007ff31ccba000) |
|
# libQt5Core.so.5 => /home/sez/build/qt5/lib/libQt5Core.so.5 (0x00007ff31c614000) |
|
# libQt5DBus.so.5 => /home/sez/build/qt5/lib/libQt5DBus.so.5 (0x00007fc97b600000) (xcb) |
|
open DEPS, "ldd $app_binary 2>&1 |" or die "Could not determine deps of $app_binary with ldd: $!\n"; |
|
my @deps_list = ( <DEPS> ); |
|
close DEPS; |
|
open DEPS, "env LD_LIBRARY_PATH=/home/ncuxer/dev/Qt/5.12.3/gcc_64/lib/ ldd $xcb_plugin 2>&1 |" or die "Could not determine deps of $xcb_plugin with ldd: $!\n"; |
|
push @deps_list, ( <DEPS> ); |
|
my %deps_done = (); |
|
for ( @deps_list ) |
|
{ |
|
next unless ( ( m/libQt/ and m/home\/ncuxer\/dev/ ) or m/libxcb/ or ( m/libicu/ and m/home\/ncuxer\/dev/ ) or m/libcrypto/ or m/libpng/ ); |
|
my ( $name, $arrow, $lib_path_link, $addr ) = split; |
|
next if ( $deps_done{ $name } ); |
|
$deps_done{ $name } = 1; |
|
my $stem = $lib_path_link; |
|
$stem =~ s/\.so.*$/\.so/ ; |
|
my @lib_lnks = glob "$stem*"; |
|
foreach my $l ( @lib_lnks ) |
|
{ |
|
# fcopy will copy symlinks as symlinks, not the target file - that's actually |
|
# what we want - note that the archivegen command also preserves those. We |
|
# want the link that $app_binary knows the library by, as well as its actual file. |
|
# We could try to not include the two links that $app_binary doesn't care about |
|
# but its just as easy to copy all the links, and get the binary too. |
|
my ( undef, undef, $fn ) = File::Spec->splitpath($l); |
|
my $ldest = "$tmp_dist/$fn"; |
|
fcopy($l, $ldest) or die "Could not copy $l -> $ldest :$!\n"; |
|
print " $l -> $ldest\n" if ( $verbose ); |
|
} |
|
} |
|
|
|
my $linux_bin_wrapper = "$script_dir/$product_name.linux.sh"; |
|
my $linux_bin_wrapper_dest = "$tmp_dist/$product_name"; |
|
fcopy( $linux_bin_wrapper, $linux_bin_wrapper_dest ) or die "Could not copy $linux_bin_wrapper -> $linux_bin_wrapper_dest: $!\n"; |
|
print " $linux_bin_wrapper -> $linux_bin_wrapper_dest\n" if ( $verbose ); |
|
|
|
my $wrapper_bin_dest = "$tmp_dist/$product_name.bin"; |
|
fcopy( $app_binary, $wrapper_bin_dest ) or die "Could not copy $app_binary -> $wrapper_bin_dest: $!\n"; |
|
print " $app_binary -> $wrapper_bin_dest\n" if ( $verbose ); |
|
|
|
my %image_size_list = ( '16x16' => '16x16', '32x32' => '32x32', '32x32@2x' => '64x64', |
|
'128x128' => '128x128', '256x256' => '256x256', '512x512' => '512x512' ); |
|
foreach my $imsz ( keys %image_size_list ) |
|
{ |
|
my $icon_src = "$src_dir/src/icons/Plistinator.iconset/icon_${imsz}.png"; |
|
my $icon_dest = "$tmp_share/icons/hicolor/" . $image_size_list{$imsz} . "/apps/" . $build_name . ".png"; |
|
fcopy( $icon_src, $icon_dest ) or die "Could not copy $icon_src -> $icon_dest: $!\n"; |
|
print " $icon_src -> $icon_dest\n" if ( $verbose ); |
|
} |
|
} |
|
if ( $^O =~ m/win/i ) |
|
{ |
|
my $plat_config = File::Spec->catfile($tmp, 'config', 'config-win.xml'); |
|
fcopy( $plat_config, $config ) or die "Could not copy $plat_config -> $config: $!\n"; |
|
print " $plat_config -> $config\n" if ( $verbose ); |
|
|
|
dircopy($bin_dir, $tmp_dist) or die "Could not copy $bin_dir -> $tmp_dist: $!\n"; |
|
print " $bin_dir -> $tmp_dist\n" if ( $verbose ); |
|
} |
|
|
|
my $var_substituted_conf = "$config.subst"; |
|
open PACKAGE_CONF, "<", $config or die "Could not read config $config: $!\n"; |
|
open MOD_PACKAGE_CONF, ">", $var_substituted_conf or die "Could not write variable substituted config $var_substituted_conf: $!\n"; |
|
while ( <PACKAGE_CONF> ) |
|
{ |
|
chomp; |
|
s/\$\{VERSION\}/$build_version/g; |
|
s/\$\{DATE\}/$build_date/g; |
|
print MOD_PACKAGE_CONF "$_\n"; |
|
} |
|
close MOD_PACKAGE_CONF; |
|
close PACKAGE_CONF; |
|
rename $var_substituted_conf, $config or die "Could not rename $var_substituted_conf -> $config: $!\n"; |
|
|
|
my $arc_dest = File::Spec->catfile( $bin_dest, "$bin_fn.7z" ); |
|
my $arc_cmd = "$arc $arc_dest $tmp_dist $tmp_share"; |
|
mkdir $bin_dest; |
|
print "Archiving Deliverables\n"; |
|
print " from: $tmp_dist $tmp_share\n" if ( $verbose ); |
|
print " to: $arc_dest\n" if ( $verbose ); |
|
print " $arc_cmd...\n" if ( $verbose ); |
|
system ( $arc_cmd ); # or die "Could not run \"$arc_cmd\": $!\n"; |
|
-f $arc_dest or warn "Archive command $arc_cmd appears to have failed\n"; |
|
print "Archived $app_binary to $arc_dest\n" if ( $verbose ); |
|
|
|
|
|
my $license_dest = $license; |
|
open PACKAGE_DEF, "<", $pkg_conf or die "Expected package config file - $pkg_conf: $!\n"; |
|
while ( <PACKAGE_DEF> ) |
|
{ |
|
m/License/ or next; |
|
my ( $lc ) = m/file="([^"]+)"/ ; |
|
if ( $lc ) |
|
{ |
|
print "Detected license file \"$lc\" from $pkg_conf\n" if ( $verbose ); |
|
$license_dest = $lc; |
|
last; |
|
} |
|
} |
|
close PACKAGE_DEF; |
|
if ( ! $license && $license_dest ) |
|
{ |
|
print "License $license_dest is required by $pkg_conf - attempting to find (use -license to specify)\n" if ( $verbose ); |
|
$license = $license_dest; |
|
} |
|
if ( -f $license ) |
|
{ |
|
my $lic_path = File::Spec->catfile($meta_dest, $license_dest); |
|
fcopy($license, $lic_path) or die "Could not copy $license -> $lic_path\n"; |
|
print "Copied $license -> $lic_path\n" if ( $verbose ); |
|
} |
|
|
|
print "\n\nCreating installer\n"; |
|
my $installer_path = File::Spec->catfile( $outdir, $installer_name . "." . $build_version); |
|
$installer_path = "$installer_path.bin" if ( $^O =~ 'nux' ); |
|
my $cmd = "$ifw $verbose_switch -f -c $config -p $packages $installer_path"; |
|
print "Cmd: $cmd\n" if ( $verbose ); |
|
|
|
system( $cmd ); |
|
|
|
print "\n\nRemoving temp build directory\n" if ( $verbose ); |
|
# rmtree( $tmp ); |
|
|
|
$installer_path = "$installer_path.exe" if ( $^O eq 'MSWin32' ); |
|
-e $installer_path or die "\n\nCannot see installer $installer_path - appears packaging failed\n"; |
|
|
|
# chmod 0755 $installer_path; |
|
print "\n\nInstaller completed at $installer_path\n"; |
|
|
|
__END__ |
|
|
|
=head1 NAME |
|
|
|
createinstaller.pl |
|
|
|
=head1 SYNOPSIS |
|
|
|
createinstaller.pl [options] binary |
|
|
|
Options: |
|
-build build version tag eg [1.0.0] |
|
-date build date string [current date/time in format 2014-02-28@11:32] |
|
-platform windows, mac or linux [detected] |
|
-install installer definitions directory [$PWD/installer, $0/../dist/installer] |
|
-outdir where to place result [.] |
|
-ifw path to IFW framework binarycreator [detected from $PATH] |
|
-package name of the package to produce [Plistinator] |
|
-domain reverse-domain for the package [au.com.smithsoft] |
|
-license license file to install [LICENSE.txt] |
|
-verbose print lots of information during build |
|
-help brief help message |
|
-man full documentation |
|
|
|
=head1 OPTIONS |
|
|
|
=over 8 |
|
|
|
=item B<-platform> |
|
|
|
Specify platform to build installer for. Defaults to detecting platform. |
|
|
|
=item B<-install> |
|
|
|
Path to the installer definitions files. This must be a directory name, and under |
|
it is expected to be found the "config" and "packages" directories for the Qt |
|
installer framework tool. |
|
|
|
By default (if this argument is not specified) first the current directory is tried |
|
for a directory called "installer" and if that fails, a directory dist/installer at |
|
the level of this scripts parent is tried. |
|
|
|
=item B<-outdir> |
|
|
|
Specify where to put the resulting installer. Defaults to current directory. |
|
|
|
=item B<-ifw> |
|
|
|
Path to the Qt Installer framework (ifw) binarycreator tool. Defaults to |
|
using the first instance found in the $PATH or %PATH%. |
|
|
|
=item B<-package> |
|
|
|
Name for the final installer package produced. Will have a platform dependent |
|
extension added eg ".exe" (so do not add this). |
|
|
|
Defaults to "Plistinator" |
|
|
|
=item B<-build> |
|
|
|
Build version for the package eg "1.0.0". Defaults to "1.0.0" |
|
|
|
=item B<-date> |
|
|
|
Build date for the package eg "2014-23-05@11:32" - must by YYYY-MM-DD@HH:MM. Defaults to today's date/time. |
|
|
|
=item B<-domain> |
|
|
|
Name of the reversed domain for the package, defaults to "au.com.smithsoft" |
|
|
|
=item B<-license> |
|
|
|
Relative path-name of the license file to be installed, defaults to "./LICENSE.txt" |
|
|
|
=item B<-verbose, -v> |
|
|
|
Be verbose during the creation of the installer printing information on stdout. |
|
|
|
=item B<-help> |
|
|
|
Print a brief help message and exits. |
|
|
|
=item B<-man> |
|
|
|
Prints the manual page and exits. |
|
|
|
|
|
|
|
=back |
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
|
|
B<createinstaller.pl> will build an end-user installer out of the binary |
|
executable specified on the command line. |
|
|
|
The Qt Installer framework tools, eg binarycreator.exe, must be either |
|
on the PATH or specified via the -ifw argument. |
|
|
|
The resulting installer will be placed in the current directory or in the |
|
directory specified by -outdir |
|
|
|
=cut |