DeploymentTools.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. """ This module is used to build a graphical installer
  2. or a standalone executable from a p3d file. It will try
  3. to build for as many platforms as possible. """
  4. __all__ = ["Standalone", "Installer"]
  5. import os, sys, subprocess, tarfile, shutil, time, zipfile, glob
  6. from direct.directnotify.DirectNotifyGlobal import *
  7. from direct.showbase.AppRunnerGlobal import appRunner
  8. from pandac.PandaModules import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile
  9. from pandac.PandaModules import TiXmlDocument, TiXmlDeclaration, TiXmlElement, readXmlStream
  10. from direct.p3d.HostInfo import HostInfo
  11. class CachedFile:
  12. def __init__(self): self.str = ""
  13. def write(self, data): self.str += data
  14. # Make sure this matches with the magic in p3dEmbed.cxx.
  15. P3DEMBED_MAGIC = "\xFF\x3D\x3D\x00"
  16. class Standalone:
  17. """ This class creates a standalone executable from a given .p3d file. """
  18. notify = directNotify.newCategory("Standalone")
  19. def __init__(self, p3dfile, tokens = {}):
  20. self.p3dfile = Filename(p3dfile)
  21. self.basename = self.p3dfile.getBasenameWoExtension()
  22. self.tokens = tokens
  23. hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
  24. hostDir.makeDir()
  25. self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = True)
  26. self.http = HTTPClient.getGlobalPtr()
  27. if not self.host.hasContentsFile:
  28. if not self.host.readContentsFile():
  29. if not self.host.downloadContentsFile(self.http):
  30. Standalone.notify.error("couldn't read host")
  31. return False
  32. def buildAll(self, outputDir = "."):
  33. """ Builds standalone executables for every known platform,
  34. into the specified output directory. """
  35. platforms = set()
  36. for package in self.host.getPackages(name = "p3dembed"):
  37. platforms.add(package.platform)
  38. if len(platforms) == 0:
  39. Standalone.notify.warning("No platforms found to build for!")
  40. outputDir = Filename(outputDir + "/")
  41. outputDir.makeDir()
  42. for platform in platforms:
  43. if platform.startswith("win"):
  44. self.build(Filename(outputDir, platform + "/" + self.basename + ".exe"), platform)
  45. else:
  46. self.build(Filename(outputDir, platform + "/" + self.basename), platform)
  47. def build(self, output, platform = None):
  48. """ Builds a standalone executable and stores it into the path
  49. indicated by the 'output' argument. You can specify to build for
  50. a different platform by altering the 'platform' argument. """
  51. if platform == None:
  52. platform = PandaSystem.getPlatform()
  53. vfs = VirtualFileSystem.getGlobalPtr()
  54. for package in self.host.getPackages(name = "p3dembed", platform = platform):
  55. if not package.downloadDescFile(self.http):
  56. Standalone.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  57. continue
  58. if not package.downloadPackage(self.http):
  59. Standalone.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  60. continue
  61. # Figure out where p3dembed might be now.
  62. if package.platform.startswith("win"):
  63. p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed.exe" % package.platform)
  64. else:
  65. p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed" % package.platform)
  66. if not vfs.exists(p3dembed):
  67. Standalone.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  68. continue
  69. return self.embed(output, p3dembed)
  70. Standalone.notify.error("Failed to build standalone for platform %s" % platform)
  71. def embed(self, output, p3dembed):
  72. """ Embeds the p3d file into the provided p3dembed executable.
  73. This function is not really useful - use build() or buildAll() instead. """
  74. # Load the p3dembed data into memory
  75. size = p3dembed.getFileSize()
  76. p3dembed_data = VirtualFileSystem.getGlobalPtr().readFile(p3dembed, True)
  77. assert len(p3dembed_data) == size
  78. # Find the magic size string and replace it with the real size,
  79. # regardless of the endianness of the p3dembed executable.
  80. hex_size = hex(size)[2:].rjust(8, "0")
  81. enc_size = "".join([chr(int(hex_size[i] + hex_size[i + 1], 16)) for i in range(0, len(hex_size), 2)])
  82. p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC, enc_size)
  83. p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC[::-1], enc_size[::-1])
  84. # Write the output file
  85. Standalone.notify.info("Creating %s..." % output)
  86. output.makeDir()
  87. ohandle = open(output.toOsSpecific(), "wb")
  88. ohandle.write(p3dembed_data)
  89. # Write out the tokens. Set log_basename to the basename by default
  90. tokens = {"log_basename" : self.basename}
  91. tokens.update(self.tokens)
  92. for token in tokens.items():
  93. ohandle.write("\0%s=%s" % token)
  94. ohandle.write("\0\0")
  95. # Buffer the p3d file to the output file. 1 MB buffer size.
  96. phandle = open(self.p3dfile.toOsSpecific(), "rb")
  97. buf = phandle.read(1024 * 1024)
  98. while len(buf) != 0:
  99. ohandle.write(buf)
  100. buf = phandle.read(1024 * 1024)
  101. ohandle.close()
  102. phandle.close()
  103. os.chmod(output.toOsSpecific(), 0755)
  104. def getExtraFiles(self, platform):
  105. """ Returns a list of extra files that will need to be included
  106. with the standalone executable in order for it to run, such as
  107. dependent libraries. The returned paths are full absolute paths. """
  108. package = self.host.getPackages(name = "p3dembed", platform = platform)[0]
  109. if not package.downloadDescFile(self.http):
  110. Standalone.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  111. return []
  112. if not package.downloadPackage(self.http):
  113. Standalone.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  114. return []
  115. filenames = []
  116. vfs = VirtualFileSystem.getGlobalPtr()
  117. for e in package.extracts:
  118. if e.basename not in ["p3dembed", "p3dembed.exe"]:
  119. filename = Filename(package.getPackageDir(), e.filename)
  120. filename.makeAbsolute()
  121. if vfs.exists(filename):
  122. filenames.append(filename)
  123. else:
  124. Standalone.notify.error("%s mentioned in xml, but does not exist" % e.filename)
  125. return filenames
  126. class Installer:
  127. """ This class creates a (graphical) installer from a given .p3d file. """
  128. notify = directNotify.newCategory("Installer")
  129. def __init__(self, shortname, fullname, p3dfile, version, tokens = {}):
  130. self.shortname = shortname
  131. self.fullname = fullname
  132. self.version = str(version)
  133. self.includeRequires = False
  134. self.licensename = ""
  135. self.authorid = "org.panda3d"
  136. self.authorname = ""
  137. self.licensefile = Filename()
  138. self.standalone = Standalone(p3dfile, tokens)
  139. self.http = self.standalone.http
  140. # Load the p3d file to read out the required packages
  141. mf = Multifile()
  142. if not mf.openRead(p3dfile):
  143. Installer.notify.error("Not a Panda3D application: %s" % (p3dFilename))
  144. return
  145. # Now load the p3dInfo file.
  146. self.hostUrl = PandaSystem.getPackageHostUrl()
  147. if not self.hostUrl:
  148. self.hostUrl = self.standalone.host.hostUrl
  149. self.requirements = []
  150. i = mf.findSubfile('p3d_info.xml')
  151. if i >= 0:
  152. stream = mf.openReadSubfile(i)
  153. p3dInfo = readXmlStream(stream)
  154. mf.closeReadSubfile(stream)
  155. if p3dInfo:
  156. p3dPackage = p3dInfo.FirstChildElement('package')
  157. p3dHost = p3dPackage.FirstChildElement('host')
  158. if p3dHost.Attribute('url'):
  159. self.hostUrl = p3dHost.Attribute('url')
  160. p3dRequires = p3dPackage.FirstChildElement('requires')
  161. while p3dRequires:
  162. self.requirements.append((p3dRequires.Attribute('name'), p3dRequires.Attribute('version')))
  163. p3dRequires = p3dRequires.NextSiblingElement('requires')
  164. def installPackagesInto(self, hostDir, platform):
  165. """ Installs the packages required by the .p3d file into
  166. the specified directory, for the given platform. """
  167. if not self.includeRequires:
  168. return
  169. packages = []
  170. host = HostInfo(self.hostUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
  171. if not host.hasContentsFile:
  172. if not host.readContentsFile():
  173. if not host.downloadContentsFile(self.http):
  174. Installer.notify.error("couldn't read host")
  175. return
  176. for name, version in self.requirements:
  177. package = host.getPackage(name, version, platform)
  178. package.installed = True # Hack not to let it install itself
  179. packages.append(package)
  180. if not package.downloadDescFile(self.http):
  181. Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  182. continue
  183. if not package.downloadPackage(self.http):
  184. Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  185. continue
  186. # Also install the 'images' package from the same host that p3dembed was downloaded from.
  187. host = HostInfo(self.standalone.host.hostUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
  188. if not host.hasContentsFile:
  189. if not host.readContentsFile():
  190. if not host.downloadContentsFile(self.http):
  191. Installer.notify.error("couldn't read host")
  192. return
  193. for package in host.getPackages(name = "images"):
  194. package.installed = True # Hack not to let it install itself
  195. packages.append(package)
  196. if not package.downloadDescFile(self.http):
  197. Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  198. continue
  199. if not package.downloadPackage(self.http):
  200. Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
  201. continue
  202. break
  203. # Remove the extracted files from the compressed archive, to save space.
  204. vfs = VirtualFileSystem.getGlobalPtr()
  205. for package in packages:
  206. if package.uncompressedArchive:
  207. archive = Filename(package.getPackageDir(), package.uncompressedArchive.filename)
  208. if not archive.exists():
  209. continue
  210. print archive
  211. mf = Multifile()
  212. # Make sure that it isn't mounted before altering it, just to be safe
  213. vfs.unmount(archive)
  214. if not mf.openRead(archive):
  215. Installer.notify.warning("Failed to open archive " + archive)
  216. continue
  217. # We don't iterate over getNumSubfiles because we're
  218. # removing subfiles while we're iterating over them.
  219. subfiles = mf.getSubfileNames()
  220. for subfile in subfiles:
  221. # We do *NOT* call vfs.exists here in case the package is mounted.
  222. if Filename(package.getPackageDir(), subfile).exists():
  223. mf.removeSubfile(subfile)
  224. # This seems essential for mf.close() not to crash later.
  225. mf.repack()
  226. # If we have no subfiles left, we can just remove the multifile.
  227. if mf.getNumSubfiles() == 0:
  228. Installer.notify.info("Removing empty archive " + package.uncompressedArchive.filename)
  229. mf.close()
  230. archive.unlink()
  231. else:
  232. mf.close()
  233. # Write out our own contents.xml file.
  234. doc = TiXmlDocument()
  235. decl = TiXmlDeclaration("1.0", "utf-8", "")
  236. doc.InsertEndChild(decl)
  237. xcontents = TiXmlElement("contents")
  238. for package in packages:
  239. xpackage = TiXmlElement('package')
  240. xpackage.SetAttribute('name', package.packageName)
  241. if package.platform:
  242. xpackage.SetAttribute('platform', package.platform)
  243. if package.packageVersion:
  244. xpackage.SetAttribute('version', version)
  245. xpackage.SetAttribute('filename', package.packageName + "/" + package.packageVersion + "/" + package.descFileBasename)
  246. else:
  247. xpackage.SetAttribute('filename', package.packageName + "/" + package.descFileBasename)
  248. xcontents.InsertEndChild(xpackage)
  249. doc.InsertEndChild(xcontents)
  250. doc.SaveFile(Filename(hostDir, "contents.xml").toOsSpecific())
  251. def buildAll(self, outputDir = "."):
  252. """ Creates a (graphical) installer for every known platform.
  253. Call this after you have set the desired parameters. """
  254. platforms = set()
  255. for package in self.standalone.host.getPackages(name = "p3dembed"):
  256. platforms.add(package.platform)
  257. if len(platforms) == 0:
  258. Installer.notify.warning("No platforms found to build for!")
  259. outputDir = Filename(outputDir + "/")
  260. outputDir.makeDir()
  261. for platform in platforms:
  262. output = Filename(outputDir, platform + "/")
  263. output.makeDir()
  264. self.build(output, platform)
  265. def build(self, output, platform = None):
  266. """ Builds a (graphical) installer and stores it into the path
  267. indicated by the 'output' argument. You can specify to build for
  268. a different platform by altering the 'platform' argument.
  269. If 'output' is a directory, the installer will be stored in it. """
  270. if platform == None:
  271. platform = PandaSystem.getPlatform()
  272. if platform == "win32":
  273. return self.buildNSIS(output, platform)
  274. elif "_" in platform:
  275. os, arch = platform.split("_", 1)
  276. if os == "linux":
  277. return self.buildDEB(output, platform)
  278. elif os == "osx":
  279. return self.buildPKG(output, platform)
  280. elif os == "freebsd":
  281. return self.buildDEB(output, platform)
  282. Installer.notify.info("Ignoring unknown platform " + platform)
  283. def buildDEB(self, output, platform):
  284. """ Builds a .deb archive and stores it in the path indicated
  285. by the 'output' argument. It will be built for the architecture
  286. specified by the 'arch' argument.
  287. If 'output' is a directory, the deb file will be stored in it. """
  288. arch = platform.rsplit("_", 1)[-1]
  289. output = Filename(output)
  290. if output.isDirectory():
  291. output = Filename(output, "%s_%s_%s.deb" % (self.shortname.lower(), self.version, arch))
  292. Installer.notify.info("Creating %s..." % output)
  293. # Create a temporary directory and write the control file + launcher to it
  294. tempdir = Filename.temporary("", self.shortname.lower() + "_deb_", "") + "/"
  295. tempdir.makeDir()
  296. controlfile = open(Filename(tempdir, "control").toOsSpecific(), "w")
  297. print >>controlfile, "Package: %s" % self.shortname.lower()
  298. print >>controlfile, "Version: %s" % self.version
  299. print >>controlfile, "Section: games"
  300. print >>controlfile, "Priority: optional"
  301. print >>controlfile, "Architecture: %s" % arch
  302. print >>controlfile, "Description: %s" % self.fullname
  303. print >>controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6, libssl0.9.8"
  304. controlfile.close()
  305. Filename(tempdir, "usr/bin/").makeDir()
  306. if self.includeRequires:
  307. self.standalone.tokens["host_dir"] = "/usr/lib/" + self.shortname.lower()
  308. elif "host_dir" in self.standalone.tokens:
  309. del self.standalone.tokens["host_dir"]
  310. self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform)
  311. if not self.licensefile.empty():
  312. Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
  313. shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific())
  314. hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
  315. hostDir.makeDir()
  316. self.installPackagesInto(hostDir, platform)
  317. # Create a control.tar.gz file in memory
  318. controlfile = Filename(tempdir, "control")
  319. controltargz = CachedFile()
  320. controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
  321. controltarfile.add(controlfile.toOsSpecific(), "control")
  322. controltarfile.close()
  323. controlfile.unlink()
  324. # Create the data.tar.gz file in the temporary directory
  325. datatargz = CachedFile()
  326. datatarfile = tarfile.TarFile.gzopen("data.tar.gz", "w", datatargz, 9)
  327. datatarfile.add(Filename(tempdir, "usr").toOsSpecific(), "/usr")
  328. datatarfile.close()
  329. # Open the deb file and write to it. It's actually
  330. # just an AR file, which is very easy to make.
  331. modtime = int(time.time())
  332. if output.exists():
  333. output.unlink()
  334. debfile = open(output.toOsSpecific(), "wb")
  335. debfile.write("!<arch>\x0A")
  336. debfile.write("debian-binary %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, 4))
  337. debfile.write("2.0\x0A")
  338. debfile.write("control.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(controltargz.str)))
  339. debfile.write(controltargz.str)
  340. if (len(controltargz.str) & 1): debfile.write("\x0A")
  341. debfile.write("data.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(datatargz.str)))
  342. debfile.write(datatargz.str)
  343. if (len(datatargz.str) & 1): debfile.write("\x0A")
  344. debfile.close()
  345. try:
  346. appRunner.rmtree(tempdir)
  347. except:
  348. try: shutil.rmtree(tempdir.toOsSpecific())
  349. except: pass
  350. def buildAPP(self, output, platform):
  351. output = Filename(output)
  352. if output.isDirectory() and output.getExtension() != 'app':
  353. output = Filename(output, "%s.app" % self.fullname)
  354. Installer.notify.info("Creating %s..." % output)
  355. # Create the executable for the application bundle
  356. exefile = Filename(output, "Contents/MacOS/" + self.shortname)
  357. exefile.makeDir()
  358. if self.includeRequires:
  359. self.standalone.tokens["host_dir"] = "../Resources"
  360. elif "host_dir" in self.standalone.tokens:
  361. del self.standalone.tokens["host_dir"]
  362. self.standalone.build(exefile, platform)
  363. hostDir = Filename(output, "Contents/Resources/")
  364. hostDir.makeDir()
  365. self.installPackagesInto(hostDir, platform)
  366. # Create the application plist file.
  367. # Although it might make more sense to use Python's plistlib module here,
  368. # it is not available on non-OSX systems before Python 2.6.
  369. plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
  370. print >>plist, '<?xml version="1.0" encoding="UTF-8"?>'
  371. print >>plist, '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
  372. print >>plist, '<plist version="1.0">'
  373. print >>plist, '<dict>'
  374. print >>plist, '\t<key>CFBundleDevelopmentRegion</key>'
  375. print >>plist, '\t<string>English</string>'
  376. print >>plist, '\t<key>CFBundleDisplayName</key>'
  377. print >>plist, '\t<string>%s</string>' % self.fullname
  378. print >>plist, '\t<key>CFBundleExecutable</key>'
  379. print >>plist, '\t<string>%s</string>' % exefile.getBasename()
  380. print >>plist, '\t<key>CFBundleIdentifier</key>'
  381. print >>plist, '\t<string>%s.%s</string>' % (self.authorid, self.shortname)
  382. print >>plist, '\t<key>CFBundleInfoDictionaryVersion</key>'
  383. print >>plist, '\t<string>6.0</string>'
  384. print >>plist, '\t<key>CFBundleName</key>'
  385. print >>plist, '\t<string>%s</string>' % self.shortname
  386. print >>plist, '\t<key>CFBundlePackageType</key>'
  387. print >>plist, '\t<string>APPL</string>'
  388. print >>plist, '\t<key>CFBundleShortVersionString</key>'
  389. print >>plist, '\t<string>%s</string>' % self.version
  390. print >>plist, '\t<key>CFBundleVersion</key>'
  391. print >>plist, '\t<string>%s</string>' % self.version
  392. print >>plist, '\t<key>LSHasLocalizedDisplayName</key>'
  393. print >>plist, '\t<false/>'
  394. print >>plist, '\t<key>NSAppleScriptEnabled</key>'
  395. print >>plist, '\t<false/>'
  396. print >>plist, '\t<key>NSPrincipalClass</key>'
  397. print >>plist, '\t<string>NSApplication</string>'
  398. print >>plist, '</dict>'
  399. print >>plist, '</plist>'
  400. plist.close()
  401. Filename(output, "hosts/").makeDir()
  402. return output
  403. def buildPKG(self, output, platform):
  404. appfn = self.buildAPP(output, platform)
  405. appname = "/Applications/" + appfn.getBasename()
  406. output = Filename(output)
  407. if output.isDirectory():
  408. output = Filename(output, "%s %s.pkg" % (self.fullname, self.version))
  409. Installer.notify.info("Creating %s..." % output)
  410. Filename(output, "Contents/Resources/en.lproj/").makeDir()
  411. if self.licensefile:
  412. shutil.copyfile(self.licensefile.toOsSpecific(), Filename(output, "Contents/Resources/License.txt").toOsSpecific())
  413. pkginfo = open(Filename(output, "Contents/PkgInfo").toOsSpecific(), "w")
  414. pkginfo.write("pkmkrpkg1")
  415. pkginfo.close()
  416. pkginfo = open(Filename(output, "Contents/Resources/package_version").toOsSpecific(), "w")
  417. pkginfo.write("major: 1\nminor: 9")
  418. pkginfo.close()
  419. # Although it might make more sense to use Python's plistlib here,
  420. # it is not available on non-OSX systems before Python 2.6.
  421. plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
  422. plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
  423. plist.write('<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
  424. plist.write('<plist version="1.0">\n')
  425. plist.write('<dict>\n')
  426. plist.write('\t<key>CFBundleIdentifier</key>\n')
  427. plist.write('\t<string>%s.pkg.%s</string>\n' % (self.authorid, self.shortname))
  428. plist.write('\t<key>CFBundleShortVersionString</key>\n')
  429. plist.write('\t<string>%s</string>\n' % self.version)
  430. plist.write('\t<key>IFMajorVersion</key>\n')
  431. plist.write('\t<integer>1</integer>\n')
  432. plist.write('\t<key>IFMinorVersion</key>\n')
  433. plist.write('\t<integer>9</integer>\n')
  434. plist.write('\t<key>IFPkgFlagAllowBackRev</key>\n')
  435. plist.write('\t<false/>\n')
  436. plist.write('\t<key>IFPkgFlagAuthorizationAction</key>\n')
  437. plist.write('\t<string>RootAuthorization</string>\n')
  438. plist.write('\t<key>IFPkgFlagDefaultLocation</key>\n')
  439. plist.write('\t<string>/</string>\n')
  440. plist.write('\t<key>IFPkgFlagFollowLinks</key>\n')
  441. plist.write('\t<true/>\n')
  442. plist.write('\t<key>IFPkgFlagIsRequired</key>\n')
  443. plist.write('\t<false/>\n')
  444. plist.write('\t<key>IFPkgFlagOverwritePermissions</key>\n')
  445. plist.write('\t<false/>\n')
  446. plist.write('\t<key>IFPkgFlagRelocatable</key>\n')
  447. plist.write('\t<false/>\n')
  448. plist.write('\t<key>IFPkgFlagRestartAction</key>\n')
  449. plist.write('\t<string>None</string>\n')
  450. plist.write('\t<key>IFPkgFlagRootVolumeOnly</key>\n')
  451. plist.write('\t<true/>\n')
  452. plist.write('\t<key>IFPkgFlagUpdateInstalledLanguages</key>\n')
  453. plist.write('\t<false/>\n')
  454. plist.write('\t<key>IFPkgFormatVersion</key>\n')
  455. plist.write('\t<real>0.10000000149011612</real>\n')
  456. plist.write('\t<key>IFPkgPathMappings</key>\n')
  457. plist.write('\t<dict>\n')
  458. plist.write('\t\t<key>%s</key>\n' % appname)
  459. plist.write('\t\t<string>{pkmk-token-2}</string>\n')
  460. plist.write('\t</dict>\n')
  461. plist.write('</dict>\n')
  462. plist.write('</plist>\n')
  463. plist.close()
  464. plist = open(Filename(output, "Contents/Resources/TokenDefinitions.plist").toOsSpecific(), "w")
  465. plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
  466. plist.write('<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
  467. plist.write('<plist version="1.0">\n')
  468. plist.write('<dict>\n')
  469. plist.write('\t<key>pkmk-token-2</key>\n')
  470. plist.write('\t<array>\n')
  471. plist.write('\t\t<dict>\n')
  472. plist.write('\t\t\t<key>identifier</key>\n')
  473. plist.write('\t\t\t<string>%s.%s</string>\n' % (self.authorid, self.shortname))
  474. plist.write('\t\t\t<key>path</key>\n')
  475. plist.write('\t\t\t<string>%s</string>\n' % appname)
  476. plist.write('\t\t\t<key>searchPlugin</key>\n')
  477. plist.write('\t\t\t<string>CommonAppSearch</string>\n')
  478. plist.write('\t\t</dict>\n')
  479. plist.write('\t</array>\n')
  480. plist.write('</dict>\n')
  481. plist.write('</plist>\n')
  482. plist.close()
  483. plist = open(Filename(output, "Contents/Resources/en.lproj/Description.plist").toOsSpecific(), "w")
  484. plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
  485. plist.write('<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
  486. plist.write('<plist version="1.0">\n')
  487. plist.write('<dict>\n')
  488. plist.write('\t<key>IFPkgDescriptionDescription</key>\n')
  489. plist.write('\t<string></string>\n')
  490. plist.write('\t<key>IFPkgDescriptionTitle</key>\n')
  491. plist.write('\t<string>%s</string>\n' % self.fullname)
  492. plist.write('</dict>\n')
  493. plist.write('</plist>\n')
  494. plist.close()
  495. if hasattr(tarfile, "PAX_FORMAT"):
  496. archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT)
  497. else:
  498. archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz")
  499. archive.add(appfn.toOsSpecific(), appname)
  500. archive.close()
  501. # Put the .pkg into a zipfile
  502. archive = Filename(output.getDirname(), "%s %s.zip" % (self.fullname, self.version))
  503. dir = Filename(output.getDirname())
  504. dir.makeAbsolute()
  505. zip = zipfile.ZipFile(archive.toOsSpecific(), 'w')
  506. for root, dirs, files in os.walk(output.toOsSpecific()):
  507. for name in files:
  508. file = Filename.fromOsSpecific(os.path.join(root, name))
  509. file.makeAbsolute()
  510. file.makeRelativeTo(dir)
  511. zip.write(os.path.join(root, name), str(file))
  512. zip.close()
  513. return output
  514. def buildNSIS(self, output, platform):
  515. # Check if we have makensis first
  516. makensis = None
  517. if (sys.platform.startswith("win")):
  518. for p in os.defpath.split(";") + os.environ["PATH"].split(";"):
  519. if os.path.isfile(os.path.join(p, "makensis.exe")):
  520. makensis = os.path.join(p, "makensis.exe")
  521. if not makensis:
  522. import pandac
  523. makensis = os.path.dirname(os.path.dirname(pandac.__file__))
  524. makensis = os.path.join(makensis, "nsis", "makensis.exe")
  525. if not os.path.isfile(makensis): makensis = None
  526. else:
  527. for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
  528. if os.path.isfile(os.path.join(p, "makensis")):
  529. makensis = os.path.join(p, "makensis")
  530. if makensis == None:
  531. Installer.notify.warning("Makensis utility not found, no Windows installer will be built!")
  532. return
  533. output = Filename(output)
  534. if output.isDirectory():
  535. output = Filename(output, "%s %s.exe" % (self.fullname, self.version))
  536. Installer.notify.info("Creating %s..." % output)
  537. output.makeAbsolute()
  538. extrafiles = self.standalone.getExtraFiles(platform)
  539. exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
  540. exefile.unlink()
  541. if self.includeRequires:
  542. self.standalone.tokens["host_dir"] = "."
  543. elif "host_dir" in self.standalone.tokens:
  544. del self.standalone.tokens["host_dir"]
  545. self.standalone.build(exefile, platform)
  546. # Temporary directory to store the hostdir in
  547. hostDir = Filename.temporary("", self.shortname.lower() + "_exe_", "") + "/"
  548. hostDir.makeDir()
  549. self.installPackagesInto(hostDir, platform)
  550. nsifile = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
  551. nsifile.unlink()
  552. nsi = open(nsifile.toOsSpecific(), "w")
  553. # Some global info
  554. nsi.write('Name "%s"\n' % self.fullname)
  555. nsi.write('OutFile "%s"\n' % output.toOsSpecific())
  556. nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % self.fullname)
  557. nsi.write('SetCompress auto\n')
  558. nsi.write('SetCompressor lzma\n')
  559. nsi.write('ShowInstDetails nevershow\n')
  560. nsi.write('ShowUninstDetails nevershow\n')
  561. nsi.write('InstType "Typical"\n')
  562. # Tell Vista that we require admin rights
  563. nsi.write('RequestExecutionLevel admin\n')
  564. nsi.write('\n')
  565. nsi.write('Function launch\n')
  566. nsi.write(' ExecShell "open" "$INSTDIR\\%s.exe"\n' % self.shortname)
  567. nsi.write('FunctionEnd\n')
  568. nsi.write('\n')
  569. nsi.write('!include "MUI2.nsh"\n')
  570. nsi.write('!define MUI_ABORTWARNING\n')
  571. nsi.write('!define MUI_FINISHPAGE_RUN\n')
  572. nsi.write('!define MUI_FINISHPAGE_RUN_FUNCTION launch\n')
  573. nsi.write('!define MUI_FINISHPAGE_RUN_TEXT "Play %s"\n' % self.fullname)
  574. nsi.write('\n')
  575. nsi.write('Var StartMenuFolder\n')
  576. nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
  577. if not self.licensefile.empty():
  578. nsi.write('!insertmacro MUI_PAGE_LICENSE "%s"\n' % self.licensefile.toOsSpecific())
  579. nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
  580. nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
  581. nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
  582. nsi.write('!insertmacro MUI_PAGE_FINISH\n')
  583. nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
  584. nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
  585. nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
  586. nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
  587. nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
  588. # This section defines the installer.
  589. nsi.write('Section "" SecCore\n')
  590. nsi.write(' SetOutPath "$INSTDIR"\n')
  591. nsi.write(' File "%s"\n' % exefile.toOsSpecific())
  592. for f in extrafiles:
  593. nsi.write(' File "%s"\n' % f.toOsSpecific())
  594. curdir = ""
  595. for root, dirs, files in os.walk(hostDir.toOsSpecific()):
  596. for name in files:
  597. file = Filename.fromOsSpecific(os.path.join(root, name))
  598. file.makeAbsolute()
  599. file.makeRelativeTo(hostDir)
  600. outdir = file.getDirname().replace('/', '\\')
  601. if curdir != outdir:
  602. nsi.write(' SetOutPath "$INSTDIR\\%s"\n' % outdir)
  603. curdir = outdir
  604. nsi.write(' File "%s"\n' % os.path.join(root, name))
  605. nsi.write(' WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
  606. nsi.write(' ; Start menu items\n')
  607. nsi.write(' !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
  608. nsi.write(' CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
  609. nsi.write(' CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
  610. nsi.write(' !insertmacro MUI_STARTMENU_WRITE_END\n')
  611. nsi.write('SectionEnd\n')
  612. # This section defines the uninstaller.
  613. nsi.write('Section Uninstall\n')
  614. nsi.write(' Delete "$INSTDIR\\%s.exe"\n' % self.shortname)
  615. for f in extrafiles:
  616. nsi.write(' Delete "%s"\n' % f.getBasename())
  617. nsi.write(' Delete "$INSTDIR\\Uninstall.exe"\n')
  618. nsi.write(' RMDir /r "$INSTDIR"\n')
  619. nsi.write(' ; Start menu items\n')
  620. nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
  621. nsi.write(' Delete "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk"\n')
  622. nsi.write(' RMDir "$SMPROGRAMS\\$StartMenuFolder"\n')
  623. nsi.write('SectionEnd')
  624. nsi.close()
  625. options = ["V2"]
  626. cmd = "\"" + makensis + "\""
  627. for o in options:
  628. if sys.platform.startswith("win"):
  629. cmd += " /" + o
  630. else:
  631. cmd += " -" + o
  632. cmd += " " + nsifile.toOsSpecific()
  633. print cmd
  634. os.system(cmd)
  635. nsifile.unlink()
  636. try:
  637. appRunner.rmtree(hostDir)
  638. except:
  639. try: shutil.rmtree(hostDir.toOsSpecific())
  640. except: pass
  641. return output