diff --git a/Mac/BuildScript/README.txt b/Mac/BuildScript/README.txt index 41df0e4adaf..0f1aeb5d2a4 100644 --- a/Mac/BuildScript/README.txt +++ b/Mac/BuildScript/README.txt @@ -37,6 +37,17 @@ Here are the steps you need to follow to build a MacPython installer: * When done the script will tell you where the DMG image is (by default somewhere in ``/tmp/_py``). +Building a 4-way universal installer +.................................... + +It is also possible to build a 4-way universal installer that runs on +OSX Leopard or later:: + + $ ./build-installer.py --dep-target=10.5 --universal-archs=all --sdk=/ + +This requires that the deployment target is 10.5 (or later), and hence +also that your building on at least OSX 10.5. + Testing ------- diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 8051256ed5a..c2cdb028cb8 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -2,7 +2,8 @@ """ This script is used to build the "official unofficial" universal build on Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its -work. +work. 64-bit or four-way universal builds require at least OS X 10.5 and +the 10.5 SDK. Please ensure that this script keeps working with Python 2.3, to avoid bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4) @@ -63,7 +64,15 @@ DEPSRC = os.path.expanduser('~/Universal/other-sources') SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk" #SDKPATH = "/" -ARCHLIST = ('i386', 'ppc',) +universal_opts_map = { '32-bit': ('i386', 'ppc',), + '64-bit': ('x86_64', 'ppc64',), + 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) } + +UNIVERSALOPTS = tuple(universal_opts_map.keys()) + +UNIVERSALARCHS = '32-bit' + +ARCHLIST = universal_opts_map[UNIVERSALARCHS] # Source directory (asume we're in Mac/BuildScript) SRCDIR = os.path.dirname( @@ -72,6 +81,9 @@ SRCDIR = os.path.dirname( os.path.abspath(__file__ )))) +# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level +DEPTARGET = '10.3' + USAGE = textwrap.dedent("""\ Usage: build_python [options] @@ -82,103 +94,108 @@ USAGE = textwrap.dedent("""\ --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r) --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r) --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r) + --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r) + --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r) """)% globals() # Instructions for building libraries that are necessary for building a # batteries included python. -LIBRARY_RECIPES = [ - dict( - name="Bzip2 1.0.4", - url="http://www.bzip.org/1.0.4/bzip2-1.0.4.tar.gz", - checksum='fc310b254f6ba5fbb5da018f04533688', - configure=None, - install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%( - shellQuote(os.path.join(WORKDIR, 'libraries')), - ' -arch '.join(ARCHLIST), - SDKPATH, - ), - ), - dict( - name="ZLib 1.2.3", - url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz", - checksum='debc62758716a169df9f62e6ab2bc634', - configure=None, - install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%( - shellQuote(os.path.join(WORKDIR, 'libraries')), - ' -arch '.join(ARCHLIST), - SDKPATH, - ), - ), - dict( - # Note that GNU readline is GPL'd software - name="GNU Readline 5.1.4", - url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" , - checksum='7ee5a692db88b30ca48927a13fd60e46', - patchlevel='0', - patches=[ - # The readline maintainers don't do actual micro releases, but - # just ship a set of patches. - 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001', - 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002', - 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003', - 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004', - ] - ), +# [The recipes are defined here for convenience but instantiated later after +# command line options have been processed.] +def library_recipes(): + return [ + dict( + name="Bzip2 1.0.4", + url="http://www.bzip.org/1.0.4/bzip2-1.0.4.tar.gz", + checksum='fc310b254f6ba5fbb5da018f04533688', + configure=None, + install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%( + shellQuote(os.path.join(WORKDIR, 'libraries')), + ' -arch '.join(ARCHLIST), + SDKPATH, + ), + ), + dict( + name="ZLib 1.2.3", + url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz", + checksum='debc62758716a169df9f62e6ab2bc634', + configure=None, + install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%( + shellQuote(os.path.join(WORKDIR, 'libraries')), + ' -arch '.join(ARCHLIST), + SDKPATH, + ), + ), + dict( + # Note that GNU readline is GPL'd software + name="GNU Readline 5.1.4", + url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" , + checksum='7ee5a692db88b30ca48927a13fd60e46', + patchlevel='0', + patches=[ + # The readline maintainers don't do actual micro releases, but + # just ship a set of patches. + 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001', + 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002', + 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003', + 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004', + ] + ), - dict( - name="SQLite 3.6.11", - url="http://www.sqlite.org/sqlite-3.6.11.tar.gz", - checksum='7ebb099696ab76cc6ff65dd496d17858', - configure_pre=[ - '--enable-threadsafe', - '--enable-tempstore', - '--enable-shared=no', - '--enable-static=yes', - '--disable-tcl', - ] - ), + dict( + name="SQLite 3.6.11", + url="http://www.sqlite.org/sqlite-3.6.11.tar.gz", + checksum='7ebb099696ab76cc6ff65dd496d17858', + configure_pre=[ + '--enable-threadsafe', + '--enable-tempstore', + '--enable-shared=no', + '--enable-static=yes', + '--disable-tcl', + ] + ), - dict( - name="NCurses 5.5", - url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz", - checksum='e73c1ac10b4bfc46db43b2ddfd6244ef', - configure_pre=[ - "--without-cxx", - "--without-ada", - "--without-progs", - "--without-curses-h", - "--enable-shared", - "--with-shared", - "--datadir=/usr/share", - "--sysconfdir=/etc", - "--sharedstatedir=/usr/com", - "--with-terminfo-dirs=/usr/share/terminfo", - "--with-default-terminfo-dir=/usr/share/terminfo", - "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),), - "--enable-termcap", - ], - patches=[ - "ncurses-5.5.patch", - ], - useLDFlags=False, - install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%( - shellQuote(os.path.join(WORKDIR, 'libraries')), - shellQuote(os.path.join(WORKDIR, 'libraries')), - getVersion(), - ), - ), - dict( - name="Sleepycat DB 4.7.25", - url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz", - checksum='ec2b87e833779681a0c3a814aa71359e', - buildDir="build_unix", - configure="../dist/configure", - configure_pre=[ - '--includedir=/usr/local/include/db4', - ] - ), -] + dict( + name="NCurses 5.5", + url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz", + checksum='e73c1ac10b4bfc46db43b2ddfd6244ef', + configure_pre=[ + "--without-cxx", + "--without-ada", + "--without-progs", + "--without-curses-h", + "--enable-shared", + "--with-shared", + "--datadir=/usr/share", + "--sysconfdir=/etc", + "--sharedstatedir=/usr/com", + "--with-terminfo-dirs=/usr/share/terminfo", + "--with-default-terminfo-dir=/usr/share/terminfo", + "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),), + "--enable-termcap", + ], + patches=[ + "ncurses-5.5.patch", + ], + useLDFlags=False, + install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%( + shellQuote(os.path.join(WORKDIR, 'libraries')), + shellQuote(os.path.join(WORKDIR, 'libraries')), + getVersion(), + ), + ), + dict( + name="Sleepycat DB 4.7.25", + url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz", + checksum='ec2b87e833779681a0c3a814aa71359e', + buildDir="build_unix", + configure="../dist/configure", + configure_pre=[ + '--includedir=/usr/local/include/db4', + ] + ), + ] # Instructions for building packages inside the .mpkg. @@ -213,8 +230,8 @@ PKG_RECIPES = [ source="/usr/local/bin", readme="""\ This package installs the unix tools in /usr/local/bin for - compatibility with older releases of MacPython. This package - is not necessary to use MacPython. + compatibility with older releases of Python. This package + is not necessary to use Python. """, required=False, ), @@ -237,7 +254,7 @@ PKG_RECIPES = [ long_name="Shell profile updater", readme="""\ This packages updates your shell profile to make sure that - the MacPython tools are found by your shell in preference of + the Python tools are found by your shell in preference of the system provided Python tools. If you don't install this package you'll have to add @@ -325,14 +342,16 @@ def parseOptions(args=None): """ Parse arguments and update global settings. """ - global WORKDIR, DEPSRC, SDKPATH, SRCDIR + global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET + global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST if args is None: args = sys.argv[1:] try: 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' ]) except getopt.error, msg: print msg sys.exit(1) @@ -342,7 +361,7 @@ def parseOptions(args=None): sys.exit(1) for k, v in options: - if k in ('-h', '-?'): + if k in ('-h', '-?', '--help'): print USAGE sys.exit(0) @@ -358,6 +377,16 @@ def parseOptions(args=None): elif k in ('--src-dir',): SRCDIR=v + elif k in ('--dep-target', ): + DEPTARGET=v + + elif k in ('--universal-archs', ): + if v in UNIVERSALOPTS: + UNIVERSALARCHS = v + ARCHLIST = universal_opts_map[UNIVERSALARCHS] + else: + raise NotImplementedError, v + else: raise NotImplementedError, k @@ -370,7 +399,9 @@ def parseOptions(args=None): print " * Source directory:", SRCDIR print " * Build directory: ", WORKDIR print " * SDK location: ", SDKPATH - print " * third-party source:", DEPSRC + print " * Third-party source:", DEPSRC + print " * Deployment target:", DEPTARGET + print " * Universal architectures:", ARCHLIST print "" @@ -480,6 +511,7 @@ def buildRecipe(recipe, basedir, archList): print "Using local copy of %s"%(name,) else: + print "Did not find local copy of %s"%(name,) print "Downloading %s"%(name,) downloadURL(url, sourceArchive) print "Archive for %s stored as %s"%(name, sourceArchive) @@ -570,7 +602,7 @@ def buildLibraries(): os.makedirs(os.path.join(universal, 'usr', 'local', 'lib')) os.makedirs(os.path.join(universal, 'usr', 'local', 'include')) - for recipe in LIBRARY_RECIPES: + for recipe in library_recipes(): buildRecipe(recipe, universal, ARCHLIST) @@ -594,7 +626,7 @@ def buildPythonDocs(): def buildPython(): - print "Building a universal python" + print "Building a universal python for %s architectures" % UNIVERSALARCHS buildDir = os.path.join(WORKDIR, '_bld', 'python') rootDir = os.path.join(WORKDIR, '_root') @@ -618,9 +650,13 @@ def buildPython(): version = getVersion() print "Running configure..." - runCommand("%s -C --enable-framework --enable-universalsdk=%s LDFLAGS='-g -L%s/libraries/usr/local/lib' OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%( - shellQuote(os.path.join(SRCDIR, 'configure')), - shellQuote(SDKPATH), shellQuote(WORKDIR)[1:-1], + runCommand("%s -C --enable-framework --enable-universalsdk=%s " + "--with-universal-archs=%s " + "LDFLAGS='-g -L%s/libraries/usr/local/lib' " + "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%( + shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH), + UNIVERSALARCHS, + shellQuote(WORKDIR)[1:-1], shellQuote(WORKDIR)[1:-1])) print "Running make" @@ -701,7 +737,7 @@ def patchFile(inPath, outPath): data = fileContents(inPath) data = data.replace('$FULL_VERSION', getFullVersion()) data = data.replace('$VERSION', getVersion()) - data = data.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later') + data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later'))) data = data.replace('$ARCHITECTURES', "i386, ppc") data = data.replace('$INSTALL_SIZE', installSize()) @@ -781,9 +817,9 @@ def packageFromRecipe(targetDir, recipe): vers = getFullVersion() major, minor = map(int, getVersion().split('.', 2)) pl = Plist( - CFBundleGetInfoString="MacPython.%s %s"%(pkgname, vers,), - CFBundleIdentifier='org.python.MacPython.%s'%(pkgname,), - CFBundleName='MacPython.%s'%(pkgname,), + CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,), + CFBundleIdentifier='org.python.Python.%s'%(pkgname,), + CFBundleName='Python.%s'%(pkgname,), CFBundleShortVersionString=vers, IFMajorVersion=major, IFMinorVersion=minor, @@ -804,7 +840,7 @@ def packageFromRecipe(targetDir, recipe): pl = Plist( IFPkgDescriptionDescription=readme, - IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)), + IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)), IFPkgDescriptionVersion=vers, ) writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist')) @@ -819,9 +855,9 @@ def makeMpkgPlist(path): major, minor = map(int, getVersion().split('.', 2)) pl = Plist( - CFBundleGetInfoString="MacPython %s"%(vers,), - CFBundleIdentifier='org.python.MacPython', - CFBundleName='MacPython', + CFBundleGetInfoString="Python %s"%(vers,), + CFBundleIdentifier='org.python.Python', + CFBundleName='Python', CFBundleShortVersionString=vers, IFMajorVersion=major, IFMinorVersion=minor, @@ -855,7 +891,7 @@ def buildInstaller(): shutil.rmtree(outdir) os.mkdir(outdir) - pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents') + pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents') pkgcontents = os.path.join(pkgroot, 'Packages') os.makedirs(pkgcontents) for recipe in PKG_RECIPES: @@ -872,7 +908,7 @@ def buildInstaller(): makeMpkgPlist(os.path.join(pkgroot, 'Info.plist')) pl = Plist( - IFPkgDescriptionTitle="Universal MacPython", + IFPkgDescriptionTitle="Python", IFPkgDescriptionVersion=getVersion(), ) @@ -912,10 +948,32 @@ def buildDMG(): imagepath = imagepath + '.dmg' os.mkdir(outdir) - runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%( - getFullVersion(), + volname='Python %s'%(getFullVersion()) + runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%( + shellQuote(volname), shellQuote(os.path.join(WORKDIR, 'installer')), - shellQuote(imagepath))) + shellQuote(imagepath + ".tmp.dmg" ))) + + + if not os.path.exists(os.path.join(WORKDIR, "mnt")): + os.mkdir(os.path.join(WORKDIR, "mnt")) + runCommand("hdiutil attach %s -mountroot %s"%( + shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt")))) + + # Custom icon for the DMG, shown when the DMG is mounted. + shutil.copy("../Icons/Disk Image.icns", + os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns")) + runCommand("/Developer/Tools/SetFile -a C %s/"%( + shellQuote(os.path.join(WORKDIR, "mnt", volname)),)) + + runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname)))) + + setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns") + runCommand("hdiutil convert %s -format UDZO -o %s"%( + shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath))) + setIcon(imagepath, "../Icons/Disk Image.icns") + + os.unlink(imagepath + ".tmp.dmg") return imagepath @@ -943,7 +1001,7 @@ def main(): parseOptions() checkEnvironment() - os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3' + os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET if os.path.exists(WORKDIR): shutil.rmtree(WORKDIR) @@ -979,13 +1037,6 @@ def main(): print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos fp.close() - # Custom icon for the DMG, shown when the DMG is mounted. - # XXX: Code is diabled because it doesn't actually work :-( -# shutil.copy("../Icons/Disk Image.icns", -# os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")) -# os.system("/Developer/Tools/SetFile -a C %s"%( -# os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))) - setIcon(os.path.join(WORKDIR, "installer"), "../Icons/Disk Image.icns") # And copy it to a DMG