Browse Source

Changes to pdeploy, add ArchLinux support, better Linux stuff etc

rdb 15 years ago
parent
commit
a16fd9f23b
2 changed files with 249 additions and 143 deletions
  1. 247 141
      direct/src/p3d/DeploymentTools.py
  2. 2 2
      direct/src/p3d/pdeploy.py

+ 247 - 141
direct/src/p3d/DeploymentTools.py

@@ -5,6 +5,7 @@ to build for as many platforms as possible. """
 __all__ = ["Standalone", "Installer"]
 
 import os, sys, subprocess, tarfile, shutil, time, zipfile, glob
+from cStringIO import StringIO
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.AppRunnerGlobal import appRunner
 from pandac.PandaModules import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile
@@ -13,43 +14,72 @@ from direct.p3d.HostInfo import HostInfo
 # This is important for some reason
 import encodings
 
-class CachedFile:
-    def __init__(self): self.str = ""
-    def write(self, data): self.str += data
-
 # Make sure this matches with the magic in p3dEmbed.cxx.
 P3DEMBED_MAGIC = "\xFF\x3D\x3D\x00"
 
+# This filter function is used when creating
+# an archive that should be owned by root.
+def archiveFilter(info):
+    basename = os.path.basename(info.name)
+    if basename in [".DS_Store", "Thumbs.db"]:
+        return None
+
+    info.uid = info.gid = 0
+    info.uname = info.gname = "root"
+
+    return info
+
+# Hack to make all files in a tar file owned by root.
+# The tarfile module doesn't have filter functionality until Python 2.7.
+# Yay duck typing!
+class TarInfoRoot(tarfile.TarInfo):
+    uid = property(lambda self: 0, lambda self, x: None)
+    gid = property(lambda self: 0, lambda self, x: None)
+    uname = property(lambda self: "root", lambda self, x: None)
+    gname = property(lambda self: "root", lambda self, x: None)
+
+# On OSX, the root group is named "wheel".
+class TarInfoRootOSX(TarInfoRoot):
+    gname = property(lambda self: "wheel", lambda self, x: None)
+
+
 class Standalone:
     """ This class creates a standalone executable from a given .p3d file. """
     notify = directNotify.newCategory("Standalone")
-    
+
     def __init__(self, p3dfile, tokens = {}):
         self.p3dfile = Filename(p3dfile)
         self.basename = self.p3dfile.getBasenameWoExtension()
         self.tokens = tokens
-        
-        hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
-        hostDir.makeDir()
-        self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = True)
-        
+
+        self.tempDir = Filename.temporary("", self.basename, "") + "/"
+        self.tempDir.makeDir()
+        self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = appRunner, hostDir = self.tempDir, asMirror = False, perPlatform = True)
+
         self.http = HTTPClient.getGlobalPtr()
         if not self.host.hasContentsFile:
             if not self.host.readContentsFile():
                 if not self.host.downloadContentsFile(self.http):
                     Standalone.notify.error("couldn't read host")
-                    return False
-    
+                    return
+
+    def __del__(self):
+        try:
+            appRunner.rmtree(self.tempDir)
+        except:
+            try: shutil.rmtree(self.tempDir.toOsSpecific())
+            except: pass
+
     def buildAll(self, outputDir = "."):
         """ Builds standalone executables for every known platform,
         into the specified output directory. """
-        
+
         platforms = set()
         for package in self.host.getPackages(name = "p3dembed"):
             platforms.add(package.platform)
         if len(platforms) == 0:
             Standalone.notify.warning("No platforms found to build for!")
-        
+
         outputDir = Filename(outputDir + "/")
         outputDir.makeDir()
         for platform in platforms:
@@ -57,17 +87,17 @@ class Standalone:
                 self.build(Filename(outputDir, platform + "/" + self.basename + ".exe"), platform)
             else:
                 self.build(Filename(outputDir, platform + "/" + self.basename), platform)
-    
-    def build(self, output, platform = None):
+
+    def build(self, output, platform = None, extraTokens = {}):
         """ Builds a standalone executable and stores it into the path
         indicated by the 'output' argument. You can specify to build for
         a different platform by altering the 'platform' argument. """
-        
+
         if platform == None:
             platform = PandaSystem.getPlatform()
-        
+
         vfs = VirtualFileSystem.getGlobalPtr()
-        
+
         for package in self.host.getPackages(name = "p3dembed", platform = platform):
             if not package.downloadDescFile(self.http):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
@@ -75,7 +105,7 @@ class Standalone:
             if not package.downloadPackage(self.http):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
-            
+
             # Figure out where p3dembed might be now.
             if package.platform.startswith("win"):
                 p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed.exe" % package.platform)
@@ -85,40 +115,41 @@ class Standalone:
             if not vfs.exists(p3dembed):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
-            
-            return self.embed(output, p3dembed)
-        
+
+            return self.embed(output, p3dembed, extraTokens)
+
         Standalone.notify.error("Failed to build standalone for platform %s" % platform)
-    
-    def embed(self, output, p3dembed):
+
+    def embed(self, output, p3dembed, extraTokens = {}):
         """ Embeds the p3d file into the provided p3dembed executable.
         This function is not really useful - use build() or buildAll() instead. """
-        
+
         # Load the p3dembed data into memory
         size = p3dembed.getFileSize()
         p3dembed_data = VirtualFileSystem.getGlobalPtr().readFile(p3dembed, True)
         assert len(p3dembed_data) == size
-        
+
         # Find the magic size string and replace it with the real size,
         # regardless of the endianness of the p3dembed executable.
         hex_size = hex(size)[2:].rjust(8, "0")
         enc_size = "".join([chr(int(hex_size[i] + hex_size[i + 1], 16)) for i in range(0, len(hex_size), 2)])
         p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC, enc_size)
         p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC[::-1], enc_size[::-1])
-        
+
         # Write the output file
         Standalone.notify.info("Creating %s..." % output)
         output.makeDir()
         ohandle = open(output.toOsSpecific(), "wb")
         ohandle.write(p3dembed_data)
-        
+
         # Write out the tokens. Set log_basename to the basename by default
         tokens = {"log_basename" : self.basename}
         tokens.update(self.tokens)
+        tokens.update(extraTokens)
         for token in tokens.items():
             ohandle.write("\0%s=%s" % token)
         ohandle.write("\0\0")
-        
+
         # Buffer the p3d file to the output file. 1 MB buffer size.
         phandle = open(self.p3dfile.toOsSpecific(), "rb")
         buf = phandle.read(1024 * 1024)
@@ -127,23 +158,23 @@ class Standalone:
             buf = phandle.read(1024 * 1024)
         ohandle.close()
         phandle.close()
-        
+
         os.chmod(output.toOsSpecific(), 0755)
 
     def getExtraFiles(self, platform):
         """ Returns a list of extra files that will need to be included
         with the standalone executable in order for it to run, such as
         dependent libraries. The returned paths are full absolute paths. """
-        
+
         package = self.host.getPackages(name = "p3dembed", platform = platform)[0]
-        
+
         if not package.downloadDescFile(self.http):
             Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
             return []
         if not package.downloadPackage(self.http):
             Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
             return []
-        
+
         filenames = []
         vfs = VirtualFileSystem.getGlobalPtr()
         for e in package.extracts:
@@ -154,7 +185,7 @@ class Standalone:
                     filenames.append(filename)
                 else:
                     Standalone.notify.error("%s mentioned in xml, but does not exist" % e.filename)
-        
+
         return filenames
 
 class Installer:
@@ -173,7 +204,9 @@ class Installer:
         self.licensefile = Filename()
         self.standalone = Standalone(p3dfile, tokens)
         self.http = self.standalone.http
-        
+        self.tempDir = Filename.temporary("", self.shortname, "") + "/"
+        self.tempDir.makeDir()
+
         # Load the p3d file to read out the required packages
         mf = Multifile()
         if not mf.openRead(p3dfile):
@@ -200,22 +233,29 @@ class Installer:
                     self.requirements.append((p3dRequires.Attribute('name'), p3dRequires.Attribute('version')))
                     p3dRequires = p3dRequires.NextSiblingElement('requires')
 
+    def __del__(self):
+        try:
+            appRunner.rmtree(self.tempDir)
+        except:
+            try: shutil.rmtree(self.tempDir.toOsSpecific())
+            except: pass
+
     def installPackagesInto(self, hostDir, platform):
         """ Installs the packages required by the .p3d file into
         the specified directory, for the given platform. """
-        
+
         if not self.includeRequires:
             return
-        
+
         packages = []
-        
+
         host = HostInfo(self.hostUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
         if not host.hasContentsFile:
             if not host.readContentsFile():
                 if not host.downloadContentsFile(self.http):
                     Installer.notify.error("couldn't read host %s" % host.hostUrl)
                     return
-        
+
         superHost = None
         if appRunner.superMirrorUrl:
             superHost = HostInfo(appRunner.superMirrorUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
@@ -224,7 +264,7 @@ class Installer:
                     if not superHost.downloadContentsFile(self.http):
                         Installer.notify.warning("couldn't read supermirror host %s" % superHost.hostUrl)
                         superHost = None
-        
+
         for name, version in self.requirements:
             package = None
             if superHost:
@@ -243,7 +283,7 @@ class Installer:
             if not package.downloadPackage(self.http):
                 Installer.notify.error("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
-        
+
         # Also install the 'images' package from the same host that p3dembed was downloaded from.
         imageHost = host
         if host.hostUrl != self.standalone.host.hostUrl:
@@ -258,7 +298,7 @@ class Installer:
         if superHost:
             imagePackages += superHost.getPackages(name = "images")
         imagePackages += imageHost.getPackages(name = "images")
-            
+
         for package in imagePackages:
             package.installed = True # Hack not to let it unnecessarily install itself
             packages.append(package)
@@ -269,7 +309,7 @@ class Installer:
                 Installer.notify.error("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             break
-        
+
         # Remove the extracted files from the compressed archive, to save space.
         vfs = VirtualFileSystem.getGlobalPtr()
         for package in packages:
@@ -277,7 +317,7 @@ class Installer:
                 archive = Filename(package.getPackageDir(), package.uncompressedArchive.filename)
                 if not archive.exists():
                     continue
-                
+
                 mf = Multifile()
                 # Make sure that it isn't mounted before altering it, just to be safe
                 vfs.unmount(archive)
@@ -285,7 +325,7 @@ class Installer:
                 if not mf.openReadWrite(archive):
                     Installer.notify.warning("Failed to open archive %s" % (archive))
                     continue
-                
+
                 # We don't iterate over getNumSubfiles because we're
                 # removing subfiles while we're iterating over them.
                 subfiles = mf.getSubfileNames()
@@ -294,10 +334,10 @@ class Installer:
                     if Filename(package.getPackageDir(), subfile).exists():
                         Installer.notify.debug("Removing already-extracted %s from multifile" % (subfile))
                         mf.removeSubfile(subfile)
-                
+
                 # This seems essential for mf.close() not to crash later.
                 mf.repack()
-                
+
                 # If we have no subfiles left, we can just remove the multifile.
                 if mf.getNumSubfiles() == 0:
                     Installer.notify.info("Removing empty archive %s" % (package.uncompressedArchive.filename))
@@ -306,12 +346,12 @@ class Installer:
                 else:
                     mf.close()
                     os.chmod(archive.toOsSpecific(), 0400)
-        
+
         # Write out our own contents.xml file.
         doc = TiXmlDocument()
         decl = TiXmlDeclaration("1.0", "utf-8", "")
         doc.InsertEndChild(decl)
-        
+
         xcontents = TiXmlElement("contents")
         for package in packages:
             xpackage = TiXmlElement('package')
@@ -331,57 +371,85 @@ class Installer:
     def buildAll(self, outputDir = "."):
         """ Creates a (graphical) installer for every known platform.
         Call this after you have set the desired parameters. """
-        
+
         platforms = set()
         for package in self.standalone.host.getPackages(name = "p3dembed"):
             platforms.add(package.platform)
         if len(platforms) == 0:
             Installer.notify.warning("No platforms found to build for!")
-        
+
         outputDir = Filename(outputDir + "/")
         outputDir.makeDir()
         for platform in platforms:
             output = Filename(outputDir, platform + "/")
             output.makeDir()
             self.build(output, platform)
-    
+
     def build(self, output, platform = None):
-        """ Builds a (graphical) installer and stores it into the path
+        """ Builds (graphical) installers and stores it into the path
         indicated by the 'output' argument. You can specify to build for
         a different platform by altering the 'platform' argument.
         If 'output' is a directory, the installer will be stored in it. """
