From 0e5b70d417ef5056007e84581e2843b97e254af8 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Wed, 7 Jun 2006 18:58:42 +0000 Subject: [PATCH] mv Mac/OSX/BuildScript one level up --- Mac/BuildScript/README.txt | 35 + Mac/BuildScript/build-installer.py | 1028 +++++++++++++++++ Mac/BuildScript/ncurses-5.5.patch | 36 + Mac/BuildScript/resources/ReadMe.txt | 31 + Mac/BuildScript/resources/Welcome.rtf | 15 + Mac/BuildScript/resources/background.jpg | Bin 0 -> 45421 bytes .../scripts/postflight.documentation | 12 + Mac/BuildScript/scripts/postflight.framework | 33 + .../scripts/postflight.patch-profile | 71 ++ 9 files changed, 1261 insertions(+) create mode 100644 Mac/BuildScript/README.txt create mode 100755 Mac/BuildScript/build-installer.py create mode 100644 Mac/BuildScript/ncurses-5.5.patch create mode 100644 Mac/BuildScript/resources/ReadMe.txt create mode 100644 Mac/BuildScript/resources/Welcome.rtf create mode 100644 Mac/BuildScript/resources/background.jpg create mode 100755 Mac/BuildScript/scripts/postflight.documentation create mode 100755 Mac/BuildScript/scripts/postflight.framework create mode 100755 Mac/BuildScript/scripts/postflight.patch-profile diff --git a/Mac/BuildScript/README.txt b/Mac/BuildScript/README.txt new file mode 100644 index 000000000000..c556de8329d1 --- /dev/null +++ b/Mac/BuildScript/README.txt @@ -0,0 +1,35 @@ +Building a MacPython distribution +================================= + +The ``build-install.py`` script creates MacPython distributions, including +sleepycat db4, sqlite3 and readline support. It builds a complete +framework-based Python out-of-tree, installs it in a funny place with +$DESTROOT, massages that installation to remove .pyc files and such, creates +an Installer package from the installation plus other files in ``resources`` +and ``scripts`` and placed that on a ``.dmg`` disk image. + +Here are the steps you ned to follow to build a MacPython installer: + +- Run ``./build-installer.py``. Optionally you can pass a number of arguments + to specify locations of various files. Please see the top of + ``build-installer.py`` for its usage. +- When done the script will tell you where the DMG image is. + +The script needs to be run on Mac OS X 10.4 with Xcode 2.2 or later and +the 10.4u SDK. + +When all is done, announcements can be posted to at least the following +places: +- pythonmac-sig@python.org +- python-dev@python.org +- python-announce@python.org +- archivist@info-mac.org +- adcnews@apple.com +- news@macnn.com +- http://www.macupdate.com +- http://guide.apple.com/usindex.lasso +- http://www.apple.com/downloads/macosx/submit +- http://www.versiontracker.com/ (userid Jack.Jansen@oratrix.com) +- http://www.macshareware.net (userid jackjansen) + +Also, check out Stephan Deibels http://pythonology.org/market contact list diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py new file mode 100755 index 000000000000..1305c0c47dda --- /dev/null +++ b/Mac/BuildScript/build-installer.py @@ -0,0 +1,1028 @@ +#!/usr/bin/python2.3 +""" +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. + +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) + +Usage: see USAGE variable in the script. +""" +import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd + +INCLUDE_TIMESTAMP=1 +VERBOSE=1 + +from plistlib import Plist + +import MacOS +import Carbon.File +import Carbon.Icn +import Carbon.Res +from Carbon.Files import kCustomIconResource, fsRdWrPerm, kHasCustomIcon +from Carbon.Files import kFSCatInfoFinderInfo + +try: + from plistlib import writePlist +except ImportError: + # We're run using python2.3 + def writePlist(plist, path): + plist.write(path) + +def shellQuote(value): + """ + Return the string value in a form that can savely be inserted into + a shell command. + """ + return "'%s'"%(value.replace("'", "'\"'\"'")) + +def grepValue(fn, variable): + variable = variable + '=' + for ln in open(fn, 'r'): + if ln.startswith(variable): + value = ln[len(variable):].strip() + return value[1:-1] + +def getVersion(): + return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION') + +def getFullVersion(): + fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h') + for ln in open(fn): + if 'PY_VERSION' in ln: + return ln.split()[-1][1:-1] + + raise RuntimeError, "Cannot find full version??" + +# The directory we'll use to create the build, will be erased and recreated +WORKDIR="/tmp/_py" + +# The directory we'll use to store third-party sources, set this to something +# else if you don't want to re-fetch required libraries every time. +DEPSRC=os.path.join(WORKDIR, 'third-party') +DEPSRC=os.path.expanduser('~/Universal/other-sources') + +# Location of the preferred SDK +SDKPATH="/Developer/SDKs/MacOSX10.4u.sdk" +#SDKPATH="/" + +# Source directory (asume we're in Mac/BuildScript) +SRCDIR=os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.abspath(__file__ + )))) + +USAGE=textwrap.dedent("""\ + Usage: build_python [options] + + Options: + -? or -h: Show this message + -b DIR + --build-dir=DIR: Create build here (default: %(WORKDIR)r) + --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) +""")% globals() + + +# Instructions for building libraries that are necessary for building a +# batteries included python. +LIBRARY_RECIPES=[ + 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" , + 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.3.5", + url="http://www.sqlite.org/sqlite-3.3.5.tar.gz", + checksum='93f742986e8bc2dfa34792e16df017a6feccf3a2', + 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", + 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.4", + url="http://downloads.sleepycat.com/db-4.4.20.tar.gz", + #name="Sleepycat DB 4.3.29", + #url="http://downloads.sleepycat.com/db-4.3.29.tar.gz", + buildDir="build_unix", + configure="../dist/configure", + configure_pre=[ + '--includedir=/usr/local/include/db4', + ] + ), +] + + +# Instructions for building packages inside the .mpkg. +PKG_RECIPES=[ + dict( + name="PythonFramework", + long_name="Python Framework", + source="/Library/Frameworks/Python.framework", + readme="""\ + This package installs Python.framework, that is the python + interpreter and the standard library. This also includes Python + wrappers for lots of Mac OS X API's. + """, + postflight="scripts/postflight.framework", + ), + dict( + name="PythonApplications", + long_name="GUI Applications", + source="/Applications/MacPython %(VER)s", + readme="""\ + This package installs IDLE (an interactive Python IDLE), + Python Launcher and Build Applet (create application bundles + from python scripts). + + It also installs a number of examples and demos. + """, + required=False, + ), + dict( + name="PythonUnixTools", + long_name="UNIX command-line tools", + 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. + """, + required=False, + ), + dict( + name="PythonDocumentation", + long_name="Python Documentation", + topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation", + source="/pydocs", + readme="""\ + This package installs the python documentation at a location + that is useable for pydoc and IDLE. If you have installed Xcode + it will also install a link to the documentation in + /Developer/Documentation/Python + """, + postflight="scripts/postflight.documentation", + required=False, + ), + dict( + name="PythonProfileChanges", + 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 system provided Python tools. + + If you don't install this package you'll have to add + "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin" + to your PATH by hand. + """, + postflight="scripts/postflight.patch-profile", + topdir="/Library/Frameworks/Python.framework", + source="/empty-dir", + required=False, + ), + dict( + name="PythonSystemFixes", + long_name="Fix system Python", + readme="""\ + This package updates the system python installation on + Mac OS X 10.3 to ensure that you can build new python extensions + using that copy of python after installing this version of + python. + """ + postflight="../Tools/fixapplepython23.py", + topdir="/Library/Frameworks/Python.framework", + source="/empty-dir", + required=False, + ) +] + +def fatal(msg): + """ + A fatal error, bail out. + """ + sys.stderr.write('FATAL: ') + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(1) + +def fileContents(fn): + """ + Return the contents of the named file + """ + return open(fn, 'rb').read() + +def runCommand(commandline): + """ + Run a command and raise RuntimeError if it fails. Output is surpressed + unless the command fails. + """ + fd = os.popen(commandline, 'r') + data = fd.read() + xit = fd.close() + if xit != None: + sys.stdout.write(data) + raise RuntimeError, "command failed: %s"%(commandline,) + + if VERBOSE: + sys.stdout.write(data); sys.stdout.flush() + +def captureCommand(commandline): + fd = os.popen(commandline, 'r') + data = fd.read() + xit = fd.close() + if xit != None: + sys.stdout.write(data) + raise RuntimeError, "command failed: %s"%(commandline,) + + return data + +def checkEnvironment(): + """ + Check that we're running on a supported system. + """ + + if platform.system() != 'Darwin': + fatal("This script should be run on a Mac OS X 10.4 system") + + if platform.release() <= '8.': + fatal("This script should be run on a Mac OS X 10.4 system") + + if not os.path.exists(SDKPATH): + fatal("Please install the latest version of Xcode and the %s SDK"%( + os.path.basename(SDKPATH[:-4]))) + + + +def parseOptions(args = None): + """ + Parse arguments and update global settings. + """ + global WORKDIR, DEPSRC, SDKPATH, SRCDIR + + if args is None: + args = sys.argv[1:] + + try: + options, args = getopt.getopt(args, '?hb', + [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=']) + except getopt.error, msg: + print msg + sys.exit(1) + + if args: + print "Additional arguments" + sys.exit(1) + + for k, v in options: + if k in ('-h', '-?'): + print USAGE + sys.exit(0) + + elif k in ('-d', '--build-dir'): + WORKDIR=v + + elif k in ('--third-party',): + DEPSRC=v + + elif k in ('--sdk-path',): + SDKPATH=v + + elif k in ('--src-dir',): + SRCDIR=v + + else: + raise NotImplementedError, k + + SRCDIR=os.path.abspath(SRCDIR) + WORKDIR=os.path.abspath(WORKDIR) + SDKPATH=os.path.abspath(SDKPATH) + DEPSRC=os.path.abspath(DEPSRC) + + print "Settings:" + print " * Source directory:", SRCDIR + print " * Build directory: ", WORKDIR + print " * SDK location: ", SDKPATH + print " * third-party source:", DEPSRC + print "" + + + + +def extractArchive(builddir, archiveName): + """ + Extract a source archive into 'builddir'. Returns the path of the + extracted archive. + + XXX: This function assumes that archives contain a toplevel directory + that is has the same name as the basename of the archive. This is + save enough for anything we use. + """ + curdir = os.getcwd() + try: + os.chdir(builddir) + if archiveName.endswith('.tar.gz'): + retval = os.path.basename(archiveName[:-7]) + if os.path.exists(retval): + shutil.rmtree(retval) + fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r') + + elif archiveName.endswith('.tar.bz2'): + retval = os.path.basename(archiveName[:-8]) + if os.path.exists(retval): + shutil.rmtree(retval) + fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r') + + elif archiveName.endswith('.tar'): + retval = os.path.basename(archiveName[:-4]) + if os.path.exists(retval): + shutil.rmtree(retval) + fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r') + + elif archiveName.endswith('.zip'): + retval = os.path.basename(archiveName[:-4]) + if os.path.exists(retval): + shutil.rmtree(retval) + fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r') + + data = fp.read() + xit = fp.close() + if xit is not None: + sys.stdout.write(data) + raise RuntimeError, "Cannot extract %s"%(archiveName,) + + return os.path.join(builddir, retval) + + finally: + os.chdir(curdir) + +KNOWNSIZES = { + "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742, + "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276, +} + +def downloadURL(url, fname): + """ + Download the contents of the url into the file. + """ + try: + size = os.path.getsize(fname) + except OSError: + pass + else: + if KNOWNSIZES.get(url) == size: + print "Using existing file for", url + return + fpIn = urllib2.urlopen(url) + fpOut = open(fname, 'wb') + block = fpIn.read(10240) + try: + while block: + fpOut.write(block) + block = fpIn.read(10240) + fpIn.close() + fpOut.close() + except: + try: + os.unlink(fname) + except: + pass + +def buildRecipe(recipe, basedir, archList): + """ + Build software using a recipe. This function does the + 'configure;make;make install' dance for C software, with a possibility + to customize this process, basically a poor-mans DarwinPorts. + """ + curdir = os.getcwd() + + name = recipe['name'] + url = recipe['url'] + configure = recipe.get('configure', './configure') + install = recipe.get('install', 'make && make install DESTDIR=%s'%( + shellQuote(basedir))) + + archiveName = os.path.split(url)[-1] + sourceArchive = os.path.join(DEPSRC, archiveName) + + if not os.path.exists(DEPSRC): + os.mkdir(DEPSRC) + + + if os.path.exists(sourceArchive): + print "Using local copy of %s"%(name,) + + else: + print "Downloading %s"%(name,) + downloadURL(url, sourceArchive) + print "Archive for %s stored as %s"%(name, sourceArchive) + + print "Extracting archive for %s"%(name,) + buildDir=os.path.join(WORKDIR, '_bld') + if not os.path.exists(buildDir): + os.mkdir(buildDir) + + workDir = extractArchive(buildDir, sourceArchive) + os.chdir(workDir) + if 'buildDir' in recipe: + os.chdir(recipe['buildDir']) + + + for fn in recipe.get('patches', ()): + if fn.startswith('http://'): + # Download the patch before applying it. + path = os.path.join(DEPSRC, os.path.basename(fn)) + downloadURL(fn, path) + fn = path + + fn = os.path.join(curdir, fn) + runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1), + shellQuote(fn),)) + + configure_args = [ + "--prefix=/usr/local", + "--enable-static", + "--disable-shared", + #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),), + ] + + if 'configure_pre' in recipe: + args = list(recipe['configure_pre']) + if '--disable-static' in args: + configure_args.remove('--enable-static') + if '--enable-shared' in args: + configure_args.remove('--disable-shared') + configure_args.extend(args) + + if recipe.get('useLDFlags', 1): + configure_args.extend([ + "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%( + ' -arch '.join(archList), + shellQuote(SDKPATH)[1:-1], + shellQuote(basedir)[1:-1],), + "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%( + shellQuote(SDKPATH)[1:-1], + shellQuote(basedir)[1:-1], + ' -arch '.join(archList)), + ]) + else: + configure_args.extend([ + "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%( + ' -arch '.join(archList), + shellQuote(SDKPATH)[1:-1], + shellQuote(basedir)[1:-1],), + ]) + + if 'configure_post' in recipe: + configure_args = configure_args = list(recipe['configure_post']) + + configure_args.insert(0, configure) + configure_args = [ shellQuote(a) for a in configure_args ] + + print "Running configure for %s"%(name,) + runCommand(' '.join(configure_args) + ' 2>&1') + + print "Running install for %s"%(name,) + runCommand('{ ' + install + ' ;} 2>&1') + + print "Done %s"%(name,) + print "" + + os.chdir(curdir) + +def buildLibraries(): + """ + Build our dependencies into $WORKDIR/libraries/usr/local + """ + print "" + print "Building required libraries" + print "" + universal = os.path.join(WORKDIR, 'libraries') + os.mkdir(universal) + os.makedirs(os.path.join(universal, 'usr', 'local', 'lib')) + os.makedirs(os.path.join(universal, 'usr', 'local', 'include')) + + for recipe in LIBRARY_RECIPES: + buildRecipe(recipe, universal, ('i386', 'ppc',)) + + + +def buildPythonDocs(): + # This stores the documentation as Resources/English.lproj/Docuentation + # inside the framwork. pydoc and IDLE will pick it up there. + print "Install python documentation" + rootDir = os.path.join(WORKDIR, '_root') + version = getVersion() + docdir = os.path.join(rootDir, 'pydocs') + + name = 'html-%s.tar.bz2'%(getFullVersion(),) + sourceArchive = os.path.join(DEPSRC, name) + if os.path.exists(sourceArchive): + print "Using local copy of %s"%(name,) + + else: + print "Downloading %s"%(name,) + downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%( + getFullVersion(), name), sourceArchive) + print "Archive for %s stored as %s"%(name, sourceArchive) + + extractArchive(os.path.dirname(docdir), sourceArchive) + os.rename( + os.path.join( + os.path.dirname(docdir), 'Python-Docs-%s'%(getFullVersion(),)), + docdir) + + +def buildPython(): + print "Building a universal python" + + buildDir = os.path.join(WORKDIR, '_bld', 'python') + rootDir = os.path.join(WORKDIR, '_root') + + if os.path.exists(buildDir): + shutil.rmtree(buildDir) + if os.path.exists(rootDir): + shutil.rmtree(rootDir) + os.mkdir(buildDir) + os.mkdir(rootDir) + os.mkdir(os.path.join(rootDir, 'empty-dir')) + curdir = os.getcwd() + os.chdir(buildDir) + + # Not sure if this is still needed, the original build script + # claims that parts of the install assume python.exe exists. + os.symlink('python', os.path.join(buildDir, 'python.exe')) + + # Extract the version from the configure file, needed to calculate + # several paths. + 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], + shellQuote(WORKDIR)[1:-1])) + + print "Running make" + runCommand("make") + + print "Runing make frameworkinstall" + runCommand("make frameworkinstall DESTDIR=%s"%( + shellQuote(rootDir))) + + print "Runing make frameworkinstallextras" + runCommand("make frameworkinstallextras DESTDIR=%s"%( + shellQuote(rootDir))) + + print "Copy required shared libraries" + if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')): + runCommand("mv %s/* %s"%( + shellQuote(os.path.join( + WORKDIR, 'libraries', 'Library', 'Frameworks', + 'Python.framework', 'Versions', getVersion(), + 'lib')), + shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks', + 'Python.framework', 'Versions', getVersion(), + 'lib')))) + + print "Fix file modes" + frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework') + for dirpath, dirnames, filenames in os.walk(frmDir): + for dn in dirnames: + os.chmod(os.path.join(dirpath, dn), 0775) + + for fn in filenames: + if os.path.islink(fn): + continue + + # "chmod g+w $fn" + p = os.path.join(dirpath, fn) + st = os.stat(p) + os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IXGRP) + + # We added some directories to the search path during the configure + # phase. Remove those because those directories won't be there on + # the end-users system. + path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework', + 'Versions', version, 'lib', 'python%s'%(version,), + 'config', 'Makefile') + fp = open(path, 'r') + data = fp.read() + fp.close() + + data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '') + data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '') + fp = open(path, 'w') + fp.write(data) + fp.close() + + # Add symlinks in /usr/local/bin, using relative links + usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin') + to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks', + 'Python.framework', 'Versions', version, 'bin') + if os.path.exists(usr_local_bin): + shutil.rmtree(usr_local_bin) + os.makedirs(usr_local_bin) + for fn in os.listdir( + os.path.join(frmDir, 'Versions', version, 'bin')): + os.symlink(os.path.join(to_framework, fn), + os.path.join(usr_local_bin, fn)) + + os.chdir(curdir) + + + +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('$ARCHITECTURES', "i386, ppc") + data = data.replace('$INSTALL_SIZE', installSize()) + fp = open(outPath, 'wb') + fp.write(data) + fp.close() + +def patchScript(inPath, outPath): + data = fileContents(inPath) + data = data.replace('@PYVER@', getVersion()) + fp = open(outPath, 'wb') + fp.write(data) + fp.close() + os.chmod(outPath, 0755) + + + +def packageFromRecipe(targetDir, recipe): + curdir = os.getcwd() + try: + pkgname = recipe['name'] + srcdir = recipe.get('source') + pkgroot = recipe.get('topdir', srcdir) + postflight = recipe.get('postflight') + readme = textwrap.dedent(recipe['readme']) + isRequired = recipe.get('required', True) + + print "- building package %s"%(pkgname,) + + # Substitute some variables + textvars = dict( + VER=getVersion(), + FULLVER=getFullVersion(), + ) + readme = readme % textvars + + if pkgroot is not None: + pkgroot = pkgroot % textvars + else: + pkgroot = '/' + + if srcdir is not None: + srcdir = os.path.join(WORKDIR, '_root', srcdir[1:]) + srcdir = srcdir % textvars + + if postflight is not None: + postflight = os.path.abspath(postflight) + + packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents') + os.makedirs(packageContents) + + if srcdir is not None: + os.chdir(srcdir) + runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),)) + runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),)) + runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),)) + + fn = os.path.join(packageContents, 'PkgInfo') + fp = open(fn, 'w') + fp.write('pmkrpkg1') + fp.close() + + rsrcDir = os.path.join(packageContents, "Resources") + os.mkdir(rsrcDir) + fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w') + fp.write(readme) + fp.close() + + if postflight is not None: + patchScript(postflight, os.path.join(rsrcDir, 'postflight')) + + 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,), + CFBundleShortVersionString=vers, + IFMajorVersion=major, + IFMinorVersion=minor, + IFPkgFormatVersion=0.10000000149011612, + IFPkgFlagAllowBackRev=False, + IFPkgFlagAuthorizationAction="RootAuthorization", + IFPkgFlagDefaultLocation=pkgroot, + IFPkgFlagFollowLinks=True, + IFPkgFlagInstallFat=True, + IFPkgFlagIsRequired=isRequired, + IFPkgFlagOverwritePermissions=False, + IFPkgFlagRelocatable=False, + IFPkgFlagRestartAction="NoRestart", + IFPkgFlagRootVolumeOnly=True, + IFPkgFlagUpdateInstalledLangauges=False, + ) + writePlist(pl, os.path.join(packageContents, 'Info.plist')) + + pl = Plist( + IFPkgDescriptionDescription=readme, + IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)), + IFPkgDescriptionVersion=vers, + ) + writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist')) + + finally: + os.chdir(curdir) + + +def makeMpkgPlist(path): + + vers = getFullVersion() + major, minor = map(int, getVersion().split('.', 2)) + + pl = Plist( + CFBundleGetInfoString="MacPython %s"%(vers,), + CFBundleIdentifier='org.python.MacPython', + CFBundleName='MacPython', + CFBundleShortVersionString=vers, + IFMajorVersion=major, + IFMinorVersion=minor, + IFPkgFlagComponentDirectory="Contents/Packages", + IFPkgFlagPackageList=[ + dict( + IFPkgFlagPackageLocation='%s.pkg'%(item['name']), + IFPkgFlagPackageSelection='selected' + ) + for item in PKG_RECIPES + ], + IFPkgFormatVersion=0.10000000149011612, + IFPkgFlagBackgroundScaling="proportional", + IFPkgFlagBackgroundAlignment="left", + ) + + writePlist(pl, path) + + +def buildInstaller(): + + # Zap all compiled files + for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')): + for fn in filenames: + if fn.endswith('.pyc') or fn.endswith('.pyo'): + os.unlink(os.path.join(dirpath, fn)) + + outdir = os.path.join(WORKDIR, 'installer') + if os.path.exists(outdir): + shutil.rmtree(outdir) + os.mkdir(outdir) + + pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents') + pkgcontents = os.path.join(pkgroot, 'Packages') + os.makedirs(pkgcontents) + for recipe in PKG_RECIPES: + packageFromRecipe(pkgcontents, recipe) + + rsrcDir = os.path.join(pkgroot, 'Resources') + + fn = os.path.join(pkgroot, 'PkgInfo') + fp = open(fn, 'w') + fp.write('pmkrpkg1') + fp.close() + + os.mkdir(rsrcDir) + + makeMpkgPlist(os.path.join(pkgroot, 'Info.plist')) + pl = Plist( + IFPkgDescriptionTitle="Universal MacPython", + IFPkgDescriptionVersion=getVersion(), + ) + + writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist')) + for fn in os.listdir('resources'): + if fn == '.svn': continue + if fn.endswith('.jpg'): + shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn)) + else: + patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn)) + + shutil.copy("../../../LICENSE", os.path.join(rsrcDir, 'License.txt')) + + +def installSize(clear=False, _saved=[]): + if clear: + del _saved[:] + if not _saved: + data = captureCommand("du -ks %s"%( + shellQuote(os.path.join(WORKDIR, '_root')))) + _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),)) + return _saved[0] + + +def buildDMG(): + """ + Create DMG containing the rootDir + """ + outdir = os.path.join(WORKDIR, 'diskimage') + if os.path.exists(outdir): + shutil.rmtree(outdir) + + imagepath = os.path.join(outdir, + 'python-%s-macosx'%(getFullVersion(),)) + if INCLUDE_TIMESTAMP: + imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3]) + imagepath = imagepath + '.dmg' + + os.mkdir(outdir) + runCommand("hdiutil create -volname 'Univeral MacPython %s' -srcfolder %s %s"%( + getFullVersion(), + shellQuote(os.path.join(WORKDIR, 'installer')), + shellQuote(imagepath))) + + return imagepath + + +def setIcon(filePath, icnsPath): + """ + Set the custom icon for the specified file or directory. + + For a directory the icon data is written in a file named 'Icon\r' inside + the directory. For both files and directories write the icon as an 'icns' + resource. Furthermore set kHasCustomIcon in the finder flags for filePath. + """ + ref, isDirectory = Carbon.File.FSPathMakeRef(icnsPath) + icon = Carbon.Icn.ReadIconFile(ref) + del ref + + # + # Open the resource fork of the target, to add the icon later on. + # For directories we use the file 'Icon\r' inside the directory. + # + + ref, isDirectory = Carbon.File.FSPathMakeRef(filePath) + + if isDirectory: + tmpPath = os.path.join(filePath, "Icon\r") + if not os.path.exists(tmpPath): + fp = open(tmpPath, 'w') + fp.close() + + tmpRef, _ = Carbon.File.FSPathMakeRef(tmpPath) + spec = Carbon.File.FSSpec(tmpRef) + + else: + spec = Carbon.File.FSSpec(ref) + + try: + Carbon.Res.HCreateResFile(*spec.as_tuple()) + except MacOS.Error: + pass + + # Try to create the resource fork again, this will avoid problems + # when adding an icon to a directory. I have no idea why this helps, + # but without this adding the icon to a directory will fail sometimes. + try: + Carbon.Res.HCreateResFile(*spec.as_tuple()) + except MacOS.Error: + pass + + refNum = Carbon.Res.FSpOpenResFile(spec, fsRdWrPerm) + + Carbon.Res.UseResFile(refNum) + + # Check if there already is an icon, remove it if there is. + try: + h = Carbon.Res.Get1Resource('icns', kCustomIconResource) + except MacOS.Error: + pass + + else: + h.RemoveResource() + del h + + # Add the icon to the resource for of the target + res = Carbon.Res.Resource(icon) + res.AddResource('icns', kCustomIconResource, '') + res.WriteResource() + res.DetachResource() + Carbon.Res.CloseResFile(refNum) + + # And now set the kHasCustomIcon property for the target. Annoyingly, + # python doesn't seem to have bindings for the API that is needed for + # this. Cop out and call SetFile + os.system("/Developer/Tools/SetFile -a C %s"%( + shellQuote(filePath),)) + + if isDirectory: + os.system('/Developer/Tools/SetFile -a V %s'%( + shellQuote(tmpPath), + )) + +def main(): + # First parse options and check if we can perform our work + parseOptions() + checkEnvironment() + + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3' + + if os.path.exists(WORKDIR): + shutil.rmtree(WORKDIR) + os.mkdir(WORKDIR) + + # Then build third-party libraries such as sleepycat DB4. + buildLibraries() + + # Now build python itself + buildPython() + buildPythonDocs() + fn = os.path.join(WORKDIR, "_root", "Applications", + "MacPython %s"%(getVersion(),), "Update Shell Profile.command") + shutil.copy("scripts/postflight.patch-profile", fn) + os.chmod(fn, 0755) + + folder = os.path.join(WORKDIR, "_root", "Applications", "MacPython %s"%( + getVersion(),)) + os.chmod(folder, 0755) + setIcon(folder, "../Icons/Python Folder.icns") + + # Create the installer + buildInstaller() + + # And copy the readme into the directory containing the installer + patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt')) + + # Ditto for the license file. + shutil.copy('../../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt')) + + fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w') + print >> fp, "# BUILD INFO" + print >> fp, "# Date:", time.ctime() + print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos + fp.close() + + # Custom icon for the DMG, shown when the DMG is mounted. + 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"))) + + + # And copy it to a DMG + buildDMG() + + +if __name__ == "__main__": + main() diff --git a/Mac/BuildScript/ncurses-5.5.patch b/Mac/BuildScript/ncurses-5.5.patch new file mode 100644 index 000000000000..0eab3d366687 --- /dev/null +++ b/Mac/BuildScript/ncurses-5.5.patch @@ -0,0 +1,36 @@ +diff -r -u ncurses-5.5-orig/test/Makefile.in ncurses-5.5/test/Makefile.in +--- ncurses-5.5-orig/test/Makefile.in 2006-03-24 12:47:40.000000000 +0100 ++++ ncurses-5.5/test/Makefile.in 2006-03-24 12:47:50.000000000 +0100 +@@ -75,7 +75,7 @@ + MATH_LIB = @MATH_LIB@ + + LD = @LD@ +-LINK = @LINK_TESTS@ $(LIBTOOL_LINK) $(CC) $(CFLAGS) ++LINK = @LINK_TESTS@ $(LIBTOOL_LINK) $(CC) + + usFLAGS = @LD_MODEL@ @LOCAL_LDFLAGS@ @LDFLAGS@ + +diff -ru ncurses-5.5-orig/ncurses/tinfo/read_entry.c ncurses-5.5/ncurses/tinfo/read_entry.c +--- ncurses-5.5-orig/ncurses/tinfo/read_entry.c 2004-01-11 02:57:05.000000000 +0100 ++++ ncurses-5.5/ncurses/tinfo/read_entry.c 2006-03-25 22:49:39.000000000 +0100 +@@ -474,7 +474,7 @@ + } + + /* truncate the terminal name to prevent buffer overflow */ +- (void) sprintf(ttn, "%c/%.*s", *tn, (int) sizeof(ttn) - 3, tn); ++ (void) sprintf(ttn, "%x/%.*s", *tn, (int) sizeof(ttn) - 3, tn); + + /* This is System V behavior, in conjunction with our requirements for + * writing terminfo entries. +diff -ru ncurses-5.5-orig/configure ncurses-5.5/configure +--- ncurses-5.5-orig/configure 2005-09-24 23:50:50.000000000 +0200 ++++ ncurses-5.5/configure 2006-03-26 22:24:59.000000000 +0200 +@@ -5027,7 +5027,7 @@ + darwin*) + EXTRA_CFLAGS="-no-cpp-precomp" + CC_SHARED_OPTS="-dynamic" +- MK_SHARED_LIB='$(CC) -dynamiclib -install_name $(DESTDIR)$(libdir)/`basename $@` -compatibility_version $(ABI_VERSION) -current_version $(ABI_VERSION) -o $@' ++ MK_SHARED_LIB='$(CC) $(CFLAGS) -dynamiclib -install_name $(DESTDIR)$(libdir)/`basename $@` -compatibility_version $(ABI_VERSION) -current_version $(ABI_VERSION) -o $@' + test "$cf_cv_shlib_version" = auto && cf_cv_shlib_version=abi + cf_cv_shlib_version_infix=yes + ;; diff --git a/Mac/BuildScript/resources/ReadMe.txt b/Mac/BuildScript/resources/ReadMe.txt new file mode 100644 index 000000000000..1a6e63764ff9 --- /dev/null +++ b/Mac/BuildScript/resources/ReadMe.txt @@ -0,0 +1,31 @@ +This package will install MacPython $FULL_VERSION for Mac OS X +$MACOSX_DEPLOYMENT_TARGET for the following +architecture(s): $ARCHITECTURES. + +Separate installers are available for older versions +of Mac OS X, see the homepage, below. + +Installation requires approximately $INSTALL_SIZE MB of disk +space, ignore the message that it will take zero bytes. + +You must install onto your current boot disk, even +though the installer does not enforce this, otherwise +things will not work. + +MacPython consists of the Python programming language +interpreter, plus a set of programs to allow easy +access to it for Mac users (an integrated development +environment, an applet builder), plus a set of pre-built +extension modules that open up specific Macintosh technologies +to Python programs (Carbon, AppleScript, Quicktime, more). + +The installer puts the applications in "MacPython $VERSION" +in your Applications folder, command-line tools in +/usr/local/bin and the underlying machinery in +$PYTHONFRAMEWORKINSTALLDIR. + +More information on MacPython can be found at +http://www.cwi.nl/~jack/macpython and +http://pythonmac.org/. More information on +Python in general can be found at +http://www.python.org. diff --git a/Mac/BuildScript/resources/Welcome.rtf b/Mac/BuildScript/resources/Welcome.rtf new file mode 100644 index 000000000000..cb65f0940971 --- /dev/null +++ b/Mac/BuildScript/resources/Welcome.rtf @@ -0,0 +1,15 @@ +{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf330 +{\fonttbl\f0\fswiss\fcharset77 Helvetica;\f1\fswiss\fcharset77 Helvetica-Bold;} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\margl1440\margr1440\vieww9920\viewh10660\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural + +\f0\fs24 \cf0 This package will install +\f1\b MacPython $FULL_VERSION +\f0\b0 for +\f1\b Mac OS X $MACOSX_DEPLOYMENT_TARGET +\f0\b0 .\ +\ +MacPython consists of the Python programming language interpreter, plus a set of programs to allow easy access to it for Mac users (an integrated development environment, an applet builder), plus a set of pre-built extension modules that open up specific Macintosh technologies to Python programs (Carbon, AppleScript, Quicktime, more).\ +\ +See the ReadMe file for more information.} \ No newline at end of file diff --git a/Mac/BuildScript/resources/background.jpg b/Mac/BuildScript/resources/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3c76406cf4a264410e12d5af1c11380164f1e66 GIT binary patch literal 45421 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�nmK4 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 "${HOME}/.cshrc" + echo "# Setting PATH for MacPython ${PYVER}" >> "${HOME}/.cshrc" + echo "# The orginal version is saved in .cshrc.pysave" >> "${HOME}/.cshrc" + echo "set path=(${PYTHON_ROOT}/bin "'$path'")" >> "${HOME}/.cshrc" + exit 0 + ;; +bash) + if [ -e "${HOME}/.profile" ]; then + PR="${HOME}/.profile" + else + PR="${HOME}/.bash_profile" + fi + ;; +*sh) + PR="${HOME}/.profile" + ;; +esac + +# Create backup copy before patching +if [ -f "${PR}" ]; then + cp -fp "${PR}" "${PR}.pysave" +fi +echo "" >> "${PR}" +echo "# Setting PATH for MacPython ${PYVER}" >> "${PR}" +echo "# The orginal version is saved in `basename ${PR}`.pysave" >> "${PR}" +echo 'PATH="'"${PYTHON_ROOT}/bin"':${PATH}"' >> "${PR}" +echo 'export PATH' >> "${PR}" +if [ `id -ur` = 0 ]; then + chown "${LOGNAME}" "${PR}" +fi +exit 0 -- 2.47.3