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
7 Please ensure that this script keeps working with Python 2.3, to avoid
8 bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
10 Usage: see USAGE variable in the script.
12 import platform
, os
, sys
, getopt
, textwrap
, shutil
, urllib2
, stat
, time
, pwd
18 from plistlib
import Plist
24 from Carbon
.Files
import kCustomIconResource
, fsRdWrPerm
, kHasCustomIcon
25 from Carbon
.Files
import kFSCatInfoFinderInfo
28 from plistlib
import writePlist
30 # We're run using python2.3
31 def writePlist(plist
, path
):
34 def shellQuote(value
):
36 Return the string value in a form that can safely be inserted into
39 return "'%s'"%(value
.replace("'", "'\"'\"'"))
41 def grepValue(fn
, variable
):
42 variable
= variable
+ '='
43 for ln
in open(fn
, 'r'):
44 if ln
.startswith(variable
):
45 value
= ln
[len(variable
):].strip()
49 return grepValue(os
.path
.join(SRCDIR
, 'configure'), 'PACKAGE_VERSION')
52 fn
= os
.path
.join(SRCDIR
, 'Include', 'patchlevel.h')
54 if 'PY_VERSION' in ln
:
55 return ln
.split()[-1][1:-1]
57 raise RuntimeError, "Cannot find full version??"
59 # The directory we'll use to create the build (will be erased and recreated)
62 # The directory we'll use to store third-party sources. Set this to something
63 # else if you don't want to re-fetch required libraries every time.
64 DEPSRC
= os
.path
.join(WORKDIR
, 'third-party')
65 DEPSRC
= os
.path
.expanduser('~/Universal/other-sources')
67 # Location of the preferred SDK
68 SDKPATH
= "/Developer/SDKs/MacOSX10.4u.sdk"
71 ARCHLIST
= ('i386', 'ppc',)
73 # Source directory (asume we're in Mac/BuildScript)
74 SRCDIR
= os
.path
.dirname(
77 os
.path
.abspath(__file__
80 USAGE
= textwrap
.dedent("""\
81 Usage: build_python [options]
84 -? or -h: Show this message
86 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
87 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
88 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
89 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
93 # Instructions for building libraries that are necessary for building a
94 # batteries included python.
98 url
="http://www.bzip.org/1.0.3/bzip2-1.0.3.tar.gz",
100 install
='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
101 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
102 ' -arch '.join(ARCHLIST
),
108 url
="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
110 install
='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
111 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
112 ' -arch '.join(ARCHLIST
),
117 # Note that GNU readline is GPL'd software
118 name
="GNU Readline 5.1.4",
119 url
="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
122 # The readline maintainers don't do actual micro releases, but
123 # just ship a set of patches.
124 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
125 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
126 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
127 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
133 url
="http://www.sqlite.org/sqlite-3.3.5.tar.gz",
134 checksum
='93f742986e8bc2dfa34792e16df017a6feccf3a2',
136 '--enable-threadsafe',
137 '--enable-tempstore',
138 '--enable-shared=no',
139 '--enable-static=yes',
146 url
="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
151 "--without-curses-h",
154 "--datadir=/usr/share",
156 "--sharedstatedir=/usr/com",
157 "--with-terminfo-dirs=/usr/share/terminfo",
158 "--with-default-terminfo-dir=/usr/share/terminfo",
159 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
166 install
='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
167 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
168 shellQuote(os
.path
.join(WORKDIR
, 'libraries')),
173 name
="Sleepycat DB 4.4",
174 url
="http://downloads.sleepycat.com/db-4.4.20.tar.gz",
175 #name="Sleepycat DB 4.3.29",
176 #url="http://downloads.sleepycat.com/db-4.3.29.tar.gz",
177 buildDir
="build_unix",
178 configure
="../dist/configure",
180 '--includedir=/usr/local/include/db4',
186 # Instructions for building packages inside the .mpkg.
189 name
="PythonFramework",
190 long_name
="Python Framework",
191 source
="/Library/Frameworks/Python.framework",
193 This package installs Python.framework, that is the python
194 interpreter and the standard library. This also includes Python
195 wrappers for lots of Mac OS X API's.
197 postflight
="scripts/postflight.framework",
200 name
="PythonApplications",
201 long_name
="GUI Applications",
202 source
="/Applications/MacPython %(VER)s",
204 This package installs IDLE (an interactive Python IDE),
205 Python Launcher and Build Applet (create application bundles
206 from python scripts).
208 It also installs a number of examples and demos.
213 name
="PythonUnixTools",
214 long_name
="UNIX command-line tools",
215 source
="/usr/local/bin",
217 This package installs the unix tools in /usr/local/bin for
218 compatibility with older releases of MacPython. This package
219 is not necessary to use MacPython.
224 name
="PythonDocumentation",
225 long_name
="Python Documentation",
226 topdir
="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
229 This package installs the python documentation at a location
230 that is useable for pydoc and IDLE. If you have installed Xcode
231 it will also install a link to the documentation in
232 /Developer/Documentation/Python
234 postflight
="scripts/postflight.documentation",
238 name
="PythonProfileChanges",
239 long_name
="Shell profile updater",
241 This packages updates your shell profile to make sure that
242 the MacPython tools are found by your shell in preference of
243 the system provided Python tools.
245 If you don't install this package you'll have to add
246 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
247 to your PATH by hand.
249 postflight
="scripts/postflight.patch-profile",
250 topdir
="/Library/Frameworks/Python.framework",
255 name
="PythonSystemFixes",
256 long_name
="Fix system Python",
258 This package updates the system python installation on
259 Mac OS X 10.3 to ensure that you can build new python extensions
260 using that copy of python after installing this version.
262 postflight
="../Tools/fixapplepython23.py",
263 topdir
="/Library/Frameworks/Python.framework",
271 A fatal error, bail out.
273 sys
.stderr
.write('FATAL: ')
274 sys
.stderr
.write(msg
)
275 sys
.stderr
.write('\n')
278 def fileContents(fn
):
280 Return the contents of the named file
282 return open(fn
, 'rb').read()
284 def runCommand(commandline
):
286 Run a command and raise RuntimeError if it fails. Output is surpressed
287 unless the command fails.
289 fd
= os
.popen(commandline
, 'r')
293 sys
.stdout
.write(data
)
294 raise RuntimeError, "command failed: %s"%(commandline
,)
297 sys
.stdout
.write(data
); sys
.stdout
.flush()
299 def captureCommand(commandline
):
300 fd
= os
.popen(commandline
, 'r')
304 sys
.stdout
.write(data
)
305 raise RuntimeError, "command failed: %s"%(commandline
,)
309 def checkEnvironment():
311 Check that we're running on a supported system.
314 if platform
.system() != 'Darwin':
315 fatal("This script should be run on a Mac OS X 10.4 system")
317 if platform
.release() <= '8.':
318 fatal("This script should be run on a Mac OS X 10.4 system")
320 if not os
.path
.exists(SDKPATH
):
321 fatal("Please install the latest version of Xcode and the %s SDK"%(
322 os
.path
.basename(SDKPATH
[:-4])))
326 def parseOptions(args
=None):
328 Parse arguments and update global settings.
330 global WORKDIR
, DEPSRC
, SDKPATH
, SRCDIR
336 options
, args
= getopt
.getopt(args
, '?hb',
337 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir='])
338 except getopt
.error
, msg
:
343 print "Additional arguments"
347 if k
in ('-h', '-?'):
351 elif k
in ('-d', '--build-dir'):
354 elif k
in ('--third-party',):
357 elif k
in ('--sdk-path',):
360 elif k
in ('--src-dir',):
364 raise NotImplementedError, k
366 SRCDIR
=os
.path
.abspath(SRCDIR
)
367 WORKDIR
=os
.path
.abspath(WORKDIR
)
368 SDKPATH
=os
.path
.abspath(SDKPATH
)
369 DEPSRC
=os
.path
.abspath(DEPSRC
)
372 print " * Source directory:", SRCDIR
373 print " * Build directory: ", WORKDIR
374 print " * SDK location: ", SDKPATH
375 print " * third-party source:", DEPSRC
381 def extractArchive(builddir
, archiveName
):
383 Extract a source archive into 'builddir'. Returns the path of the
386 XXX: This function assumes that archives contain a toplevel directory
387 that is has the same name as the basename of the archive. This is
388 save enough for anything we use.
393 if archiveName
.endswith('.tar.gz'):
394 retval
= os
.path
.basename(archiveName
[:-7])
395 if os
.path
.exists(retval
):
396 shutil
.rmtree(retval
)
397 fp
= os
.popen("tar zxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
399 elif archiveName
.endswith('.tar.bz2'):
400 retval
= os
.path
.basename(archiveName
[:-8])
401 if os
.path
.exists(retval
):
402 shutil
.rmtree(retval
)
403 fp
= os
.popen("tar jxf %s 2>&1"%(shellQuote(archiveName
),), 'r')
405 elif archiveName
.endswith('.tar'):
406 retval
= os
.path
.basename(archiveName
[:-4])
407 if os
.path
.exists(retval
):
408 shutil
.rmtree(retval
)
409 fp
= os
.popen("tar xf %s 2>&1"%(shellQuote(archiveName
),), 'r')
411 elif archiveName
.endswith('.zip'):
412 retval
= os
.path
.basename(archiveName
[:-4])
413 if os
.path
.exists(retval
):
414 shutil
.rmtree(retval
)
415 fp
= os
.popen("unzip %s 2>&1"%(shellQuote(archiveName
),), 'r')
420 sys
.stdout
.write(data
)
421 raise RuntimeError, "Cannot extract %s"%(archiveName
,)
423 return os
.path
.join(builddir
, retval
)
429 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
430 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
433 def downloadURL(url
, fname
):
435 Download the contents of the url into the file.
438 size
= os
.path
.getsize(fname
)
442 if KNOWNSIZES
.get(url
) == size
:
443 print "Using existing file for", url
445 fpIn
= urllib2
.urlopen(url
)
446 fpOut
= open(fname
, 'wb')
447 block
= fpIn
.read(10240)
451 block
= fpIn
.read(10240)
460 def buildRecipe(recipe
, basedir
, archList
):
462 Build software using a recipe. This function does the
463 'configure;make;make install' dance for C software, with a possibility
464 to customize this process, basically a poor-mans DarwinPorts.
468 name
= recipe
['name']
470 configure
= recipe
.get('configure', './configure')
471 install
= recipe
.get('install', 'make && make install DESTDIR=%s'%(
472 shellQuote(basedir
)))
474 archiveName
= os
.path
.split(url
)[-1]
475 sourceArchive
= os
.path
.join(DEPSRC
, archiveName
)
477 if not os
.path
.exists(DEPSRC
):
481 if os
.path
.exists(sourceArchive
):
482 print "Using local copy of %s"%(name
,)
485 print "Downloading %s"%(name
,)
486 downloadURL(url
, sourceArchive
)
487 print "Archive for %s stored as %s"%(name
, sourceArchive
)
489 print "Extracting archive for %s"%(name
,)
490 buildDir
=os
.path
.join(WORKDIR
, '_bld')
491 if not os
.path
.exists(buildDir
):
494 workDir
= extractArchive(buildDir
, sourceArchive
)
496 if 'buildDir' in recipe
:
497 os
.chdir(recipe
['buildDir'])
500 for fn
in recipe
.get('patches', ()):
501 if fn
.startswith('http://'):
502 # Download the patch before applying it.
503 path
= os
.path
.join(DEPSRC
, os
.path
.basename(fn
))
504 downloadURL(fn
, path
)
507 fn
= os
.path
.join(curdir
, fn
)
508 runCommand('patch -p%s < %s'%(recipe
.get('patchlevel', 1),
511 if configure
is not None:
513 "--prefix=/usr/local",
516 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
519 if 'configure_pre' in recipe
:
520 args
= list(recipe
['configure_pre'])
521 if '--disable-static' in args
:
522 configure_args
.remove('--enable-static')
523 if '--enable-shared' in args
:
524 configure_args
.remove('--disable-shared')
525 configure_args
.extend(args
)
527 if recipe
.get('useLDFlags', 1):
528 configure_args
.extend([
529 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
530 ' -arch '.join(archList
),
531 shellQuote(SDKPATH
)[1:-1],
532 shellQuote(basedir
)[1:-1],),
533 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
534 shellQuote(SDKPATH
)[1:-1],
535 shellQuote(basedir
)[1:-1],
536 ' -arch '.join(archList
)),
539 configure_args
.extend([
540 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
541 ' -arch '.join(archList
),
542 shellQuote(SDKPATH
)[1:-1],
543 shellQuote(basedir
)[1:-1],),
546 if 'configure_post' in recipe
:
547 configure_args
= configure_args
= list(recipe
['configure_post'])
549 configure_args
.insert(0, configure
)
550 configure_args
= [ shellQuote(a
) for a
in configure_args
]
552 print "Running configure for %s"%(name
,)
553 runCommand(' '.join(configure_args
) + ' 2>&1')
555 print "Running install for %s"%(name
,)
556 runCommand('{ ' + install
+ ' ;} 2>&1')
558 print "Done %s"%(name
,)
563 def buildLibraries():
565 Build our dependencies into $WORKDIR/libraries/usr/local
568 print "Building required libraries"
570 universal
= os
.path
.join(WORKDIR
, 'libraries')
572 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'lib'))
573 os
.makedirs(os
.path
.join(universal
, 'usr', 'local', 'include'))
575 for recipe
in LIBRARY_RECIPES
:
576 buildRecipe(recipe
, universal
, ARCHLIST
)
580 def buildPythonDocs():
581 # This stores the documentation as Resources/English.lproj/Docuentation
582 # inside the framwork. pydoc and IDLE will pick it up there.
583 print "Install python documentation"
584 rootDir
= os
.path
.join(WORKDIR
, '_root')
585 version
= getVersion()
586 docdir
= os
.path
.join(rootDir
, 'pydocs')
588 name
= 'html-%s.tar.bz2'%(getFullVersion(),)
589 sourceArchive
= os
.path
.join(DEPSRC
, name
)
590 if os
.path
.exists(sourceArchive
):
591 print "Using local copy of %s"%(name
,)
594 print "Downloading %s"%(name
,)
595 downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%(
596 getFullVersion(), name
), sourceArchive
)
597 print "Archive for %s stored as %s"%(name
, sourceArchive
)
599 extractArchive(os
.path
.dirname(docdir
), sourceArchive
)
602 os
.path
.dirname(docdir
), 'Python-Docs-%s'%(getFullVersion(),)),
607 print "Building a universal python"
609 buildDir
= os
.path
.join(WORKDIR
, '_bld', 'python')
610 rootDir
= os
.path
.join(WORKDIR
, '_root')
612 if os
.path
.exists(buildDir
):
613 shutil
.rmtree(buildDir
)
614 if os
.path
.exists(rootDir
):
615 shutil
.rmtree(rootDir
)
618 os
.mkdir(os
.path
.join(rootDir
, 'empty-dir'))
622 # Not sure if this is still needed, the original build script
623 # claims that parts of the install assume python.exe exists.
624 os
.symlink('python', os
.path
.join(buildDir
, 'python.exe'))
626 # Extract the version from the configure file, needed to calculate
628 version
= getVersion()
630 print "Running configure..."
631 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"%(
632 shellQuote(os
.path
.join(SRCDIR
, 'configure')),
633 shellQuote(SDKPATH
), shellQuote(WORKDIR
)[1:-1],
634 shellQuote(WORKDIR
)[1:-1]))
639 print "Running make frameworkinstall"
640 runCommand("make frameworkinstall DESTDIR=%s"%(
641 shellQuote(rootDir
)))
643 print "Running make frameworkinstallextras"
644 runCommand("make frameworkinstallextras DESTDIR=%s"%(
645 shellQuote(rootDir
)))
647 print "Copying required shared libraries"
648 if os
.path
.exists(os
.path
.join(WORKDIR
, 'libraries', 'Library')):
649 runCommand("mv %s/* %s"%(
650 shellQuote(os
.path
.join(
651 WORKDIR
, 'libraries', 'Library', 'Frameworks',
652 'Python.framework', 'Versions', getVersion(),
654 shellQuote(os
.path
.join(WORKDIR
, '_root', 'Library', 'Frameworks',
655 'Python.framework', 'Versions', getVersion(),
658 print "Fix file modes"
659 frmDir
= os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework')
660 gid
= grp
.getgrnam('admin').gr_gid
662 for dirpath
, dirnames
, filenames
in os
.walk(frmDir
):
664 os
.chmod(os
.path
.join(dirpath
, dn
), 0775)
665 os
.chown(os
.path
.join(dirpath
, dn
), -1, gid
)
669 if os
.path
.islink(fn
):
673 p
= os
.path
.join(dirpath
, fn
)
675 os
.chmod(p
, stat
.S_IMODE(st
.st_mode
) | stat
.S_IWGRP
)
678 # We added some directories to the search path during the configure
679 # phase. Remove those because those directories won't be there on
680 # the end-users system.
681 path
=os
.path
.join(rootDir
, 'Library', 'Frameworks', 'Python.framework',
682 'Versions', version
, 'lib', 'python%s'%(version
,),
683 'config', 'Makefile')
688 data
= data
.replace('-L%s/libraries/usr/local/lib'%(WORKDIR
,), '')
689 data
= data
.replace('-I%s/libraries/usr/local/include'%(WORKDIR
,), '')
694 # Add symlinks in /usr/local/bin, using relative links
695 usr_local_bin
= os
.path
.join(rootDir
, 'usr', 'local', 'bin')
696 to_framework
= os
.path
.join('..', '..', '..', 'Library', 'Frameworks',
697 'Python.framework', 'Versions', version
, 'bin')
698 if os
.path
.exists(usr_local_bin
):
699 shutil
.rmtree(usr_local_bin
)
700 os
.makedirs(usr_local_bin
)
701 for fn
in os
.listdir(
702 os
.path
.join(frmDir
, 'Versions', version
, 'bin')):
703 os
.symlink(os
.path
.join(to_framework
, fn
),
704 os
.path
.join(usr_local_bin
, fn
))
710 def patchFile(inPath
, outPath
):
711 data
= fileContents(inPath
)
712 data
= data
.replace('$FULL_VERSION', getFullVersion())
713 data
= data
.replace('$VERSION', getVersion())
714 data
= data
.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later')
715 data
= data
.replace('$ARCHITECTURES', "i386, ppc")
716 data
= data
.replace('$INSTALL_SIZE', installSize())
718 # This one is not handy as a template variable
719 data
= data
.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
720 fp
= open(outPath
, 'wb')
724 def patchScript(inPath
, outPath
):
725 data
= fileContents(inPath
)
726 data
= data
.replace('@PYVER@', getVersion())
727 fp
= open(outPath
, 'wb')
730 os
.chmod(outPath
, 0755)
734 def packageFromRecipe(targetDir
, recipe
):
737 # The major version (such as 2.5) is included in the package name
738 # because having two version of python installed at the same time is
740 pkgname
= '%s-%s'%(recipe
['name'], getVersion())
741 srcdir
= recipe
.get('source')
742 pkgroot
= recipe
.get('topdir', srcdir
)
743 postflight
= recipe
.get('postflight')
744 readme
= textwrap
.dedent(recipe
['readme'])
745 isRequired
= recipe
.get('required', True)
747 print "- building package %s"%(pkgname
,)
749 # Substitute some variables
752 FULLVER
=getFullVersion(),
754 readme
= readme
% textvars
756 if pkgroot
is not None:
757 pkgroot
= pkgroot
% textvars
761 if srcdir
is not None:
762 srcdir
= os
.path
.join(WORKDIR
, '_root', srcdir
[1:])
763 srcdir
= srcdir
% textvars
765 if postflight
is not None:
766 postflight
= os
.path
.abspath(postflight
)
768 packageContents
= os
.path
.join(targetDir
, pkgname
+ '.pkg', 'Contents')
769 os
.makedirs(packageContents
)
771 if srcdir
is not None:
773 runCommand("pax -wf %s . 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
774 runCommand("gzip -9 %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.pax')),))
775 runCommand("mkbom . %s 2>&1"%(shellQuote(os
.path
.join(packageContents
, 'Archive.bom')),))
777 fn
= os
.path
.join(packageContents
, 'PkgInfo')
782 rsrcDir
= os
.path
.join(packageContents
, "Resources")
784 fp
= open(os
.path
.join(rsrcDir
, 'ReadMe.txt'), 'w')
788 if postflight
is not None:
789 patchScript(postflight
, os
.path
.join(rsrcDir
, 'postflight'))
791 vers
= getFullVersion()
792 major
, minor
= map(int, getVersion().split('.', 2))
794 CFBundleGetInfoString
="MacPython.%s %s"%(pkgname
, vers
,),
795 CFBundleIdentifier
='org.python.MacPython.%s'%(pkgname
,),
796 CFBundleName
='MacPython.%s'%(pkgname
,),
797 CFBundleShortVersionString
=vers
,
798 IFMajorVersion
=major
,
799 IFMinorVersion
=minor
,
800 IFPkgFormatVersion
=0.10000000149011612,
801 IFPkgFlagAllowBackRev
=False,
802 IFPkgFlagAuthorizationAction
="RootAuthorization",
803 IFPkgFlagDefaultLocation
=pkgroot
,
804 IFPkgFlagFollowLinks
=True,
805 IFPkgFlagInstallFat
=True,
806 IFPkgFlagIsRequired
=isRequired
,
807 IFPkgFlagOverwritePermissions
=False,
808 IFPkgFlagRelocatable
=False,
809 IFPkgFlagRestartAction
="NoRestart",
810 IFPkgFlagRootVolumeOnly
=True,
811 IFPkgFlagUpdateInstalledLangauges
=False,
813 writePlist(pl
, os
.path
.join(packageContents
, 'Info.plist'))
816 IFPkgDescriptionDescription
=readme
,
817 IFPkgDescriptionTitle
=recipe
.get('long_name', "MacPython.%s"%(pkgname
,)),
818 IFPkgDescriptionVersion
=vers
,
820 writePlist(pl
, os
.path
.join(packageContents
, 'Resources', 'Description.plist'))
826 def makeMpkgPlist(path
):
828 vers
= getFullVersion()
829 major
, minor
= map(int, getVersion().split('.', 2))
832 CFBundleGetInfoString
="MacPython %s"%(vers
,),
833 CFBundleIdentifier
='org.python.MacPython',
834 CFBundleName
='MacPython',
835 CFBundleShortVersionString
=vers
,
836 IFMajorVersion
=major
,
837 IFMinorVersion
=minor
,
838 IFPkgFlagComponentDirectory
="Contents/Packages",
839 IFPkgFlagPackageList
=[
841 IFPkgFlagPackageLocation
='%s-%s.pkg'%(item
['name'], getVersion()),
842 IFPkgFlagPackageSelection
='selected'
844 for item
in PKG_RECIPES
846 IFPkgFormatVersion
=0.10000000149011612,
847 IFPkgFlagBackgroundScaling
="proportional",
848 IFPkgFlagBackgroundAlignment
="left",
849 IFPkgFlagAuthorizationAction
="RootAuthorization",
855 def buildInstaller():
857 # Zap all compiled files
858 for dirpath
, _
, filenames
in os
.walk(os
.path
.join(WORKDIR
, '_root')):
860 if fn
.endswith('.pyc') or fn
.endswith('.pyo'):
861 os
.unlink(os
.path
.join(dirpath
, fn
))
863 outdir
= os
.path
.join(WORKDIR
, 'installer')
864 if os
.path
.exists(outdir
):
865 shutil
.rmtree(outdir
)
868 pkgroot
= os
.path
.join(outdir
, 'MacPython.mpkg', 'Contents')
869 pkgcontents
= os
.path
.join(pkgroot
, 'Packages')
870 os
.makedirs(pkgcontents
)
871 for recipe
in PKG_RECIPES
:
872 packageFromRecipe(pkgcontents
, recipe
)
874 rsrcDir
= os
.path
.join(pkgroot
, 'Resources')
876 fn
= os
.path
.join(pkgroot
, 'PkgInfo')
883 makeMpkgPlist(os
.path
.join(pkgroot
, 'Info.plist'))
885 IFPkgDescriptionTitle
="Universal MacPython",
886 IFPkgDescriptionVersion
=getVersion(),
889 writePlist(pl
, os
.path
.join(pkgroot
, 'Resources', 'Description.plist'))
890 for fn
in os
.listdir('resources'):
891 if fn
== '.svn': continue
892 if fn
.endswith('.jpg'):
893 shutil
.copy(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
895 patchFile(os
.path
.join('resources', fn
), os
.path
.join(rsrcDir
, fn
))
897 shutil
.copy("../../LICENSE", os
.path
.join(rsrcDir
, 'License.txt'))
900 def installSize(clear
=False, _saved
=[]):
904 data
= captureCommand("du -ks %s"%(
905 shellQuote(os
.path
.join(WORKDIR
, '_root'))))
906 _saved
.append("%d"%((0.5 + (int(data
.split()[0]) / 1024.0)),))
912 Create DMG containing the rootDir.
914 outdir
= os
.path
.join(WORKDIR
, 'diskimage')
915 if os
.path
.exists(outdir
):
916 shutil
.rmtree(outdir
)
918 imagepath
= os
.path
.join(outdir
,
919 'python-%s-macosx'%(getFullVersion(),))
920 if INCLUDE_TIMESTAMP
:
921 imagepath
= imagepath
+ '%04d-%02d-%02d'%(time
.localtime()[:3])
922 imagepath
= imagepath
+ '.dmg'
925 runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
927 shellQuote(os
.path
.join(WORKDIR
, 'installer')),
928 shellQuote(imagepath
)))
933 def setIcon(filePath
, icnsPath
):
935 Set the custom icon for the specified file or directory.
937 For a directory the icon data is written in a file named 'Icon\r' inside
938 the directory. For both files and directories write the icon as an 'icns'
939 resource. Furthermore set kHasCustomIcon in the finder flags for filePath.
941 ref
, isDirectory
= Carbon
.File
.FSPathMakeRef(icnsPath
)
942 icon
= Carbon
.Icn
.ReadIconFile(ref
)
946 # Open the resource fork of the target, to add the icon later on.
947 # For directories we use the file 'Icon\r' inside the directory.
950 ref
, isDirectory
= Carbon
.File
.FSPathMakeRef(filePath
)
953 # There is a problem with getting this into the pax(1) archive,
954 # just ignore directory icons for now.
957 tmpPath
= os
.path
.join(filePath
, "Icon\r")
958 if not os
.path
.exists(tmpPath
):
959 fp
= open(tmpPath
, 'w')
962 tmpRef
, _
= Carbon
.File
.FSPathMakeRef(tmpPath
)
963 spec
= Carbon
.File
.FSSpec(tmpRef
)
966 spec
= Carbon
.File
.FSSpec(ref
)
969 Carbon
.Res
.HCreateResFile(*spec
.as_tuple())
973 # Try to create the resource fork again, this will avoid problems
974 # when adding an icon to a directory. I have no idea why this helps,
975 # but without this adding the icon to a directory will fail sometimes.
977 Carbon
.Res
.HCreateResFile(*spec
.as_tuple())
981 refNum
= Carbon
.Res
.FSpOpenResFile(spec
, fsRdWrPerm
)
983 Carbon
.Res
.UseResFile(refNum
)
985 # Check if there already is an icon, remove it if there is.
987 h
= Carbon
.Res
.Get1Resource('icns', kCustomIconResource
)
995 # Add the icon to the resource for of the target
996 res
= Carbon
.Res
.Resource(icon
)
997 res
.AddResource('icns', kCustomIconResource
, '')
1000 Carbon
.Res
.CloseResFile(refNum
)
1002 # And now set the kHasCustomIcon property for the target. Annoyingly,
1003 # python doesn't seem to have bindings for the API that is needed for
1004 # this. Cop out and call SetFile
1005 os
.system("/Developer/Tools/SetFile -a C %s"%(
1006 shellQuote(filePath
),))
1009 os
.system('/Developer/Tools/SetFile -a V %s'%(
1010 shellQuote(tmpPath
),
1014 # First parse options and check if we can perform our work
1018 os
.environ
['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
1020 if os
.path
.exists(WORKDIR
):
1021 shutil
.rmtree(WORKDIR
)
1024 # Then build third-party libraries such as sleepycat DB4.
1027 # Now build python itself
1030 fn
= os
.path
.join(WORKDIR
, "_root", "Applications",
1031 "MacPython %s"%(getVersion(),), "Update Shell Profile.command")
1032 patchFile("scripts/postflight.patch-profile", fn
)
1035 folder
= os
.path
.join(WORKDIR
, "_root", "Applications", "MacPython %s"%(
1037 os
.chmod(folder
, 0755)
1038 setIcon(folder
, "../Icons/Python Folder.icns")
1040 # Create the installer
1043 # And copy the readme into the directory containing the installer
1044 patchFile('resources/ReadMe.txt', os
.path
.join(WORKDIR
, 'installer', 'ReadMe.txt'))
1046 # Ditto for the license file.
1047 shutil
.copy('../../LICENSE', os
.path
.join(WORKDIR
, 'installer', 'License.txt'))
1049 fp
= open(os
.path
.join(WORKDIR
, 'installer', 'Build.txt'), 'w')
1050 print >> fp
, "# BUILD INFO"
1051 print >> fp
, "# Date:", time
.ctime()
1052 print >> fp
, "# By:", pwd
.getpwuid(os
.getuid()).pw_gecos
1055 # Custom icon for the DMG, shown when the DMG is mounted.
1056 shutil
.copy("../Icons/Disk Image.icns",
1057 os
.path
.join(WORKDIR
, "installer", ".VolumeIcon.icns"))
1058 os
.system("/Developer/Tools/SetFile -a C %s"%(
1059 os
.path
.join(WORKDIR
, "installer", ".VolumeIcon.icns")))
1062 # And copy it to a DMG
1066 if __name__
== "__main__":