michael@0: #!/usr/bin/env python michael@0: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: # You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: # Min version of python is 2.7 michael@0: import sys michael@0: if ((sys.version_info.major != 2) or (sys.version_info.minor < 7)): michael@0: raise Exception("You need to use Python version 2.7 or higher") michael@0: michael@0: import os, shutil, re, zipfile michael@0: from ConfigParser import SafeConfigParser michael@0: michael@0: # Platform-specific support michael@0: # see https://developer.mozilla.org/en/XULRunner/Deploying_XULRunner_1.8 michael@0: if sys.platform.startswith('linux') or sys.platform == "win32": michael@0: def installApp(appLocation, installDir, appName, greDir): michael@0: zipApp, iniParser, appName = validateArguments(appLocation, installDir, appName, greDir) michael@0: if (zipApp): michael@0: zipApp.extractAll(installDir) michael@0: else: michael@0: shutil.copytree(appLocation, installDir) michael@0: shutil.copy2(os.path.join(greDir, xulrunnerStubName), michael@0: os.path.join(installDir, appName)) michael@0: copyGRE(greDir, os.path.join(installDir, "xulrunner")) michael@0: michael@0: if sys.platform.startswith('linux'): michael@0: xulrunnerStubName = "xulrunner-stub" michael@0: michael@0: def makeAppName(leafName): michael@0: return leafName.lower() michael@0: michael@0: elif sys.platform == "win32": michael@0: xulrunnerStubName = "xulrunner-stub.exe" michael@0: michael@0: def makeAppName(leafName): michael@0: return leafName + ".exe" michael@0: michael@0: elif sys.platform == "darwin": michael@0: xulrunnerStubName = "xulrunner" michael@0: michael@0: def installApp(appLocation, installDir, appName, greDir): michael@0: zipApp, iniparser, appName = validateArguments(appLocation, installDir, appName, greDir) michael@0: installDir += "/" + appName + ".app" michael@0: resourcesDir = os.path.join(installDir, "Contents/Resources") michael@0: if (zipApp): michael@0: zipApp.extractAll(resourcesDir) michael@0: else: michael@0: shutil.copytree(appLocation, resourcesDir) michael@0: MacOSDir = os.path.join(installDir, "Contents/MacOS") michael@0: os.makedirs(MacOSDir) michael@0: shutil.copy2(os.path.join(greDir, xulrunnerStubName), MacOSDir) michael@0: copyGRE(greDir, michael@0: os.path.join(installDir, "Contents/Frameworks/XUL.framework")) michael@0: michael@0: # Contents/Info.plist michael@0: contents = """ michael@0: michael@0: michael@0: michael@0: CFBundleInfoDictionaryVersion michael@0: 6.0 michael@0: CFBundlePackageType michael@0: APPL michael@0: CFBundleSignature michael@0: ???? michael@0: CFBundleExecutable michael@0: xulrunner michael@0: NSAppleScriptEnabled michael@0: michael@0: CFBundleGetInfoString michael@0: $infoString michael@0: CFBundleName michael@0: $appName michael@0: CFBundleShortVersionString michael@0: $version michael@0: CFBundleVersion michael@0: $version.$buildID michael@0: CFBundleIdentifier michael@0: $reverseVendor michael@0: michael@0: michael@0: """ michael@0: version = iniparser.get("App", "Version") michael@0: buildID = iniparser.get("App", "BuildID") michael@0: infoString = appName + " " + version michael@0: reverseVendor = "com.vendor.unknown" michael@0: appID = iniparser.get("App", "ID") michael@0: colonIndex = appID.find("@") + 1 michael@0: if (colonIndex != 0): michael@0: vendor = appID[colonIndex:] michael@0: reverseVendor = ".".join(vendor.split(".")[::-1]) michael@0: contents = contents.replace("$infoString", infoString) michael@0: contents = contents.replace("$appName", appName) michael@0: contents = contents.replace("$version", version) michael@0: contents = contents.replace("$buildID", buildID) michael@0: contents = contents.replace("$reverseVendor", reverseVendor) michael@0: infoPList = open(os.path.join(installDir, "Contents/Info.plist"), "w+b") michael@0: infoPList.write(contents) michael@0: infoPList.close() michael@0: michael@0: def makeAppName(leafName): michael@0: return leafName michael@0: michael@0: else: michael@0: # Implement xulrunnerStubName, installApp and makeAppName as above. michael@0: raise Exception("This operating system isn't supported for install_app.py yet!") michael@0: # End platform-specific support michael@0: michael@0: def resolvePath(path): michael@0: return os.path.realpath(path) michael@0: michael@0: def requireINIOption(iniparser, section, option): michael@0: if not (iniparser.has_option(section, option)): michael@0: raise Exception("application.ini must have a " + option + " option under the " + section + " section") michael@0: michael@0: def checkAppINI(appLocation): michael@0: if (os.path.isdir(appLocation)): michael@0: zipApp = None michael@0: appINIPath = os.path.join(appLocation, "application.ini") michael@0: if not (os.path.isfile(appINIPath)): michael@0: raise Exception(appINIPath + " does not exist") michael@0: appINI = open(appINIPath) michael@0: elif (zipfile.is_zipfile(appLocation)): michael@0: zipApp = zipfile.ZipFile(appLocation) michael@0: if not ("application.ini" in zipApp.namelist()): michael@0: raise Exception("jar:" + appLocation + "!/application.ini does not exist") michael@0: appINI = zipApp.open("application.ini") michael@0: else: michael@0: raise Exception("appLocation must be a directory containing application.ini or a zip file with application.ini at its root") michael@0: michael@0: # application.ini verification michael@0: iniparser = SafeConfigParser() michael@0: iniparser.readfp(appINI) michael@0: if not (iniparser.has_section("App")): michael@0: raise Exception("application.ini must have an App section") michael@0: if not (iniparser.has_section("Gecko")): michael@0: raise Exception("application.ini must have a Gecko section") michael@0: requireINIOption(iniparser, "App", "Name") michael@0: requireINIOption(iniparser, "App", "Version") michael@0: requireINIOption(iniparser, "App", "BuildID") michael@0: requireINIOption(iniparser, "App", "ID") michael@0: requireINIOption(iniparser, "Gecko", "MinVersion") michael@0: michael@0: return zipApp, iniparser michael@0: pass michael@0: michael@0: def copyGRE(greDir, targetDir): michael@0: shutil.copytree(greDir, targetDir, symlinks=True) michael@0: michael@0: def validateArguments(appLocation, installDir, appName, greDir): michael@0: # application directory / zip verification michael@0: appLocation = resolvePath(appLocation) michael@0: michael@0: # target directory michael@0: installDir = resolvePath(installDir) michael@0: michael@0: if (os.path.exists(installDir)): michael@0: raise Exception("installDir must not exist: " + cmds.installDir) michael@0: michael@0: greDir = resolvePath(greDir) michael@0: xulrunnerStubPath = os.path.isfile(os.path.join(greDir, xulrunnerStubName)) michael@0: if not xulrunnerStubPath: michael@0: raise Exception("XULRunner stub executable not found: " + os.path.join(greDir, xulrunnerStubName)) michael@0: michael@0: # appName michael@0: zipApp, iniparser = checkAppINI(appLocation) michael@0: if not appName: michael@0: appName = iniparser.get("App", "Name") michael@0: appName = makeAppName(appName) michael@0: pattern = re.compile("[\\\/\:*?\"<>|\x00]") michael@0: if pattern.search(appName): michael@0: raise Exception("App name has illegal characters for at least one operating system") michael@0: return zipApp, iniparser, appName michael@0: michael@0: def handleCommandLine(): michael@0: import argparse michael@0: michael@0: # Argument parsing. michael@0: parser = argparse.ArgumentParser( michael@0: description="XULRunner application installer", michael@0: usage="""install_app.py appLocation installDir greDir [--appName APPNAME] michael@0: install_app.py -h michael@0: install_app.py --version michael@0: """ michael@0: ) michael@0: parser.add_argument( michael@0: "appLocation", michael@0: action="store", michael@0: help="The directory or ZIP file containing application.ini as a top-level child file" michael@0: ) michael@0: parser.add_argument( michael@0: "installDir", michael@0: action="store", michael@0: help="The directory to install the application to" michael@0: ) michael@0: parser.add_argument( michael@0: "--greDir", michael@0: action="store", michael@0: help="The directory containing the Gecko SDK (usually where this Python script lives)", michael@0: default=os.path.dirname(sys.argv[0]) michael@0: ) michael@0: parser.add_argument( michael@0: "--appName", michael@0: action="store", michael@0: help="The name of the application to install" michael@0: ) michael@0: parser.add_argument("--version", action="version", version="%(prog)s 1.0") michael@0: michael@0: # The command code. michael@0: cmds = parser.parse_args() michael@0: try: michael@0: installApp(cmds.appLocation, cmds.installDir, cmds.appName, cmds.greDir) michael@0: except exn: michael@0: shutil.rmtree(cmds.installDir) michael@0: raise exn michael@0: print cmds.appName + " application installed to " + cmds.installDir michael@0: michael@0: if __name__ == '__main__': michael@0: handleCommandLine()