-        
+
         if platform == None:
             platform = PandaSystem.getPlatform()
-        
+
         if platform == "win32":
-            return self.buildNSIS(output, platform)
+            self.buildNSIS(output, platform)
+            return
         elif "_" in platform:
-            os, arch = platform.split("_", 1)
-            if os == "linux":
-                return self.buildDEB(output, platform)
-            elif os == "osx":
-                return self.buildPKG(output, platform)
-            elif os == "freebsd":
-                return self.buildDEB(output, platform)
+            osname, arch = platform.split("_", 1)
+            if osname == "linux":
+                self.buildDEB(output, platform)
+                self.buildArch(output, platform)
+                return
+            elif osname == "osx":
+                self.buildPKG(output, platform)
+                return
         Installer.notify.info("Ignoring unknown platform " + platform)
 
+    def __buildTempLinux(self, platform):
+        """ Builds a filesystem for Linux.  Used so that buildDEB,
+        buildRPM and buildArch can share the same temp directory. """
+
+        tempdir = Filename(self.tempDir, platform)
+        if tempdir.exists():
+            return tempdir
+
+        tempdir.makeDir()
+        Filename(tempdir, "usr/bin/").makeDir()
+        if self.includeRequires:
+            extraTokens = {"host_dir" : "/usr/lib/" + self.shortname.lower()}
+        else:
+            extraTokens = {}
+        self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform, extraTokens)
+        if not self.licensefile.empty():
+            Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
+            shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific())
+            shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/LICENSE" % self.shortname.lower()).toOsSpecific())
+
+        if self.includeRequires:
+            hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
+            hostDir.makeDir()
+            self.installPackagesInto(hostDir, platform)
+
+        return tempdir
+
     def buildDEB(self, output, platform):
         """ Builds a .deb archive and stores it in the path indicated
         by the 'output' argument. It will be built for the architecture
         specified by the 'arch' argument.
         If 'output' is a directory, the deb file will be stored in it. """
