use lipo to support universal binaries (mostly) automatically
This commit is contained in:
parent
086e53a453
commit
5201b9bfcd
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue