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",
255 name
="PythonApplications",
256 long_name
="GUI Applications",
257 source
="/Applications/Python %(VER)s",
259 This package installs IDLE (an interactive Python IDE),
260 Python Launcher and Build Applet (create application bundles
261 from python scripts).
263 It also installs a number of examples and demos.
269 name
="PythonUnixTools",
270 long_name
="UNIX command-line tools",
271 source
="/usr/local/bin",
273 This package installs the unix tools in /usr/local/bin for
274 compatibility with older releases of Python. This package
275 is not necessary to use Python.
278 selected
='unselected',
281 name
="PythonDocumentation",
282 long_name
="Python Documentation",
283 topdir
="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
286 This package installs the python documentation at a location
287 that is useable for pydoc and IDLE. If you have installed Xcode
288 it will also install a link to the documentation in
289 /Developer/Documentation/Python
291 postflight
="scripts/postflight.documentation",
296 name
="PythonProfileChanges",
297 long_name
="Shell profile updater",
299 This packages updates your shell profile to make sure that
300 the Python tools are found by your shell in preference of
301 the system provided Python tools.
303 If you don't install this package you'll have to add
304 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
305 to your PATH by hand.
307 postflight
="scripts/postflight.patch-profile",
308 topdir
="/Library/Frameworks/Python.framework",
315 if DEPTARGET
< '10.4':
318 name
="PythonSystemFixes",
319 long_name
="Fix system Python",
321 This package updates the system python installation on
322 Mac OS X 10.3 to ensure that you can build new python extensions
323 using that copy of python after installing this version.
325 postflight
="../Tools/fixapplepython23.py",
326 topdir
="/Library/Frameworks/Python.framework",
336 A fatal error, bail out.
338 sys
.stderr
.write('FATAL: ')
339 sys
.stderr
.write(msg
)
340 sys
.stderr
.write('\n')
343 def fileContents(fn
):
345 Return the contents of the named file
347 return open(fn
, 'rb').read()
349 def runCommand(commandline
):
351 Run a command and raise RuntimeError if it fails. Output is surpressed
352 unless the command fails.
354 fd
= os
.popen(commandline
, 'r')
358 sys
.stdout
.write(data
)
359 raise RuntimeError, "command failed: %s"%(commandline
,)
362 sys
.stdout
.write(data
); sys
.stdout
.flush()
364 def captureCommand(commandline
):
365 fd
= os
.popen(commandline
, 'r')
369 sys
.stdout
.write(data
)
370 raise RuntimeError, "command failed: %s"%(commandline
,)
374 def checkEnvironment():
376 Check that we're running on a supported system.
379 if platform
.system() != 'Darwin':
380 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
382 if int(platform
.release().split('.')[0]) < 8:
383 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
385 if not os
.path
.exists(SDKPATH
):
386 fatal("Please install the latest version of Xcode and the %s SDK"%(
387 os
.path
.basename(SDKPATH
[:-4])))
391 def parseOptions(args
=None):
393 Parse arguments and update global settings.
395 global WORKDIR
, DEPSRC
, SDKPATH
, SRCDIR
, DEPTARGET
396 global UNIVERSALOPTS
, UNIVERSALARCHS
, ARCHLIST
, CC
402 options
, args
= getopt
.getopt(args
, '?hb',
403 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
404 'dep-target=', 'universal-archs=', 'help' ])
405 except getopt
.error
, msg
:
410 print "Additional arguments"
415 if k
in ('-h', '-?', '--help'):
419 elif k
in ('-d', '--build-dir'):
422 elif k
in ('--third-party',):
425 elif k
in ('--sdk-path',):
428 elif k
in ('--src-dir',):
431 elif k
in ('--dep-target', ):
435 elif k
in ('--universal-archs', ):
436 if v
in UNIVERSALOPTS
:
438 ARCHLIST
= universal_opts_map
[UNIVERSALARCHS
]
439 if deptarget
is None:
440 # Select alternate default deployment
442 DEPTARGET
= default_target_map
.get(v
, '10.3')
444 raise NotImplementedError, v
447 raise NotImplementedError, k
449 SRCDIR
=os
.path
.abspath(SRCDIR
)
450 WORKDIR
=os
.path
.abspath(WORKDIR
)
451 SDKPATH
=os
.path
.abspath(SDKPATH
)
452 DEPSRC
=os
.path
.abspath(DEPSRC
)
454 CC
=target_cc_map
[DEPTARGET
]
457 print " * Source directory:", SRCDIR
458 print " * Build directory: ", WORKDIR
459 print " * SDK location: ", SDKPATH
460 print " * Third-party source:", DEPSRC
461 print " * Deployment target:", DEPTARGET
462 print " * Universal architectures:", ARCHLIST
463 print " * C compiler:", CC
469 def extractArchive(builddir
, archiveName
):
471 Extract a source archive into 'builddir'. Returns the path of the
474 XXX: This function assumes that archives contain a toplevel directory
475 that is has the same name as the basename of the archive. This is
476 save enough for anything we use.
481 if archiveName
.endswith('.tar.gz'):
482 retval
= os
.path
.basename(archiveName
[:-7])
483 if os
.path
.exists(retval
):
484 shutil
.rmtree(retval
)
485 fp
= os
.popen("tar zxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
487 elif archiveName
.endswith('.tar.bz2'):
488 retval
= os
.path
.basename(archiveName
[:-8])
489 if os
.path
.exists(retval
):
490 shutil
.rmtree(retval
)
491 fp
= os
.popen("tar jxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
493 elif archiveName
.endswith('.tar'):
494 retval
= os
.path
.basename(archiveName
[:-4])
495 if os
.path
.exists(retval
):
496 shutil
.rmtree(retval
)
497 fp
= os
.popen("tar xf %s 2>&1"%(shellQuote(archiveName
),), 'r')
499 elif archiveName
.endswith('.zip'):
500 retval
= os
.path
.basename(archiveName
[:-4])
501 if os
.path
.exists(retval
):
502 shutil
.rmtree(retval
)
503 fp
= os
.popen("unzip %s 2>&1"%(shellQuote(archiveName
),), 'r')
508 sys
.stdout
.write(data
)
509 raise RuntimeError, "Cannot extract %s"%(archiveName
,)
511 return os
.path
.join(builddir
, retval
)
517 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
518 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
521 def downloadURL(url
, fname
):
523 Download the contents of the url into the file.
526 size
= os
.path
.getsize(fname
)
530 if KNOWNSIZES
.get(url
) == size
:
531 print "Using existing file for", url
533 fpIn
= urllib2
.urlopen(url
)
534 fpOut
= open(fname
, 'wb')
535 block
= fpIn
.read(10240)
539 block
= fpIn
.read(10240)
548 def buildRecipe(recipe
, basedir
, archList
):
550 Build software using a recipe. This function does the
551 'configure;make;make install' dance for C software, with a possibility
552 to customize this process, basically a poor-mans DarwinPorts.
556 name
= recipe
['name']
558 configure
= recipe
.get('configure', './configure')
559 install
= recipe
.get('install', 'make && make install DESTDIR=%s'%(
560 shellQuote(basedir
)))
562 archiveName
= os
.path
.split(url
)[-1]
563 sourceArchive
= os
.path
.join(DEPSRC
, archiveName
)
565 if not os
.path
.exists(DEPSRC
):
569 if os
.path
.exists(sourceArchive
):
570 print "Using local copy of %s"%(name
,)
573 print "Did not find local copy of %s"%(name
,)
574 print "Downloading %s"%(name
,)
575 downloadURL(url
, sourceArchive
)
576 print "Archive for %s stored as %s"%(name
, sourceArchive
)
578 print "Extracting archive for %s"%(name
,)
579 buildDir
=os
.path
.join(WORKDIR
, '_bld')
580 if not os
.path
.exists(buildDir
):
583 workDir
= extractArchive(buildDir
, sourceArchive
)
585 if 'buildDir' in recipe
:
586 os
.chdir(recipe
['buildDir'])
589 for fn
in recipe
.get('patches', ()):
590 if fn
.startswith('http://'):
591 # Download the patch before applying it.
592 path
= os
.path
.join(DEPSRC
, os
.path
.basename(fn
))
593 downloadURL(fn
, path
)
596 fn
= os
.path
.join(curdir
, fn
)
597 runCommand('patch -p%s < %s'%(recipe
.get('patchlevel', 1),
600 if configure
is not None:
602 "--prefix=/usr/local",
605 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
608 if 'configure_pre' in recipe
:
609 args
= list(recipe
['configure_pre'])
610 if '--disable-static' in args
:
611 configure_args
.remove('--enable-static')
612 if '--enable-shared' in args
:
613 configure_args
.remove('--disable-shared')
614 configure_args
.extend(args
)
616 if recipe
.get('useLDFlags', 1):
617 configure_args
.extend([
618 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
619 ' -arch '.join(archList
),
620 shellQuote(SDKPATH
)[1:-1],
621 shellQuote(basedir
)[1:-1],),
622 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
623 shellQuote(SDKPATH
)[1:-1],
624 shellQuote(basedir
)[1:-1],
625 ' -arch '.join(archList
)),
628 configure_args
.extend([
629 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
630 ' -arch '.join(archList
),
631 shellQuote(SDKPATH
)[1:-1],
632 shellQuote(basedir
)[1:-1],),
635 if 'configure_post' in recipe
:
636 configure_args
= configure_args
= list(recipe
['configure_post'])
638 configure_args
.insert(0, configure
)
639 configure_args
= [ shellQuote(a
) for a
in configure_args
]
641 print "Running configure for %s"%(name
,)
642 runCommand(' '.join(configure_args
) + ' 2>&1')
644 print "Running install for %s"%(name
,)
645 runCommand('{ ' + install
+ ' ;} 2>&1')
647 print "Done %s"%(name
,)
652 def buildLibraries():
654 Build our dependencies into $WORKDIR/libraries/usr/local
657 print "Building required libraries"
659 universal
= os
.path
.join(WORKDIR
, 'libraries')
661 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'lib'))
662 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'include'))
664 for recipe
in library_recipes():
665 buildRecipe(recipe
, universal
, ARCHLIST
)
669 def buildPythonDocs():
670 # This stores the documentation as Resources/English.lproj/Documentation
671 # inside the framwork. pydoc and IDLE will pick it up there.
672 print "Install python documentation"
673 rootDir
= os
.path
.join(WORKDIR
, '_root')
674 buildDir
= os
.path
.join('../../Doc')
675 docdir
= os
.path
.join(rootDir
, 'pydocs')
678 runCommand('make update')
679 runCommand('make html')
681 if not os
.path
.exists(docdir
):
683 os
.rename(os
.path
.join(buildDir
, 'build', 'html'), docdir
)
687 print "Building a universal python for %s architectures" % UNIVERSALARCHS
689 buildDir
= os
.path
.join(WORKDIR
, '_bld', 'python')
690 rootDir
= os
.path
.join(WORKDIR
, '_root')
692 if os
.path
.exists(buildDir
):
693 shutil
.rmtree(buildDir
)
694 if os
.path
.exists(rootDir
):
695 shutil
.rmtree(rootDir
)
698 os
.mkdir(os
.path
.join(rootDir
, 'empty-dir'))
702 # Not sure if this is still needed, the original build script
703 # claims that parts of the install assume python.exe exists.
704 os
.symlink('python', os
.path
.join(buildDir
, 'python.exe'))
706 # Extract the version from the configure file, needed to calculate
708 version
= getVersion()
710 # Since the extra libs are not in their installed framework location
711 # during the build, augment the library path so that the interpreter
712 # will find them during its extension import sanity checks.
713 os
.environ
['DYLD_LIBRARY_PATH'] = os
.path
.join(WORKDIR
,
714 'libraries', 'usr', 'local', 'lib')
715 print "Running configure..."
716 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
717 "--with-universal-archs=%s "
718 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
719 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
720 shellQuote(os
.path
.join(SRCDIR
, 'configure')), shellQuote(SDKPATH
),
722 shellQuote(WORKDIR
)[1:-1],
723 shellQuote(WORKDIR
)[1:-1]))
728 print "Running make frameworkinstall"
729 runCommand("make frameworkinstall DESTDIR=%s"%(
730 shellQuote(rootDir
)))
732 print "Running make frameworkinstallextras"
733 runCommand("make frameworkinstallextras DESTDIR=%s"%(
734 shellQuote(rootDir
)))
736 del os
.environ
['DYLD_LIBRARY_PATH']
737 print "Copying required shared libraries"
738 if os
.path
.exists(os
.path
.join(WORKDIR
, 'libraries', 'Library')):
739 runCommand("mv %s/* %s"%(
740 shellQuote(os
.path
.join(
741 WORKDIR
, 'libraries', 'Library', 'Frameworks',
742 'Python.framework', 'Versions', getVersion(),
744 shellQuote(os
.path
.join(WORKDIR
, '_root', 'Library', 'Frameworks',
745 'Python.framework', 'Versions', getVersion(),
748 print "Fix file modes"
749 frmDir
= os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework')
750 gid
= grp
.getgrnam('admin').gr_gid
752 for dirpath
, dirnames
, filenames
in os
.walk(frmDir
):
754 os
.chmod(os
.path
.join(dirpath
, dn
), 0775)
755 os
.chown(os
.path
.join(dirpath
, dn
), -1, gid
)
759 if os
.path
.islink(fn
):
763 p
= os
.path
.join(dirpath
, fn
)
765 os
.chmod(p
, stat
.S_IMODE(st
.st_mode
) | stat
.S_IWGRP
)
768 # We added some directories to the search path during the configure
769 # phase. Remove those because those directories won't be there on
770 # the end-users system.
771 path
=os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework',
772 'Versions', version
, 'lib', 'python%s'%(version
,),
773 'config', 'Makefile')
778 data
= data
.replace('-L%s/libraries/usr/local/lib'%(WORKDIR
,), '')
779 data
= data
.replace('-I%s/libraries/usr/local/include'%(WORKDIR
,), '')
784 # Add symlinks in /usr/local/bin, using relative links
785 usr_local_bin
= os
.path
.join(rootDir
, 'usr', 'local', 'bin')
786 to_framework
= os
.path
.join('..', '..', '..', 'Library', 'Frameworks',
787 'Python.framework', 'Versions', version
, 'bin')
788 if os
.path
.exists(usr_local_bin
):
789 shutil
.rmtree(usr_local_bin
)
790 os
.makedirs(usr_local_bin
)
791 for fn
in os
.listdir(
792 os
.path
.join(frmDir
, 'Versions', version
, 'bin')):
793 os
.symlink(os
.path
.join(to_framework
, fn
),
794 os
.path
.join(usr_local_bin
, fn
))
800 def patchFile(inPath
, outPath
):
801 data
= fileContents(inPath
)
802 data
= data
.replace('$FULL_VERSION', getFullVersion())
803 data
= data
.replace('$VERSION', getVersion())
804 data
= data
.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET
, ' or later')))
805 data
= data
.replace('$ARCHITECTURES', "i386, ppc")
806 data
= data
.replace('$INSTALL_SIZE', installSize())
808 # This one is not handy as a template variable
809 data
= data
.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
810 fp
= open(outPath
, 'wb')
814 def patchScript(inPath
, outPath
):
815 data
= fileContents(inPath
)
816 data
= data
.replace('@PYVER@', getVersion())
817 fp
= open(outPath
, 'wb')
820 os
.chmod(outPath
, 0755)
824 def packageFromRecipe(targetDir
, recipe
):
827 # The major version (such as 2.5) is included in the package name
828 # because having two version of python installed at the same time is
830 pkgname
= '%s-%s'%(recipe
['name'], getVersion())
831 srcdir
= recipe
.get('source')
832 pkgroot
= recipe
.get('topdir', srcdir
)
833 postflight
= recipe
.get('postflight')
834 readme
= textwrap
.dedent(recipe
['readme'])
835 isRequired
= recipe
.get('required', True)
837 print "- building package %s"%(pkgname
,)
839 # Substitute some variables
842 FULLVER
=getFullVersion(),
844 readme
= readme
% textvars
846 if pkgroot
is not None:
847 pkgroot
= pkgroot
% textvars
851 if srcdir
is not None:
852 srcdir
= os
.path
.join(WORKDIR
, '_root', srcdir
[1:])
853 srcdir
= srcdir
% textvars
855 if postflight
is not None:
856 postflight
= os
.path
.abspath(postflight
)
858 packageContents
= os
.path
.join(targetDir
, pkgname
+ '.pkg', 'Contents')
859 os
.makedirs(packageContents
)
861 if srcdir
is not None:
863 runCommand("pax -wf %s . 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
864 runCommand("gzip -9 %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
865 runCommand("mkbom . %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.bom')),))
867 fn
= os
.path
.join(packageContents
, 'PkgInfo')
872 rsrcDir
= os
.path
.join(packageContents
, "Resources")
874 fp
= open(os
.path
.join(rsrcDir
, 'ReadMe.txt'), 'w')
878 if postflight
is not None:
879 patchScript(postflight
, os
.path
.join(rsrcDir
, 'postflight'))
881 vers
= getFullVersion()
882 major
, minor
= map(int, getVersion().split('.', 2))
884 CFBundleGetInfoString
="Python.%s %s"%(pkgname
, vers
,),
885 CFBundleIdentifier
='org.python.Python.%s'%(pkgname
,),
886 CFBundleName
='Python.%s'%(pkgname
,),
887 CFBundleShortVersionString
=vers
,
888 IFMajorVersion
=major
,
889 IFMinorVersion
=minor
,
890 IFPkgFormatVersion
=0.10000000149011612,
891 IFPkgFlagAllowBackRev
=False,
892 IFPkgFlagAuthorizationAction
="RootAuthorization",
893 IFPkgFlagDefaultLocation
=pkgroot
,
894 IFPkgFlagFollowLinks
=True,
895 IFPkgFlagInstallFat
=True,
896 IFPkgFlagIsRequired
=isRequired
,
897 IFPkgFlagOverwritePermissions
=False,
898 IFPkgFlagRelocatable
=False,
899 IFPkgFlagRestartAction
="NoRestart",
900 IFPkgFlagRootVolumeOnly
=True,
901 IFPkgFlagUpdateInstalledLangauges
=False,
903 writePlist(pl
, os
.path
.join(packageContents
, 'Info.plist'))
906 IFPkgDescriptionDescription
=readme
,
907 IFPkgDescriptionTitle
=recipe
.get('long_name', "Python.%s"%(pkgname
,)),
908 IFPkgDescriptionVersion
=vers
,
910 writePlist(pl
, os
.path
.join(packageContents
, 'Resources', 'Description.plist'))
916 def makeMpkgPlist(path
):
918 vers
= getFullVersion()
919 major
, minor
= map(int, getVersion().split('.', 2))
922 CFBundleGetInfoString
="Python %s"%(vers
,),
923 CFBundleIdentifier
='org.python.Python',
924 CFBundleName
='Python',
925 CFBundleShortVersionString
=vers
,
926 IFMajorVersion
=major
,
927 IFMinorVersion
=minor
,
928 IFPkgFlagComponentDirectory
="Contents/Packages",
929 IFPkgFlagPackageList
=[
931 IFPkgFlagPackageLocation
='%s-%s.pkg'%(item
['name'], getVersion()),
932 IFPkgFlagPackageSelection
=item
.get('selected', 'selected'),
934 for item
in pkg_recipes()
936 IFPkgFormatVersion
=0.10000000149011612,
937 IFPkgFlagBackgroundScaling
="proportional",
938 IFPkgFlagBackgroundAlignment
="left",
939 IFPkgFlagAuthorizationAction
="RootAuthorization",
945 def buildInstaller():
947 # Zap all compiled files
948 for dirpath
, _
, filenames
in os
.walk(os
.path
.join(WORKDIR
, '_root')):
950 if fn
.endswith('.pyc') or fn
.endswith('.pyo'):
951 os
.unlink(os
.path
.join(dirpath
, fn
))
953 outdir
= os
.path
.join(WORKDIR
, 'installer')
954 if os
.path
.exists(outdir
):
955 shutil
.rmtree(outdir
)
958 pkgroot
= os
.path
.join(outdir
, 'Python.mpkg', 'Contents')
959 pkgcontents
= os
.path
.join(pkgroot
, 'Packages')
960 os
.makedirs(pkgcontents
)
961 for recipe
in pkg_recipes():
962 packageFromRecipe(pkgcontents
, recipe
)
964 rsrcDir
= os
.path
.join(pkgroot
, 'Resources')
966 fn
= os
.path
.join(pkgroot
, 'PkgInfo')
973 makeMpkgPlist(os
.path
.join(pkgroot
, 'Info.plist'))
975 IFPkgDescriptionTitle
="Python",
976 IFPkgDescriptionVersion
=getVersion(),
979 writePlist(pl
, os
.path
.join(pkgroot
, 'Resources', 'Description.plist'))
980 for fn
in os
.listdir('resources'):
981 if fn
== '.svn': continue
982 if fn
.endswith('.jpg'):
983 shutil
.copy(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
985 patchFile(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
987 shutil
.copy("../../LICENSE", os
.path
.join(rsrcDir
, 'License.txt'))
990 def installSize(clear
=False, _saved
=[]):
994 data
= captureCommand("du -ks %s"%(
995 shellQuote(os
.path
.join(WORKDIR
, '_root'))))
996 _saved
.append("%d"%((0.5 + (int(data
.split()[0]) / 1024.0)),))
1002 Create DMG containing the rootDir.
1004 outdir
= os
.path
.join(WORKDIR
, 'diskimage')
1005 if os
.path
.exists(outdir
):
1006 shutil
.rmtree(outdir
)
1008 imagepath
= os
.path
.join(outdir
,
1009 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET
))
1010 if INCLUDE_TIMESTAMP
:
1011 imagepath
= imagepath
+ '-%04d-%02d-%02d'%(time
.localtime()[:3])
1012 imagepath
= imagepath
+ '.dmg'
1015 volname
='Python %s'%(getFullVersion())
1016 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1017 shellQuote(volname
),
1018 shellQuote(os
.path
.join(WORKDIR
, 'installer')),
1019 shellQuote(imagepath
+ ".tmp.dmg" )))
1022 if not os
.path
.exists(os
.path
.join(WORKDIR
, "mnt")):
1023 os
.mkdir(os
.path
.join(WORKDIR
, "mnt"))
1024 runCommand("hdiutil attach %s -mountroot %s"%(
1025 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(os
.path
.join(WORKDIR
, "mnt"))))
1027 # Custom icon for the DMG, shown when the DMG is mounted.
1028 shutil
.copy("../Icons/Disk Image.icns",
1029 os
.path
.join(WORKDIR
, "mnt", volname
, ".VolumeIcon.icns"))
1030 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1031 shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
)),))
1033 runCommand("hdiutil detach %s"%(shellQuote(os
.path
.join(WORKDIR
, "mnt", volname
))))
1035 setIcon(imagepath
+ ".tmp.dmg", "../Icons/Disk Image.icns")
1036 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1037 shellQuote(imagepath
+ ".tmp.dmg"), shellQuote(imagepath
)))
1038 setIcon(imagepath
, "../Icons/Disk Image.icns")
1040 os
.unlink(imagepath
+ ".tmp.dmg")
1045 def setIcon(filePath
, icnsPath
):
1047 Set the custom icon for the specified file or directory.
1050 toolPath
= os
.path
.join(os
.path
.dirname(__file__
), "seticon.app/Contents/MacOS/seticon")
1051 dirPath
= os
.path
.dirname(__file__
)
1052 if not os
.path
.exists(toolPath
) or os
.stat(toolPath
).st_mtime
< os
.stat(dirPath
+ '/seticon.m').st_mtime
:
1053 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1054 # to connections to the window server.
1055 if not os
.path
.exists('seticon.app/Contents/MacOS'):
1056 os
.makedirs('seticon.app/Contents/MacOS')
1057 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1058 shellQuote(toolPath
), shellQuote(dirPath
)))
1060 runCommand("%s %s %s"%(shellQuote(os
.path
.abspath(toolPath
)), shellQuote(icnsPath
),
1061 shellQuote(filePath
)))
1064 # First parse options and check if we can perform our work
1068 os
.environ
['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
1069 os
.environ
['CC'] = CC
1071 if os
.path
.exists(WORKDIR
):
1072 shutil
.rmtree(WORKDIR
)
1075 # Then build third-party libraries such as sleepycat DB4.
1078 # Now build python itself
1081 # And then build the documentation
1082 # Remove the Deployment Target from the shell
1083 # environment, it's no longer needed and
1084 # an unexpected build target can cause problems
1085 # when Sphinx and its dependencies need to
1086 # be (re-)installed.
1087 del os
.environ
['MACOSX_DEPLOYMENT_TARGET']
1091 # Prepare the applications folder
1092 fn
= os
.path
.join(WORKDIR
, "_root", "Applications",
1093 "Python %s"%(getVersion(),), "Update Shell Profile.command")
1094 patchScript("scripts/postflight.patch-profile", fn
)
1096 folder
= os
.path
.join(WORKDIR
, "_root", "Applications", "Python %s"%(
1098 os
.chmod(folder
, 0755)
1099 setIcon(folder
, "../Icons/Python Folder.icns")
1101 # Create the installer
1104 # And copy the readme into the directory containing the installer
1105 patchFile('resources/ReadMe.txt', os
.path
.join(WORKDIR
, 'installer', 'ReadMe.txt'))
1107 # Ditto for the license file.
1108 shutil
.copy('../../LICENSE', os
.path
.join(WORKDIR
, 'installer', 'License.txt'))
1110 fp
= open(os
.path
.join(WORKDIR
, 'installer', 'Build.txt'), 'w')
1111 print >> fp
, "# BUILD INFO"
1112 print >> fp
, "# Date:", time
.ctime()
1113 print >> fp
, "# By:", pwd
.getpwuid(os
.getuid()).pw_gecos
1116 # And copy it to a DMG
1119 if __name__
== "__main__":