Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 #!/usr/bin/env python
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 # You can obtain one at http://mozilla.org/MPL/2.0/.
7 # Min version of python is 2.7
8 import sys
9 if ((sys.version_info.major != 2) or (sys.version_info.minor < 7)):
10 raise Exception("You need to use Python version 2.7 or higher")
12 import os, shutil, re, zipfile
13 from ConfigParser import SafeConfigParser
15 # Platform-specific support
16 # see https://developer.mozilla.org/en/XULRunner/Deploying_XULRunner_1.8
17 if sys.platform.startswith('linux') or sys.platform == "win32":
18 def installApp(appLocation, installDir, appName, greDir):
19 zipApp, iniParser, appName = validateArguments(appLocation, installDir, appName, greDir)
20 if (zipApp):
21 zipApp.extractAll(installDir)
22 else:
23 shutil.copytree(appLocation, installDir)
24 shutil.copy2(os.path.join(greDir, xulrunnerStubName),
25 os.path.join(installDir, appName))
26 copyGRE(greDir, os.path.join(installDir, "xulrunner"))
28 if sys.platform.startswith('linux'):
29 xulrunnerStubName = "xulrunner-stub"
31 def makeAppName(leafName):
32 return leafName.lower()
34 elif sys.platform == "win32":
35 xulrunnerStubName = "xulrunner-stub.exe"
37 def makeAppName(leafName):
38 return leafName + ".exe"
40 elif sys.platform == "darwin":
41 xulrunnerStubName = "xulrunner"
43 def installApp(appLocation, installDir, appName, greDir):
44 zipApp, iniparser, appName = validateArguments(appLocation, installDir, appName, greDir)
45 installDir += "/" + appName + ".app"
46 resourcesDir = os.path.join(installDir, "Contents/Resources")
47 if (zipApp):
48 zipApp.extractAll(resourcesDir)
49 else:
50 shutil.copytree(appLocation, resourcesDir)
51 MacOSDir = os.path.join(installDir, "Contents/MacOS")
52 os.makedirs(MacOSDir)
53 shutil.copy2(os.path.join(greDir, xulrunnerStubName), MacOSDir)
54 copyGRE(greDir,
55 os.path.join(installDir, "Contents/Frameworks/XUL.framework"))
57 # Contents/Info.plist
58 contents = """<?xml version="1.0" encoding="UTF-8"?>
59 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
60 <plist version="1.0">
61 <dict>
62 <key>CFBundleInfoDictionaryVersion</key>
63 <string>6.0</string>
64 <key>CFBundlePackageType</key>
65 <string>APPL</string>
66 <key>CFBundleSignature</key>
67 <string>????</string>
68 <key>CFBundleExecutable</key>
69 <string>xulrunner</string>
70 <key>NSAppleScriptEnabled</key>
71 <true/>
72 <key>CFBundleGetInfoString</key>
73 <string>$infoString</string>
74 <key>CFBundleName</key>
75 <string>$appName</string>
76 <key>CFBundleShortVersionString</key>
77 <string>$version</string>
78 <key>CFBundleVersion</key>
79 <string>$version.$buildID</string>
80 <key>CFBundleIdentifier</key>
81 <string>$reverseVendor</string>
82 </dict>
83 </plist>
84 """
85 version = iniparser.get("App", "Version")
86 buildID = iniparser.get("App", "BuildID")
87 infoString = appName + " " + version
88 reverseVendor = "com.vendor.unknown"
89 appID = iniparser.get("App", "ID")
90 colonIndex = appID.find("@") + 1
91 if (colonIndex != 0):
92 vendor = appID[colonIndex:]
93 reverseVendor = ".".join(vendor.split(".")[::-1])
94 contents = contents.replace("$infoString", infoString)
95 contents = contents.replace("$appName", appName)
96 contents = contents.replace("$version", version)
97 contents = contents.replace("$buildID", buildID)
98 contents = contents.replace("$reverseVendor", reverseVendor)
99 infoPList = open(os.path.join(installDir, "Contents/Info.plist"), "w+b")
100 infoPList.write(contents)
101 infoPList.close()
103 def makeAppName(leafName):
104 return leafName
106 else:
107 # Implement xulrunnerStubName, installApp and makeAppName as above.
108 raise Exception("This operating system isn't supported for install_app.py yet!")
109 # End platform-specific support
111 def resolvePath(path):
112 return os.path.realpath(path)
114 def requireINIOption(iniparser, section, option):
115 if not (iniparser.has_option(section, option)):
116 raise Exception("application.ini must have a " + option + " option under the " + section + " section")
118 def checkAppINI(appLocation):
119 if (os.path.isdir(appLocation)):
120 zipApp = None
121 appINIPath = os.path.join(appLocation, "application.ini")
122 if not (os.path.isfile(appINIPath)):
123 raise Exception(appINIPath + " does not exist")
124 appINI = open(appINIPath)
125 elif (zipfile.is_zipfile(appLocation)):
126 zipApp = zipfile.ZipFile(appLocation)
127 if not ("application.ini" in zipApp.namelist()):
128 raise Exception("jar:" + appLocation + "!/application.ini does not exist")
129 appINI = zipApp.open("application.ini")
130 else:
131 raise Exception("appLocation must be a directory containing application.ini or a zip file with application.ini at its root")
133 # application.ini verification
134 iniparser = SafeConfigParser()
135 iniparser.readfp(appINI)
136 if not (iniparser.has_section("App")):
137 raise Exception("application.ini must have an App section")
138 if not (iniparser.has_section("Gecko")):
139 raise Exception("application.ini must have a Gecko section")
140 requireINIOption(iniparser, "App", "Name")
141 requireINIOption(iniparser, "App", "Version")
142 requireINIOption(iniparser, "App", "BuildID")
143 requireINIOption(iniparser, "App", "ID")
144 requireINIOption(iniparser, "Gecko", "MinVersion")
146 return zipApp, iniparser
147 pass
149 def copyGRE(greDir, targetDir):
150 shutil.copytree(greDir, targetDir, symlinks=True)
152 def validateArguments(appLocation, installDir, appName, greDir):
153 # application directory / zip verification
154 appLocation = resolvePath(appLocation)
156 # target directory
157 installDir = resolvePath(installDir)
159 if (os.path.exists(installDir)):
160 raise Exception("installDir must not exist: " + cmds.installDir)
162 greDir = resolvePath(greDir)
163 xulrunnerStubPath = os.path.isfile(os.path.join(greDir, xulrunnerStubName))
164 if not xulrunnerStubPath:
165 raise Exception("XULRunner stub executable not found: " + os.path.join(greDir, xulrunnerStubName))
167 # appName
168 zipApp, iniparser = checkAppINI(appLocation)
169 if not appName:
170 appName = iniparser.get("App", "Name")
171 appName = makeAppName(appName)
172 pattern = re.compile("[\\\/\:*?\"<>|\x00]")
173 if pattern.search(appName):
174 raise Exception("App name has illegal characters for at least one operating system")
175 return zipApp, iniparser, appName
177 def handleCommandLine():
178 import argparse
180 # Argument parsing.
181 parser = argparse.ArgumentParser(
182 description="XULRunner application installer",
183 usage="""install_app.py appLocation installDir greDir [--appName APPNAME]
184 install_app.py -h
185 install_app.py --version
186 """
187 )
188 parser.add_argument(
189 "appLocation",
190 action="store",
191 help="The directory or ZIP file containing application.ini as a top-level child file"
192 )
193 parser.add_argument(
194 "installDir",
195 action="store",
196 help="The directory to install the application to"
197 )
198 parser.add_argument(
199 "--greDir",
200 action="store",
201 help="The directory containing the Gecko SDK (usually where this Python script lives)",
202 default=os.path.dirname(sys.argv[0])
203 )
204 parser.add_argument(
205 "--appName",
206 action="store",
207 help="The name of the application to install"
208 )
209 parser.add_argument("--version", action="version", version="%(prog)s 1.0")
211 # The command code.
212 cmds = parser.parse_args()
213 try:
214 installApp(cmds.appLocation, cmds.installDir, cmds.appName, cmds.greDir)
215 except exn:
216 shutil.rmtree(cmds.installDir)
217 raise exn
218 print cmds.appName + " application installed to " + cmds.installDir
220 if __name__ == '__main__':
221 handleCommandLine()