-        
+
         arch = platform.rsplit("_", 1)[-1]
         output = Filename(output)
         if output.isDirectory():
             output = Filename(output, "%s_%s_%s.deb" % (self.shortname.lower(), self.version, arch))
         Installer.notify.info("Creating %s..." % output)
+        modtime = int(time.time())
 
-        # Create a temporary directory and write the control file + launcher to it
-        tempdir = Filename.temporary("", self.shortname.lower() + "_deb_", "") + "/"
-        tempdir.makeDir()
-        controlfile = open(Filename(tempdir, "control").toOsSpecific(), "w")
+        # Create a control file in memory.
+        controlfile = StringIO()
         print >>controlfile, "Package: %s" % self.shortname.lower()
         print >>controlfile, "Version: %s" % self.version
         print >>controlfile, "Maintainer: %s <%s>" % (self.authorname, self.authoremail)
@@ -390,75 +458,117 @@ class Installer:
         print >>controlfile, "Architecture: %s" % arch
         print >>controlfile, "Description: %s" % self.fullname
         print >>controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6"
-        controlfile.close()
-        Filename(tempdir, "usr/bin/").makeDir()
-        if self.includeRequires:
-            self.standalone.tokens["host_dir"] = "/usr/lib/" + self.shortname.lower()
-        elif "host_dir" in self.standalone.tokens:
-            del self.standalone.tokens["host_dir"]
-        self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform)
-        if not self.licensefile.empty():
-            Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
-            shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific())
-        hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
-        hostDir.makeDir()
-        self.installPackagesInto(hostDir, platform)
+        controlinfo = TarInfoRoot("control")
+        controlinfo.mtime = modtime
+        controlinfo.size = controlfile.tell()
+        controlfile.seek(0)
 
