|
1 #!/usr/bin/env python |
|
2 |
|
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/. |
|
6 |
|
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") |
|
11 |
|
12 import os, shutil, re, zipfile |
|
13 from ConfigParser import SafeConfigParser |
|
14 |
|
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")) |
|
27 |
|
28 if sys.platform.startswith('linux'): |
|
29 xulrunnerStubName = "xulrunner-stub" |
|
30 |
|
31 def makeAppName(leafName): |
|
32 return leafName.lower() |
|
33 |
|
34 elif sys.platform == "win32": |
|
35 xulrunnerStubName = "xulrunner-stub.exe" |
|
36 |
|
37 def makeAppName(leafName): |
|
38 return leafName + ".exe" |
|
39 |
|
40 elif sys.platform == "darwin": |
|
41 xulrunnerStubName = "xulrunner" |
|
42 |
|
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")) |
|
56 |
|
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() |
|
102 |
|
103 def makeAppName(leafName): |
|
104 return leafName |
|
105 |
|
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 |
|
110 |
|
111 def resolvePath(path): |
|
112 return os.path.realpath(path) |
|
113 |
|
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") |
|
117 |
|
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") |
|
132 |
|
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") |
|
145 |
|
146 return zipApp, iniparser |
|
147 pass |
|
148 |
|
149 def copyGRE(greDir, targetDir): |
|
150 shutil.copytree(greDir, targetDir, symlinks=True) |
|
151 |
|
152 def validateArguments(appLocation, installDir, appName, greDir): |
|
153 # application directory / zip verification |
|
154 appLocation = resolvePath(appLocation) |
|
155 |
|
156 # target directory |
|
157 installDir = resolvePath(installDir) |
|
158 |
|
159 if (os.path.exists(installDir)): |
|
160 raise Exception("installDir must not exist: " + cmds.installDir) |
|
161 |
|
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)) |
|
166 |
|
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 |
|
176 |
|
177 def handleCommandLine(): |
|
178 import argparse |
|
179 |
|
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") |
|
210 |
|
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 |
|
219 |
|
220 if __name__ == '__main__': |
|
221 handleCommandLine() |