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()
45 return grepValue(os
.path
.join(SRCDIR
, 'configure'), 'PACKAGE_VERSION')
48 fn
= os
.path
.join(SRCDIR
, 'Include', 'patchlevel.h')
50 if 'PY_VERSION' in ln
:
51 return ln
.split()[-1][1:-1]
53 raise RuntimeError, "Cannot find full version??"
55 # The directory we'll use to create the build (will be erased and recreated)
58 # The directory we'll use to store third-party sources. Set this to something
59 # else if you don't want to re-fetch required libraries every time.
60 DEPSRC
= os
.path
.join(WORKDIR
, 'third-party')
61 DEPSRC
= os
.path
.expanduser('~/Universal/other-sources')
63 # Location of the preferred SDK
65 ### There are some issues with the SDK selection below here,
66 ### The resulting binary doesn't work on all platforms that
67 ### it should. Always default to the 10.4u SDK until that
70 ##if int(os.uname()[2].split('.')[0]) == 8:
71 ## # Explicitly use the 10.4u (universal) SDK when
72 ## # building on 10.4, the system headers are not
73 ## # useable for a universal build
74 ## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
78 SDKPATH
= "/Developer/SDKs/MacOSX10.4u.sdk"
80 universal_opts_map
= { '32-bit': ('i386', 'ppc',),
81 '64-bit': ('x86_64', 'ppc64',),
82 'intel': ('i386', 'x86_64'),
83 '3-way': ('ppc', 'i386', 'x86_64'),
84 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
85 default_target_map
= {
92 UNIVERSALOPTS
= tuple(universal_opts_map
.keys())
94 UNIVERSALARCHS
= '32-bit'
96 ARCHLIST
= universal_opts_map
[UNIVERSALARCHS
]
98 # Source directory (asume we're in Mac/BuildScript)
99 SRCDIR
= os
.path
.dirname(
102 os
.path
.abspath(__file__
105 # $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
115 CC
= target_cc_map
[DEPTARGET
]
117 USAGE
= textwrap
.dedent("""\
118 Usage: build_python [options]
121 -? or -h: Show this message
123 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
124 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
125 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
126 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
127 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
128 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
132 # Instructions for building libraries that are necessary for building a
133 # batteries included python.
134 # [The recipes are defined here for convenience but instantiated later after
135 # command line options have been processed.]
136 def library_recipes():
139 if DEPTARGET
< '10.5':
143 url
="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
144 checksum
='3c15a0c8d1d3ee1c46a1634d00617b1a',
146 install
='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
148 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
149 ' -arch '.join(ARCHLIST
),
155 url
="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
156 checksum
='debc62758716a169df9f62e6ab2bc634',
158 install
='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
160 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
161 ' -arch '.join(ARCHLIST
),
166 # Note that GNU readline is GPL'd software
167 name
="GNU Readline 5.1.4",
168 url
="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
169 checksum
='7ee5a692db88b30ca48927a13fd60e46',
172 # The readline maintainers don't do actual micro releases, but
173 # just ship a set of patches.
174 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
175 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
176 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
177 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
181 name
="SQLite 3.6.11",
182 url
="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
183 checksum
='7ebb099696ab76cc6ff65dd496d17858',
185 '--enable-threadsafe',
186 '--enable-tempstore',
187 '--enable-shared=no',
188 '--enable-static=yes',
194 url
="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
195 checksum
='e73c1ac10b4bfc46db43b2ddfd6244ef',
200 "--without-curses-h",
203 "--datadir=/usr/share",
205 "--sharedstatedir=/usr/com",
206 "--with-terminfo-dirs=/usr/share/terminfo",
207 "--with-default-terminfo-dir=/usr/share/terminfo",
208 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
215 install
='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
216 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
217 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
225 name
="Sleepycat DB 4.7.25",
226 url
="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
227 checksum
='ec2b87e833779681a0c3a814aa71359e',
228 buildDir
="build_unix",
229 configure
="../dist/configure",
231 '--includedir=/usr/local/include/db4',
239 # Instructions for building packages inside the .mpkg.
243 name
="PythonFramework",
244 long_name
="Python Framework",
245 source
="/Library/Frameworks/Python.framework",
247 This package installs Python.framework, that is the python
248 interpreter and the standard library. This also includes Python
249 wrappers for lots of Mac OS X API's.
251 postflight
="scripts/postflight.framework",
254 name
="PythonApplications",
255 long_name
="GUI Applications",
256 source
="/Applications/Python %(VER)s",
258 This package installs IDLE (an interactive Python IDE),
259 Python Launcher and Build Applet (create application bundles
260 from python scripts).
262 It also installs a number of examples and demos.
267 name
="PythonUnixTools",
268 long_name
="UNIX command-line tools",
269 source
="/usr/local/bin",
271 This package installs the unix tools in /usr/local/bin for
272 compatibility with older releases of Python. This package
273 is not necessary to use Python.
278 name
="PythonDocumentation",
279 long_name
="Python Documentation",
280 topdir
="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
283 This package installs the python documentation at a location
284 that is useable for pydoc and IDLE. If you have installed Xcode
285 it will also install a link to the documentation in
286 /Developer/Documentation/Python
288 postflight
="scripts/postflight.documentation",
292 name
="PythonProfileChanges",
293 long_name
="Shell profile updater",
295 This packages updates your shell profile to make sure that
296 the Python tools are found by your shell in preference of
297 the system provided Python tools.
299 If you don't install this package you'll have to add
300 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
301 to your PATH by hand.
303 postflight
="scripts/postflight.patch-profile",
304 topdir
="/Library/Frameworks/Python.framework",
310 if DEPTARGET
< '10.4':
313 name
="PythonSystemFixes",
314 long_name
="Fix system Python",
316 This package updates the system python installation on
317 Mac OS X 10.3 to ensure that you can build new python extensions
318 using that copy of python after installing this version.
320 postflight
="../Tools/fixapplepython23.py",
321 topdir
="/Library/Frameworks/Python.framework",
330 A fatal error, bail out.
332 sys
.stderr
.write('FATAL: ')
333 sys
.stderr
.write(msg
)
334 sys
.stderr
.write('\n')
337 def fileContents(fn
):
339 Return the contents of the named file
341 return open(fn
, 'rb').read()
343 def runCommand(commandline
):
345 Run a command and raise RuntimeError if it fails. Output is surpressed
346 unless the command fails.
348 fd
= os
.popen(commandline
, 'r')
352 sys
.stdout
.write(data
)
353 raise RuntimeError, "command failed: %s"%(commandline
,)
356 sys
.stdout
.write(data
); sys
.stdout
.flush()
358 def captureCommand(commandline
):
359 fd
= os
.popen(commandline
, 'r')
363 sys
.stdout
.write(data
)
364 raise RuntimeError, "command failed: %s"%(commandline
,)
368 def checkEnvironment():
370 Check that we're running on a supported system.
373 if platform
.system() != 'Darwin':
374 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
376 if int(platform
.release().split('.')[0]) < 8:
377 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
379 if not os
.path
.exists(SDKPATH
):
380 fatal("Please install the latest version of Xcode and the %s SDK"%(
381 os
.path
.basename(SDKPATH
[:-4])))
385 def parseOptions(args
=None):
387 Parse arguments and update global settings.
389 global WORKDIR
, DEPSRC
, SDKPATH
, SRCDIR
, DEPTARGET
390 global UNIVERSALOPTS
, UNIVERSALARCHS
, ARCHLIST
, CC
396 options
, args
= getopt
.getopt(args
, '?hb',
397 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
398 'dep-target=', 'universal-archs=', 'help' ])
399 except getopt
.error
, msg
:
404 print "Additional arguments"
409 if k
in ('-h', '-?', '--help'):
413 elif k
in ('-d', '--build-dir'):
416 elif k
in ('--third-party',):
419 elif k
in ('--sdk-path',):
422 elif k
in ('--src-dir',):
425 elif k
in ('--dep-target', ):
429 elif k
in ('--universal-archs', ):
430 if v
in UNIVERSALOPTS
:
432 ARCHLIST
= universal_opts_map
[UNIVERSALARCHS
]
433 if deptarget
is None:
434 # Select alternate default deployment
436 DEPTARGET
= default_target_map
.get(v
, '10.3')
438 raise NotImplementedError, v
441 raise NotImplementedError, k
443 SRCDIR
=os
.path
.abspath(SRCDIR
)
444 WORKDIR
=os
.path
.abspath(WORKDIR
)
445 SDKPATH
=os
.path
.abspath(SDKPATH
)
446 DEPSRC
=os
.path
.abspath(DEPSRC
)
448 CC
=target_cc_map
[DEPTARGET
]
451 print " * Source directory:", SRCDIR
452 print " * Build directory: ", WORKDIR
453 print " * SDK location: ", SDKPATH
454 print " * Third-party source:", DEPSRC
455 print " * Deployment target:", DEPTARGET
456 print " * Universal architectures:", ARCHLIST
457 print " * C compiler:", CC
463 def extractArchive(builddir
, archiveName
):
465 Extract a source archive into 'builddir'. Returns the path of the
468 XXX: This function assumes that archives contain a toplevel directory
469 that is has the same name as the basename of the archive. This is
470 save enough for anything we use.
475 if archiveName
.endswith('.tar.gz'):
476 retval
= os
.path
.basename(archiveName
[:-7])
477 if os
.path
.exists(retval
):
478 shutil
.rmtree(retval
)
479 fp
= os
.popen("tar zxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
481 elif archiveName
.endswith('.tar.bz2'):
482 retval
= os
.path
.basename(archiveName
[:-8])
483 if os
.path
.exists(retval
):
484 shutil
.rmtree(retval
)
485 fp
= os
.popen("tar jxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
487 elif archiveName
.endswith('.tar'):
488 retval
= os
.path
.basename(archiveName
[:-4])
489 if os
.path
.exists(retval
):
490 shutil
.rmtree(retval
)
491 fp
= os
.popen("tar xf %s 2>&1"%(shellQuote(archiveName
),), 'r')
493 elif archiveName
.endswith('.zip'):
494 retval
= os
.path
.basename(archiveName
[:-4])
495 if os
.path
.exists(retval
):
496 shutil
.rmtree(retval
)
497 fp
= os
.popen("unzip %s 2>&1"%(shellQuote(archiveName
),), 'r')
502 sys
.stdout
.write(data
)
503 raise RuntimeError, "Cannot extract %s"%(archiveName
,)
505 return os
.path
.join(builddir
, retval
)
511 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
512 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
515 def downloadURL(url
, fname
):
517 Download the contents of the url into the file.
520 size
= os
.path
.getsize(fname
)
524 if KNOWNSIZES
.get(url
) == size
:
525 print "Using existing file for", url
527 fpIn
= urllib2
.urlopen(url
)
528 fpOut
= open(fname
, 'wb')
529 block
= fpIn
.read(10240)
533 block
= fpIn
.read(10240)
542 def buildRecipe(recipe
, basedir
, archList
):
544 Build software using a recipe. This function does the
545 'configure;make;make install' dance for C software, with a possibility
546 to customize this process, basically a poor-mans DarwinPorts.
550 name
= recipe
['name']
552 configure
= recipe
.get('configure', './configure')
553 install
= recipe
.get('install', 'make && make install DESTDIR=%s'%(
554 shellQuote(basedir
)))
556 archiveName
= os
.path
.split(url
)[-1]
557 sourceArchive
= os
.path
.join(DEPSRC
, archiveName
)
559 if not os
.path
.exists(DEPSRC
):
563 if os
.path
.exists(sourceArchive
):
564 print "Using local copy of %s"%(name
,)
567 print "Did not find local copy of %s"%(name
,)
568 print "Downloading %s"%(name
,)
569 downloadURL(url
, sourceArchive
)
570 print "Archive for %s stored as %s"%(name
, sourceArchive
)
572 print "Extracting archive for %s"%(name
,)
573 buildDir
=os
.path
.join(WORKDIR
, '_bld')
574 if not os
.path
.exists(buildDir
):
577 workDir
= extractArchive(buildDir
, sourceArchive
)
579 if 'buildDir' in recipe
:
580 os
.chdir(recipe
['buildDir'])
583 for fn
in recipe
.get('patches', ()):
584 if fn
.startswith('http://'):
585 # Download the patch before applying it.
586 path
= os
.path
.join(DEPSRC
, os
.path
.basename(fn
))
587 downloadURL(fn
, path
)
590 fn
= os
.path
.join(curdir
, fn
)
591 runCommand('patch -p%s < %s'%(recipe
.get('patchlevel', 1),
594 if configure
is not None:
596 "--prefix=/usr/local",
599 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
602 if 'configure_pre' in recipe
:
603 args
= list(recipe
['configure_pre'])
604 if '--disable-static' in args
:
605 configure_args
.remove('--enable-static')
606 if '--enable-shared' in args
:
607 configure_args
.remove('--disable-shared')
608 configure_args
.extend(args
)
610 if recipe
.get('useLDFlags', 1):
611 configure_args
.extend([
612 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
613 ' -arch '.join(archList
),
614 shellQuote(SDKPATH
)[1:-1],
615 shellQuote(basedir
)[1:-1],),
616 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
617 shellQuote(SDKPATH
)[1:-1],
618 shellQuote(basedir
)[1:-1],
619 ' -arch '.join(archList
)),
622 configure_args
.extend([
623 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
624 ' -arch '.join(archList
),
625 shellQuote(SDKPATH
)[1:-1],
626 shellQuote(basedir
)[1:-1],),
629 if 'configure_post' in recipe
:
630 configure_args
= configure_args
= list(recipe
['configure_post'])
632 configure_args
.insert(0, configure
)
633 configure_args
= [ shellQuote(a
) for a
in configure_args
]
635 print "Running configure for %s"%(name
,)
636 runCommand(' '.join(configure_args
) + ' 2>&1')
638 print "Running install for %s"%(name
,)
639 runCommand('{ ' + install
+ ' ;} 2>&1')
641 print "Done %s"%(name
,)
646 def buildLibraries():
648 Build our dependencies into $WORKDIR/libraries/usr/local
651 print "Building required libraries"
653 universal
= os
.path
.join(WORKDIR
, 'libraries')
655 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'lib'))
656 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'include'))
658 for recipe
in library_recipes():
659 buildRecipe(recipe
, universal
, ARCHLIST
)
663 def buildPythonDocs():
664 # This stores the documentation as Resources/English.lproj/Documentation
665 # inside the framwork. pydoc and IDLE will pick it up there.
666 print "Install python documentation"
667 rootDir
= os
.path
.join(WORKDIR
, '_root')
668 buildDir
= os
.path
.join('../../Doc')
669 docdir
= os
.path
.join(rootDir
, 'pydocs')
672 runCommand('make update')
673 runCommand('make html')
675 if not os
.path
.exists(docdir
):
677 os
.rename(os
.path
.join(buildDir
, 'build', 'html'), docdir
)
681 print "Building a universal python for %s architectures" % UNIVERSALARCHS
683 buildDir
= os
.path
.join(WORKDIR
, '_bld', 'python')
684 rootDir
= os
.path
.join(WORKDIR
, '_root')
686 if os
.path
.exists(buildDir
):
687 shutil
.rmtree(buildDir
)
688 if os
.path
.exists(rootDir
):
689 shutil
.rmtree(rootDir
)
692 os
.mkdir(os
.path
.join(rootDir
, 'empty-dir'))
696 # Not sure if this is still needed, the original build script
697 # claims that parts of the install assume python.exe exists.
698 os
.symlink('python', os
.path
.join(buildDir
, 'python.exe'))
700 # Extract the version from the configure file, needed to calculate
702 version
= getVersion()
704 # Since the extra libs are not in their installed framework location
705 # during the build, augment the library path so that the interpreter
706 # will find them during its extension import sanity checks.
707 os
.environ
['DYLD_LIBRARY_PATH'] = os
.path
.join(WORKDIR
,
708 'libraries', 'usr', 'local', 'lib')
709 print "Running configure..."
710 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
711 "--with-universal-archs=%s "
712 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
713 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
714 shellQuote(os
.path
.join(SRCDIR
, 'configure')), shellQuote(SDKPATH
),
716 shellQuote(WORKDIR
)[1:-1],
717 shellQuote(WORKDIR
)[1:-1]))
722 print "Running make frameworkinstall"
723 runCommand("make frameworkinstall DESTDIR=%s"%(
724 shellQuote(rootDir
)))
726 print "Running make frameworkinstallextras"
727 runCommand("make frameworkinstallextras DESTDIR=%s"%(
728 shellQuote(rootDir
)))
730 del os
.environ
['DYLD_LIBRARY_PATH']
731 print "Copying required shared libraries"
732 if os
.path
.exists(os
.path
.join(WORKDIR
, 'libraries', 'Library')):
733 runCommand("mv %s/* %s"%(
734 shellQuote(os
.path
.join(
735 WORKDIR
, 'libraries', 'Library', 'Frameworks',
736 'Python.framework', 'Versions', getVersion(),
738 shellQuote(os
.path
.join(WORKDIR
, '_root', 'Library', 'Frameworks',
739 'Python.framework', 'Versions', getVersion(),
742 print "Fix file modes"
743 frmDir
= os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework')
744 gid
= grp
.getgrnam('admin').gr_gid
746 for dirpath
, dirnames
, filenames
in os
.walk(frmDir
):
748 os
.chmod(os
.path
.join(dirpath
, dn
), 0775)
749 os
.chown(os
.path
.join(dirpath
, dn
), -1, gid
)
753 if os
.path
.islink(fn
):
757 p
= os
.path
.join(dirpath
, fn
)
759 os
.chmod(p
, stat
.S_IMODE(st
.st_mode
) | stat
.S_IWGRP
)
762 # We added some directories to the search path during the configure
763 # phase. Remove those because those directories won't be there on
764 # the end-users system.
765 path
=os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework',
766 'Versions', version
, 'lib', 'python%s'%(version
,),
767 'config', 'Makefile')
772 data
= data
.replace('-L%s/libraries/usr/local/lib'%(WORKDIR
,), '')
773 data
= data
.replace('-I%s/libraries/usr/local/include'%(WORKDIR
,), '')
778 # Add symlinks in /usr/local/bin, using relative links
779 usr_local_bin
= os
.path
.join(rootDir
, 'usr', 'local', 'bin')
780 to_framework
= os
.path
.join('..', '..', '..', 'Library', 'Frameworks',
781 'Python.framework', 'Versions', version
, 'bin')
782 if os
.path
.exists(usr_local_bin
):
783 shutil
.rmtree(usr_local_bin
)
784 os
.makedirs(usr_local_bin
)
785 for fn
in os
.listdir(
786 os
.path
.join(frmDir
, 'Versions', version
, 'bin')):
787 os
.symlink(os
.path
.join(to_framework
, fn
),
788 os
.path
.join(usr_local_bin
, fn
))
794 def patchFile(inPath
, outPath
):
795 data
= fileContents(inPath
)
796 data
= data
.replace('$FULL_VERSION', getFullVersion())
797 data
= data
.replace('$VERSION', getVersion())
798 data
= data
.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET
, ' or later')))
799 data
= data
.replace('$ARCHITECTURES', "i386, ppc")
800 data
= data
.replace('$INSTALL_SIZE', installSize())
802 # This one is not handy as a template variable
803 data
= data
.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
804 fp
= open(outPath
, 'wb')
808 def patchScript(inPath
, outPath
):
809 data
= fileContents(inPath
)
810 data
= data
.replace('@PYVER@', getVersion())
811 fp
= open(outPath
, 'wb')
814 os
.chmod(outPath
, 0755)
818 def packageFromRecipe(targetDir
, recipe
):
821 # The major version (such as 2.5) is included in the package name
822 # because having two version of python installed at the same time is
824 pkgname
= '%s-%s'%(recipe
['name'], getVersion())
825 srcdir
= recipe
.get('source')
826 pkgroot
= recipe
.get('topdir', srcdir
)
827 postflight
= recipe
.get('postflight')
828 readme
= textwrap
.dedent(recipe
['readme'])
829 isRequired
= recipe
.get('required', True)
831 print "- building package %s"%(pkgname
,)
833 # Substitute some variables
836 FULLVER
=getFullVersion(),
838 readme
= readme
% textvars
840 if pkgroot
is not None:
841 pkgroot
= pkgroot
% textvars
845 if srcdir
is not None:
846 srcdir
= os
.path
.join(WORKDIR
, '_root', srcdir
[1:])
847 srcdir
= srcdir
% textvars
849 if postflight
is not None:
850 postflight
= os
.path
.abspath(postflight
)
852 packageContents
= os
.path
.join(targetDir
, pkgname
+ '.pkg', 'Contents')
853 os
.makedirs(packageContents
)
855 if srcdir
is not None:
857 runCommand("pax -wf %s . 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
858 runCommand("gzip -9 %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
859 runCommand("mkbom . %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.bom')),))
861 fn
= os
.path
.join(packageContents
, 'PkgInfo')
866 rsrcDir
= os
.path
.join(packageContents
, "Resources")
868 fp
= open(os
.path
.join(rsrcDir
, 'ReadMe.txt'), 'w')
872 if postflight
is not None:
873 patchScript(postflight
, os
.path
.join(rsrcDir
, 'postflight'))
875 vers
= getFullVersion()
876 major
, minor
= map(int, getVersion().split('.', 2))
878 CFBundleGetInfoString
="Python.%s %s"%(pkgname
, vers
,),
879 CFBundleIdentifier
='org.python.Python.%s'%(pkgname
,),
880 CFBundleName
='Python.%s'%(pkgname
,),
881 CFBundleShortVersionString
=vers
,
882 IFMajorVersion
=major
,
883 IFMinorVersion
=minor
,
884 IFPkgFormatVersion
=0.10000000149011612,
885 IFPkgFlagAllowBackRev
=False,
886 IFPkgFlagAuthorizationAction
="RootAuthorization",
887 IFPkgFlagDefaultLocation
=pkgroot
,
888 IFPkgFlagFollowLinks
=True,
889 IFPkgFlagInstallFat
=True,
890 IFPkgFlagIsRequired
=isRequired
,
891 IFPkgFlagOverwritePermissions
=False,
892 IFPkgFlagRelocatable
=False,
893 IFPkgFlagRestartAction
="NoRestart",
894 IFPkgFlagRootVolumeOnly
=True,
895 IFPkgFlagUpdateInstalledLangauges
=False,
897 writePlist(pl
, os
.path
.join(packageContents
, 'Info.plist'))
900 IFPkgDescriptionDescription
=readme
,
901 IFPkgDescriptionTitle
=recipe
.get('long_name', "Python.%s"%(pkgname
,)),
902 IFPkgDescriptionVersion
=vers
,
904 writePlist(pl
, os
.path
.join(packageContents
, 'Resources', 'Description.plist'))
910 def makeMpkgPlist(path
):
912 vers
= getFullVersion()
913 major
, minor
= map(int, getVersion().split('.', 2))
916 CFBundleGetInfoString
="Python %s"%(vers
,),
917 CFBundleIdentifier
='org.python.Python',
918 CFBundleName
='Python',
919 CFBundleShortVersionString
=vers
,
920 IFMajorVersion
=major
,
921 IFMinorVersion
=minor
,
922 IFPkgFlagComponentDirectory
="Contents/Packages",
923 IFPkgFlagPackageList
=[
925 IFPkgFlagPackageLocation
='%s-%s.pkg'%(item
['name'], getVersion()),
926 IFPkgFlagPackageSelection
='selected'
928 for item
in pkg_recipes()
930 IFPkgFormatVersion
=0.10000000149011612,
931 IFPkgFlagBackgroundScaling
="proportional",
932 IFPkgFlagBackgroundAlignment
="left",
933 IFPkgFlagAuthorizationAction
="RootAuthorization",
939 def buildInstaller():
941 # Zap all compiled files
942 for dirpath
, _
, filenames
in os
.walk(os
.path
.join(WORKDIR
, '_root')):
944 if fn
.endswith('.pyc') or fn
.endswith('.pyo'):
945 os
.unlink(os
.path
.join(dirpath
, fn
))
947 outdir
= os
.path
.join(WORKDIR
, 'installer')
948 if os
.path
.exists(outdir
):
949 shutil
.rmtree(outdir
)
952 pkgroot
= os
.path
.join(outdir
, 'Python.mpkg', 'Contents')
953 pkgcontents
= os
.path
.join(pkgroot
, 'Packages')
954 os
.makedirs(pkgcontents
)
955 for recipe
in pkg_recipes():
956 packageFromRecipe(pkgcontents
, recipe
)
958 rsrcDir
= os
.path
.join(pkgroot
, 'Resources')
960 fn
= os
.path
.join(pkgroot
, 'PkgInfo')
967 makeMpkgPlist(os
.path
.join(pkgroot
, 'Info.plist'))
969 IFPkgDescriptionTitle
="Python",
970 IFPkgDescriptionVersion
=getVersion(),
973 writePlist(pl
, os
.path
.join(pkgroot
, 'Resources', 'Description.plist'))
974 for fn
in os
.listdir('resources'):
975 if fn
== '.svn': continue
976 if fn
.endswith('.jpg'):
977 shutil
.copy(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
979 patchFile(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
981 shutil
.copy("../../LICENSE", os
.path
.join(rsrcDir
, 'License.txt'))
984 def installSize(clear
=False, _saved
=[]):
988 data
= captureCommand("du -ks %s"%(
989 shellQuote(os
.path
.join(WORKDIR
, '_root'))))
990 _saved
.append("%d"%((0.5 + (int(data
.split()[0]) / 1024.0)),))
996 Create DMG containing the rootDir.
998 outdir
= os
.path
.join(WORKDIR
, 'diskimage')
999 if os
.path
.exists(outdir
):
1000 shutil
.rmtree(outdir
)
1002 imagepath
= os
.path
.join(outdir
,
1003 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET
))
1004 if INCLUDE_TIMESTAMP
:
1005 imagepath
= imagepath
+ '-%04d-%02d-%02d'%(time
.localtime()[:3])
1006 imagepath
= imagepath
+ '.dmg'
1009 volname
='Python %s'%(getFullVersion())
1010 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1011 shellQuote(volname
),
1012 shellQuote(os
.path
.join(WORKDIR
, 'installer')),
1013 shellQuote(imagepath
+ ".tmp.dmg" )))
1016 if not os
.path
.exists(os
.path
.join(WORKDIR
, "mnt")):
1017 os
.mkdir(os
.path
.join(WORKDIR
, "mnt"))
1018 runCommand("hdiutil attach %s -mountroot %s"%(
1019 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(os
.path
.join(WORKDIR
, "mnt"))))
1021 # Custom icon for the DMG, shown when the DMG is mounted.
1022 shutil
.copy("../Icons/Disk Image.icns",
1023 os
.path
.join(WORKDIR
, "mnt", volname
, ".VolumeIcon.icns"))
1024 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1025 shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
)),))
1027 runCommand("hdiutil detach %s"%(shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
))))
1029 setIcon(imagepath
+ ".tmp.dmg", "../Icons/Disk Image.icns")
1030 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1031 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(imagepath
)))
1032 setIcon(imagepath
, "../Icons/Disk Image.icns")
1034 os
.unlink(imagepath
+ ".tmp.dmg")
1039 def setIcon(filePath
, icnsPath
):
1041 Set the custom icon for the specified file or directory.
1044 toolPath
= os
.path
.join(os
.path
.dirname(__file__
), "seticon.app/Contents/MacOS/seticon")
1045 dirPath
= os
.path
.dirname(__file__
)
1046 if not os
.path
.exists(toolPath
) or os
.stat(toolPath
).st_mtime
< os
.stat(dirPath
+ '/seticon.m').st_mtime
:
1047 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1048 # to connections to the window server.
1049 if not os
.path
.exists('seticon.app/Contents/MacOS'):
1050 os
.makedirs('seticon.app/Contents/MacOS')
1051 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1052 shellQuote(toolPath
), shellQuote(dirPath
)))
1054 runCommand("%s %s %s"%(shellQuote(os
.path
.abspath(toolPath
)), shellQuote(icnsPath
),
1055 shellQuote(filePath
)))
1058 # First parse options and check if we can perform our work
1062 os
.environ
['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
1063 os
.environ
['CC'] = CC
1065 if os
.path
.exists(WORKDIR
):
1066 shutil
.rmtree(WORKDIR
)
1069 # Then build third-party libraries such as sleepycat DB4.
1072 # Now build python itself
1075 # And then build the documentation
1076 # Remove the Deployment Target from the shell
1077 # environment, it's no longer needed and
1078 # an unexpected build target can cause problems
1079 # when Sphinx and its dependencies need to
1080 # be (re-)installed.
1081 del os
.environ
['MACOSX_DEPLOYMENT_TARGET']
1085 # Prepare the applications folder
1086 fn
= os
.path
.join(WORKDIR
, "_root", "Applications",
1087 "Python %s"%(getVersion(),), "Update Shell Profile.command")
1088 patchScript("scripts/postflight.patch-profile", fn
)
1090 folder
= os
.path
.join(WORKDIR
, "_root", "Applications", "Python %s"%(
1092 os
.chmod(folder
, 0755)
1093 setIcon(folder
, "../Icons/Python Folder.icns")
1095 # Create the installer
1098 # And copy the readme into the directory containing the installer
1099 patchFile('resources/ReadMe.txt', os
.path
.join(WORKDIR
, 'installer', 'ReadMe.txt'))
1101 # Ditto for the license file.
1102 shutil
.copy('../../LICENSE', os
.path
.join(WORKDIR
, 'installer', 'License.txt'))
1104 fp
= open(os
.path
.join(WORKDIR
, 'installer', 'Build.txt'), 'w')
1105 print >> fp
, "# BUILD INFO"
1106 print >> fp
, "# Date:", time
.ctime()
1107 print >> fp
, "# By:", pwd
.getpwuid(os
.getuid()).pw_gecos
1110 # And copy it to a DMG
1113 if __name__
== "__main__":