3 This script is used to build the "official unofficial" universal build on
4 Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its
5 work. 64-bit or four-way universal builds require at least OS X 10.5 and
8 Please ensure that this script keeps working with Python 2.3, to avoid
9 bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
11 Usage: see USAGE variable in the script.
13 import platform
, os
, sys
, getopt
, textwrap
, shutil
, urllib2
, stat
, time
, pwd
19 from plistlib
import Plist
24 from plistlib
import writePlist
26 # We're run using python2.3
27 def writePlist(plist
, path
):
30 def shellQuote(value
):
32 Return the string value in a form that can safely be inserted into
35 return "'%s'"%(value
.replace("'", "'\"'\"'"))
37 def grepValue(fn
, variable
):
38 variable
= variable
+ '='
39 for ln
in open(fn
, 'r'):
40 if ln
.startswith(variable
):
41 value
= ln
[len(variable
):].strip()
43 raise RuntimeError, "Cannot find variable %s" % variable
[:-1]
46 return grepValue(os
.path
.join(SRCDIR
, 'configure'), 'PACKAGE_VERSION')
48 def getVersionTuple():
49 return tuple([int(n
) for n
in getVersion().split('.')])
52 fn
= os
.path
.join(SRCDIR
, 'Include', 'patchlevel.h')
54 if 'PY_VERSION' in ln
:
55 return ln
.split()[-1][1:-1]
56 raise RuntimeError, "Cannot find full version??"
58 # The directory we'll use to create the build (will be erased and recreated)
61 # The directory we'll use to store third-party sources. Set this to something
62 # else if you don't want to re-fetch required libraries every time.
63 DEPSRC
= os
.path
.join(WORKDIR
, 'third-party')
64 DEPSRC
= os
.path
.expanduser('~/Universal/other-sources')
66 # Location of the preferred SDK
68 ### There are some issues with the SDK selection below here,
69 ### The resulting binary doesn't work on all platforms that
70 ### it should. Always default to the 10.4u SDK until that
73 ##if int(os.uname()[2].split('.')[0]) == 8:
74 ## # Explicitly use the 10.4u (universal) SDK when
75 ## # building on 10.4, the system headers are not
76 ## # useable for a universal build
77 ## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
81 SDKPATH
= "/Developer/SDKs/MacOSX10.4u.sdk"
83 universal_opts_map
= { '32-bit': ('i386', 'ppc',),
84 '64-bit': ('x86_64', 'ppc64',),
85 'intel': ('i386', 'x86_64'),
86 '3-way': ('ppc', 'i386', 'x86_64'),
87 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
88 default_target_map
= {
95 UNIVERSALOPTS
= tuple(universal_opts_map
.keys())
97 UNIVERSALARCHS
= '32-bit'
99 ARCHLIST
= universal_opts_map
[UNIVERSALARCHS
]
101 # Source directory (asume we're in Mac/BuildScript)
102 SRCDIR
= os
.path
.dirname(
105 os
.path
.abspath(__file__
108 # $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
118 CC
= target_cc_map
[DEPTARGET
]
120 PYTHON_3
= getVersionTuple() >= (3, 0)
122 USAGE
= textwrap
.dedent("""\
123 Usage: build_python [options]
126 -? or -h: Show this message
128 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
129 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
130 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
131 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
132 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
133 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
137 # Instructions for building libraries that are necessary for building a
138 # batteries included python.
139 # [The recipes are defined here for convenience but instantiated later after
140 # command line options have been processed.]
141 def library_recipes():
144 if DEPTARGET
< '10.5':
148 url
="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
149 checksum
='3c15a0c8d1d3ee1c46a1634d00617b1a',
151 install
='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
153 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
154 ' -arch '.join(ARCHLIST
),
160 url
="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
161 checksum
='debc62758716a169df9f62e6ab2bc634',
163 install
='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
165 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
166 ' -arch '.join(ARCHLIST
),
171 # Note that GNU readline is GPL'd software
172 name
="GNU Readline 5.1.4",
173 url
="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
174 checksum
='7ee5a692db88b30ca48927a13fd60e46',
177 # The readline maintainers don't do actual micro releases, but
178 # just ship a set of patches.
179 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
180 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
181 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
182 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
186 name
="SQLite 3.6.11",
187 url
="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
188 checksum
='7ebb099696ab76cc6ff65dd496d17858',
190 '--enable-threadsafe',
191 '--enable-tempstore',
192 '--enable-shared=no',
193 '--enable-static=yes',
199 url
="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
200 checksum
='e73c1ac10b4bfc46db43b2ddfd6244ef',
205 "--without-curses-h",
208 "--datadir=/usr/share",
210 "--sharedstatedir=/usr/com",
211 "--with-terminfo-dirs=/usr/share/terminfo",
212 "--with-default-terminfo-dir=/usr/share/terminfo",
213 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
220 install
='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
221 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
222 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
230 name
="Sleepycat DB 4.7.25",
231 url
="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
232 checksum
='ec2b87e833779681a0c3a814aa71359e',
233 buildDir
="build_unix",
234 configure
="../dist/configure",
236 '--includedir=/usr/local/include/db4',
244 # Instructions for building packages inside the .mpkg.
246 unselected_for_python3
= ('selected', 'unselected')[PYTHON_3
]
249 name
="PythonFramework",
250 long_name
="Python Framework",
251 source
="/Library/Frameworks/Python.framework",
253 This package installs Python.framework, that is the python
254 interpreter and the standard library. This also includes Python
255 wrappers for lots of Mac OS X API's.
257 postflight
="scripts/postflight.framework",
261 name
="PythonApplications",
262 long_name
="GUI Applications",
263 source
="/Applications/Python %(VER)s",
265 This package installs IDLE (an interactive Python IDE),
266 Python Launcher and Build Applet (create application bundles
267 from python scripts).
269 It also installs a number of examples and demos.
275 name
="PythonUnixTools",
276 long_name
="UNIX command-line tools",
277 source
="/usr/local/bin",
279 This package installs the unix tools in /usr/local/bin for
280 compatibility with older releases of Python. This package
281 is not necessary to use Python.
287 name
="PythonDocumentation",
288 long_name
="Python Documentation",
289 topdir
="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
292 This package installs the python documentation at a location
293 that is useable for pydoc and IDLE. If you have installed Xcode
294 it will also install a link to the documentation in
295 /Developer/Documentation/Python
297 postflight
="scripts/postflight.documentation",
302 name
="PythonProfileChanges",
303 long_name
="Shell profile updater",
305 This packages updates your shell profile to make sure that
306 the Python tools are found by your shell in preference of
307 the system provided Python tools.
309 If you don't install this package you'll have to add
310 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
311 to your PATH by hand.
313 postflight
="scripts/postflight.patch-profile",
314 topdir
="/Library/Frameworks/Python.framework",
317 selected
=unselected_for_python3
,
321 if DEPTARGET
< '10.4':
324 name
="PythonSystemFixes",
325 long_name
="Fix system Python",
327 This package updates the system python installation on
328 Mac OS X 10.3 to ensure that you can build new python extensions
329 using that copy of python after installing this version.
331 postflight
="../Tools/fixapplepython23.py",
332 topdir
="/Library/Frameworks/Python.framework",
335 selected
=unselected_for_python3
,
342 A fatal error, bail out.
344 sys
.stderr
.write('FATAL: ')
345 sys
.stderr
.write(msg
)
346 sys
.stderr
.write('\n')
349 def fileContents(fn
):
351 Return the contents of the named file
353 return open(fn
, 'rb').read()
355 def runCommand(commandline
):
357 Run a command and raise RuntimeError if it fails. Output is surpressed
358 unless the command fails.
360 fd
= os
.popen(commandline
, 'r')
364 sys
.stdout
.write(data
)
365 raise RuntimeError, "command failed: %s"%(commandline
,)
368 sys
.stdout
.write(data
); sys
.stdout
.flush()
370 def captureCommand(commandline
):
371 fd
= os
.popen(commandline
, 'r')
375 sys
.stdout
.write(data
)
376 raise RuntimeError, "command failed: %s"%(commandline
,)
380 def checkEnvironment():
382 Check that we're running on a supported system.
385 if platform
.system() != 'Darwin':
386 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
388 if int(platform
.release().split('.')[0]) < 8:
389 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
391 if not os
.path
.exists(SDKPATH
):
392 fatal("Please install the latest version of Xcode and the %s SDK"%(
393 os
.path
.basename(SDKPATH
[:-4])))
397 def parseOptions(args
=None):
399 Parse arguments and update global settings.
401 global WORKDIR
, DEPSRC
, SDKPATH
, SRCDIR
, DEPTARGET
402 global UNIVERSALOPTS
, UNIVERSALARCHS
, ARCHLIST
, CC
408 options
, args
= getopt
.getopt(args
, '?hb',
409 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
410 'dep-target=', 'universal-archs=', 'help' ])
411 except getopt
.error
, msg
:
416 print "Additional arguments"
421 if k
in ('-h', '-?', '--help'):
425 elif k
in ('-d', '--build-dir'):
428 elif k
in ('--third-party',):
431 elif k
in ('--sdk-path',):
434 elif k
in ('--src-dir',):
437 elif k
in ('--dep-target', ):
441 elif k
in ('--universal-archs', ):
442 if v
in UNIVERSALOPTS
:
444 ARCHLIST
= universal_opts_map
[UNIVERSALARCHS
]
445 if deptarget
is None:
446 # Select alternate default deployment
448 DEPTARGET
= default_target_map
.get(v
, '10.3')
450 raise NotImplementedError, v
453 raise NotImplementedError, k
455 SRCDIR
=os
.path
.abspath(SRCDIR
)
456 WORKDIR
=os
.path
.abspath(WORKDIR
)
457 SDKPATH
=os
.path
.abspath(SDKPATH
)
458 DEPSRC
=os
.path
.abspath(DEPSRC
)
460 CC
=target_cc_map
[DEPTARGET
]
463 print " * Source directory:", SRCDIR
464 print " * Build directory: ", WORKDIR
465 print " * SDK location: ", SDKPATH
466 print " * Third-party source:", DEPSRC
467 print " * Deployment target:", DEPTARGET
468 print " * Universal architectures:", ARCHLIST
469 print " * C compiler:", CC
475 def extractArchive(builddir
, archiveName
):
477 Extract a source archive into 'builddir'. Returns the path of the
480 XXX: This function assumes that archives contain a toplevel directory
481 that is has the same name as the basename of the archive. This is
482 save enough for anything we use.
487 if archiveName
.endswith('.tar.gz'):
488 retval
= os
.path
.basename(archiveName
[:-7])
489 if os
.path
.exists(retval
):
490 shutil
.rmtree(retval
)
491 fp
= os
.popen("tar zxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
493 elif archiveName
.endswith('.tar.bz2'):
494 retval
= os
.path
.basename(archiveName
[:-8])
495 if os
.path
.exists(retval
):
496 shutil
.rmtree(retval
)
497 fp
= os
.popen("tar jxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
499 elif archiveName
.endswith('.tar'):
500 retval
= os
.path
.basename(archiveName
[:-4])
501 if os
.path
.exists(retval
):
502 shutil
.rmtree(retval
)
503 fp
= os
.popen("tar xf %s 2>&1"%(shellQuote(archiveName
),), 'r')
505 elif archiveName
.endswith('.zip'):
506 retval
= os
.path
.basename(archiveName
[:-4])
507 if os
.path
.exists(retval
):
508 shutil
.rmtree(retval
)
509 fp
= os
.popen("unzip %s 2>&1"%(shellQuote(archiveName
),), 'r')
514 sys
.stdout
.write(data
)
515 raise RuntimeError, "Cannot extract %s"%(archiveName
,)
517 return os
.path
.join(builddir
, retval
)
523 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
524 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
527 def downloadURL(url
, fname
):
529 Download the contents of the url into the file.
532 size
= os
.path
.getsize(fname
)
536 if KNOWNSIZES
.get(url
) == size
:
537 print "Using existing file for", url
539 fpIn
= urllib2
.urlopen(url
)
540 fpOut
= open(fname
, 'wb')
541 block
= fpIn
.read(10240)
545 block
= fpIn
.read(10240)
554 def buildRecipe(recipe
, basedir
, archList
):
556 Build software using a recipe. This function does the
557 'configure;make;make install' dance for C software, with a possibility
558 to customize this process, basically a poor-mans DarwinPorts.
562 name
= recipe
['name']
564 configure
= recipe
.get('configure', './configure')
565 install
= recipe
.get('install', 'make && make install DESTDIR=%s'%(
566 shellQuote(basedir
)))
568 archiveName
= os
.path
.split(url
)[-1]
569 sourceArchive
= os
.path
.join(DEPSRC
, archiveName
)
571 if not os
.path
.exists(DEPSRC
):
575 if os
.path
.exists(sourceArchive
):
576 print "Using local copy of %s"%(name
,)
579 print "Did not find local copy of %s"%(name
,)
580 print "Downloading %s"%(name
,)
581 downloadURL(url
, sourceArchive
)
582 print "Archive for %s stored as %s"%(name
, sourceArchive
)
584 print "Extracting archive for %s"%(name
,)
585 buildDir
=os
.path
.join(WORKDIR
, '_bld')
586 if not os
.path
.exists(buildDir
):
589 workDir
= extractArchive(buildDir
, sourceArchive
)
591 if 'buildDir' in recipe
:
592 os
.chdir(recipe
['buildDir'])
595 for fn
in recipe
.get('patches', ()):
596 if fn
.startswith('http://'):
597 # Download the patch before applying it.
598 path
= os
.path
.join(DEPSRC
, os
.path
.basename(fn
))
599 downloadURL(fn
, path
)
602 fn
= os
.path
.join(curdir
, fn
)
603 runCommand('patch -p%s < %s'%(recipe
.get('patchlevel', 1),
606 if configure
is not None:
608 "--prefix=/usr/local",
611 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
614 if 'configure_pre' in recipe
:
615 args
= list(recipe
['configure_pre'])
616 if '--disable-static' in args
:
617 configure_args
.remove('--enable-static')
618 if '--enable-shared' in args
:
619 configure_args
.remove('--disable-shared')
620 configure_args
.extend(args
)
622 if recipe
.get('useLDFlags', 1):
623 configure_args
.extend([
624 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
625 ' -arch '.join(archList
),
626 shellQuote(SDKPATH
)[1:-1],
627 shellQuote(basedir
)[1:-1],),
628 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
629 shellQuote(SDKPATH
)[1:-1],
630 shellQuote(basedir
)[1:-1],
631 ' -arch '.join(archList
)),
634 configure_args
.extend([
635 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
636 ' -arch '.join(archList
),
637 shellQuote(SDKPATH
)[1:-1],
638 shellQuote(basedir
)[1:-1],),
641 if 'configure_post' in recipe
:
642 configure_args
= configure_args
= list(recipe
['configure_post'])
644 configure_args
.insert(0, configure
)
645 configure_args
= [ shellQuote(a
) for a
in configure_args
]
647 print "Running configure for %s"%(name
,)
648 runCommand(' '.join(configure_args
) + ' 2>&1')
650 print "Running install for %s"%(name
,)
651 runCommand('{ ' + install
+ ' ;} 2>&1')
653 print "Done %s"%(name
,)
658 def buildLibraries():
660 Build our dependencies into $WORKDIR/libraries/usr/local
663 print "Building required libraries"
665 universal
= os
.path
.join(WORKDIR
, 'libraries')
667 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'lib'))
668 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'include'))
670 for recipe
in library_recipes():
671 buildRecipe(recipe
, universal
, ARCHLIST
)
675 def buildPythonDocs():
676 # This stores the documentation as Resources/English.lproj/Documentation
677 # inside the framwork. pydoc and IDLE will pick it up there.
678 print "Install python documentation"
679 rootDir
= os
.path
.join(WORKDIR
, '_root')
680 buildDir
= os
.path
.join('../../Doc')
681 docdir
= os
.path
.join(rootDir
, 'pydocs')
684 runCommand('make update')
685 runCommand('make html')
687 if not os
.path
.exists(docdir
):
689 os
.rename(os
.path
.join(buildDir
, 'build', 'html'), docdir
)
693 print "Building a universal python for %s architectures" % UNIVERSALARCHS
695 buildDir
= os
.path
.join(WORKDIR
, '_bld', 'python')
696 rootDir
= os
.path
.join(WORKDIR
, '_root')
698 if os
.path
.exists(buildDir
):
699 shutil
.rmtree(buildDir
)
700 if os
.path
.exists(rootDir
):
701 shutil
.rmtree(rootDir
)
704 os
.mkdir(os
.path
.join(rootDir
, 'empty-dir'))
708 # Not sure if this is still needed, the original build script
709 # claims that parts of the install assume python.exe exists.
710 os
.symlink('python', os
.path
.join(buildDir
, 'python.exe'))
712 # Extract the version from the configure file, needed to calculate
714 version
= getVersion()
716 # Since the extra libs are not in their installed framework location
717 # during the build, augment the library path so that the interpreter
718 # will find them during its extension import sanity checks.
719 os
.environ
['DYLD_LIBRARY_PATH'] = os
.path
.join(WORKDIR
,
720 'libraries', 'usr', 'local', 'lib')
721 print "Running configure..."
722 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
723 "--with-universal-archs=%s "
725 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
726 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
727 shellQuote(os
.path
.join(SRCDIR
, 'configure')), shellQuote(SDKPATH
),
729 (' ', '--with-computed-gotos ')[PYTHON_3
],
730 shellQuote(WORKDIR
)[1:-1],
731 shellQuote(WORKDIR
)[1:-1]))
736 print "Running make install"
737 runCommand("make install DESTDIR=%s"%(
738 shellQuote(rootDir
)))
740 print "Running make frameworkinstallextras"
741 runCommand("make frameworkinstallextras DESTDIR=%s"%(
742 shellQuote(rootDir
)))
744 del os
.environ
['DYLD_LIBRARY_PATH']
745 print "Copying required shared libraries"
746 if os
.path
.exists(os
.path
.join(WORKDIR
, 'libraries', 'Library')):
747 runCommand("mv %s/* %s"%(
748 shellQuote(os
.path
.join(
749 WORKDIR
, 'libraries', 'Library', 'Frameworks',
750 'Python.framework', 'Versions', getVersion(),
752 shellQuote(os
.path
.join(WORKDIR
, '_root', 'Library', 'Frameworks',
753 'Python.framework', 'Versions', getVersion(),
756 print "Fix file modes"
757 frmDir
= os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework')
758 gid
= grp
.getgrnam('admin').gr_gid
760 for dirpath
, dirnames
, filenames
in os
.walk(frmDir
):
762 os
.chmod(os
.path
.join(dirpath
, dn
), 0775)
763 os
.chown(os
.path
.join(dirpath
, dn
), -1, gid
)
767 if os
.path
.islink(fn
):
771 p
= os
.path
.join(dirpath
, fn
)
773 os
.chmod(p
, stat
.S_IMODE(st
.st_mode
) | stat
.S_IWGRP
)
776 # We added some directories to the search path during the configure
777 # phase. Remove those because those directories won't be there on
778 # the end-users system.
779 path
=os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework',
780 'Versions', version
, 'lib', 'python%s'%(version
,),
781 'config', 'Makefile')
786 data
= data
.replace('-L%s/libraries/usr/local/lib'%(WORKDIR
,), '')
787 data
= data
.replace('-I%s/libraries/usr/local/include'%(WORKDIR
,), '')
792 # Add symlinks in /usr/local/bin, using relative links
793 usr_local_bin
= os
.path
.join(rootDir
, 'usr', 'local', 'bin')
794 to_framework
= os
.path
.join('..', '..', '..', 'Library', 'Frameworks',
795 'Python.framework', 'Versions', version
, 'bin')
796 if os
.path
.exists(usr_local_bin
):
797 shutil
.rmtree(usr_local_bin
)
798 os
.makedirs(usr_local_bin
)
799 for fn
in os
.listdir(
800 os
.path
.join(frmDir
, 'Versions', version
, 'bin')):
801 os
.symlink(os
.path
.join(to_framework
, fn
),
802 os
.path
.join(usr_local_bin
, fn
))
807 # Remove the 'Current' link, that way we don't accidently mess
808 # with an already installed version of python 2
809 os
.unlink(os
.path
.join(rootDir
, 'Library', 'Frameworks',
810 'Python.framework', 'Versions', 'Current'))
812 def patchFile(inPath
, outPath
):
813 data
= fileContents(inPath
)
814 data
= data
.replace('$FULL_VERSION', getFullVersion())
815 data
= data
.replace('$VERSION', getVersion())
816 data
= data
.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET
, ' or later')))
817 data
= data
.replace('$ARCHITECTURES', "i386, ppc")
818 data
= data
.replace('$INSTALL_SIZE', installSize())
820 # This one is not handy as a template variable
821 data
= data
.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
822 fp
= open(outPath
, 'wb')
826 def patchScript(inPath
, outPath
):
827 data
= fileContents(inPath
)
828 data
= data
.replace('@PYVER@', getVersion())
829 fp
= open(outPath
, 'wb')
832 os
.chmod(outPath
, 0755)
836 def packageFromRecipe(targetDir
, recipe
):
839 # The major version (such as 2.5) is included in the package name
840 # because having two version of python installed at the same time is
842 pkgname
= '%s-%s'%(recipe
['name'], getVersion())
843 srcdir
= recipe
.get('source')
844 pkgroot
= recipe
.get('topdir', srcdir
)
845 postflight
= recipe
.get('postflight')
846 readme
= textwrap
.dedent(recipe
['readme'])
847 isRequired
= recipe
.get('required', True)
849 print "- building package %s"%(pkgname
,)
851 # Substitute some variables
854 FULLVER
=getFullVersion(),
856 readme
= readme
% textvars
858 if pkgroot
is not None:
859 pkgroot
= pkgroot
% textvars
863 if srcdir
is not None:
864 srcdir
= os
.path
.join(WORKDIR
, '_root', srcdir
[1:])
865 srcdir
= srcdir
% textvars
867 if postflight
is not None:
868 postflight
= os
.path
.abspath(postflight
)
870 packageContents
= os
.path
.join(targetDir
, pkgname
+ '.pkg', 'Contents')
871 os
.makedirs(packageContents
)
873 if srcdir
is not None:
875 runCommand("pax -wf %s . 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
876 runCommand("gzip -9 %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
877 runCommand("mkbom . %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.bom')),))
879 fn
= os
.path
.join(packageContents
, 'PkgInfo')
884 rsrcDir
= os
.path
.join(packageContents
, "Resources")
886 fp
= open(os
.path
.join(rsrcDir
, 'ReadMe.txt'), 'w')
890 if postflight
is not None:
891 patchScript(postflight
, os
.path
.join(rsrcDir
, 'postflight'))
893 vers
= getFullVersion()
894 major
, minor
= map(int, getVersion().split('.', 2))
896 CFBundleGetInfoString
="Python.%s %s"%(pkgname
, vers
,),
897 CFBundleIdentifier
='org.python.Python.%s'%(pkgname
,),
898 CFBundleName
='Python.%s'%(pkgname
,),
899 CFBundleShortVersionString
=vers
,
900 IFMajorVersion
=major
,
901 IFMinorVersion
=minor
,
902 IFPkgFormatVersion
=0.10000000149011612,
903 IFPkgFlagAllowBackRev
=False,
904 IFPkgFlagAuthorizationAction
="RootAuthorization",
905 IFPkgFlagDefaultLocation
=pkgroot
,
906 IFPkgFlagFollowLinks
=True,
907 IFPkgFlagInstallFat
=True,
908 IFPkgFlagIsRequired
=isRequired
,
909 IFPkgFlagOverwritePermissions
=False,
910 IFPkgFlagRelocatable
=False,
911 IFPkgFlagRestartAction
="NoRestart",
912 IFPkgFlagRootVolumeOnly
=True,
913 IFPkgFlagUpdateInstalledLangauges
=False,
915 writePlist(pl
, os
.path
.join(packageContents
, 'Info.plist'))
918 IFPkgDescriptionDescription
=readme
,
919 IFPkgDescriptionTitle
=recipe
.get('long_name', "Python.%s"%(pkgname
,)),
920 IFPkgDescriptionVersion
=vers
,
922 writePlist(pl
, os
.path
.join(packageContents
, 'Resources', 'Description.plist'))
928 def makeMpkgPlist(path
):
930 vers
= getFullVersion()
931 major
, minor
= map(int, getVersion().split('.', 2))
934 CFBundleGetInfoString
="Python %s"%(vers
,),
935 CFBundleIdentifier
='org.python.Python',
936 CFBundleName
='Python',
937 CFBundleShortVersionString
=vers
,
938 IFMajorVersion
=major
,
939 IFMinorVersion
=minor
,
940 IFPkgFlagComponentDirectory
="Contents/Packages",
941 IFPkgFlagPackageList
=[
943 IFPkgFlagPackageLocation
='%s-%s.pkg'%(item
['name'], getVersion()),
944 IFPkgFlagPackageSelection
=item
.get('selected', 'selected'),
946 for item
in pkg_recipes()
948 IFPkgFormatVersion
=0.10000000149011612,
949 IFPkgFlagBackgroundScaling
="proportional",
950 IFPkgFlagBackgroundAlignment
="left",
951 IFPkgFlagAuthorizationAction
="RootAuthorization",
957 def buildInstaller():
959 # Zap all compiled files
960 for dirpath
, _
, filenames
in os
.walk(os
.path
.join(WORKDIR
, '_root')):
962 if fn
.endswith('.pyc') or fn
.endswith('.pyo'):
963 os
.unlink(os
.path
.join(dirpath
, fn
))
965 outdir
= os
.path
.join(WORKDIR
, 'installer')
966 if os
.path
.exists(outdir
):
967 shutil
.rmtree(outdir
)
970 pkgroot
= os
.path
.join(outdir
, 'Python.mpkg', 'Contents')
971 pkgcontents
= os
.path
.join(pkgroot
, 'Packages')
972 os
.makedirs(pkgcontents
)
973 for recipe
in pkg_recipes():
974 packageFromRecipe(pkgcontents
, recipe
)
976 rsrcDir
= os
.path
.join(pkgroot
, 'Resources')
978 fn
= os
.path
.join(pkgroot
, 'PkgInfo')
985 makeMpkgPlist(os
.path
.join(pkgroot
, 'Info.plist'))
987 IFPkgDescriptionTitle
="Python",
988 IFPkgDescriptionVersion
=getVersion(),
991 writePlist(pl
, os
.path
.join(pkgroot
, 'Resources', 'Description.plist'))
992 for fn
in os
.listdir('resources'):
993 if fn
== '.svn': continue
994 if fn
.endswith('.jpg'):
995 shutil
.copy(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
997 patchFile(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
999 shutil
.copy("../../LICENSE", os
.path
.join(rsrcDir
, 'License.txt'))
1002 def installSize(clear
=False, _saved
=[]):
1006 data
= captureCommand("du -ks %s"%(
1007 shellQuote(os
.path
.join(WORKDIR
, '_root'))))
1008 _saved
.append("%d"%((0.5 + (int(data
.split()[0]) / 1024.0)),))
1014 Create DMG containing the rootDir.
1016 outdir
= os
.path
.join(WORKDIR
, 'diskimage')
1017 if os
.path
.exists(outdir
):
1018 shutil
.rmtree(outdir
)
1020 imagepath
= os
.path
.join(outdir
,
1021 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET
))
1022 if INCLUDE_TIMESTAMP
:
1023 imagepath
= imagepath
+ '-%04d-%02d-%02d'%(time
.localtime()[:3])
1024 imagepath
= imagepath
+ '.dmg'
1027 volname
='Python %s'%(getFullVersion())
1028 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1029 shellQuote(volname
),
1030 shellQuote(os
.path
.join(WORKDIR
, 'installer')),
1031 shellQuote(imagepath
+ ".tmp.dmg" )))
1034 if not os
.path
.exists(os
.path
.join(WORKDIR
, "mnt")):
1035 os
.mkdir(os
.path
.join(WORKDIR
, "mnt"))
1036 runCommand("hdiutil attach %s -mountroot %s"%(
1037 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(os
.path
.join(WORKDIR
, "mnt"))))
1039 # Custom icon for the DMG, shown when the DMG is mounted.
1040 shutil
.copy("../Icons/Disk Image.icns",
1041 os
.path
.join(WORKDIR
, "mnt", volname
, ".VolumeIcon.icns"))
1042 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1043 shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
)),))
1045 runCommand("hdiutil detach %s"%(shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
))))
1047 setIcon(imagepath
+ ".tmp.dmg", "../Icons/Disk Image.icns")
1048 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1049 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(imagepath
)))
1050 setIcon(imagepath
, "../Icons/Disk Image.icns")
1052 os
.unlink(imagepath
+ ".tmp.dmg")
1057 def setIcon(filePath
, icnsPath
):
1059 Set the custom icon for the specified file or directory.
1062 toolPath
= os
.path
.join(os
.path
.dirname(__file__
), "seticon.app/Contents/MacOS/seticon")
1063 dirPath
= os
.path
.dirname(__file__
)
1064 if not os
.path
.exists(toolPath
) or os
.stat(toolPath
).st_mtime
< os
.stat(dirPath
+ '/seticon.m').st_mtime
:
1065 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1066 # to connections to the window server.
1067 if not os
.path
.exists('seticon.app/Contents/MacOS'):
1068 os
.makedirs('seticon.app/Contents/MacOS')
1069 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1070 shellQuote(toolPath
), shellQuote(dirPath
)))
1072 runCommand("%s %s %s"%(shellQuote(os
.path
.abspath(toolPath
)), shellQuote(icnsPath
),
1073 shellQuote(filePath
)))
1076 # First parse options and check if we can perform our work
1080 os
.environ
['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
1081 os
.environ
['CC'] = CC
1083 if os
.path
.exists(WORKDIR
):
1084 shutil
.rmtree(WORKDIR
)
1087 # Then build third-party libraries such as sleepycat DB4.
1090 # Now build python itself
1093 # And then build the documentation
1094 # Remove the Deployment Target from the shell
1095 # environment, it's no longer needed and
1096 # an unexpected build target can cause problems
1097 # when Sphinx and its dependencies need to
1098 # be (re-)installed.
1099 del os
.environ
['MACOSX_DEPLOYMENT_TARGET']
1103 # Prepare the applications folder
1104 fn
= os
.path
.join(WORKDIR
, "_root", "Applications",
1105 "Python %s"%(getVersion(),), "Update Shell Profile.command")
1106 patchScript("scripts/postflight.patch-profile", fn
)
1108 folder
= os
.path
.join(WORKDIR
, "_root", "Applications", "Python %s"%(
1110 os
.chmod(folder
, 0755)
1111 setIcon(folder
, "../Icons/Python Folder.icns")
1113 # Create the installer
1116 # And copy the readme into the directory containing the installer
1117 patchFile('resources/ReadMe.txt', os
.path
.join(WORKDIR
, 'installer', 'ReadMe.txt'))
1119 # Ditto for the license file.
1120 shutil
.copy('../../LICENSE', os
.path
.join(WORKDIR
, 'installer', 'License.txt'))
1122 fp
= open(os
.path
.join(WORKDIR
, 'installer', 'Build.txt'), 'w')
1123 print >> fp
, "# BUILD INFO"
1124 print >> fp
, "# Date:", time
.ctime()
1125 print >> fp
, "# By:", pwd
.getpwuid(os
.getuid()).pw_gecos
1128 # And copy it to a DMG
1131 if __name__
== "__main__":