-        # Create a control.tar.gz file in memory
-        controlfile = Filename(tempdir, "control")
-        controltargz = CachedFile()
-        controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
-        controltarfile.add(controlfile.toOsSpecific(), "control")
-        controltarfile.close()
-        controlfile.unlink()
-
-        # Create the data.tar.gz file in the temporary directory
-        datatargz = CachedFile()
-        datatarfile = tarfile.TarFile.gzopen("data.tar.gz", "w", datatargz, 9)
-        datatarfile.add(Filename(tempdir, "usr").toOsSpecific(), "/usr")
-        datatarfile.close()
+        # Create a temporary directory and write the launcher and dependencies to it.
+        tempdir = self.__buildTempLinux(platform)
 
         # Open the deb file and write to it. It's actually
         # just an AR file, which is very easy to make.
-        modtime = int(time.time())
         if output.exists():
             output.unlink()
         debfile = open(output.toOsSpecific(), "wb")
         debfile.write("!<arch>\x0A")
         debfile.write("debian-binary   %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 4))
         debfile.write("2.0\x0A")
-        debfile.write("control.tar.gz  %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, len(controltargz.str)))
-        debfile.write(controltargz.str)
-        if (len(controltargz.str) & 1): debfile.write("\x0A")
-        debfile.write("data.tar.gz     %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, len(datatargz.str)))
-        debfile.write(datatargz.str)
-        if (len(datatargz.str) & 1): debfile.write("\x0A")
+
+        # Write the control.tar.gz to the archive.
+        debfile.write("control.tar.gz  %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 0))
+        ctaroffs = debfile.tell()
+        ctarfile = tarfile.open("control.tar.gz", "w:gz", debfile, tarinfo = TarInfoRoot)
+        ctarfile.addfile(controlinfo, controlfile)
+        ctarfile.close()
+        ctarsize = debfile.tell() - ctaroffs
+        if (ctarsize & 1): debfile.write("\x0A")
+
+        # Write the data.tar.gz to the archive.
+        debfile.write("data.tar.gz     %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 0))
+        dtaroffs = debfile.tell()
+        dtarfile = tarfile.open("data.tar.gz", "w:gz", debfile, tarinfo = TarInfoRoot)
+        dtarfile.add(Filename(tempdir, "usr").toOsSpecific(), "/usr")
+        dtarfile.close()
+        dtarsize = debfile.tell() - dtaroffs
+        if (dtarsize & 1): debfile.write("\x0A")
+
+        # Write the correct sizes of the archives.
+        debfile.seek(ctaroffs - 12)
+        debfile.write("%-10ld" % ctarsize)
+        debfile.seek(dtaroffs - 12)
+        debfile.write("%-10ld" % dtarsize)
+
         debfile.close()
-        try:
-            appRunner.rmtree(tempdir)
-        except:
-            try: shutil.rmtree(tempdir.toOsSpecific())
-            except: pass
+
+        return output
+
+    def buildArch(self, output, platform):
+        """ Builds an ArchLinux package and stores it in the path
+        indicated by the 'output' argument. It will be built for the
+        architecture specified by the 'arch' argument.
+        If 'output' is a directory, the deb file will be stored in it. """
+
+        arch = platform.rsplit("_", 1)[-1]
+        assert arch in ("i386", "amd64")
+        arch = {"i386" : "i686", "amd64" : "x86_64"}[arch]
+        pkgver = self.version + "-1"
+
+        output = Filename(output)
+        if output.isDirectory():
+            output = Filename(output, "%s-%s-%s.pkg.tar.gz" % (self.shortname.lower(), pkgver, arch))
+        Installer.notify.info("Creating %s..." % output)
+        modtime = int(time.time())
+
+        # Create a pkginfo file in memory.
+        pkginfo = StringIO()
+        print >>pkginfo, "# Generated using pdeploy"
+        print >>pkginfo, "# %s" % time.ctime(modtime)
+        print >>pkginfo, "pkgname = %s" % self.shortname.lower()
+        print >>pkginfo, "pkgver = %s" % pkgver
+        print >>pkginfo, "pkgdesc = %s" % self.fullname
+        print >>pkginfo, "builddate = %s" % modtime
+        print >>pkginfo, "packager = %s <%s>" % (self.authorname, self.authoremail)
+        print >>pkginfo, "arch = %s" % arch
+        if self.licensename != "":
+            print >>pkginfo, "license = %s" % self.licensename
+        pkginfoinfo = TarInfoRoot(".PKGINFO")
+        pkginfoinfo.mtime = modtime
+        pkginfoinfo.size = pkginfo.tell()
+        pkginfoinfo.seek(0)
+
+        # Create a temporary directory and write the launcher and dependencies to it.
+        tempdir = self.__buildTempLinux(platform)
+
+        # Create the actual package now.
+        pkgfile = tarfile.open(output.toOsSpecific(), "w:gz", tarinfo = TarInfoRoot)
+        pkgfile.addfile(pkginfoinfo, pkginfo)
+        pkgfile.add(tempdir.toOsSpecific(), "/")
+        if not self.licensefile.empty():
+            pkgfile.add(self.licensefile.toOsSpecific(), "/usr/share/licenses/%s/LICENSE" % self.shortname.lower())
+        pkgfile.close()
+
+        return output
 
     def buildAPP(self, output, platform):
-        
+
         output = Filename(output)
         if output.isDirectory() and output.getExtension() != 'app':
             output = Filename(output, "%s.app" % self.fullname)
         Installer.notify.info("Creating %s..." % output)
-        
+
         # Create the executable for the application bundle
         exefile = Filename(output, "Contents/MacOS/" + self.shortname)
         exefile.makeDir()
         if self.includeRequires:
-            self.standalone.tokens["host_dir"] = "../Resources"
-        elif "host_dir" in self.standalone.tokens:
-            del self.standalone.tokens["host_dir"]
-        self.standalone.build(exefile, platform)
+            extraTokens = {"host_dir" : "../Resources"}
+        else:
+            extraTokens = {}
+        self.standalone.build(exefile, platform, extraTokens)
         hostDir = Filename(output, "Contents/Resources/")
         hostDir.makeDir()
         self.installPackagesInto(hostDir, platform)
-        
+
         # Create the application plist file.
         # Although it might make more sense to use Python's plistlib module here,
         # it is not available on non-OSX systems before Python 2.6.
@@ -494,7 +604,7 @@ class Installer:
         print >>plist, '</dict>'
         print >>plist, '</plist>'
         plist.close()
-        
+
         return output
 
     def buildPKG(self, output, platform):
@@ -504,7 +614,7 @@ class Installer:
         if output.isDirectory():
             output = Filename(output, "%s %s.pkg" % (self.fullname, self.version))
         Installer.notify.info("Creating %s..." % output)
-        
+
         Filename(output, "Contents/Resources/en.lproj/").makeDir()
         if self.licensefile:
             shutil.copyfile(self.licensefile.toOsSpecific(), Filename(output, "Contents/Resources/License.txt").toOsSpecific())
@@ -514,7 +624,7 @@ class Installer:
         pkginfo = open(Filename(output, "Contents/Resources/package_version").toOsSpecific(), "w")
         pkginfo.write("major: 1\nminor: 9")
         pkginfo.close()
-        
+
         # Although it might make more sense to use Python's plistlib here,
         # it is not available on non-OSX systems before Python 2.6.
         plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
@@ -560,7 +670,7 @@ class Installer:
         plist.write('</dict>\n')
         plist.write('</plist>\n')
         plist.close()
-        
+
         plist = open(Filename(output, "Contents/Resources/TokenDefinitions.plist").toOsSpecific(), "w")
         plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
         plist.write('<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
@@ -580,7 +690,7 @@ class Installer:
         plist.write('</dict>\n')
         plist.write('</plist>\n')
         plist.close()
-        
+
         plist = open(Filename(output, "Contents/Resources/en.lproj/Description.plist").toOsSpecific(), "w")
         plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
         plist.write('<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
@@ -593,27 +703,27 @@ class Installer:
         plist.write('</dict>\n')
         plist.write('</plist>\n')
         plist.close()
-        
+
         if hasattr(tarfile, "PAX_FORMAT"):
-            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT)
+            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT, tarinfo = TarInfoRootOSX)
         else:
-            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz")
+            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", tarinfo = TarInfoRootOSX)
         archive.add(appfn.toOsSpecific(), appname)
         archive.close()
-        
+
         # Put the .pkg into a zipfile
         archive = Filename(output.getDirname(), "%s %s.zip" % (self.fullname, self.version))
         dir = Filename(output.getDirname())
         dir.makeAbsolute()
         zip = zipfile.ZipFile(archive.toOsSpecific(), 'w')
-        for root, dirs, files in os.walk(output.toOsSpecific()):
+        for root, dirs, files in self.os_walk(output.toOsSpecific()):
             for name in files:
                 file = Filename.fromOsSpecific(os.path.join(root, name))
                 file.makeAbsolute()
                 file.makeRelativeTo(dir)
                 zip.write(os.path.join(root, name), str(file))
         zip.close()
-        
+
         return output
 
     def buildNSIS(self, output, platform):
@@ -632,11 +742,11 @@ class Installer:
             for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
                 if os.path.isfile(os.path.join(p, "makensis")):
                     makensis = os.path.join(p, "makensis")
-        
+
         if makensis == None:
             Installer.notify.warning("Makensis utility not found, no Windows installer will be built!")
-            return
-        
+            return None
+
         output = Filename(output)
         if output.isDirectory():
             output = Filename(output, "%s %s.exe" % (self.fullname, self.version))
@@ -647,15 +757,16 @@ class Installer:
         exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
         exefile.unlink()
         if self.includeRequires:
-            self.standalone.tokens["host_dir"] = "."
-        elif "host_dir" in self.standalone.tokens:
-            del self.standalone.tokens["host_dir"]
-        self.standalone.build(exefile, platform)
-        
+            extraTokens = {"host_dir" : "."}
+        else:
+            extraTokens = {}
+        self.standalone.build(exefile, platform, extraTokens)
+
         # Temporary directory to store the hostdir in
-        hostDir = Filename.temporary("", self.shortname.lower() + "_exe_", "") + "/"
-        hostDir.makeDir()
-        self.installPackagesInto(hostDir, platform)
+        hostDir = Filename(self.tempDir, platform + "/")
+        if not hostDir.exists():
+            hostDir.makeDir()
+            self.installPackagesInto(hostDir, platform)
 
         nsifile = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
         nsifile.unlink()
@@ -755,11 +866,6 @@ class Installer:
             self.notify.warning("Unable to invoke NSIS command.")
 
         nsifile.unlink()
-        try:
-            appRunner.rmtree(hostDir)
-        except:
-            try: shutil.rmtree(hostDir.toOsSpecific())
-            except: pass
         return output
 
     def os_walk(self, top):

+ 2 - 2
direct/src/p3d/pdeploy.py

@@ -218,7 +218,7 @@ elif not outputDir.isDirectory():
 if deploy_mode == 'standalone':
     s = Standalone(appFilename, tokens)
     s.basename = shortname
-    
+
     if currentPlatform:
         platform = PandaSystem.getPlatform()
         if platform.startswith("win"):
@@ -244,7 +244,7 @@ elif deploy_mode == 'installer':
     i.authorname = authorname
     i.authoremail = authoremail
     i.includeRequires = includeRequires
-    
+
     if currentPlatform:
         platform = PandaSystem.getPlatform()
         if platform.startswith("win"):