0
|
1 |
#!/usr/bin/env python
|
|
2 |
# -*- coding: utf-8 -*-
|
|
3 |
|
|
4 |
##############################################################################
|
|
5 |
# cmd_py2sis.py - Ensymble command line tool, py2sis command
|
|
6 |
# Copyright 2006, 2007, 2008, 2009 Jussi Ylänen
|
|
7 |
#
|
|
8 |
# Portions Copyright (c) 2008-2009 Nokia Corporation
|
|
9 |
#
|
|
10 |
# This file is part of Ensymble developer utilities for Symbian OS(TM).
|
|
11 |
#
|
|
12 |
# Ensymble is free software; you can redistribute it and/or modify
|
|
13 |
# it under the terms of the GNU General Public License as published by
|
|
14 |
# the Free Software Foundation; either version 2 of the License, or
|
|
15 |
# (at your option) any later version.
|
|
16 |
#
|
|
17 |
# Ensymble is distributed in the hope that it will be useful,
|
|
18 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20 |
# GNU General Public License for more details.
|
|
21 |
#
|
|
22 |
# You should have received a copy of the GNU General Public License
|
|
23 |
# along with Ensymble; if not, write to the Free Software
|
|
24 |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
25 |
##############################################################################
|
|
26 |
|
|
27 |
import sys
|
|
28 |
import os
|
|
29 |
import re
|
|
30 |
import imp
|
|
31 |
import getopt
|
|
32 |
import getpass
|
|
33 |
import locale
|
|
34 |
import zlib
|
|
35 |
import shutil
|
|
36 |
import time
|
|
37 |
import zipfile
|
|
38 |
|
|
39 |
import sisfile
|
|
40 |
import sisfield
|
|
41 |
import symbianutil
|
|
42 |
import rscfile
|
|
43 |
import miffile
|
|
44 |
import module_repo
|
|
45 |
import py_compile
|
|
46 |
|
|
47 |
|
|
48 |
##############################################################################
|
|
49 |
# Specify the magic number of the Python interpreter used in PyS60.
|
|
50 |
##############################################################################
|
|
51 |
pys60_magic_number = '\xb3\xf2\r\n'
|
|
52 |
|
|
53 |
|
|
54 |
##############################################################################
|
|
55 |
# Help texts
|
|
56 |
##############################################################################
|
|
57 |
|
|
58 |
shorthelp = 'Create a SIS package for a "Python for S60" application'
|
|
59 |
longhelp = '''py2sis
|
|
60 |
[--uid=0x01234567] [--appname=AppName] [--version=1.0.0]
|
|
61 |
[--lang=EN,...] [--icon=icon.svg] [--shortcaption="App. Name",...]
|
|
62 |
[--caption="Application Name",...] [--drive=C] [--extrasdir=root]
|
|
63 |
[--textfile=mytext_%C.txt] [--cert=mycert.cer] [--privkey=mykey.key]
|
|
64 |
[--passphrase=12345] [--heapsize=min,max] [--caps=Cap1+Cap2+...]
|
|
65 |
[--vendor="Vendor Name",...] [--autostart] [--runinstall]
|
|
66 |
[--encoding=terminal,filesystem] [--verbose] [--profile=s60ui]
|
|
67 |
[--mode=pys60] [--extra-modules=mod1,...] [--sourcecode]
|
|
68 |
[--ignore-missing-deps] [--platform-uid=0x101f7961,...]
|
|
69 |
<src> [sisfile]
|
|
70 |
|
|
71 |
Create a SIS package for a "Python for S60" application.
|
|
72 |
|
|
73 |
Options:
|
|
74 |
src - Source script or directory
|
|
75 |
sisfile - Path of the created SIS file
|
|
76 |
uid - Symbian OS UID for the application
|
|
77 |
appname - Name of the application
|
|
78 |
version - Application version: X.Y.Z or X,Y,Z (major, minor, build)
|
|
79 |
lang - Comma separated list of two-character language codes
|
|
80 |
icon - Icon file in SVG-Tiny format
|
|
81 |
shortcaption - Comma separated list of short captions in all languages
|
|
82 |
caption - Comma separated list of long captions in all languages
|
|
83 |
drive - Drive where the package will be installed (any by default)
|
|
84 |
extrasdir - Name of dir. tree placed under drive root (none by default)
|
|
85 |
textfile - Text file (or pattern, see below) to display during install
|
|
86 |
cert - Certificate to use for signing (PEM format)
|
|
87 |
privkey - Private key of the certificate (PEM format)
|
|
88 |
passphrase - Pass phrase of the private key (insecure, use stdin instead)
|
|
89 |
caps - Capability names, separated by "+" (LocalServices, NetworkServices,
|
|
90 |
ReadUserData, WriteUserData and UserEnvironment by default)
|
|
91 |
vendor - Vendor name or a comma separated list of names in all lang.
|
|
92 |
autostart - Application is registered to start on each device boot
|
|
93 |
runinstall - Application is automatically started after installation
|
|
94 |
heapsize - Application heap size, min. and/or max. ("100K,4M" by default)
|
|
95 |
encoding - Local character encodings for terminal and filesystem
|
|
96 |
verbose - Print extra statistics
|
|
97 |
profile - Script execution environment. Either console or s60ui
|
|
98 |
mode - Module to be packaged when names conflict. Either from pycore or pys60
|
|
99 |
extra-modules - Additional dependency modules that should be packaged with the application
|
|
100 |
sourcecode - Package scripts as source code. By default the scripts are packaged as bytecode
|
|
101 |
ignore-missing-deps - Ignore any missing dependencies during packaging
|
|
102 |
platform-uid - UID of the supported S60 platforms
|
|
103 |
|
|
104 |
If no certificate and its private key are given, a default self-signed
|
|
105 |
certificate is used to sign the SIS file. Software authors are encouraged
|
|
106 |
to create their own unique certificates for SIS packages that are to be
|
|
107 |
distributed.
|
|
108 |
|
|
109 |
If no icon is given, the Python logo is used as the icon. The Python
|
|
110 |
logo is a trademark of the Python Software Foundation.
|
|
111 |
|
|
112 |
Text to display uses UTF-8 encoding. The file name may contain formatting
|
|
113 |
characters that are substituted for each selected language. If no formatting
|
|
114 |
characters are present, the same text will be used for all languages.
|
|
115 |
|
|
116 |
%% - literal %
|
|
117 |
%n - language number (01 - 99)
|
|
118 |
%c - two-character language code in lowercase letters
|
|
119 |
%C - two-character language code in capital letters
|
|
120 |
%l - language name in English, using only lowercase letters
|
|
121 |
%l - language name in English, using mixed case letters
|
|
122 |
'''
|
|
123 |
|
|
124 |
|
|
125 |
##############################################################################
|
|
126 |
# Parameters
|
|
127 |
##############################################################################
|
|
128 |
|
|
129 |
MAXPASSPHRASELENGTH = 256
|
|
130 |
MAXCERTIFICATELENGTH = 65536
|
|
131 |
MAXPRIVATEKEYLENGTH = 65536
|
|
132 |
MAXICONFILESIZE = 65536
|
|
133 |
MAXOTHERFILESIZE = 1024 * 1024 * 8 # Eight megabytes
|
|
134 |
MAXTEXTFILELENGTH = 1024
|
|
135 |
|
|
136 |
|
|
137 |
##############################################################################
|
|
138 |
# Public module-level functions
|
|
139 |
##############################################################################
|
|
140 |
|
|
141 |
|
|
142 |
def run(pgmname, argv):
|
|
143 |
# Determine system character encodings.
|
|
144 |
try:
|
|
145 |
# getdefaultlocale() may sometimes return None.
|
|
146 |
# Fall back to ASCII encoding in that case.
|
|
147 |
terminalenc = locale.getdefaultlocale()[1] + ""
|
|
148 |
except TypeError:
|
|
149 |
# Invalid locale, fall back to ASCII terminal encoding.
|
|
150 |
terminalenc = "ascii"
|
|
151 |
|
|
152 |
try:
|
|
153 |
# sys.getfilesystemencoding() was introduced in Python v2.3 and
|
|
154 |
# it can sometimes return None. Fall back to ASCII if something
|
|
155 |
# goes wrong.
|
|
156 |
filesystemenc = sys.getfilesystemencoding() + ""
|
|
157 |
except (AttributeError, TypeError):
|
|
158 |
filesystemenc = "ascii"
|
|
159 |
|
|
160 |
try:
|
|
161 |
gopt = getopt.gnu_getopt
|
|
162 |
except:
|
|
163 |
# Python <v2.3, GNU-style parameter ordering not supported.
|
|
164 |
gopt = getopt.getopt
|
|
165 |
|
|
166 |
# Parse command line arguments.
|
|
167 |
short_opts = "u:n:r:l:i:s:c:f:x:t:a:k:p:b:d:gRH:e:vhP:m:o:Sy:Iq"
|
|
168 |
long_opts = [
|
|
169 |
"uid=", "appname=", "version=", "lang=", "icon=",
|
|
170 |
"shortcaption=", "caption=", "drive=", "extrasdir=", "textfile=",
|
|
171 |
"cert=", "privkey=", "passphrase=", "caps=", "vendor=",
|
|
172 |
"autostart", "runinstall", "heapsize=",
|
|
173 |
"encoding=", "verbose", "debug", "help", "profile=", "mode=",
|
|
174 |
"extra-modules=", "sourcecode", "ignore-missing-deps", "platform-uid="]
|
|
175 |
args = gopt(argv, short_opts, long_opts)
|
|
176 |
|
|
177 |
opts = dict(args[0])
|
|
178 |
pargs = args[1]
|
|
179 |
|
|
180 |
if len(pargs) == 0:
|
|
181 |
raise ValueError("no source file name given")
|
|
182 |
|
|
183 |
# Override character encoding of command line and filesystem.
|
|
184 |
encs = opts.get("--encoding", opts.get("-e", "%s,%s" % (terminalenc,
|
|
185 |
filesystemenc)))
|
|
186 |
try:
|
|
187 |
terminalenc, filesystemenc = encs.split(",")
|
|
188 |
except (ValueError, TypeError):
|
|
189 |
raise ValueError("invalid encoding string '%s'" % encs)
|
|
190 |
|
|
191 |
# Determine if debug output is requested.
|
|
192 |
if "--debug" in opts.keys():
|
|
193 |
# Enable debug output for OpenSSL-related functions.
|
|
194 |
import cryptutil
|
|
195 |
cryptutil.setdebug(True)
|
|
196 |
module_repo.debug = True
|
|
197 |
|
|
198 |
module_repo.debug_log = open("debug.txt", "w")
|
|
199 |
|
|
200 |
# Check if ignore-missing-deps flag is set. If not then abort sis
|
|
201 |
# generation printing the missing dependencies as errors.
|
|
202 |
if "--ignore-missing-deps" in opts.keys() or "-I" in opts.keys():
|
|
203 |
module_repo.ignore_missing_deps = True
|
|
204 |
|
|
205 |
# If sourcecode option is set then the application is packaged as source
|
|
206 |
# code. By default it is packaged as bytecode.
|
|
207 |
bytecode = True
|
|
208 |
if "--sourcecode" in opts.keys() or "-S" in opts.keys():
|
|
209 |
bytecode = False
|
|
210 |
|
|
211 |
# Get source name, either a Python program or a directory.
|
|
212 |
src = pargs[0].decode(terminalenc).encode(filesystemenc)
|
|
213 |
|
|
214 |
extrasdir = opts.get("--extrasdir", opts.get("-x", None))
|
|
215 |
if extrasdir != None:
|
|
216 |
extrasdir = extrasdir.decode(terminalenc).encode(filesystemenc)
|
|
217 |
if extrasdir[-1] == os.sep:
|
|
218 |
# Strip trailing slash (or backslash).
|
|
219 |
extrasdir = extrasdir[:-1]
|
|
220 |
|
|
221 |
if os.sep in extrasdir:
|
|
222 |
raise ValueError("%s: too many path components" % extrasdir)
|
|
223 |
if not os.path.isdir(src):
|
|
224 |
raise RuntimeError("extrasdir option can only be used when the" +
|
|
225 |
" source is a directory")
|
|
226 |
if not os.path.isdir(os.path.join(os.path.abspath(src), extrasdir)):
|
|
227 |
raise RuntimeError("Directory specified using --extrasdir" +\
|
|
228 |
" option does not exist")
|
|
229 |
|
|
230 |
# Get the profile in which the python script should be executed - OpenC
|
|
231 |
# console mode or s60ui mode
|
|
232 |
profile = opts.get("--profile", opts.get("-P", "s60ui"))
|
|
233 |
if profile not in ['console', 's60ui']:
|
|
234 |
raise ValueError("Invalid profile. Set either console or s60ui")
|
|
235 |
|
|
236 |
# Check if Python core modules take priority over PyS60 modules
|
|
237 |
mode = opts.get("--mode", opts.get("-m", "pycore"))
|
|
238 |
if mode not in ['pycore', 'pys60']:
|
|
239 |
raise ValueError("Invalid mode. Set either pys60 or pycore")
|
|
240 |
|
|
241 |
# Get the S60 platform uid of the devices on which the Python
|
|
242 |
# application is supported
|
|
243 |
platform_uid = opts.get("--platform-uid", "0x101f7961,0x1028315F")
|
|
244 |
|
|
245 |
# Extra module(s) to be packaged with application sis
|
|
246 |
extra_modules = opts.get("--extra-modules", opts.get("-o", None))
|
|
247 |
|
|
248 |
# module_repo.get_dependency_list returns a map with the following format:
|
|
249 |
# {'repo': [<dependency list of standard repo modules>],
|
|
250 |
# 'dev': [<dependency list of dev modules>]}
|
|
251 |
extra_modules_map = module_repo.get_dependency_list(src, extra_modules)
|
|
252 |
|
|
253 |
if mode == 'pys60':
|
|
254 |
# Add calendar.py and socket.py wrappers to app's private directory
|
|
255 |
conflicting_modules = {'calendar': 'e32calendar', 'socket': 'btsocket'}
|
|
256 |
|
|
257 |
for module in conflicting_modules:
|
|
258 |
if module in extra_modules_map[module_repo.std_repo_module]:
|
|
259 |
extra_modules_map[module_repo.std_repo_module].remove(module)
|
|
260 |
extra_modules_map[module_repo.dev_repo_module].append(
|
|
261 |
conflicting_modules[module])
|
|
262 |
|
|
263 |
tmpdir_suffix = '_' + time.strftime("%H%M%S")
|
|
264 |
|
|
265 |
if not os.path.isdir(src):
|
|
266 |
# app is a file. Create app directory, rename app file to
|
|
267 |
# default.py, and add dependency
|
|
268 |
srcdir, srcfile = os.path.split(src)
|
|
269 |
appdir = os.path.join(srcdir, srcfile[:-3] + tmpdir_suffix)
|
|
270 |
os.mkdir(appdir)
|
|
271 |
shutil.copy(src, appdir)
|
|
272 |
os.rename(os.path.join(appdir, srcfile),
|
|
273 |
os.path.join(appdir, "default.py"))
|
|
274 |
src = appdir
|
|
275 |
else:
|
|
276 |
module_repo.debug_print("Copying '%s' to '%s' " %
|
|
277 |
(os.path.abspath(src), os.path.abspath(src) + tmpdir_suffix))
|
|
278 |
shutil.copytree(os.path.abspath(src),
|
|
279 |
os.path.abspath(src) + tmpdir_suffix)
|
|
280 |
src += tmpdir_suffix
|
|
281 |
appdir = src
|
|
282 |
# appdir should be deleted if there is any exception during packaging
|
|
283 |
try:
|
|
284 |
if extra_modules_map:
|
|
285 |
module_repo.debug_print("Extra modules list:" + str(extra_modules_map))
|
|
286 |
module_repo.appdir = appdir
|
|
287 |
module_repo.extrasdir = extrasdir
|
|
288 |
dep_module_paths = module_repo.search_module(extra_modules_map)
|
|
289 |
if module_repo.error_count != 0:
|
|
290 |
raise RuntimeError(str(module_repo.error_count) +\
|
|
291 |
" dependency files not found")
|
|
292 |
module_repo.process_dependent_modules(dep_module_paths)
|
|
293 |
|
|
294 |
extrasdir = module_repo.extrasdir
|
|
295 |
|
|
296 |
if os.path.isdir(src):
|
|
297 |
# Add calendar.py and socket.py wrappers to app's lib.zip
|
|
298 |
conflicting_modules = {'calendar_py': 'calendar.py',
|
|
299 |
'socket_py': 'socket.py'}
|
|
300 |
if mode == 'pys60':
|
|
301 |
for module in conflicting_modules:
|
|
302 |
module_repo.debug_print("Adding socket & calendar " +
|
|
303 |
"wrappers")
|
|
304 |
shutil.copy(
|
|
305 |
os.path.join("templates", conflicting_modules[module]),
|
|
306 |
os.path.join(src, conflicting_modules[module]))
|
|
307 |
# All the py[c|o] files in the app directory except for default.py
|
|
308 |
# are zipped into lib.zip. This file is added to sys.path by
|
|
309 |
# launcher.py
|
|
310 |
py_ignore_list = []
|
|
311 |
lib_zip = zipfile.ZipFile(os.path.join(src,
|
|
312 |
"lib.zip"), "w", zipfile.ZIP_DEFLATED)
|
|
313 |
|
|
314 |
def process_bytecode(dirpath, name, file_ext):
|
|
315 |
# Process the files to be written to the lib_zip based on the
|
|
316 |
# `bytecode` option
|
|
317 |
if not bytecode or file_ext in ['.pyc', '.pyo']:
|
|
318 |
final_filename = name + file_ext
|
|
319 |
elif bytecode and file_ext == '.py':
|
|
320 |
if imp.get_magic() != pys60_magic_number:
|
|
321 |
raise RuntimeError("Python Version on \
|
|
322 |
the host system not bytecode compatible with \
|
|
323 |
the Python version used in PyS60")
|
|
324 |
py_compile.compile(os.path.join(dirpath, name + file_ext))
|
|
325 |
# Compilation can result in either a pyc or a pyo.
|
|
326 |
for ext in ['.pyc', '.pyo']:
|
|
327 |
if os.path.exists(os.path.join(dirpath, name + ext)):
|
|
328 |
final_filename = name + ext
|
|
329 |
|
|
330 |
return final_filename
|
|
331 |
|
|
332 |
for dirpath, dirs, files in os.walk(src):
|
|
333 |
if extrasdir in dirs:
|
|
334 |
dirs.remove(extrasdir)
|
|
335 |
for filename in files:
|
|
336 |
name, file_ext = os.path.splitext(os.path.basename(
|
|
337 |
filename))
|
|
338 |
file_ext = file_ext.lower()
|
|
339 |
if file_ext == '.py':
|
|
340 |
for ext in ['.pyc', '.pyo']:
|
|
341 |
if os.path.exists(os.path.join(dirpath,
|
|
342 |
name + ext)):
|
|
343 |
os.remove(os.path.join(dirpath, name + ext))
|
|
344 |
files.remove(name + ext)
|
|
345 |
|
|
346 |
if file_ext in ['.py', '.pyc', '.pyo'] and \
|
|
347 |
filename != 'default.py':
|
|
348 |
final_filename = process_bytecode(dirpath, name,
|
|
349 |
file_ext)
|
|
350 |
relative_path = \
|
|
351 |
os.path.join(dirpath, final_filename).split(
|
|
352 |
os.path.basename(src) + os.sep)[-1]
|
|
353 |
module_repo.debug_print("Adding %s to lib.zip as %s" %\
|
|
354 |
(os.path.join(src, relative_path), relative_path))
|
|
355 |
lib_zip.write(os.path.join(src, relative_path),
|
|
356 |
relative_path)
|
|
357 |
if extra_modules_map:
|
|
358 |
# After copying the files to the lib_zip delete
|
|
359 |
# them otherwise they too will find their way into
|
|
360 |
# the zip.
|
|
361 |
os.remove(os.path.join(src, relative_path))
|
|
362 |
if bytecode and file_ext == '.py':
|
|
363 |
# The `py` files which will be compiled will
|
|
364 |
# also have to be deleted.
|
|
365 |
os.remove(os.path.join(dirpath, filename))
|
|
366 |
else:
|
|
367 |
py_ignore_list.append(relative_path)
|
|
368 |
|
|
369 |
lib_zip.close()
|
|
370 |
if not len(lib_zip.infolist()):
|
|
371 |
# Delete the lib_zip if it is empty
|
|
372 |
os.remove(os.path.join(src, "lib.zip"))
|
|
373 |
|
|
374 |
# Remove trailing slashes (or whatever the separator is).
|
|
375 |
src = os.path.split(src + os.sep)[0]
|
|
376 |
|
|
377 |
# Use last directory component as the name.
|
|
378 |
basename = os.path.basename(src)
|
|
379 |
|
|
380 |
# Source is a directory, recursively collect files it contains.
|
|
381 |
srcdir = src
|
|
382 |
srcfiles = []
|
|
383 |
prefixlen = len(srcdir) + len(os.sep)
|
|
384 |
def getfiles(arg, dirname, names):
|
|
385 |
for name in names:
|
|
386 |
path = os.path.join(dirname, name)
|
|
387 |
if not os.path.isdir(path) and \
|
|
388 |
path[prefixlen:] not in py_ignore_list:
|
|
389 |
arg.append(path[prefixlen:])
|
|
390 |
os.path.walk(srcdir, getfiles, srcfiles)
|
|
391 |
|
|
392 |
# Read application version and UID3 from default.py.
|
|
393 |
version, uid3 = scandefaults(os.path.join(srcdir, "default.py"))
|
|
394 |
else:
|
|
395 |
if src.lower().endswith(".py"):
|
|
396 |
# Use program name without the .py extension.
|
|
397 |
basename = os.path.basename(src)[:-3]
|
|
398 |
else:
|
|
399 |
# Unknown extension, use program name as-is.
|
|
400 |
basename = os.path.basename(src)
|
|
401 |
|
|
402 |
# Source is a file, use it.
|
|
403 |
srcdir, srcfiles = os.path.split(src)
|
|
404 |
srcfiles = [srcfiles]
|
|
405 |
|
|
406 |
# Read application version and UID3 from file.
|
|
407 |
version, uid3 = scandefaults(os.path.join(srcdir, srcfiles[0]))
|
|
408 |
|
|
409 |
# The sis file name should not have the tmpdir_suffix appended to src
|
|
410 |
basename = basename.rsplit(tmpdir_suffix, 1)[0]
|
|
411 |
|
|
412 |
# Parse version string, use 1.0.0 by default.
|
|
413 |
version = opts.get("--version", opts.get("-r", version))
|
|
414 |
if version == None:
|
|
415 |
version = "1.0.0"
|
|
416 |
print ("%s: warning: no application version given, "
|
|
417 |
"using %s" % (pgmname, version))
|
|
418 |
try:
|
|
419 |
version = parseversion(version)
|
|
420 |
except (ValueError, IndexError, TypeError):
|
|
421 |
raise ValueError("invalid version string '%s'" % version)
|
|
422 |
|
|
423 |
# Determine output SIS file name.
|
|
424 |
if len(pargs) == 1:
|
|
425 |
# Derive output file name from input file name.
|
|
426 |
outfile = "%s_v%d_%d_%d.sis" % (basename, version[0],
|
|
427 |
version[1], version[2])
|
|
428 |
elif len(pargs) == 2:
|
|
429 |
outfile = pargs[1].decode(terminalenc).encode(filesystemenc)
|
|
430 |
if os.path.isdir(outfile):
|
|
431 |
# Output to directory, derive output name from input file name.
|
|
432 |
outfile = os.path.join(outfile, "%s_v%d_%d_%d.sis" % (
|
|
433 |
basename, version[0], version[1], version[2]))
|
|
434 |
if not outfile.lower().endswith(".sis"):
|
|
435 |
outfile += ".sis"
|
|
436 |
else:
|
|
437 |
raise ValueError("wrong number of arguments")
|
|
438 |
|
|
439 |
# Determine application name (install dir.), use basename by default.
|
|
440 |
appname = opts.get("--appname", opts.get("-n", basename))
|
|
441 |
appname = appname.decode(terminalenc)
|
|
442 |
|
|
443 |
# Auto-generate a test-range UID from application name.
|
|
444 |
autouid = symbianutil.uidfromname(appname)
|
|
445 |
|
|
446 |
# Get UID3.
|
|
447 |
uid3 = opts.get("--uid", opts.get("-u", uid3))
|
|
448 |
if uid3 == None:
|
|
449 |
# No UID given, use auto-generated UID.
|
|
450 |
uid3 = autouid
|
|
451 |
print ("%s: warning: no UID given, using auto-generated "
|
|
452 |
"test-range UID 0x%08x" % (pgmname, uid3))
|
|
453 |
elif uid3.lower().startswith("0x"):
|
|
454 |
# Prefer hex UIDs with leading "0x".
|
|
455 |
uid3 = long(uid3, 16)
|
|
456 |
else:
|
|
457 |
try:
|
|
458 |
if len(uid3) == 8:
|
|
459 |
# Assuming hex UID even without leading "0x".
|
|
460 |
print ('%s: warning: assuming hex UID even '
|
|
461 |
'without leading "0x"' % pgmname)
|
|
462 |
uid3 = long(uid3, 16)
|
|
463 |
else:
|
|
464 |
# Decimal UID.
|
|
465 |
uid3 = long(uid3)
|
|
466 |
print ('%s: warning: decimal UID converted to 0x%08x' %
|
|
467 |
(pgmname, uid3))
|
|
468 |
except ValueError:
|
|
469 |
raise ValueError("invalid UID string '%s'" % uid3)
|
|
470 |
|
|
471 |
# Warn against specifying a test-range UID manually.
|
|
472 |
if uid3 & 0xf0000000L == 0xe0000000L and uid3 != autouid:
|
|
473 |
print ("%s: warning: manually specifying a test-range UID is "
|
|
474 |
"not recommended" % pgmname)
|
|
475 |
|
|
476 |
# Determine application language(s), use "EN" by default.
|
|
477 |
lang = opts.get("--lang", opts.get("-l", "EN")).split(",")
|
|
478 |
numlang = len(lang)
|
|
479 |
|
|
480 |
# Verify that the language codes are correct.
|
|
481 |
for l in lang:
|
|
482 |
try:
|
|
483 |
symbianutil.langidtonum[l]
|
|
484 |
except KeyError:
|
|
485 |
raise ValueError("%s: no such language code" % l)
|
|
486 |
|
|
487 |
# Get icon file name.
|
|
488 |
icon = opts.get("--icon", opts.get("-i", None))
|
|
489 |
if icon != None:
|
|
490 |
icon = icon.decode(terminalenc).encode(filesystemenc)
|
|
491 |
|
|
492 |
# Read icon file.
|
|
493 |
f = file(icon, "rb")
|
|
494 |
icondata = f.read(MAXICONFILESIZE + 1)
|
|
495 |
f.close()
|
|
496 |
|
|
497 |
if len(icondata) > MAXICONFILESIZE:
|
|
498 |
raise ValueError("icon file too large")
|
|
499 |
else:
|
|
500 |
# No icon given, use a default icon.
|
|
501 |
icondata = zlib.decompress(defaulticondata.decode("base-64"))
|
|
502 |
|
|
503 |
# Determine application short caption(s).
|
|
504 |
shortcaption = opts.get("--shortcaption", opts.get("-s", ""))
|
|
505 |
shortcaption = shortcaption.decode(terminalenc)
|
|
506 |
if len(shortcaption) == 0:
|
|
507 |
# Short caption not given, use application name.
|
|
508 |
shortcaption = [appname] * numlang
|
|
509 |
else:
|
|
510 |
shortcaption = shortcaption.split(",")
|
|
511 |
|
|
512 |
# Determine application long caption(s), use short caption by default.
|
|
513 |
caption = opts.get("--caption", opts.get("-c", ""))
|
|
514 |
caption = caption.decode(terminalenc)
|
|
515 |
if len(caption) == 0:
|
|
516 |
# Caption not given, use short caption.
|
|
517 |
caption = shortcaption
|
|
518 |
else:
|
|
519 |
caption = caption.split(",")
|
|
520 |
|
|
521 |
# Compare the number of languages and captions.
|
|
522 |
if len(shortcaption) != numlang or len(caption) != numlang:
|
|
523 |
raise ValueError("invalid number of captions")
|
|
524 |
|
|
525 |
# Determine installation drive, any by default.
|
|
526 |
drive = opts.get("--drive", opts.get("-f", "any")).upper()
|
|
527 |
if drive == "ANY" or drive == "!":
|
|
528 |
drive = "!"
|
|
529 |
elif drive != "C" and drive != "E":
|
|
530 |
raise ValueError("%s: invalid drive letter" % drive)
|
|
531 |
|
|
532 |
# Determine vendor name(s), use "Ensymble" by default.
|
|
533 |
vendor = opts.get("--vendor", opts.get("-d", "Ensymble"))
|
|
534 |
vendor = vendor.decode(terminalenc)
|
|
535 |
vendor = vendor.split(",")
|
|
536 |
if len(vendor) == 1:
|
|
537 |
# Only one vendor name given, use it for all languages.
|
|
538 |
vendor = vendor * numlang
|
|
539 |
elif len(vendor) != numlang:
|
|
540 |
raise ValueError("invalid number of vendor names")
|
|
541 |
|
|
542 |
# Load text files.
|
|
543 |
texts = []
|
|
544 |
textfile = opts.get("--textfile", opts.get("-t", None))
|
|
545 |
if textfile != None:
|
|
546 |
texts = readtextfiles(textfile, lang)
|
|
547 |
|
|
548 |
# Get certificate and its private key file names.
|
|
549 |
cert = opts.get("--cert", opts.get("-a", None))
|
|
550 |
privkey = opts.get("--privkey", opts.get("-k", None))
|
|
551 |
if cert != None and privkey != None:
|
|
552 |
# Convert file names from terminal encoding to filesystem encoding.
|
|
553 |
cert = cert.decode(terminalenc).encode(filesystemenc)
|
|
554 |
privkey = privkey.decode(terminalenc).encode(filesystemenc)
|
|
555 |
|
|
556 |
# Read certificate file.
|
|
557 |
f = file(cert, "rb")
|
|
558 |
certdata = f.read(MAXCERTIFICATELENGTH + 1)
|
|
559 |
f.close()
|
|
560 |
|
|
561 |
if len(certdata) > MAXCERTIFICATELENGTH:
|
|
562 |
raise ValueError("certificate file too large")
|
|
563 |
|
|
564 |
# Read private key file.
|
|
565 |
f = file(privkey, "rb")
|
|
566 |
privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1)
|
|
567 |
f.close()
|
|
568 |
|
|
569 |
if len(privkeydata) > MAXPRIVATEKEYLENGTH:
|
|
570 |
raise ValueError("private key file too large")
|
|
571 |
elif cert == None and privkey == None:
|
|
572 |
# No certificate given, use the Ensymble default certificate.
|
|
573 |
# defaultcert.py is not imported when not needed. This speeds
|
|
574 |
# up program start-up a little.
|
|
575 |
import defaultcert
|
|
576 |
certdata = defaultcert.cert
|
|
577 |
privkeydata = defaultcert.privkey
|
|
578 |
|
|
579 |
print ("%s: warning: no certificate given, using "
|
|
580 |
"insecure built-in one" % pgmname)
|
|
581 |
|
|
582 |
# Warn if the UID is in the protected range.
|
|
583 |
# Resulting SIS file will probably not install.
|
|
584 |
if uid3 < 0x80000000L:
|
|
585 |
print ("%s: warning: UID is in the protected range "
|
|
586 |
"(0x00000000 - 0x7ffffff)" % pgmname)
|
|
587 |
else:
|
|
588 |
raise ValueError("missing certificate or private key")
|
|
589 |
|
|
590 |
# Get pass phrase. Pass phrase remains in terminal encoding.
|
|
591 |
passphrase = opts.get("--passphrase", opts.get("-p", None))
|
|
592 |
if passphrase == None and privkey != None:
|
|
593 |
# Private key given without "--passphrase" option, ask it.
|
|
594 |
if sys.stdin.isatty():
|
|
595 |
# Standard input is a TTY, ask password interactively.
|
|
596 |
passphrase = getpass.getpass("Enter private key pass phrase:")
|
|
597 |
else:
|
|
598 |
# Not connected to a TTY, read stdin non-interactively instead.
|
|
599 |
passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1)
|
|
600 |
|
|
601 |
if len(passphrase) > MAXPASSPHRASELENGTH:
|
|
602 |
raise ValueError("pass phrase too long")
|
|
603 |
|
|
604 |
passphrase = passphrase.strip()
|
|
605 |
|
|
606 |
# Get capabilities and normalize the names.
|
|
607 |
caps = opts.get("--caps", opts.get("-b",
|
|
608 |
"LocalServices+NetworkServices+ReadUserData+WriteUserData+" +
|
|
609 |
"UserEnvironment"))
|
|
610 |
capmask = symbianutil.capstringtomask(caps)
|
|
611 |
caps = symbianutil.capmasktostring(capmask, True)
|
|
612 |
|
|
613 |
# Determine if the application is requested to start on each device
|
|
614 |
# boot.
|
|
615 |
autostart = False
|
|
616 |
if "--autostart" in opts.keys() or "-g" in opts.keys():
|
|
617 |
autostart = True
|
|
618 |
|
|
619 |
runinstall = False
|
|
620 |
if "--runinstall" in opts.keys() or "-R" in opts.keys():
|
|
621 |
runinstall = True
|
|
622 |
|
|
623 |
# Get heap sizes.
|
|
624 |
heapsize = \
|
|
625 |
opts.get("--heapsize", opts.get("-H", "100K,4M")).split(",", 1)
|
|
626 |
try:
|
|
627 |
heapsizemin = symbianutil.parseintmagnitude(heapsize[0])
|
|
628 |
if len(heapsize) == 1:
|
|
629 |
# Only one size given, use it as both.
|
|
630 |
heapsizemax = heapsizemin
|
|
631 |
else:
|
|
632 |
heapsizemax = symbianutil.parseintmagnitude(heapsize[1])
|
|
633 |
except (ValueError, TypeError, IndexError):
|
|
634 |
raise ValueError(
|
|
635 |
"%s: invalid heap size, one or two values expected" %
|
|
636 |
",".join(heapsize))
|
|
637 |
|
|
638 |
# Warn if the minimum heap size is larger than the maximum heap size.
|
|
639 |
# Resulting SIS file will probably not install.
|
|
640 |
if heapsizemin > heapsizemax:
|
|
641 |
print ("%s: warning: minimum heap size larger than "
|
|
642 |
"maximum heap size" % pgmname)
|
|
643 |
|
|
644 |
# Determine verbosity.
|
|
645 |
verbose = False
|
|
646 |
if "--verbose" in opts.keys() or "-v" in opts.keys():
|
|
647 |
verbose = True
|
|
648 |
|
|
649 |
|
|
650 |
# Ingredients for successful SIS generation:
|
|
651 |
#
|
|
652 |
# terminalenc Terminal character encoding (autodetected)
|
|
653 |
# filesystemenc File system name encoding (autodetected)
|
|
654 |
# basename Base for generated file names on host, filesystemenc encoded
|
|
655 |
# srcdir Directory of source files, filesystemenc encoded
|
|
656 |
# srcfiles List of filesystemenc encoded source file names in srcdir
|
|
657 |
# outfile Output SIS file name, filesystemenc encoded
|
|
658 |
# uid3 Application UID3, long integer
|
|
659 |
# appname Application name and install directory in device, in Unicode
|
|
660 |
# version A triple-item tuple (major, minor, build)
|
|
661 |
# lang List of two-character language codes, ASCII strings
|
|
662 |
# icon Icon data, a binary string typically containing a SVG-T file
|
|
663 |
# shortcaption List of Unicode short captions, one per language
|
|
664 |
# caption List of Unicode long captions, one per language
|
|
665 |
# drive Installation drive letter or "!"
|
|
666 |
# extrasdir Path prefix for extra files, filesystemenc encoded or None
|
|
667 |
# textfile File name pattern of text file(s) to display during install
|
|
668 |
# texts Actual texts to display during install, one per language
|
|
669 |
# cert Certificate in PEM format
|
|
670 |
# privkey Certificate private key in PEM format
|
|
671 |
# passphrase Pass phrase of private key, terminalenc encoded string
|
|
672 |
# caps, capmask Capability names and bitmask
|
|
673 |
# vendor List of Unicode vendor names, one per language
|
|
674 |
# autostart Boolean requesting application autostart on device boot
|
|
675 |
# runinstall Boolean requesting application autorun after installation
|
|
676 |
# heapsizemin Heap that must be available for the application to start
|
|
677 |
# heapsizemax Maximum amount of heap the application can allocate
|
|
678 |
# verbose Boolean indicating verbose terminal output
|
|
679 |
# profile console/s60ui stub exe to be packaged with script
|
|
680 |
# mode Selects the module to be packaged either from Python core
|
|
681 |
# or PyS60
|
|
682 |
# extra-modules Additional dependency modules that should be packaged with
|
|
683 |
# the application
|
|
684 |
|
|
685 |
if verbose:
|
|
686 |
print
|
|
687 |
print "Input file(s) %s" % " ".join(
|
|
688 |
[s.decode(filesystemenc).encode(terminalenc) for s in srcfiles])
|
|
689 |
print "Output SIS file %s" % (
|
|
690 |
outfile.decode(filesystemenc).encode(terminalenc))
|
|
691 |
print "UID 0x%08x" % uid3
|
|
692 |
print "Application name %s" % appname.encode(terminalenc)
|
|
693 |
print "Version %d.%d.%d" % (
|
|
694 |
version[0], version[1], version[2])
|
|
695 |
print "Language(s) %s" % ", ".join(lang)
|
|
696 |
print "Icon %s" % ((icon and
|
|
697 |
icon.decode(filesystemenc).encode(terminalenc)) or "<default>")
|
|
698 |
print "Short caption(s) %s" % ", ".join(
|
|
699 |
[s.encode(terminalenc) for s in shortcaption])
|
|
700 |
print "Long caption(s) %s" % ", ".join(
|
|
701 |
[s.encode(terminalenc) for s in caption])
|
|
702 |
print "Install drive %s" % ((drive == "!") and
|
|
703 |
"<any>" or drive)
|
|
704 |
print "Extras directory %s" % ((extrasdir and
|
|
705 |
extrasdir.decode(filesystemenc).encode(terminalenc)) or "<none>")
|
|
706 |
print "Text file(s) %s" % ((textfile and
|
|
707 |
textfile.decode(filesystemenc).encode(terminalenc)) or "<none>")
|
|
708 |
print "Certificate %s" % ((cert and
|
|
709 |
cert.decode(filesystemenc).encode(terminalenc)) or "<default>")
|
|
710 |
print "Private key %s" % ((privkey and
|
|
711 |
privkey.decode(filesystemenc).encode(terminalenc)) or "<default>")
|
|
712 |
print "Capabilities 0x%x (%s)" % (capmask, caps)
|
|
713 |
print "Vendor name(s) %s" % ", ".join(
|
|
714 |
[s.encode(terminalenc) for s in vendor])
|
|
715 |
print "Autostart on boot %s" % ((autostart and "Yes") or "No")
|
|
716 |
print "Run after install %s" % ((runinstall and "Yes") or "No")
|
|
717 |
print "Heap size in bytes %d, %d" % (heapsizemin, heapsizemax)
|
|
718 |
print "Profile %s" % profile
|
|
719 |
print "Mode %s" % mode
|
|
720 |
print "Extra Modules %s" % extra_modules_map
|
|
721 |
print
|
|
722 |
|
|
723 |
# Generate SimpleSISWriter object.
|
|
724 |
sw = sisfile.SimpleSISWriter(lang, caption, uid3, version,
|
|
725 |
vendor[0], vendor)
|
|
726 |
|
|
727 |
# Add text file or files to the SIS object. Text dialog is
|
|
728 |
# supposed to be displayed before anything else is installed.
|
|
729 |
if len(texts) == 1:
|
|
730 |
sw.addfile(texts[0], operation = sisfield.EOpText)
|
|
731 |
elif len(texts) > 1:
|
|
732 |
sw.addlangdepfile(texts, operation = sisfield.EOpText)
|
|
733 |
|
|
734 |
# Generate "Python for S60" resource file.
|
|
735 |
rsctarget = u"%s:\\resource\\apps\\%s_0x%08x.rsc" % (drive, appname, uid3)
|
|
736 |
string = zlib.decompress(pythons60rscdata.decode("base-64"))
|
|
737 |
sw.addfile(string, rsctarget)
|
|
738 |
del string
|
|
739 |
|
|
740 |
# Generate application registration resource file.
|
|
741 |
regtarget = u"%s:\\private\\10003a3f\\import\\apps\\%s_0x%08x_reg.rsc" % (
|
|
742 |
drive, appname, uid3)
|
|
743 |
exename = u"%s_0x%08x" % (appname, uid3)
|
|
744 |
locpath = u"\\resource\\apps\\%s_0x%08x_loc" % (appname, uid3)
|
|
745 |
rw = rscfile.RSCWriter(uid2 = 0x101f8021, uid3 = uid3)
|
|
746 |
# STRUCT APP_REGISTRATION_INFO from appinfo.rh
|
|
747 |
res = rscfile.Resource(["LONG", "LLINK", "LTEXT", "LONG", "LTEXT", "LONG",
|
|
748 |
"BYTE", "BYTE", "BYTE", "BYTE", "LTEXT", "BYTE",
|
|
749 |
"WORD", "WORD", "WORD", "LLINK"],
|
|
750 |
0, 0, exename, 0, locpath, 1,
|
|
751 |
0, 0, 0, 0, "", 0,
|
|
752 |
0, 0, 0, 0)
|
|
753 |
rw.addresource(res)
|
|
754 |
string = rw.tostring()
|
|
755 |
del rw
|
|
756 |
sw.addfile(string, regtarget)
|
|
757 |
del string
|
|
758 |
|
|
759 |
# EXE target name
|
|
760 |
exetarget = u"%s:\\sys\\bin\\%s_0x%08x.exe" % (drive, appname, uid3)
|
|
761 |
|
|
762 |
# Generate autostart registration resource file, if requested.
|
|
763 |
if autostart:
|
|
764 |
autotarget = u"%s:\\private\\101f875a\\import\\[%08x].rsc" % (
|
|
765 |
drive, uid3)
|
|
766 |
rw = rscfile.RSCWriter(uid2 = 0, offset = " ")
|
|
767 |
# STRUCT STARTUP_ITEM_INFO from startupitem.rh
|
|
768 |
res = rscfile.Resource(["BYTE", "LTEXT", "WORD",
|
|
769 |
"LONG", "BYTE", "BYTE"],
|
|
770 |
0, exetarget, 0, 0, 0, 0)
|
|
771 |
rw.addresource(res)
|
|
772 |
string = rw.tostring()
|
|
773 |
del rw
|
|
774 |
sw.addfile(string, autotarget)
|
|
775 |
del string
|
|
776 |
|
|
777 |
# Generate localisable icon/caption definition resource files.
|
|
778 |
iconpath = "\\resource\\apps\\%s_0x%08x_aif.mif" % (appname, uid3)
|
|
779 |
for n in xrange(numlang):
|
|
780 |
loctarget = u"%s:\\resource\\apps\\%s_0x%08x_loc.r%02d" % (
|
|
781 |
drive, appname, uid3, symbianutil.langidtonum[lang[n]])
|
|
782 |
rw = rscfile.RSCWriter(uid2 = 0, offset = " ")
|
|
783 |
# STRUCT LOCALISABLE_APP_INFO from appinfo.rh
|
|
784 |
res = rscfile.Resource(["LONG", "LLINK", "LTEXT",
|
|
785 |
"LONG", "LLINK", "LTEXT",
|
|
786 |
"WORD", "LTEXT", "WORD", "LTEXT"],
|
|
787 |
0, 0, shortcaption[n],
|
|
788 |
0, 0, caption[n],
|
|
789 |
1, iconpath, 0, "")
|
|
790 |
rw.addresource(res)
|
|
791 |
string = rw.tostring()
|
|
792 |
del rw
|
|
793 |
sw.addfile(string, loctarget)
|
|
794 |
del string
|
|
795 |
|
|
796 |
# Generate MIF file for icon.
|
|
797 |
icontarget = "%s:\\resource\\apps\\%s_0x%08x_aif.mif" % (
|
|
798 |
drive, appname, uid3)
|
|
799 |
mw = miffile.MIFWriter()
|
|
800 |
mw.addfile(icondata)
|
|
801 |
del icondata
|
|
802 |
string = mw.tostring()
|
|
803 |
del mw
|
|
804 |
sw.addfile(string, icontarget)
|
|
805 |
del string
|
|
806 |
if profile == 's60ui':
|
|
807 |
target = "launcher.py"
|
|
808 |
string = open(os.path.join("templates", target), "r").read()
|
|
809 |
sw.addfile(string, "%s:\\private\\%08x\\%s" % (drive, uid3, target))
|
|
810 |
del string
|
|
811 |
|
|
812 |
# Add files to SIS object.
|
|
813 |
if len(srcfiles) == 1:
|
|
814 |
# Read file.
|
|
815 |
f = file(os.path.join(srcdir, srcfiles[0]), "rb")
|
|
816 |
string = f.read(MAXOTHERFILESIZE + 1)
|
|
817 |
f.close()
|
|
818 |
|
|
819 |
if len(string) > MAXOTHERFILESIZE:
|
|
820 |
raise ValueError("%s: input file too large" % srcfiles[0])
|
|
821 |
|
|
822 |
# Add file to the SIS object. One file only, rename it to default.py.
|
|
823 |
target = "default.py"
|
|
824 |
sw.addfile(string, "%s:\\private\\%08x\\%s" % (drive, uid3, target))
|
|
825 |
del string
|
|
826 |
else:
|
|
827 |
if extrasdir != None:
|
|
828 |
sysbinprefix = os.path.join(extrasdir, "sys", "bin", "")
|
|
829 |
else:
|
|
830 |
sysbinprefix = os.path.join(os.sep, "sys", "bin", "")
|
|
831 |
|
|
832 |
white_list = {}
|
|
833 |
# More than one file, use original path names.
|
|
834 |
for srcfile in srcfiles:
|
|
835 |
# Read file.
|
|
836 |
f = file(os.path.join(srcdir, srcfile), "rb")
|
|
837 |
string = f.read(MAXOTHERFILESIZE + 1)
|
|
838 |
f.close()
|
|
839 |
|
|
840 |
if len(string) > MAXOTHERFILESIZE:
|
|
841 |
raise ValueError("%s: input file too large" % srcfile)
|
|
842 |
|
|
843 |
# Split path into components.
|
|
844 |
srcpathcomp = srcfile.split(os.sep)
|
|
845 |
|
|
846 |
# Check if the file is an E32Image (EXE or DLL).
|
|
847 |
filecapmask = symbianutil.e32imagecaps(string)
|
|
848 |
|
|
849 |
# Warn against common mistakes when dealing with E32Image files.
|
|
850 |
if filecapmask != None:
|
|
851 |
if not srcfile.startswith(sysbinprefix):
|
|
852 |
# Warn against E32Image files outside /sys/bin.
|
|
853 |
print ("%s: warning: %s is an E32Image (EXE or DLL) "
|
|
854 |
"outside %s" % (pgmname, srcfile, sysbinprefix))
|
|
855 |
if filecapmask != capmask:
|
|
856 |
# Warn capas of dll not equal to exe. Warn before
|
|
857 |
# usind the exe capas
|
|
858 |
module_repo.debug_print("%s: warning: Capas of %s is not "
|
|
859 |
"equal to that of the exe. Capas of the exe will be"
|
|
860 |
" assigned to this pyd" % (pgmname, srcfile))
|
|
861 |
string = symbianutil.e32imagecrc(string,
|
|
862 |
capabilities=capmask)
|
|
863 |
filecapmask = capmask
|
|
864 |
|
|
865 |
srcfile_name = srcpathcomp.pop()
|
|
866 |
try:
|
|
867 |
file_name, file_ext = srcfile_name.split(".")
|
|
868 |
except:
|
|
869 |
file_ext = ""
|
|
870 |
# Modify the file name if the file is a pyd and update the
|
|
871 |
# white list with the new name.
|
|
872 |
if srcfile.startswith(sysbinprefix) and file_ext == "pyd":
|
|
873 |
srcfile_name = "%s_%08x.%s" % (file_name, uid3, file_ext)
|
|
874 |
if not srcfile_name.startswith("${{PREFIX}}"):
|
|
875 |
raise RuntimeError(
|
|
876 |
'PYD is not prefixed with "${{PREFIX}}"')
|
|
877 |
module_name = file_name.split("${{PREFIX}}")[1]
|
|
878 |
white_list[module_name] = srcfile_name.split(".pyd")[0]
|
|
879 |
srcpathcomp.append(srcfile_name)
|
|
880 |
|
|
881 |
targetpathcomp = [s.decode(filesystemenc) for s in srcpathcomp]
|
|
882 |
|
|
883 |
# Handle the extras directory.
|
|
884 |
if extrasdir != None and extrasdir == srcpathcomp[0]:
|
|
885 |
# Path is rooted at the drive root.
|
|
886 |
targetfile = \
|
|
887 |
u"%s:\\%s" % (drive, "\\".join(targetpathcomp[1:]))
|
|
888 |
else:
|
|
889 |
# Path is rooted at the application private directory.
|
|
890 |
targetfile = u"%s:\\private\\%08x\\%s" % (
|
|
891 |
drive, uid3, "\\".join(targetpathcomp))
|
|
892 |
|
|
893 |
# Add file to the SIS object.
|
|
894 |
sw.addfile(string, targetfile, capabilities = filecapmask)
|
|
895 |
del string
|
|
896 |
|
|
897 |
# Write the white list to a file and package it
|
|
898 |
if white_list != {}:
|
|
899 |
whitelist_file = os.path.join(srcdir, "white-list.cfg")
|
|
900 |
whitelist_f = open(whitelist_file, "wt")
|
|
901 |
whitelist_f.write(repr(white_list))
|
|
902 |
whitelist_f.close()
|
|
903 |
|
|
904 |
string = open(whitelist_file, "rb").read()
|
|
905 |
sw.addfile(string, "%s:\\private\\%08x\\%s" % \
|
|
906 |
(drive, uid3, os.path.basename(whitelist_file)))
|
|
907 |
del string
|
|
908 |
os.remove(whitelist_file)
|
|
909 |
|
|
910 |
${{if INCLUDE_INTERNAL_SRC
|
|
911 |
# Package iad client dll
|
|
912 |
string = \
|
|
913 |
open(os.path.join("templates", "Py_iad_client.dll"), "rb").read()
|
|
914 |
string = symbianutil.e32imagecrc(string, capabilities=capmask)
|
|
915 |
sw.addfile(
|
|
916 |
string, "%s:\\sys\\bin\\${{PREFIX}}Py_iad_client_0x%08x.dll" % \
|
|
917 |
(drive, uid3), None, capabilities = capmask)
|
|
918 |
del string
|
|
919 |
}}
|
|
920 |
|
|
921 |
# Add target device dependency.
|
|
922 |
platform_uids = platform_uid.split(",")
|
|
923 |
for platform_uid in platform_uids:
|
|
924 |
sw.addtargetdevice(eval(platform_uid), (0, 0, 0), None,
|
|
925 |
["Series60ProductID"] * numlang)
|
|
926 |
|
|
927 |
# Add Python runtime as dependency
|
|
928 |
sw.adddependency(${{PYS60_UID_CORE}}L, (${{PYS60_VERSION_MAJOR}},
|
|
929 |
${{PYS60_VERSION_MINOR}}, ${{PYS60_VERSION_MICRO}}),
|
|
930 |
None, ["Python runtime"] * numlang)
|
|
931 |
|
|
932 |
# Add certificate.
|
|
933 |
sw.addcertificate(privkeydata, certdata, passphrase)
|
|
934 |
|
|
935 |
# Generate an EXE stub and add it to the SIS object.
|
|
936 |
string = ""
|
|
937 |
if profile == 's60ui':
|
|
938 |
string = \
|
|
939 |
open(os.path.join("templates", "python_ui.exe"), "rb").read()
|
|
940 |
else:
|
|
941 |
string = open(os.path.join("templates", "python_console.exe"),
|
|
942 |
"rb").read()
|
|
943 |
|
|
944 |
string = symbianutil.e32imagecrc(string, uid3, uid3, None,
|
|
945 |
heapsizemin, heapsizemax, capmask)
|
|
946 |
|
|
947 |
if runinstall:
|
|
948 |
# To avoid running without dependencies, this has to be in the end.
|
|
949 |
sw.addfile(string, exetarget, None, capabilities = capmask,
|
|
950 |
operation = sisfield.EOpRun,
|
|
951 |
options = sisfield.EInstFileRunOptionInstall)
|
|
952 |
else:
|
|
953 |
sw.addfile(string, exetarget, None, capabilities = capmask)
|
|
954 |
|
|
955 |
del string
|
|
956 |
|
|
957 |
# Generate SIS file out of the SimpleSISWriter object.
|
|
958 |
sw.tofile(outfile)
|
|
959 |
except:
|
|
960 |
raise
|
|
961 |
finally:
|
|
962 |
if os.path.exists(appdir):
|
|
963 |
shutil.rmtree(appdir)
|
|
964 |
|
|
965 |
if os.path.exists(os.path.join(src, "lib.zip")):
|
|
966 |
os.remove(os.path.join(src, "lib.zip"))
|
|
967 |
module_repo.debug_log.close()
|
|
968 |
|
|
969 |
##############################################################################
|
|
970 |
# Module-level functions which are normally only used by this module
|
|
971 |
##############################################################################
|
|
972 |
|
|
973 |
def scandefaults(filename):
|
|
974 |
'''Scan a Python source file for application version string and UID3.'''
|
|
975 |
|
|
976 |
version = None
|
|
977 |
uid3 = None
|
|
978 |
|
|
979 |
# Regular expression for the version string. Version may optionally
|
|
980 |
# be enclosed in double or single quotes.
|
|
981 |
version_ro = re.compile(r'SIS_VERSION\s*=\s*(?:(?:"([^"]*)")|'
|
|
982 |
r"(?:'([^']*)')|(\S+))")
|
|
983 |
|
|
984 |
# Original py2is uses a regular expression
|
|
985 |
# r"SYMBIAN_UID\s*=\s*(0x[0-9a-fA-F]{8})".
|
|
986 |
# This version is a bit more lenient.
|
|
987 |
uid3_ro = re.compile(r"SYMBIAN_UID\s*=\s*(\S+)")
|
|
988 |
|
|
989 |
# First match of each regular expression is used.
|
|
990 |
f = file(filename, "rb")
|
|
991 |
try:
|
|
992 |
while version == None or uid3 == None:
|
|
993 |
line = f.readline()
|
|
994 |
if line == "":
|
|
995 |
break
|
|
996 |
if version == None:
|
|
997 |
mo = version_ro.search(line)
|
|
998 |
if mo:
|
|
999 |
# Get first group that matched in the regular expression.
|
|
1000 |
version = filter(None, mo.groups())[0]
|
|
1001 |
if uid3 == None:
|
|
1002 |
mo = uid3_ro.search(line)
|
|
1003 |
if mo:
|
|
1004 |
uid3 = mo.group(1)
|
|
1005 |
finally:
|
|
1006 |
f.close()
|
|
1007 |
return version, uid3
|
|
1008 |
|
|
1009 |
def parseversion(version):
|
|
1010 |
'''Parse a version string: "v1.2.3" or similar.
|
|
1011 |
|
|
1012 |
Initial "v" can optionally be a capital "V" or omitted altogether. Minor
|
|
1013 |
and build numbers can also be omitted. Separator can be a comma or a
|
|
1014 |
period.'''
|
|
1015 |
|
|
1016 |
version = version.strip().lower()
|
|
1017 |
|
|
1018 |
# Strip initial "v" or "V".
|
|
1019 |
if version[0] == "v":
|
|
1020 |
version = version[1:]
|
|
1021 |
|
|
1022 |
if "." in version:
|
|
1023 |
parts = [int(n) for n in version.split(".")]
|
|
1024 |
else:
|
|
1025 |
parts = [int(n) for n in version.split(",")]
|
|
1026 |
|
|
1027 |
# Allow missing minor and build numbers.
|
|
1028 |
parts.extend([0, 0])
|
|
1029 |
|
|
1030 |
return parts[0:3]
|
|
1031 |
|
|
1032 |
def readtextfiles(pattern, languages):
|
|
1033 |
'''Read language dependent text files.
|
|
1034 |
|
|
1035 |
Files are assumed to be in UTF-8 encoding and re-encoded
|
|
1036 |
in UCS-2 (UTF-16LE) for Symbian OS to display during installation.'''
|
|
1037 |
|
|
1038 |
if "%" not in pattern:
|
|
1039 |
# Only one file, read it.
|
|
1040 |
filenames = [pattern]
|
|
1041 |
else:
|
|
1042 |
filenames = []
|
|
1043 |
for langid in languages:
|
|
1044 |
langnum = symbianutil.langidtonum[langid]
|
|
1045 |
langname = symbianutil.langnumtoname[langnum]
|
|
1046 |
|
|
1047 |
# Replace formatting characters in file name pattern.
|
|
1048 |
filename = pattern
|
|
1049 |
filename = filename.replace("%n", "%02d" % langnum)
|
|
1050 |
filename = filename.replace("%c", langid.lower())
|
|
1051 |
filename = filename.replace("%C", langid.upper())
|
|
1052 |
filename = filename.replace("%l", langname.lower())
|
|
1053 |
filename = filename.replace("%L", langname)
|
|
1054 |
filename = filename.replace("%%", "%")
|
|
1055 |
|
|
1056 |
filenames.append(filename)
|
|
1057 |
|
|
1058 |
texts = []
|
|
1059 |
|
|
1060 |
for filename in filenames:
|
|
1061 |
f = file(filename, "r") # Read as text.
|
|
1062 |
text = f.read(MAXTEXTFILELENGTH + 1)
|
|
1063 |
f.close()
|
|
1064 |
|
|
1065 |
if len(text) > MAXTEXTFILELENGTH:
|
|
1066 |
raise ValueError("%s: text file too large" % filename)
|
|
1067 |
|
|
1068 |
texts.append(text.decode("UTF-8").encode("UTF-16LE"))
|
|
1069 |
|
|
1070 |
return texts
|
|
1071 |
|
|
1072 |
##############################################################################
|
|
1073 |
# Embedded data: Icon and application resource
|
|
1074 |
##############################################################################
|
|
1075 |
|
|
1076 |
# Python logo as a base-64-encoded, zlib-compressed SVG XML data
|
|
1077 |
defaulticondata = '''
|
|
1078 |
eJyFVF1v20YQfC/Q/3Bl0be74+3tfQZRA1h24gJJa6COij66EiMSdSVDUiW3vz5zRyp2CgMVLHrJ
|
|
1079 |
/ZjZmRNfv3n8614cu91+2G5mDWnTiG6z3K6GzXrWfLx9q1Lz5sdvv3n93eUv89vfb67E/rgWNx8v
|
|
1080 |
3v80F41q29943raXt5fi18U7QZrE7bD5p22vfm5E0x8OD6/a9nQ66RPr7W7dvtvdPfTDct+iukV1
|
|
1081 |
6WwxkUgd0KdXh1VT0ArIH3f77ma3/TTcd7OmZJvnPKkRYL7Zv9qD6wO+szPc+YHeb//eLbtPwO30
|
|
1082 |
pjuMWFNSmYo1zpi9wNQaYwqzM8zj/bD586VCyjm3NduI07A69GBnzA+N6Lth3R/Od8ehO11sH2eN
|
|
1083 |
EUYEh7+66LpcHu4OvcCe97Pmew4hZ9eI1az5QEln5yVZ7YxfGsXaJyeNzoGU156dDNqGpIJ2gZes
|
|
1084 |
g2HsFZhk0tZaxJFKt8cTI4RAiTOMAT7Y2pola5PjFNcxC+u05aVBxmG01TERMkzqXMR0bb22ZJcK
|
|
1085 |
pd5Ko6JOHNERbJjiqKP1R69DdD3K2OSCjw2CwwZgH0PA8GAL++DLlbGjIm2NRUN2CMlTGVd2Vlgj
|
|
1086 |
EETQOSfkyUaJa5oaZUHlMe6djoavmbRLR0zxsWBfj2IuRjHffyXtv037Xxuu4oVnM9rgnDZkpTfa
|
|
1087 |
ulSVgQ1YhYik15zTkzRlBcC7LElz8CpBaliATYJ6bgS6mbwqVgY1mmh15qzOhmLSgpN21lbfnbHS
|
|
1088 |
FmFLir7YztSPU5fQIkKmqkMsMpswxUVAeyznhQKkwekaT0JwCfXgHyJGR5qmTlvAB89QOOdCH/bh
|
|
1089 |
SEVbECYjilOoZh1jqka6sVM9m9KPN5MVxYkE7InyYtTzBe3f1s+ovbXaRKhJQIASVLbHS8plYDJw
|
|
1090 |
cPVjWChnD4K4q/obn6a45svWpu7iVUm6+pjVUwnPLUy1SRLrnFieseGM9fI5k/8hzQFuZOkBwzyy
|
|
1091 |
xhGtoJWre6LtKu080q41YYxq8olzrpypPo7qS0Wcc8TPFYcTZ0SecctKVn7FYmLc1vdNea/h/2dD
|
|
1092 |
N2YO'''
|
|
1093 |
|
|
1094 |
# "Python for S60" compiled resource as a base-64-encoded, zlib-compressed data
|
|
1095 |
pythons60rscdata = '''
|
|
1096 |
eJzL9pIXYACCVnFWhouea4oYtRk4QHwWIGYMqAgEs6E0CEgxnOGAsRnYGRlYgXKcnK4VJal5xZn5
|
|
1097 |
eYJg8f9AwDDkgQSDAhDaMCQypDPkMhQzVDLUM7QydDNMZJjOMJdhMcNKhvUMWxl2MxxkOM5wluEy
|
|
1098 |
w02G+wxPGV4zfGT4zvCXgZmRk5GfUZRRmhEAjnEjdg=='''
|