src/tools/py2sis/ensymble/cmd_py2sis.py.in
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     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=='''