1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xulrunner/app/install_app.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,221 @@ 1.4 +#!/usr/bin/env python 1.5 + 1.6 +# This Source Code Form is subject to the terms of the Mozilla Public 1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 +# You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + 1.10 +# Min version of python is 2.7 1.11 +import sys 1.12 +if ((sys.version_info.major != 2) or (sys.version_info.minor < 7)): 1.13 + raise Exception("You need to use Python version 2.7 or higher") 1.14 + 1.15 +import os, shutil, re, zipfile 1.16 +from ConfigParser import SafeConfigParser 1.17 + 1.18 +# Platform-specific support 1.19 +# see https://developer.mozilla.org/en/XULRunner/Deploying_XULRunner_1.8 1.20 +if sys.platform.startswith('linux') or sys.platform == "win32": 1.21 + def installApp(appLocation, installDir, appName, greDir): 1.22 + zipApp, iniParser, appName = validateArguments(appLocation, installDir, appName, greDir) 1.23 + if (zipApp): 1.24 + zipApp.extractAll(installDir) 1.25 + else: 1.26 + shutil.copytree(appLocation, installDir) 1.27 + shutil.copy2(os.path.join(greDir, xulrunnerStubName), 1.28 + os.path.join(installDir, appName)) 1.29 + copyGRE(greDir, os.path.join(installDir, "xulrunner")) 1.30 + 1.31 +if sys.platform.startswith('linux'): 1.32 + xulrunnerStubName = "xulrunner-stub" 1.33 + 1.34 + def makeAppName(leafName): 1.35 + return leafName.lower() 1.36 + 1.37 +elif sys.platform == "win32": 1.38 + xulrunnerStubName = "xulrunner-stub.exe" 1.39 + 1.40 + def makeAppName(leafName): 1.41 + return leafName + ".exe" 1.42 + 1.43 +elif sys.platform == "darwin": 1.44 + xulrunnerStubName = "xulrunner" 1.45 + 1.46 + def installApp(appLocation, installDir, appName, greDir): 1.47 + zipApp, iniparser, appName = validateArguments(appLocation, installDir, appName, greDir) 1.48 + installDir += "/" + appName + ".app" 1.49 + resourcesDir = os.path.join(installDir, "Contents/Resources") 1.50 + if (zipApp): 1.51 + zipApp.extractAll(resourcesDir) 1.52 + else: 1.53 + shutil.copytree(appLocation, resourcesDir) 1.54 + MacOSDir = os.path.join(installDir, "Contents/MacOS") 1.55 + os.makedirs(MacOSDir) 1.56 + shutil.copy2(os.path.join(greDir, xulrunnerStubName), MacOSDir) 1.57 + copyGRE(greDir, 1.58 + os.path.join(installDir, "Contents/Frameworks/XUL.framework")) 1.59 + 1.60 + # Contents/Info.plist 1.61 + contents = """<?xml version="1.0" encoding="UTF-8"?> 1.62 +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 1.63 +<plist version="1.0"> 1.64 +<dict> 1.65 +<key>CFBundleInfoDictionaryVersion</key> 1.66 +<string>6.0</string> 1.67 +<key>CFBundlePackageType</key> 1.68 +<string>APPL</string> 1.69 +<key>CFBundleSignature</key> 1.70 +<string>????</string> 1.71 +<key>CFBundleExecutable</key> 1.72 +<string>xulrunner</string> 1.73 +<key>NSAppleScriptEnabled</key> 1.74 +<true/> 1.75 +<key>CFBundleGetInfoString</key> 1.76 +<string>$infoString</string> 1.77 +<key>CFBundleName</key> 1.78 +<string>$appName</string> 1.79 +<key>CFBundleShortVersionString</key> 1.80 +<string>$version</string> 1.81 +<key>CFBundleVersion</key> 1.82 +<string>$version.$buildID</string> 1.83 +<key>CFBundleIdentifier</key> 1.84 +<string>$reverseVendor</string> 1.85 +</dict> 1.86 +</plist> 1.87 +""" 1.88 + version = iniparser.get("App", "Version") 1.89 + buildID = iniparser.get("App", "BuildID") 1.90 + infoString = appName + " " + version 1.91 + reverseVendor = "com.vendor.unknown" 1.92 + appID = iniparser.get("App", "ID") 1.93 + colonIndex = appID.find("@") + 1 1.94 + if (colonIndex != 0): 1.95 + vendor = appID[colonIndex:] 1.96 + reverseVendor = ".".join(vendor.split(".")[::-1]) 1.97 + contents = contents.replace("$infoString", infoString) 1.98 + contents = contents.replace("$appName", appName) 1.99 + contents = contents.replace("$version", version) 1.100 + contents = contents.replace("$buildID", buildID) 1.101 + contents = contents.replace("$reverseVendor", reverseVendor) 1.102 + infoPList = open(os.path.join(installDir, "Contents/Info.plist"), "w+b") 1.103 + infoPList.write(contents) 1.104 + infoPList.close() 1.105 + 1.106 + def makeAppName(leafName): 1.107 + return leafName 1.108 + 1.109 +else: 1.110 + # Implement xulrunnerStubName, installApp and makeAppName as above. 1.111 + raise Exception("This operating system isn't supported for install_app.py yet!") 1.112 +# End platform-specific support 1.113 + 1.114 +def resolvePath(path): 1.115 + return os.path.realpath(path) 1.116 + 1.117 +def requireINIOption(iniparser, section, option): 1.118 + if not (iniparser.has_option(section, option)): 1.119 + raise Exception("application.ini must have a " + option + " option under the " + section + " section") 1.120 + 1.121 +def checkAppINI(appLocation): 1.122 + if (os.path.isdir(appLocation)): 1.123 + zipApp = None 1.124 + appINIPath = os.path.join(appLocation, "application.ini") 1.125 + if not (os.path.isfile(appINIPath)): 1.126 + raise Exception(appINIPath + " does not exist") 1.127 + appINI = open(appINIPath) 1.128 + elif (zipfile.is_zipfile(appLocation)): 1.129 + zipApp = zipfile.ZipFile(appLocation) 1.130 + if not ("application.ini" in zipApp.namelist()): 1.131 + raise Exception("jar:" + appLocation + "!/application.ini does not exist") 1.132 + appINI = zipApp.open("application.ini") 1.133 + else: 1.134 + raise Exception("appLocation must be a directory containing application.ini or a zip file with application.ini at its root") 1.135 + 1.136 + # application.ini verification 1.137 + iniparser = SafeConfigParser() 1.138 + iniparser.readfp(appINI) 1.139 + if not (iniparser.has_section("App")): 1.140 + raise Exception("application.ini must have an App section") 1.141 + if not (iniparser.has_section("Gecko")): 1.142 + raise Exception("application.ini must have a Gecko section") 1.143 + requireINIOption(iniparser, "App", "Name") 1.144 + requireINIOption(iniparser, "App", "Version") 1.145 + requireINIOption(iniparser, "App", "BuildID") 1.146 + requireINIOption(iniparser, "App", "ID") 1.147 + requireINIOption(iniparser, "Gecko", "MinVersion") 1.148 + 1.149 + return zipApp, iniparser 1.150 + pass 1.151 + 1.152 +def copyGRE(greDir, targetDir): 1.153 + shutil.copytree(greDir, targetDir, symlinks=True) 1.154 + 1.155 +def validateArguments(appLocation, installDir, appName, greDir): 1.156 + # application directory / zip verification 1.157 + appLocation = resolvePath(appLocation) 1.158 + 1.159 + # target directory 1.160 + installDir = resolvePath(installDir) 1.161 + 1.162 + if (os.path.exists(installDir)): 1.163 + raise Exception("installDir must not exist: " + cmds.installDir) 1.164 + 1.165 + greDir = resolvePath(greDir) 1.166 + xulrunnerStubPath = os.path.isfile(os.path.join(greDir, xulrunnerStubName)) 1.167 + if not xulrunnerStubPath: 1.168 + raise Exception("XULRunner stub executable not found: " + os.path.join(greDir, xulrunnerStubName)) 1.169 + 1.170 + # appName 1.171 + zipApp, iniparser = checkAppINI(appLocation) 1.172 + if not appName: 1.173 + appName = iniparser.get("App", "Name") 1.174 + appName = makeAppName(appName) 1.175 + pattern = re.compile("[\\\/\:*?\"<>|\x00]") 1.176 + if pattern.search(appName): 1.177 + raise Exception("App name has illegal characters for at least one operating system") 1.178 + return zipApp, iniparser, appName 1.179 + 1.180 +def handleCommandLine(): 1.181 + import argparse 1.182 + 1.183 + # Argument parsing. 1.184 + parser = argparse.ArgumentParser( 1.185 + description="XULRunner application installer", 1.186 + usage="""install_app.py appLocation installDir greDir [--appName APPNAME] 1.187 + install_app.py -h 1.188 + install_app.py --version 1.189 + """ 1.190 + ) 1.191 + parser.add_argument( 1.192 + "appLocation", 1.193 + action="store", 1.194 + help="The directory or ZIP file containing application.ini as a top-level child file" 1.195 + ) 1.196 + parser.add_argument( 1.197 + "installDir", 1.198 + action="store", 1.199 + help="The directory to install the application to" 1.200 + ) 1.201 + parser.add_argument( 1.202 + "--greDir", 1.203 + action="store", 1.204 + help="The directory containing the Gecko SDK (usually where this Python script lives)", 1.205 + default=os.path.dirname(sys.argv[0]) 1.206 + ) 1.207 + parser.add_argument( 1.208 + "--appName", 1.209 + action="store", 1.210 + help="The name of the application to install" 1.211 + ) 1.212 + parser.add_argument("--version", action="version", version="%(prog)s 1.0") 1.213 + 1.214 + # The command code. 1.215 + cmds = parser.parse_args() 1.216 + try: 1.217 + installApp(cmds.appLocation, cmds.installDir, cmds.appName, cmds.greDir) 1.218 + except exn: 1.219 + shutil.rmtree(cmds.installDir) 1.220 + raise exn 1.221 + print cmds.appName + " application installed to " + cmds.installDir 1.222 + 1.223 +if __name__ == '__main__': 1.224 + handleCommandLine()