diff --git a/direct/src/p3d/Packager.py b/direct/src/p3d/Packager.py index 8361c7412a..3e3824ba26 100644 --- a/direct/src/p3d/Packager.py +++ b/direct/src/p3d/Packager.py @@ -141,16 +141,17 @@ class Packager: return False class ExcludeFilename: - def __init__(self, filename, caseSensitive): + def __init__(self, packager, filename, caseSensitive): + self.packager = packager self.localOnly = (not filename.getDirname()) if not self.localOnly: filename = Filename(filename) filename.makeCanonical() self.glob = GlobPattern(filename.cStr()) - if PandaSystem.getPlatform().startswith('win'): + if self.packager.platform.startswith('win'): self.glob.setCaseSensitive(False) - elif PandaSystem.getPlatform().startswith('osx'): + elif self.packager.platform.startswith('osx'): self.glob.setCaseSensitive(False) def matches(self, filename): @@ -293,8 +294,15 @@ class Packager: self.packageName = packageName self.packager = packager self.notify = packager.notify - + + # The platform is initially None until we know the file is + # platform-specific. self.platform = None + + # The arch string, though, is pre-loaded from the system + # arch string, so we can sensibly call otool. + self.arch = self.packager.arch + self.version = None self.host = None self.p3dApplication = False @@ -326,7 +334,7 @@ class Packager: # This records the current list of modules we have added so # far. - self.freezer = FreezeTool.Freezer() + self.freezer = FreezeTool.Freezer(platform = self.packager.platform) def close(self): """ Writes out the contents of the current package. """ @@ -414,7 +422,11 @@ class Packager: if platformSpecific and self.platformSpecificConfig is not False: if not self.platform: - self.platform = PandaSystem.getPlatform() + self.platform = self.packager.platform + + if self.platform and self.platform.startswith('osx_'): + # Get the OSX "arch" specification. + self.arch = self.platform[4:] def installMultifile(self): @@ -518,9 +530,9 @@ class Packager: self.components.append(('m', newName.lower(), xmodule)) # Now look for implicit shared-library dependencies. - if PandaSystem.getPlatform().startswith('win'): + if self.packager.platform.startswith('win'): self.__addImplicitDependenciesWindows() - elif PandaSystem.getPlatform().startswith('osx'): + elif self.packager.platform.startswith('osx'): self.__addImplicitDependenciesOSX() else: self.__addImplicitDependenciesPosix() @@ -752,7 +764,7 @@ class Packager: def excludeFile(self, filename): """ Excludes the named file (or glob pattern) from the package. """ - xfile = Packager.ExcludeFilename(filename, self.packager.caseSensitive) + xfile = Packager.ExcludeFilename(self.packager, filename, self.packager.caseSensitive) self.excludedFilenames.append(xfile) def __addImplicitDependenciesWindows(self): @@ -910,13 +922,17 @@ class Packager: continue tempFile = Filename.temporary('', 'p3d_', '.txt') - command = '/usr/bin/otool -L "%s" >"%s"' % ( + command = '/usr/bin/otool -arch all -L "%s" >"%s"' % ( file.filename.toOsSpecific(), tempFile.toOsSpecific()) - try: - os.system(command) - except: - pass + if self.arch: + command = '/usr/bin/otool -arch %s -L "%s" >"%s"' % ( + self.arch, + file.filename.toOsSpecific(), + tempFile.toOsSpecific()) + exitStatus = os.system(command) + if exitStatus != 0: + self.notify.warning('Command failed: %s' % (command)) filenames = None if tempFile.exists(): @@ -1619,7 +1635,13 @@ class Packager: stream = StringStream(file.text) self.multifile.addSubfile(file.newName, stream, compressionLevel) self.multifile.flush() + + elif file.executable and self.arch: + if not self.__addOsxExecutable(file): + return + else: + # Copy an ordinary file into the multifile. self.multifile.addSubfile(file.newName, file.filename, compressionLevel) if file.extract: if file.text: @@ -1639,6 +1661,72 @@ class Packager: xcomponent.SetAttribute('filename', file.newName) self.components.append(('c', file.newName.lower(), xcomponent)) + def __addOsxExecutable(self, file): + """ Adds an executable or shared library to the multifile, + with respect to OSX's fat-binary features. Returns true + on success, false on failure. """ + + compressionLevel = 0 + if file.compress: + compressionLevel = self.compressionLevel + + # If we're on OSX and adding only files for a + # particular architecture, use lipo to strip out the + # part of the file for that architecture. + + # First, we need to verify that it is in fact a + # universal binary. + tfile = Filename.temporary('', 'p3d_') + command = '/usr/bin/lipo -info "%s" >"%s"' % ( + file.filename.toOsSpecific(), + tfile.toOsSpecific()) + exitStatus = os.system(command) + if exitStatus != 0: + self.notify.warning("Not an executable file: %s" % (file.filename)) + # Just add it anyway. + self.multifile.addSubfile(file.newName, file.filename, compressionLevel) + return True + + # The lipo command succeeded, so it really is an + # executable file. Parse the lipo output to figure out + # which architectures the file supports. + arches = [] + lipoData = open(tfile.toOsSpecific(), 'r').read() + tfile.unlink() + if ':' in lipoData: + arches = lipoData.rsplit(':', 1)[1] + arches = arches.split() + + if arches == [self.arch]: + # The file only contains the one architecture that + # we want anyway. + self.multifile.addSubfile(file.newName, file.filename, compressionLevel) + return True + + if self.arch not in arches: + # The file doesn't support the architecture that we + # want at all. Omit the file. + self.notify.warning("%s doesn't support architecture %s" % ( + file.filename, self.arch)) + return False + + + # The file contains multiple architectures. Get + # out just the one we want. + command = '/usr/bin/lipo -thin %s -output "%s" "%s"' % ( + self.arch, tfile.toOsSpecific(), + file.filename.toOsSpecific()) + exitStatus = os.system(command) + if exitStatus != 0: + self.notify.error('Command failed: %s' % (command)) + self.multifile.addSubfile(file.newName, tfile, compressionLevel) + if file.deleteTemp: + file.filename.unlink() + file.filename = tfile + file.deleteTemp = True + return True + + def requirePackage(self, package): """ Indicates a dependency on the given package. This also implicitly requires all of the package's requirements @@ -1655,11 +1743,14 @@ class Packager: self.skipModules[moduleName] = mdef # Packager constructor - def __init__(self): + def __init__(self, platform = None): # The following are config settings that the caller may adjust # before calling any of the command methods. + # The platform string. + self.setPlatform(platform) + # This should be set to a Filename. self.installDir = None @@ -1670,13 +1761,13 @@ class Packager: self.addHost(self.host) # A search list for previously-built local packages. - self.installSearch = ConfigVariableSearchPath('pdef-path') + self.installSearch = list(ConfigVariableSearchPath('pdef-path').getDirectories()) # The system PATH, for searching dll's and exe's. self.executablePath = DSearchPath() - if PandaSystem.getPlatform().startswith('win'): + if self.platform.startswith('win'): self.addWindowsSearchPath(self.executablePath, "PATH") - elif PandaSystem.getPlatform().startswith('osx'): + elif self.platform.startswith('osx'): self.addPosixSearchPath(self.executablePath, "DYLD_LIBRARY_PATH") self.addPosixSearchPath(self.executablePath, "LD_LIBRARY_PATH") self.addPosixSearchPath(self.executablePath, "PATH") @@ -1690,6 +1781,7 @@ class Packager: self.executablePath.appendDirectory('/usr/lib') self.executablePath.appendDirectory('/usr/local/lib') + import platform if platform.uname()[1]=="pcbsd": self.executablePath.appendDirectory('/usr/PCBSD/local/lib') @@ -1702,9 +1794,6 @@ class Packager: # generated. self.signParams = [] - # The platform string. - self.platform = PandaSystem.getPlatform() - # Optional signing and encrypting features. self.encryptionKey = None self.prcEncryptionKey = None @@ -1729,9 +1818,9 @@ class Packager: # Is this file system case-sensitive? self.caseSensitive = True - if PandaSystem.getPlatform().startswith('win'): + if self.platform.startswith('win'): self.caseSensitive = False - elif PandaSystem.getPlatform().startswith('osx'): + elif self.platform.startswith('osx'): self.caseSensitive = False # Get the list of filename extensions that are recognized as @@ -1847,6 +1936,24 @@ class Packager: # file. self.contents = {} + def setPlatform(self, platform = None): + """ Sets the platform that this Packager will compute for. On + OSX, this can be used to specify the particular architecture + we are building; on other platforms, it is probably a mistake + to set this. + + You should call this before doing anything else with the + Packager. It's even better to pass the platform string to the + constructor. """ + + self.platform = platform or PandaSystem.getPlatform() + + # OSX uses this "arch" string for the otool and lipo commands. + self.arch = None + if self.platform.startswith('osx_'): + self.arch = self.platform[4:] + + def setHost(self, host, downloadUrl = None, descriptiveName = None, hostDir = None, mirrors = None): @@ -1989,7 +2096,7 @@ class Packager: globals = {} globals['__name__'] = packageDef.getBasenameWoExtension() - globals['platform'] = PandaSystem.getPlatform() + globals['platform'] = self.platform globals['packager'] = self # We'll stuff all of the predefined functions, and the @@ -2176,7 +2283,7 @@ class Packager: return package # Look on the searchlist. - for dirname in self.installSearch.getDirectories(): + for dirname in self.installSearch: package = self.__scanPackageDir(dirname, packageName, platform or self.platform, version, host, requires = requires) if not package: package = self.__scanPackageDir(dirname, packageName, platform, version, host, requires = requires) @@ -2590,7 +2697,7 @@ class Packager: self.do_file('p3dcert.exe') self.do_file('p3dpython.exe') - if PandaSystem.getPlatform().startswith('win'): + if self.platform.startswith('win'): self.do_file('p3dpythonw.exe') self.do_file('libp3dpython.dll') @@ -2640,7 +2747,7 @@ class Packager: deleteTemp = True, explicit = True, extract = True) package.addExtensionModules() if not package.platform: - package.platform = PandaSystem.getPlatform() + package.platform = self.platform # Reset the freezer for more Python files. freezer.reset() diff --git a/direct/src/p3d/ppackage.py b/direct/src/p3d/ppackage.py index 47a2ad4f06..55960c3f28 100755 --- a/direct/src/p3d/ppackage.py +++ b/direct/src/p3d/ppackage.py @@ -79,10 +79,23 @@ Options: initially, but should not be set on an application intended for deployment. + -u + On the Mac OSX platform, this means that Panda was built with + universal binaries, and the package should be built that way as + well (that is, a version of the the package should be created for + each supported architecture). On other platforms, this option + does nothing. This is therefore safe to apply in all cases, if + you wish to take advantage of universal binaries. This is + equivalent to "-P osx_ppc -P osx_i386" on Mac platforms. + -P platform Specify the platform to masquerade as. The default is whatever - platform Panda has been built for. It is probably unwise to set - this, unless you know what you are doing. + platform Panda has been built for. You can use this on Mac OSX + in order to build packages for an alternate architecture if you + have built Panda with universal binaries; you may repeat this + option for each architecture you wish to support. For other + platforms, it is probably a mistake to set this. However, see + the option -u. -h Display this help @@ -100,31 +113,38 @@ def usage(code, msg = ''): print >> sys.stderr, msg sys.exit(code) -packager = Packager.Packager() +installDir = None buildPatches = False +installSearch = [] +signParams = [] +allowPythonDev = False +universalBinaries = False +platforms = [] try: - opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DP:h') + opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DuP:h') except getopt.error, msg: usage(1, msg) for opt, arg in opts: if opt == '-i': - packager.installDir = Filename.fromOsSpecific(arg) + installDir = Filename.fromOsSpecific(arg) elif opt == '-p': buildPatches = True elif opt == '-s': - packager.installSearch.appendDirectory(Filename.fromOsSpecific(arg)) + installSearch.appendDirectory(Filename.fromOsSpecific(arg)) elif opt == '-S': tokens = arg.split(',') while len(tokens) < 4: tokens.append('') certificate, chain, pkey, password = tokens[:4] - packager.signParams.append((certificate, chain, pkey, password)) + signParams.append((certificate, chain, pkey, password)) elif opt == '-D': - packager.allowPythonDev = True + allowPythonDev = True + elif opt == '-u': + universalBinaries = True elif opt == '-P': - packager.platform = arg + platforms.append(arg) elif opt == '-h': usage(0) @@ -140,24 +160,41 @@ packageNames = None if len(args) > 1: packageNames = args[1:] -if not packager.installDir: +if not installDir: print '\nYou must name the target install directory with the -i parameter.\n' sys.exit(1) -packager.installSearch.prependDirectory(packager.installDir) -try: - packager.setup() - packages = packager.readPackageDef(packageDef, packageNames = packageNames) - packager.close() - if buildPatches: - packager.buildPatches(packages) - -except Packager.PackagerError: - # Just print the error message and exit gracefully. - inst = sys.exc_info()[1] - print inst.args[0] - #raise - sys.exit(1) +if universalBinaries: + if platforms: + print '\nYou may not specify both -u and -P.\n' + sys.exit(1) + if PandaSystem.getPlatform().startswith('osx_'): + # Maybe soon we'll add osx_x86_64 to this default list. + platforms = ['osx_i386', 'osx_ppc'] + +if not platforms: + platforms = [PandaSystem.getPlatform()] + +for platform in platforms: + packager = Packager.Packager(platform = platform) + packager.installDir = installDir + packager.installSearch = [installDir] + installSearch + packager.installSearch + packager.signParams = signParams + packager.allowPythonDev = allowPythonDev + + try: + packager.setup() + packages = packager.readPackageDef(packageDef, packageNames = packageNames) + packager.close() + if buildPatches: + packager.buildPatches(packages) + + except Packager.PackagerError: + # Just print the error message and exit gracefully. + inst = sys.exc_info()[1] + print inst.args[0] + #raise + sys.exit(1) # An explicit call to exit() is required to exit the program, when # this module is packaged in a p3d file. diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index ef71fc175a..ab7c193d1d 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -46,7 +46,9 @@ class CompilationEnvironment: can create a custom instance of this class (or simply set the compile strings directly) to customize the build environment. """ - def __init__(self): + def __init__(self, platform): + self.platform = platform + # The command to compile a c to an object file. Replace %(basename)s # with the basename of the source file, and an implicit .c extension. self.compileObj = 'error' @@ -82,7 +84,7 @@ class CompilationEnvironment: self.determineStandardSetup() def determineStandardSetup(self): - if sys.platform == 'win32': + if self.platform == 'win32': self.Python = PREFIX if ('VCINSTALLDIR' in os.environ): @@ -123,10 +125,9 @@ class CompilationEnvironment: self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\lib" /LIBPATH:"%(python)s\libs" /out:%(basename)s.exe %(basename)s.obj' self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\lib" /LIBPATH:"%(python)s\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj' - elif sys.platform == 'darwin': + elif self.platform.startswith('osx_'): # OSX - plat = PandaSystem.getPlatform() - proc = plat.split('_', 1)[1] + proc = self.platform.split('_', 1)[1] if proc == 'i386': self.arch = '-arch i386' elif proc == 'ppc': @@ -535,10 +536,11 @@ class Freezer: args.append('allowChildren = True') return 'ModuleDef(%s)' % (', '.join(args)) - def __init__(self, previous = None, debugLevel = 0): + def __init__(self, previous = None, debugLevel = 0, + platform = None): # Normally, we are freezing for our own platform. Change this # if untrue. - self.platform = sys.platform + self.platform = platform or PandaSystem.getPlatform() # This is the compilation environment. Fill in your own # object here if you have custom needs (for instance, for a @@ -1216,7 +1218,7 @@ class Freezer: dllimport = '__declspec(dllimport) ' if not self.cenv: - self.cenv = CompilationEnvironment() + self.cenv = CompilationEnvironment(platform = self.platform) if compileToExe: code = self.frozenMainCode