1
#!/usr/bin/perl
2
#############################################################################
3
##
4
## Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5
## All rights reserved.
6
## Contact: Nokia Corporation (qt-info@nokia.com)
7
##
8
## This file is part of the S60 port of the Qt Toolkit.
9
##
10
## $QT_BEGIN_LICENSE:LGPL$
11
## GNU Lesser General Public License Usage
12
## This file may be used under the terms of the GNU Lesser General Public
13
## License version 2.1 as published by the Free Software Foundation and
14
## appearing in the file LICENSE.LGPL included in the packaging of this
15
## file. Please review the following information to ensure the GNU Lesser
16
## General Public License version 2.1 requirements will be met:
17
## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
##
19
## In addition, as a special exception, Nokia gives you certain additional
20
## rights. These rights are described in the Nokia Qt LGPL Exception
21
## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22
##
23
## GNU General Public License Usage
24
## Alternatively, this file may be used under the terms of the GNU General
25
## Public License version 3.0 as published by the Free Software Foundation
26
## and appearing in the file LICENSE.GPL included in the packaging of this
27
## file. Please review the following information to ensure the GNU General
28
## Public License version 3.0 requirements will be met:
29
## http://www.gnu.org/copyleft/gpl.html.
30
##
31
## Other Usage
32
## Alternatively, this file may be used in accordance with the terms and
33
## conditions contained in a signed written agreement between you and Nokia.
34
##
35
##
36
##
37
##
38
##
39
## $QT_END_LICENSE$
40
##
41
#############################################################################
42
43
############################################################################################
44
#
45
# Convenience script for creating signed packages you can install on your phone.
46
#
47
############################################################################################
48
49
use strict;
50
51
# use a command-line parsing module
52
use Getopt::Long;
53
# Use file name parsing module
54
use File::Basename;
55
# Use File::Spec services mainly rel2abs
56
use File::Spec;
57
# Use File::Path - to make stub sis target directory
58
use File::Path;
59
# use CWD abs_bath, which is exported only on request
60
use Cwd 'abs_path';
61
use File::Copy;
62
63
sub Usage() {
64
    print <<ENDUSAGESTRING;
65
66
==============================================================================================
67
Convenience script for creating signed packages you can install on your phone.
68
69
Usage: createpackage.pl [options] templatepkg [target]-[platform] [certificate key [passphrase]]
70
71
Where supported options are as follows:
72
     [-i|install]            = Install the package right away using PC suite.
73
     [-p|preprocess]         = Only preprocess the template .pkg file.
74
     [-c|certfile <file>]    = The file containing certificate information for signing.
75
                               The file can have several certificates, each specified in
76
                               separate line. The certificate, key and passphrase in line
77
                               must be ';' separated. Lines starting with '#' are treated
78
                               as a comments. Also empty lines are ignored. The paths in
79
                               <file> can be absolute or relative to <file>.
80
     [-u|unsigned]           = Preserves the unsigned package.
81
     [-o|only-unsigned]      = Creates only unsigned package.
82
     [-s|stub]               = Generates stub sis for ROM.
83
     [-n|sisname <name>]     = Specifies the final sis name.
84
     [-g|gcce-is-armv5]      = Convert gcce platform to armv5.
85
     [-d|dont-patch]         = Skip automatic patching of capabilities and pkg file if default certificate
86
                               is used. Instead non-self-signable capabilities just cause warnings.
87
     [-t|tmp-dir <path>]     = Specifies temporary directory to be used for package creation.
88
                               Defaults to 'createpackage_tmp' under same directory as templatepkg.
89
Where parameters are as follows:
90
     templatepkg             = Name of .pkg file template
91
     target                  = Either debug or release
92
     platform                = One of the supported platform
93
                               winscw | gcce | armv5 | armv6 | armv7
94
                               Note that when packaging binaries built using gcce and symbian-sbsv2
95
                               mkspec, armv5 must be used for platform instead of gcce.
96
     certificate             = The certificate file used for signing
97
     key                     = The certificate's private key file
98
     passphrase              = The passphrase of the certificate's private key file
99
100
Example:
101
     createpackage.pl fluidlauncher_template.pkg release-armv5
102
103
Example with certfile:
104
     createpackage.pl -c=mycerts.txt fluidlauncher_template.pkg release-armv5
105
106
     Content of 'mycerts.txt' must be something like this:
107
        # This is comment line, also the empty lines are ignored
108
        rd.cer;rd-key.pem
109
        .\\cert\\mycert.cer;.\\cert\\mykey.key;yourpassword
110
        X:\\QtS60\\s60installs\\selfsigned.cer;X:\\QtS60\\s60installs\\selfsigned.key
111
112
If no certificate and key files are provided, either a RnD certificate or
113
a self-signed certificate from QtDir\\src\\s60installs directory is used.
114
In the latter case the resulting package will also be automatically patched
115
using patch_capabilities.pl script, which makes it unsuitable for distribution.
116
Always specify certificates explicitly if you wish to distribute your package.
117
118
==============================================================================================
119
120
ENDUSAGESTRING
121
122
    exit();
123
}
124
125
# Read given options
126
my $install = "";
127
my $preprocessonly = "";
128
my $certfile = "";
129
my $preserveUnsigned = "";
130
my $stub = "";
131
my $signed_sis_name = "";
132
my $onlyUnsigned = "";
133
my $convertGcce = "";
134
my $dontPatchCaps = "";
135
my $tempPackageDir = "";
136
137
unless (GetOptions('i|install' => \$install,
138
                   'p|preprocess' => \$preprocessonly,
139
                   'c|certfile=s' => \$certfile,
140
                   'u|unsigned' => \$preserveUnsigned,
141
                   'o|only-unsigned' => \$onlyUnsigned,
142
                   's|stub' => \$stub,
143
                   'n|sisname=s' => \$signed_sis_name,
144
                   'g|gcce-is-armv5' => \$convertGcce,
145
                   'd|dont-patch' => \$dontPatchCaps,
146
                   't|tmp-dir=s' => \$tempPackageDir,)) {
147
    Usage();
148
}
149
150
my $epocroot = $ENV{EPOCROOT};
151
my $epocToolsDir = "";
152
if ($epocroot ne "") {
153
    $epocroot =~ s,\\,/,g;
154
    if ($epocroot =~ m,[^/]$,) {
155
        $epocroot = $epocroot."/";
156
    }
157
    $epocToolsDir = "${epocroot}epoc32/tools/";
158
}
159
160
my $certfilepath = abs_path(dirname($certfile));
161
162
# Read params to variables
163
my $templatepkg = $ARGV[0];
164
my $targetplatform = lc $ARGV[1];
165
166
if ($targetplatform eq "") {
167
    $targetplatform = "-";
168
}
169
170
my @tmpvalues = split('-', $targetplatform);
171
my $target;
172
$target = $tmpvalues[0] or $target = "";
173
my $platform;
174
$platform = $tmpvalues[1] or $platform = "";
175
176
if ($platform =~ m/^gcce$/i) {
177
    if (($convertGcce ne "")) {
178
        $platform = "armv5";
179
    } elsif ($ENV{SBS_HOME}) {
180
        # Print a informative note in case suspected misuse is detected.
181
        print "\nNote: You should use armv5 as platform or specify -g parameter to convert platform\n";
182
        print "      when packaging gcce binaries built using symbian-sbsv2 mkspec.\n\n";
183
    }
184
}
185
186
# Convert visual target to real target (debug->udeb and release->urel)
187
$target =~ s/debug/udeb/i;
188
$target =~ s/release/urel/i;
189
190
my $certificate;
191
$certificate = $ARGV[2] or $certificate = "";
192
my $key;
193
$key = $ARGV[3] or $key = "";
194
my $passphrase;
195
$passphrase = $ARGV[4] or $passphrase = "";
196
197
if ($tempPackageDir eq "") {
198
    my ($templateVolume, $templatePath, $templateFileName) = File::Spec->splitpath($templatepkg);
199
    $tempPackageDir = File::Spec->catpath($templateVolume, $templatePath."createpackage_tmp", "");
200
}
201
202
mkpath($tempPackageDir);
203
204
# Generate output pkg basename (i.e. file name without extension)
205
my $pkgoutputbasename = $templatepkg;
206
$pkgoutputbasename =~ s/_template/_$targetplatform/g;
207
$pkgoutputbasename =~ s/_installer\.pkg/_installer___temp\.pkg/g;
208
$pkgoutputbasename =~ s/\.pkg//g;
209
210
# Store output file names to variables
211
my ($dummy1, $dummy2, $pkgoutput) = File::Spec->splitpath($pkgoutputbasename.".pkg");
212
$pkgoutput = $tempPackageDir."/".$pkgoutput;
213
my $sisoutputbasename;
214
if ($signed_sis_name eq "") {
215
    $sisoutputbasename = $pkgoutputbasename;
216
    $sisoutputbasename =~ s/_$targetplatform//g;
217
    $sisoutputbasename =~ s/_installer___temp/_installer/g;
218
    $signed_sis_name = $sisoutputbasename.".sis";
219
} else {
220
    $sisoutputbasename = $signed_sis_name;
221
    if ($sisoutputbasename =~ m/(\.sis$|\.sisx$)/i) {
222
        $sisoutputbasename =~ s/$1//i;
223
    } else {
224
        $signed_sis_name = $signed_sis_name.".sis";
225
    }
226
}
227
228
my $installer_unsigned_app_sis_name = "";
229
my $installer_app_sis_name = "";
230
231
if ($templatepkg =~ m/_installer\.pkg$/i && $onlyUnsigned) {
232
    $installer_unsigned_app_sis_name = $templatepkg;
233
    $installer_unsigned_app_sis_name =~ s/_installer.pkg$/_unsigned.sis/i;
234
    $installer_app_sis_name = $installer_unsigned_app_sis_name;
235
    $installer_app_sis_name =~ s/_unsigned.sis$/.sis/;
236
}
237
238
my $unsigned_sis_name = $sisoutputbasename."_unsigned.sis";
239
my $stub_sis_name = $sisoutputbasename.".sis";
240
241
my $certtext = $certificate;
242
243
# Check some pre-conditions and print error messages if needed.
244
unless (length($templatepkg)) {
245
    print "\nERROR: Template PKG filename is not defined!\n";
246
    Usage();
247
}
248
249
# Check template exist
250
stat($templatepkg);
251
unless( -e _ ) {
252
    print "\nERROR: Package description file '$templatepkg' does not exist!\n";
253
    Usage();
254
}
255
256
# Check certifcate preconditions and set default certificate variables if needed
257
if (length($certificate)) {
258
    unless(length($key)) {
259
        print "\nERROR: Custom certificate key file parameter missing.!\n";
260
        Usage();
261
    }
262
} else {
263
    #If no certificate is given, check default options
264
    my $scriptpath = dirname(__FILE__);
265
    my $certpath = File::Spec->catdir($scriptpath, File::Spec->updir(), "src/s60installs");
266
267
    unless (-e $certpath) {
268
        my $qmakeCmd = File::Spec->catfile($scriptpath, "qmake");
269
        $certpath = `$qmakeCmd -query QT_INSTALL_PREFIX`;
270
        $certpath =~ s/\s+$//;
271
        $certpath = File::Spec->catdir($certpath, "src/s60installs");
272
    }
273
274
    $certtext = "RnD";
275
    $certificate = File::Spec->catfile($certpath, "rd.cer");
276
    $key = File::Spec->catfile($certpath, "rd-key.pem");
277
278
    stat($certificate);
279
    unless( -e _ ) {
280
        $certtext = "Self Signed";
281
        $certificate = File::Spec->catfile($certpath, "selfsigned.cer");
282
        $key = File::Spec->catfile($certpath, "selfsigned.key");
283
    }
284
}
285
286
# Read the certificates from file to two dimensional array
287
my @certificates;
288
if (length($certfile)) {
289
    open CERTFILE, "<$certfile" or die $!;
290
    while(<CERTFILE>){
291
        s/#.*//;                            # ignore comments by erasing them
292
        next if /^(\s)*$/;                  # skip blank lines
293
        chomp;                              # remove trailing newline characters
294
        my @certinfo = split(';', $_);      # split row to certinfo
295
296
        # Trim spaces
297
        for(@certinfo) {
298
            s/^\s+//;
299
            s/\s+$//;
300
        }
301
302
        # Do some validation
303
        unless(scalar(@certinfo) >= 2 && scalar(@certinfo) <= 3 && length($certinfo[0]) && length($certinfo[1]) ) {
304
            print "\nERROR: $certfile line '$_' does not contain valid information!\n";
305
            Usage();
306
        }
307
308
        push @certificates, [@certinfo];    # push data to two dimensional array
309
    }
310
}
311
312
# Remove any existing .sis packages
313
unlink $unsigned_sis_name;
314
if (!$onlyUnsigned) {
315
    unlink $signed_sis_name;
316
}
317
unlink $pkgoutput;
318
319
# Preprocess PKG
320
321
local $/;
322
# read template file
323
open( TEMPLATE, $templatepkg) or die "ERROR: '$templatepkg': $!";
324
$_=<TEMPLATE>;
325
close (TEMPLATE);
326
327
# If the pkg file does not contain macros, there is no need for platform or target.
328
if (m/\$\(PLATFORM\)/) {
329
    unless (length($platform) && length($target)) {
330
        print "\nERROR: Platform or target is not defined!\n";
331
        Usage();
332
    }
333
}
334
335
# replace the PKG variables
336
s/\$\(PLATFORM\)/$platform/gm;
337
s/\$\(TARGET\)/$target/gm;
338
339
if ($installer_unsigned_app_sis_name ne "") {
340
    s/$installer_app_sis_name\"/$installer_unsigned_app_sis_name\"/;
341
}
342
343
#write the output
344
open( OUTPUT, ">$pkgoutput" ) or die "ERROR: '$pkgoutput' $!";
345
print OUTPUT $_;
346
close OUTPUT;
347
348
if ($preprocessonly) {
349
    # Copy preprocessed file from tmp dir to pkg file dir
350
    my ($templateVolume, $templatePath, $templateFileName) = File::Spec->splitpath($templatepkg);
351
    my ($dummy1, $dummy2, $copyFileName) = File::Spec->splitpath($pkgoutput);
352
    my $copyTarget = File::Spec->catpath($templateVolume, $templatePath, $copyFileName);
353
    copy($pkgoutput, $copyTarget) or die "Preprocessed pkg file '$pkgoutput' cannot be copied.";
354
    exit;
355
}
356
357
if($stub) {
358
    if(!($epocroot)) { die("ERROR: EPOCROOT must be set to create stub sis files"); }
359
    my $systeminstall = "${epocroot}epoc32/data/z/system/install";
360
    mkpath($systeminstall);
361
    my $stub_sis_name = $systeminstall."/".$stub_sis_name;
362
    # Create stub SIS.
363
    system ("${epocToolsDir}makesis -s $pkgoutput $stub_sis_name");
364
} else {
365
    if ($certtext eq "Self Signed"
366
        && !@certificates
367
        && $templatepkg !~ m/_installer\.pkg$/i
368
        && !$onlyUnsigned) {
369
        my $patch_capabilities = File::Spec->catfile(dirname($0), "patch_capabilities");
370
        if ($dontPatchCaps) {
371
            system ("$patch_capabilities -c $pkgoutput") and print ("Warning: Package check for self-signing viability failed. Installing the package on a device will most likely fail!\n\n");
372
        } else {
373
            print("Auto-patching self-signed package.\n");
374
            system ("$patch_capabilities -t $tempPackageDir $pkgoutput") and die ("ERROR: Automatic patching failed");
375
        }
376
    }
377
378
    # Create SIS.
379
    # The 'and' is because system uses 0 to indicate success.
380
    system ("${epocToolsDir}makesis $pkgoutput $unsigned_sis_name") and die ("ERROR: makesis failed");
381
382
    print("\n");
383
384
    my $targetInsert = "";
385
    if ($targetplatform ne "-") {
386
        $targetInsert = " for $targetplatform";
387
    }
388
389
    if ($onlyUnsigned) {
390
        stat($unsigned_sis_name);
391
        if( -e _ ) {
392
            print ("Successfully created unsigned package ${unsigned_sis_name}${targetInsert}!\n");
393
        } else {
394
            print ("\nUnsigned package creation failed!\n");
395
        }
396
397
        print ("\n");
398
        exit;
399
    }
400
401
    # Sign SIS with certificate info given as an argument.
402
    my $relcert = File::Spec->abs2rel($certificate);
403
    my $relkey = File::Spec->abs2rel($key);
404
    # The 'and' is because system uses 0 to indicate success.
405
    system ("${epocToolsDir}signsis $unsigned_sis_name $signed_sis_name $relcert $relkey $passphrase") and die ("ERROR: signsis failed");
406
407
    # Check if creating signed SIS Succeeded
408
    stat($signed_sis_name);
409
    if( -e _ ) {
410
        print ("Successfully created signed package ${signed_sis_name}${targetInsert} using certificate: $certtext!\n");
411
412
        # Sign with additional certificates & keys
413
        for my $row ( @certificates ) {
414
            # Get certificate absolute file names, relative paths are relative to certfilepath
415
            my $relcert = File::Spec->abs2rel(File::Spec->rel2abs( $row->[0], $certfilepath));
416
            my $relkey = File::Spec->abs2rel(File::Spec->rel2abs( $row->[1], $certfilepath));
417
418
            system ("${epocToolsDir}signsis $signed_sis_name $signed_sis_name $relcert $relkey $row->[2]");
419
            print ("\tAdditionally signed the SIS with certificate: $row->[0]!\n");
420
        }
421
422
        # remove temporary unsigned sis
423
        if (!$preserveUnsigned) {
424
            unlink $unsigned_sis_name;
425
        }
426
427
        # Install the sis if requested
428
        if ($install) {
429
            print ("\nInstalling $signed_sis_name...\n");
430
            system ("$signed_sis_name");
431
        }
432
    } else {
433
        # Lets leave the generated PKG for problem solving purposes
434
        print ("\nSIS creation failed!\n");
435
    }
436
    print ("\n");
437
}
438
439
#end of file