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