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.6.3.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.7.25",
174
url="http://download.oracle.com/berkeley-db/db-4.7.25.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/Python %(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
novername = 'python-docs-html.tar.bz2'
589
name = 'html-%s.tar.bz2'%(getFullVersion(),)
590
sourceArchive = os.path.join(DEPSRC, name)
591
if os.path.exists(sourceArchive):
592
print "Using local copy of %s"%(name,)
595
print "Downloading %s"%(novername,)
596
downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%(
597
getFullVersion(), novername), sourceArchive)
598
print "Archive for %s stored as %s"%(name, sourceArchive)
600
extractArchive(os.path.dirname(docdir), sourceArchive)
604
os.path.dirname(docdir), 'python-docs-html'),
609
print "Building a universal python"
611
buildDir = os.path.join(WORKDIR, '_bld', 'python')
612
rootDir = os.path.join(WORKDIR, '_root')
614
if os.path.exists(buildDir):
615
shutil.rmtree(buildDir)
616
if os.path.exists(rootDir):
617
shutil.rmtree(rootDir)
620
os.mkdir(os.path.join(rootDir, 'empty-dir'))
624
# Not sure if this is still needed, the original build script
625
# claims that parts of the install assume python.exe exists.
626
os.symlink('python', os.path.join(buildDir, 'python.exe'))
628
# Extract the version from the configure file, needed to calculate
630
version = getVersion()
632
print "Running configure..."
633
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"%(
634
shellQuote(os.path.join(SRCDIR, 'configure')),
635
shellQuote(SDKPATH), shellQuote(WORKDIR)[1:-1],
636
shellQuote(WORKDIR)[1:-1]))
641
print "Running make frameworkinstall"
642
runCommand("make frameworkinstall DESTDIR=%s"%(
643
shellQuote(rootDir)))
645
print "Running make frameworkinstallextras"
646
runCommand("make frameworkinstallextras DESTDIR=%s"%(
647
shellQuote(rootDir)))
649
print "Copying required shared libraries"
650
if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
651
runCommand("mv %s/* %s"%(
652
shellQuote(os.path.join(
653
WORKDIR, 'libraries', 'Library', 'Frameworks',
654
'Python.framework', 'Versions', getVersion(),
656
shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
657
'Python.framework', 'Versions', getVersion(),
660
print "Fix file modes"
661
frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
662
gid = grp.getgrnam('admin').gr_gid
664
for dirpath, dirnames, filenames in os.walk(frmDir):
666
os.chmod(os.path.join(dirpath, dn), 0775)
667
os.chown(os.path.join(dirpath, dn), -1, gid)
671
if os.path.islink(fn):
675
p = os.path.join(dirpath, fn)
677
os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
680
# We added some directories to the search path during the configure
681
# phase. Remove those because those directories won't be there on
682
# the end-users system.
683
path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
684
'Versions', version, 'lib', 'python%s'%(version,),
685
'config', 'Makefile')
690
data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
691
data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
696
# Add symlinks in /usr/local/bin, using relative links
697
usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
698
to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
699
'Python.framework', 'Versions', version, 'bin')
700
if os.path.exists(usr_local_bin):
701
shutil.rmtree(usr_local_bin)
702
os.makedirs(usr_local_bin)
703
for fn in os.listdir(
704
os.path.join(frmDir, 'Versions', version, 'bin')):
705
os.symlink(os.path.join(to_framework, fn),
706
os.path.join(usr_local_bin, fn))
712
def patchFile(inPath, outPath):
713
data = fileContents(inPath)
714
data = data.replace('$FULL_VERSION', getFullVersion())
715
data = data.replace('$VERSION', getVersion())
716
data = data.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later')
717
data = data.replace('$ARCHITECTURES', "i386, ppc")
718
data = data.replace('$INSTALL_SIZE', installSize())
720
# This one is not handy as a template variable
721
data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
722
fp = open(outPath, 'wb')
726
def patchScript(inPath, outPath):
727
data = fileContents(inPath)
728
data = data.replace('@PYVER@', getVersion())
729
fp = open(outPath, 'wb')
732
os.chmod(outPath, 0755)
736
def packageFromRecipe(targetDir, recipe):
739
# The major version (such as 2.5) is included in the package name
740
# because having two version of python installed at the same time is
742
pkgname = '%s-%s'%(recipe['name'], getVersion())
743
srcdir = recipe.get('source')
744
pkgroot = recipe.get('topdir', srcdir)
745
postflight = recipe.get('postflight')
746
readme = textwrap.dedent(recipe['readme'])
747
isRequired = recipe.get('required', True)
749
print "- building package %s"%(pkgname,)
751
# Substitute some variables
754
FULLVER=getFullVersion(),
756
readme = readme % textvars
758
if pkgroot is not None:
759
pkgroot = pkgroot % textvars
763
if srcdir is not None:
764
srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
765
srcdir = srcdir % textvars
767
if postflight is not None:
768
postflight = os.path.abspath(postflight)
770
packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
771
os.makedirs(packageContents)
773
if srcdir is not None:
775
runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
776
runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
777
runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
779
fn = os.path.join(packageContents, 'PkgInfo')
784
rsrcDir = os.path.join(packageContents, "Resources")
786
fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
790
if postflight is not None:
791
patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
793
vers = getFullVersion()
794
major, minor = map(int, getVersion().split('.', 2))
796
CFBundleGetInfoString="MacPython.%s %s"%(pkgname, vers,),
797
CFBundleIdentifier='org.python.MacPython.%s'%(pkgname,),
798
CFBundleName='MacPython.%s'%(pkgname,),
799
CFBundleShortVersionString=vers,
800
IFMajorVersion=major,
801
IFMinorVersion=minor,
802
IFPkgFormatVersion=0.10000000149011612,
803
IFPkgFlagAllowBackRev=False,
804
IFPkgFlagAuthorizationAction="RootAuthorization",
805
IFPkgFlagDefaultLocation=pkgroot,
806
IFPkgFlagFollowLinks=True,
807
IFPkgFlagInstallFat=True,
808
IFPkgFlagIsRequired=isRequired,
809
IFPkgFlagOverwritePermissions=False,
810
IFPkgFlagRelocatable=False,
811
IFPkgFlagRestartAction="NoRestart",
812
IFPkgFlagRootVolumeOnly=True,
813
IFPkgFlagUpdateInstalledLangauges=False,
815
writePlist(pl, os.path.join(packageContents, 'Info.plist'))
818
IFPkgDescriptionDescription=readme,
819
IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)),
820
IFPkgDescriptionVersion=vers,
822
writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
828
def makeMpkgPlist(path):
830
vers = getFullVersion()
831
major, minor = map(int, getVersion().split('.', 2))
834
CFBundleGetInfoString="MacPython %s"%(vers,),
835
CFBundleIdentifier='org.python.MacPython',
836
CFBundleName='MacPython',
837
CFBundleShortVersionString=vers,
838
IFMajorVersion=major,
839
IFMinorVersion=minor,
840
IFPkgFlagComponentDirectory="Contents/Packages",
841
IFPkgFlagPackageList=[
843
IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
844
IFPkgFlagPackageSelection='selected'
846
for item in PKG_RECIPES
848
IFPkgFormatVersion=0.10000000149011612,
849
IFPkgFlagBackgroundScaling="proportional",
850
IFPkgFlagBackgroundAlignment="left",
851
IFPkgFlagAuthorizationAction="RootAuthorization",
857
def buildInstaller():
859
# Zap all compiled files
860
for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
862
if fn.endswith('.pyc') or fn.endswith('.pyo'):
863
os.unlink(os.path.join(dirpath, fn))
865
outdir = os.path.join(WORKDIR, 'installer')
866
if os.path.exists(outdir):
867
shutil.rmtree(outdir)
870
pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents')
871
pkgcontents = os.path.join(pkgroot, 'Packages')
872
os.makedirs(pkgcontents)
873
for recipe in PKG_RECIPES:
874
packageFromRecipe(pkgcontents, recipe)
876
rsrcDir = os.path.join(pkgroot, 'Resources')
878
fn = os.path.join(pkgroot, 'PkgInfo')
885
makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
887
IFPkgDescriptionTitle="Universal MacPython",
888
IFPkgDescriptionVersion=getVersion(),
891
writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
892
for fn in os.listdir('resources'):
893
if fn == '.svn': continue
894
if fn.endswith('.jpg'):
895
shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
897
patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
899
shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
902
def installSize(clear=False, _saved=[]):
906
data = captureCommand("du -ks %s"%(
907
shellQuote(os.path.join(WORKDIR, '_root'))))
908
_saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
914
Create DMG containing the rootDir.
916
outdir = os.path.join(WORKDIR, 'diskimage')
917
if os.path.exists(outdir):
918
shutil.rmtree(outdir)
920
imagepath = os.path.join(outdir,
921
'python-%s-macosx'%(getFullVersion(),))
922
if INCLUDE_TIMESTAMP:
923
imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3])
924
imagepath = imagepath + '.dmg'
927
runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
929
shellQuote(os.path.join(WORKDIR, 'installer')),
930
shellQuote(imagepath)))
935
def setIcon(filePath, icnsPath):
937
Set the custom icon for the specified file or directory.
939
For a directory the icon data is written in a file named 'Icon\r' inside
940
the directory. For both files and directories write the icon as an 'icns'
941
resource. Furthermore set kHasCustomIcon in the finder flags for filePath.
943
ref, isDirectory = Carbon.File.FSPathMakeRef(icnsPath)
944
icon = Carbon.Icn.ReadIconFile(ref)
948
# Open the resource fork of the target, to add the icon later on.
949
# For directories we use the file 'Icon\r' inside the directory.
952
ref, isDirectory = Carbon.File.FSPathMakeRef(filePath)
955
# There is a problem with getting this into the pax(1) archive,
956
# just ignore directory icons for now.
959
tmpPath = os.path.join(filePath, "Icon\r")
960
if not os.path.exists(tmpPath):
961
fp = open(tmpPath, 'w')
964
tmpRef, _ = Carbon.File.FSPathMakeRef(tmpPath)
965
spec = Carbon.File.FSSpec(tmpRef)
968
spec = Carbon.File.FSSpec(ref)
971
Carbon.Res.HCreateResFile(*spec.as_tuple())
975
# Try to create the resource fork again, this will avoid problems
976
# when adding an icon to a directory. I have no idea why this helps,
977
# but without this adding the icon to a directory will fail sometimes.
979
Carbon.Res.HCreateResFile(*spec.as_tuple())
983
refNum = Carbon.Res.FSpOpenResFile(spec, fsRdWrPerm)
985
Carbon.Res.UseResFile(refNum)
987
# Check if there already is an icon, remove it if there is.
989
h = Carbon.Res.Get1Resource('icns', kCustomIconResource)
997
# Add the icon to the resource for of the target
998
res = Carbon.Res.Resource(icon)
999
res.AddResource('icns', kCustomIconResource, '')
1001
res.DetachResource()
1002
Carbon.Res.CloseResFile(refNum)
1004
# And now set the kHasCustomIcon property for the target. Annoyingly,
1005
# python doesn't seem to have bindings for the API that is needed for
1006
# this. Cop out and call SetFile
1007
os.system("/Developer/Tools/SetFile -a C %s"%(
1008
shellQuote(filePath),))
1011
os.system('/Developer/Tools/SetFile -a V %s'%(
1012
shellQuote(tmpPath),
1016
# First parse options and check if we can perform our work
1020
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
1022
if os.path.exists(WORKDIR):
1023
shutil.rmtree(WORKDIR)
1026
# Then build third-party libraries such as sleepycat DB4.
1029
# Now build python itself
1032
fn = os.path.join(WORKDIR, "_root", "Applications",
1033
"Python %s"%(getVersion(),), "Update Shell Profile.command")
1034
patchFile("scripts/postflight.patch-profile", fn)
1037
folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
1039
os.chmod(folder, 0755)
1040
setIcon(folder, "../Icons/Python Folder.icns")
1042
# Create the installer
1045
# And copy the readme into the directory containing the installer
1046
patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1048
# Ditto for the license file.
1049
shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
1051
fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1052
print >> fp, "# BUILD INFO"
1053
print >> fp, "# Date:", time.ctime()
1054
print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1057
# Custom icon for the DMG, shown when the DMG is mounted.
1058
shutil.copy("../Icons/Disk Image.icns",
1059
os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))
1060
os.system("/Developer/Tools/SetFile -a C %s"%(
1061
os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")))
1064
# And copy it to a DMG
1068
if __name__ == "__main__":