Update OS X installer build script to be Python 3 compatible.

Builds are not yet fully supported on Python 3 as some parts
of the build process (like the documentation build chain)
are still dependent on Python 2.
This commit is contained in:
Ned Deily 2010-11-27 16:42:15 -08:00
parent 5b0eca116a
commit 7d9cf83f2f
1 changed files with 88 additions and 64 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env python
""" """
This script is used to build "official" universal installers on Mac OS X. This script is used to build "official" universal installers on Mac OS X.
It requires at least Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK for It requires at least Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK for
@ -12,16 +12,25 @@ Python 2.4.
Usage: see USAGE variable in the script. Usage: see USAGE variable in the script.
""" """
import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
import grp try:
import urllib2 as urllib_request
except ImportError:
import urllib.request as urllib_request
STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH )
STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH )
INCLUDE_TIMESTAMP = 1 INCLUDE_TIMESTAMP = 1
VERBOSE = 1 VERBOSE = 1
from plistlib import Plist from plistlib import Plist
import MacOS
try: try:
from plistlib import writePlist from plistlib import writePlist
except ImportError: except ImportError:
@ -42,20 +51,35 @@ def grepValue(fn, variable):
if ln.startswith(variable): if ln.startswith(variable):
value = ln[len(variable):].strip() value = ln[len(variable):].strip()
return value[1:-1] return value[1:-1]
raise RuntimeError, "Cannot find variable %s" % variable[:-1] raise RuntimeError("Cannot find variable %s" % variable[:-1])
_cache_getVersion = None
def getVersion(): def getVersion():
return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION') global _cache_getVersion
if _cache_getVersion is None:
_cache_getVersion = grepValue(
os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
return _cache_getVersion
def getVersionTuple(): def getVersionTuple():
return tuple([int(n) for n in getVersion().split('.')]) return tuple([int(n) for n in getVersion().split('.')])
def getVersionMajorMinor():
return tuple([int(n) for n in getVersion().split('.', 2)])
_cache_getFullVersion = None
def getFullVersion(): def getFullVersion():
global _cache_getFullVersion
if _cache_getFullVersion is not None:
return _cache_getFullVersion
fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h') fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
for ln in open(fn): for ln in open(fn):
if 'PY_VERSION' in ln: if 'PY_VERSION' in ln:
return ln.split()[-1][1:-1] _cache_getFullVersion = ln.split()[-1][1:-1]
raise RuntimeError, "Cannot find full version??" return _cache_getFullVersion
raise RuntimeError("Cannot find full version??")
# The directory we'll use to create the build (will be erased and recreated) # The directory we'll use to create the build (will be erased and recreated)
WORKDIR = "/tmp/_py" WORKDIR = "/tmp/_py"
@ -369,7 +393,7 @@ def fileContents(fn):
""" """
Return the contents of the named file Return the contents of the named file
""" """
return open(fn, 'rb').read() return open(fn, 'r').read()
def runCommand(commandline): def runCommand(commandline):
""" """
@ -381,7 +405,7 @@ def runCommand(commandline):
xit = fd.close() xit = fd.close()
if xit is not None: if xit is not None:
sys.stdout.write(data) sys.stdout.write(data)
raise RuntimeError, "command failed: %s"%(commandline,) raise RuntimeError("command failed: %s"%(commandline,))
if VERBOSE: if VERBOSE:
sys.stdout.write(data); sys.stdout.flush() sys.stdout.write(data); sys.stdout.flush()
@ -392,7 +416,7 @@ def captureCommand(commandline):
xit = fd.close() xit = fd.close()
if xit is not None: if xit is not None:
sys.stdout.write(data) sys.stdout.write(data)
raise RuntimeError, "command failed: %s"%(commandline,) raise RuntimeError("command failed: %s"%(commandline,))
return data return data
@ -461,12 +485,12 @@ def checkEnvironment():
for ev in list(os.environ): for ev in list(os.environ):
for prefix in environ_var_prefixes: for prefix in environ_var_prefixes:
if ev.startswith(prefix) : if ev.startswith(prefix) :
print "INFO: deleting environment variable %s=%s" % ( print("INFO: deleting environment variable %s=%s" % (
ev, os.environ[ev]) ev, os.environ[ev]))
del os.environ[ev] del os.environ[ev]
os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin' os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
print "Setting default PATH: %s"%(os.environ['PATH']) print("Setting default PATH: %s"%(os.environ['PATH']))
def parseOptions(args=None): def parseOptions(args=None):
@ -483,18 +507,18 @@ def parseOptions(args=None):
options, args = getopt.getopt(args, '?hb', options, args = getopt.getopt(args, '?hb',
[ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=', [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
'dep-target=', 'universal-archs=', 'help' ]) 'dep-target=', 'universal-archs=', 'help' ])
except getopt.error, msg: except getopt.GetoptError:
print msg print(sys.exc_info()[1])
sys.exit(1) sys.exit(1)
if args: if args:
print "Additional arguments" print("Additional arguments")
sys.exit(1) sys.exit(1)
deptarget = None deptarget = None
for k, v in options: for k, v in options:
if k in ('-h', '-?', '--help'): if k in ('-h', '-?', '--help'):
print USAGE print(USAGE)
sys.exit(0) sys.exit(0)
elif k in ('-d', '--build-dir'): elif k in ('-d', '--build-dir'):
@ -522,10 +546,10 @@ def parseOptions(args=None):
# target # target
DEPTARGET = default_target_map.get(v, '10.3') DEPTARGET = default_target_map.get(v, '10.3')
else: else:
raise NotImplementedError, v raise NotImplementedError(v)
else: else:
raise NotImplementedError, k raise NotImplementedError(k)
SRCDIR=os.path.abspath(SRCDIR) SRCDIR=os.path.abspath(SRCDIR)
WORKDIR=os.path.abspath(WORKDIR) WORKDIR=os.path.abspath(WORKDIR)
@ -534,15 +558,15 @@ def parseOptions(args=None):
CC=target_cc_map[DEPTARGET] CC=target_cc_map[DEPTARGET]
print "Settings:" print("Settings:")
print " * Source directory:", SRCDIR print(" * Source directory:", SRCDIR)
print " * Build directory: ", WORKDIR print(" * Build directory: ", WORKDIR)
print " * SDK location: ", SDKPATH print(" * SDK location: ", SDKPATH)
print " * Third-party source:", DEPSRC print(" * Third-party source:", DEPSRC)
print " * Deployment target:", DEPTARGET print(" * Deployment target:", DEPTARGET)
print " * Universal architectures:", ARCHLIST print(" * Universal architectures:", ARCHLIST)
print " * C compiler:", CC print(" * C compiler:", CC)
print "" print("")
@ -587,7 +611,7 @@ def extractArchive(builddir, archiveName):
xit = fp.close() xit = fp.close()
if xit is not None: if xit is not None:
sys.stdout.write(data) sys.stdout.write(data)
raise RuntimeError, "Cannot extract %s"%(archiveName,) raise RuntimeError("Cannot extract %s"%(archiveName,))
return os.path.join(builddir, retval) return os.path.join(builddir, retval)
@ -609,9 +633,9 @@ def downloadURL(url, fname):
pass pass
else: else:
if KNOWNSIZES.get(url) == size: if KNOWNSIZES.get(url) == size:
print "Using existing file for", url print("Using existing file for", url)
return return
fpIn = urllib2.urlopen(url) fpIn = urllib_request.urlopen(url)
fpOut = open(fname, 'wb') fpOut = open(fname, 'wb')
block = fpIn.read(10240) block = fpIn.read(10240)
try: try:
@ -648,15 +672,15 @@ def buildRecipe(recipe, basedir, archList):
if os.path.exists(sourceArchive): if os.path.exists(sourceArchive):
print "Using local copy of %s"%(name,) print("Using local copy of %s"%(name,))
else: else:
print "Did not find local copy of %s"%(name,) print("Did not find local copy of %s"%(name,))
print "Downloading %s"%(name,) print("Downloading %s"%(name,))
downloadURL(url, sourceArchive) downloadURL(url, sourceArchive)
print "Archive for %s stored as %s"%(name, sourceArchive) print("Archive for %s stored as %s"%(name, sourceArchive))
print "Extracting archive for %s"%(name,) print("Extracting archive for %s"%(name,))
buildDir=os.path.join(WORKDIR, '_bld') buildDir=os.path.join(WORKDIR, '_bld')
if not os.path.exists(buildDir): if not os.path.exists(buildDir):
os.mkdir(buildDir) os.mkdir(buildDir)
@ -722,14 +746,14 @@ def buildRecipe(recipe, basedir, archList):
if 'configure_env' in recipe: if 'configure_env' in recipe:
configure_args.insert(0, recipe['configure_env']) configure_args.insert(0, recipe['configure_env'])
print "Running configure for %s"%(name,) print("Running configure for %s"%(name,))
runCommand(' '.join(configure_args) + ' 2>&1') runCommand(' '.join(configure_args) + ' 2>&1')
print "Running install for %s"%(name,) print("Running install for %s"%(name,))
runCommand('{ ' + install + ' ;} 2>&1') runCommand('{ ' + install + ' ;} 2>&1')
print "Done %s"%(name,) print("Done %s"%(name,))
print "" print("")
os.chdir(curdir) os.chdir(curdir)
@ -737,9 +761,9 @@ def buildLibraries():
""" """
Build our dependencies into $WORKDIR/libraries/usr/local Build our dependencies into $WORKDIR/libraries/usr/local
""" """
print "" print("")
print "Building required libraries" print("Building required libraries")
print "" print("")
universal = os.path.join(WORKDIR, 'libraries') universal = os.path.join(WORKDIR, 'libraries')
os.mkdir(universal) os.mkdir(universal)
os.makedirs(os.path.join(universal, 'usr', 'local', 'lib')) os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
@ -753,7 +777,7 @@ def buildLibraries():
def buildPythonDocs(): def buildPythonDocs():
# This stores the documentation as Resources/English.lproj/Documentation # This stores the documentation as Resources/English.lproj/Documentation
# inside the framwork. pydoc and IDLE will pick it up there. # inside the framwork. pydoc and IDLE will pick it up there.
print "Install python documentation" print("Install python documentation")
rootDir = os.path.join(WORKDIR, '_root') rootDir = os.path.join(WORKDIR, '_root')
buildDir = os.path.join('../../Doc') buildDir = os.path.join('../../Doc')
docdir = os.path.join(rootDir, 'pydocs') docdir = os.path.join(rootDir, 'pydocs')
@ -768,7 +792,7 @@ def buildPythonDocs():
def buildPython(): def buildPython():
print "Building a universal python for %s architectures" % UNIVERSALARCHS print("Building a universal python for %s architectures" % UNIVERSALARCHS)
buildDir = os.path.join(WORKDIR, '_bld', 'python') buildDir = os.path.join(WORKDIR, '_bld', 'python')
rootDir = os.path.join(WORKDIR, '_root') rootDir = os.path.join(WORKDIR, '_root')
@ -796,7 +820,7 @@ def buildPython():
# will find them during its extension import sanity checks. # will find them during its extension import sanity checks.
os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR, os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
'libraries', 'usr', 'local', 'lib') 'libraries', 'usr', 'local', 'lib')
print "Running configure..." print("Running configure...")
runCommand("%s -C --enable-framework --enable-universalsdk=%s " runCommand("%s -C --enable-framework --enable-universalsdk=%s "
"--with-universal-archs=%s " "--with-universal-archs=%s "
"%s " "%s "
@ -808,19 +832,19 @@ def buildPython():
shellQuote(WORKDIR)[1:-1], shellQuote(WORKDIR)[1:-1],
shellQuote(WORKDIR)[1:-1])) shellQuote(WORKDIR)[1:-1]))
print "Running make" print("Running make")
runCommand("make") runCommand("make")
print "Running make install" print("Running make install")
runCommand("make install DESTDIR=%s"%( runCommand("make install DESTDIR=%s"%(
shellQuote(rootDir))) shellQuote(rootDir)))
print "Running make frameworkinstallextras" print("Running make frameworkinstallextras")
runCommand("make frameworkinstallextras DESTDIR=%s"%( runCommand("make frameworkinstallextras DESTDIR=%s"%(
shellQuote(rootDir))) shellQuote(rootDir)))
del os.environ['DYLD_LIBRARY_PATH'] del os.environ['DYLD_LIBRARY_PATH']
print "Copying required shared libraries" print("Copying required shared libraries")
if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')): if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
runCommand("mv %s/* %s"%( runCommand("mv %s/* %s"%(
shellQuote(os.path.join( shellQuote(os.path.join(
@ -831,13 +855,13 @@ def buildPython():
'Python.framework', 'Versions', getVersion(), 'Python.framework', 'Versions', getVersion(),
'lib')))) 'lib'))))
print "Fix file modes" print("Fix file modes")
frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework') frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
gid = grp.getgrnam('admin').gr_gid gid = grp.getgrnam('admin').gr_gid
for dirpath, dirnames, filenames in os.walk(frmDir): for dirpath, dirnames, filenames in os.walk(frmDir):
for dn in dirnames: for dn in dirnames:
os.chmod(os.path.join(dirpath, dn), 0775) os.chmod(os.path.join(dirpath, dn), STAT_0o775)
os.chown(os.path.join(dirpath, dn), -1, gid) os.chown(os.path.join(dirpath, dn), -1, gid)
@ -918,17 +942,17 @@ def patchFile(inPath, outPath):
# This one is not handy as a template variable # This one is not handy as a template variable
data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework') data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
fp = open(outPath, 'wb') fp = open(outPath, 'w')
fp.write(data) fp.write(data)
fp.close() fp.close()
def patchScript(inPath, outPath): def patchScript(inPath, outPath):
data = fileContents(inPath) data = fileContents(inPath)
data = data.replace('@PYVER@', getVersion()) data = data.replace('@PYVER@', getVersion())
fp = open(outPath, 'wb') fp = open(outPath, 'w')
fp.write(data) fp.write(data)
fp.close() fp.close()
os.chmod(outPath, 0755) os.chmod(outPath, STAT_0o755)
@ -945,7 +969,7 @@ def packageFromRecipe(targetDir, recipe):
readme = textwrap.dedent(recipe['readme']) readme = textwrap.dedent(recipe['readme'])
isRequired = recipe.get('required', True) isRequired = recipe.get('required', True)
print "- building package %s"%(pkgname,) print("- building package %s"%(pkgname,))
# Substitute some variables # Substitute some variables
textvars = dict( textvars = dict(
@ -990,7 +1014,7 @@ def packageFromRecipe(targetDir, recipe):
patchScript(postflight, os.path.join(rsrcDir, 'postflight')) patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
vers = getFullVersion() vers = getFullVersion()
major, minor = map(int, getVersion().split('.', 2)) major, minor = getVersionMajorMinor()
pl = Plist( pl = Plist(
CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,), CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
CFBundleIdentifier='org.python.Python.%s'%(pkgname,), CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
@ -1027,7 +1051,7 @@ def packageFromRecipe(targetDir, recipe):
def makeMpkgPlist(path): def makeMpkgPlist(path):
vers = getFullVersion() vers = getFullVersion()
major, minor = map(int, getVersion().split('.', 2)) major, minor = getVersionMajorMinor()
pl = Plist( pl = Plist(
CFBundleGetInfoString="Python %s"%(vers,), CFBundleGetInfoString="Python %s"%(vers,),
@ -1209,7 +1233,7 @@ def main():
folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%( folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
getVersion(),)) getVersion(),))
os.chmod(folder, 0755) os.chmod(folder, STAT_0o755)
setIcon(folder, "../Icons/Python Folder.icns") setIcon(folder, "../Icons/Python Folder.icns")
# Create the installer # Create the installer
@ -1222,9 +1246,9 @@ def main():
shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt')) shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w') fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
print >> fp, "# BUILD INFO" fp.write("# BUILD INFO\n")
print >> fp, "# Date:", time.ctime() fp.write("# Date: %s\n" % time.ctime())
print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
fp.close() fp.close()
# And copy it to a DMG # And copy it to a DMG