Skip to content

Instantly share code, notes, and snippets.

@janodev
Created May 31, 2017 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janodev/866a06bf1a38cf5df4f29f4c77e6adbf to your computer and use it in GitHub Desktop.
Save janodev/866a06bf1a38cf5df4f29f4c77e6adbf to your computer and use it in GitHub Desktop.
PackageApplication script from Xcode 8.2.1
#!/usr/bin/perl
#
# PackageApplication
#
# Copyright (c) 2009-2012 Apple Inc. All rights reserved.
#
# Package an iPhone Application into an .ipa wrapper
#
use Pod::Usage;
use Getopt::Long;
use File::Temp qw/ tempdir :POSIX /;
use File::Basename;
use File::Path qw/ remove_tree /;
use Cwd qw/ chdir /;
print "\n\n\nwarning: PackageApplication is deprecated, use `xcodebuild -exportArchive` instead.\n\n\n";
$|=1; # don't buffer stdout
my $program = $0;
my %opt = ();
GetOptions ( \%opt,
"sign|s=s",
"embed|e=s",
"output|o=s",
"symbols=s",
"verbose|v!",
"help|h|?",
"man",
"plugin=s@" => \@plugins,
) or pod2usage(2);
pod2usage(2) if $opt{help};
pod2usage(-exitstatus => 0, -verbose => 2) if $opt{man};
fatal("An application was not specified.") unless $ARGV[0];
my $origApp = shift @ARGV;
fatal("Specified application doesn't exist or isn't a bundle directory : '$origApp'") unless -d $origApp;
if ( $opt{verbose} ) {
print "Packaging application: '$origApp'\n";
print "Arguments: ";
while( ($key,$value) = each %opt)
{
print "$key=$value ";
}
print "\n";
print "Environment variables:\n";
while( ($key,$value) = each %ENV)
{
print "$key = $value\n";
}
print "\n";
}
# check any plugins that might be specified
foreach $plugin (@plugins) {
print "Plugin: '$plugin'\n" if $opt{verbose};
fatal("Specified plugin doesn't exist or isn't a bundle directory : '$plugin'") unless -d $plugin;
}
# setup the output name if it isn't specified
# setup the output if it isn't specified
if ( !defined($opt{output}) ) {
$opt{output} = dirname($origApp).'/'.basename($origApp, ".app")."\.ipa";
}
print "Output directory: '$opt{output}'\n" if $opt{verbose};
# Make sure we have a temp dir to work with
my $tmpDir = tempdir( CLEANUP => !defined($opt{verbose}) );
print "Temporary Directory: '$tmpDir' (will NOT be deleted on exit when verbose set)\n" if $opt{verbose};
################## Start Packaging #####################
### Step One : Make a copy of the application (and any plugins)
my $appName = basename($origApp);
chomp $appName;
my $destAppDir = "$tmpDir/Payload";
my $destApp = "$destAppDir/$appName";
mkdir $destAppDir;
fatal("Unable to create directory : '$destAppDir'") unless -d $destAppDir;
runCmd("/bin/cp", "-Rp", $origApp, $destAppDir);
fatal("Unable to copy application '$origApp' into '$destAppDir'") unless -e $destApp;
foreach $plugin (@plugins) {
my $pluginName = basename($plugin);
chomp $pluginName;
my $destPlugin = "$destAppDir/$pluginName";
my $result = runCmd("/usr/bin/codesign", "--verify", "-vvvv", , $plugin );
if ( $result !~ /valid on disk/ ) {
fatal("Codesign check fails : $result\n");
}
runCmd("/bin/cp", "-Rp", $plugin, $destAppDir);
fatal("Unable to copy application '$plugin' into '$destAppDir'") unless -e $destPlugin;
}
if ( $opt{symbols} ) {
my $destSymbols = "$tmpDir/Symbols";
runCmd("/bin/cp", "-Rp", $opt{symbols}, $destSymbols);
fatal("Unable to copy symbols '$opt{symbols}' into '$destSymbols'") unless -e $destSymbols;
}
### Step Two : recode sign it if necessary
if ( $opt{verbose} ) {
print "### Checking original app\n";
my $result = runCmd("/usr/bin/codesign", "--verify", "-vvvv", , $origApp );
if ( $result !~ /valid on disk/ ) {
print "Codesign check fails : $result\n";
}
print "Done checking the original app\n";
}
if ( defined $opt{sign} ) {
if ( $opt{embed} ) {
print "### Embedding '$opt{embed}'\n" if $opt{verbose};
runCmd("/bin/rm", "-rf", "$destApp/embedded.mobileprovision" );
fatal("Unable to remove '$destApp/embedded.mobileprovision'\n") if ( -e "$destApp/embedded.mobileprovision" );
runCmd("/bin/cp", "-rp", $opt{embed}, "$destApp/embedded.mobileprovision");
fatal("Unable to copy '$opt{embed}' to '$destApp/embedded.mobileprovision'\n") unless ( -e "$destApp/embedded.mobileprovision" );
}
my $entitlements_plist = File::Temp::tempnam($tmpDir, "entitlements_plist");
# If re-signing with a distribution profile and get-task-allow is
# true, the app store will reject the submission. Setting
# get-task-allow to false here.
if ( $opt{sign} =~ /^i(Phone|OS) Distribution/ ) {
my $entitlements_raw = File::Temp::tempnam($tmpDir, "entitlements_raw");
runCmd("/usr/bin/codesign", "-d", "--entitlements", $entitlements_raw, $destApp);
$? == 0 or fatal("Failed to read entitlements from '$destApp'");
if ( -e $entitlements_raw ) {
$plist = read_raw_entitlements($entitlements_raw);
unlink($entitlements_raw) or fatal("Cannot delete '$entitlements_raw': $!");
open(my $ofh, '>', $entitlements_plist) or fatal("Cannot open '$entitlements_plist': $!");
print $ofh $plist or fatal("Cannot write entitlements to '$entitlements_plist': $!");
close($ofh) or fatal("Cannot close file handle for '$entitlements_plist': $!");
runCmd('/usr/libexec/PlistBuddy', '-c', 'Set :get-task-allow NO', $entitlements_plist);
# PlistBuddy will fail if get-task-allow doesn't exist. That's okay.
runCmd('/usr/bin/plutil', '-lint', $entitlements_plist);
$? == 0 or fatal("Invalid plist at '$entitlements_plist'");
}
}
my @codesign_args = ("/usr/bin/codesign", "--force", "--preserve-metadata=identifier,entitlements,resource-rules",
"--sign", $opt{sign},
"--resource-rules=$destApp/ResourceRules.plist");
if ( -e $entitlements_plist ) {
push(@codesign_args, '--entitlements');
push(@codesign_args, $entitlements_plist);
}
push(@codesign_args, $destApp);
print "### Codesigning '$opt{embed}' with '$opt{sign}'\n" if $opt{verbose};
my $codesign_output = runCmd(@codesign_args);
$? == 0 or fatal("@codesign_args failed with error " . ($? >> 8) . ". Output: $codesign_output");
if ( -e $entitlements_plist ) {
unlink($entitlements_plist) or fatal("Cannot delete '$entitlements_plist': $!");
}
}
### Step Three : zip up the package
remove_tree("$opt{output}");
fatal("Unable to remove older '$opt{output}'") if ( -e "$opt{output}" );
chdir $tmpDir;
if($opt{verbose}) {
runCmd("/usr/bin/zip", "--symlinks", "--verbose", "--recurse-paths", "$opt{output}", ".");
} else {
runCmd("/usr/bin/zip", "--symlinks", "--quiet", "--recurse-paths", "$opt{output}", ".");
}
fatal("Unable to create '$opt{output}'") if ( ! -e "$opt{output}" );
print "Results at '$opt{output}' \n" if $opt{verbose};
################## Finished ############################
exit 0;
########################################################
sub fatal {
my ($msg) = @_;
print STDERR "error: $msg\n";
exit 1;
}
use POSIX qw(:sys_wait_h);
sub runCmd {
my (@cmds) = @_;
my $output = ();
if ( $opt{verbose} ) {
my $_cmd = join(" ", @cmds);
print "+ $_cmd\n" ;
}
my $program = shift @cmds;
my ($readme, $writeme);
pipe $readme, $writeme;
my $pid = fork;
defined $pid or die "cannot fork: $!";
if ( $pid == 0 ) {
# child
open(STDOUT, ">&=", $writeme) or die "can't redirect STDOUT: $!";
open(STDERR, ">&=", $writeme) or die "can't redirect STDERR: $!";
close $readme;
exec($program, @cmds) or die "can't run $program: $!";
}
close $writeme;
while(<$readme>) {
$output .= $_;
}
close $readme;
my $waitpid_ret;
do { $waitpid_ret = waitpid($pid, 0); } while ( $waitpid_ret == 0 );
$waitpid_ret == $pid or die "waitpid returned $waitpid_ret: $!";
my $return_code = $? >> 8;
$opt{verbose} and print "Program $program returned $return_code : [$output]\n";
# Let clients handle error checking.
# $return_code == 0 or fatal("Program $program failed with return code $return_code.");
return $output;
}
# Param: path to codesign --entitlements output file. Returns xml plist in a string.
sub read_raw_entitlements {
my $entitlements_raw = shift;
open(my $ifh, '<', $entitlements_raw) or fatal("Cannot open '$entitlements_raw': $!");
read($ifh, my $format_tag, 4) or fatal("Cannot read format tag from '$entitlements_raw': $!");
$format_tag = unpack('H*', $format_tag);
my $expected_format_tag = 'fade7171';
$format_tag eq $expected_format_tag
or fatal("Format tag '0x$format_tag' does not match '0x$expected_format_tag'.");
read($ifh, my $plist_size, 4) or fatal("Cannot read plist size from '$entitlements_raw': $!");
$plist_size = unpack('L>', $plist_size);
read($ifh, my $plist, $plist_size) or fatal("Cannot read $plist_size bytes of plist data from '$entitlements_raw': $!");
close($ifh) or fatal("Cannot close file handle for '$entitlements_raw': $!");
return $plist;
}
__END__
=head1 NAME
PackageApplication - prepare an application for submission to AppStore or installation by iTunes.
=head1 SYNOPSIS
PackageApplication [-s signature] application [-o output_directory] [-verbose] [-plugin plugin] || -man || -help
Options:
-s <signature> certificate name to resign application before packaging
-o specify output filename
-plugin specify an optional plugin
-help brief help message
-man full documentation
-v[erbose] provide details during operation
=head1 OPTIONS
=over 8
=item B<-s>
Optional codesigning certificate identity common name. If provided, the application is will be re-codesigned prior to packaging.
=item B<-o>
Optional output filename. The packaged application will be written to this location.
=item B<-plugin>
Specify optional plugin. The packaged application will include the specified plugin(s).
=item B<-help>
Print a brief help message and exits.
=item B<-man>
Prints the manual page and exits.
=item B<-verbose>
Provides additional details during the packaging.
=back
=head1 DESCRIPTION
This program will package the specified application for submission to the AppStore or installation by iTunes.
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment