blob: dd606d947faaf845d1eafec87ceecba23bd8d0f2 [file] [log] [blame]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001#!/usr/bin/python2.3
2"""
3This script is used to build the "official unofficial" universal build on
4Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its
5work.
6
7Please ensure that this script keeps working with Python 2.3, to avoid
8bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
9
10Usage: see USAGE variable in the script.
11"""
Benjamin Peterson623918e2008-12-20 22:50:25 +000012import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Thomas Wouters89f507f2006-12-13 04:49:30 +000013import grp
Thomas Wouters477c8d52006-05-27 19:21:47 +000014
Thomas Wouters89f507f2006-12-13 04:49:30 +000015INCLUDE_TIMESTAMP = 1
16VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000017
18from plistlib import Plist
19
20import MacOS
21import Carbon.File
22import Carbon.Icn
23import Carbon.Res
24from Carbon.Files import kCustomIconResource, fsRdWrPerm, kHasCustomIcon
25from Carbon.Files import kFSCatInfoFinderInfo
26
27try:
28 from plistlib import writePlist
29except ImportError:
30 # We're run using python2.3
31 def writePlist(plist, path):
32 plist.write(path)
33
34def shellQuote(value):
35 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000036 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000037 a shell command.
38 """
39 return "'%s'"%(value.replace("'", "'\"'\"'"))
40
41def grepValue(fn, variable):
42 variable = variable + '='
43 for ln in open(fn, 'r'):
44 if ln.startswith(variable):
45 value = ln[len(variable):].strip()
46 return value[1:-1]
47
48def getVersion():
49 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
50
51def getFullVersion():
52 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
53 for ln in open(fn):
54 if 'PY_VERSION' in ln:
55 return ln.split()[-1][1:-1]
56
Benjamin Peterson623918e2008-12-20 22:50:25 +000057 raise RuntimeError, "Cannot find full version??"
Thomas Wouters477c8d52006-05-27 19:21:47 +000058
Thomas Wouters89f507f2006-12-13 04:49:30 +000059# The directory we'll use to create the build (will be erased and recreated)
60WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000061
Thomas Wouters89f507f2006-12-13 04:49:30 +000062# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000063# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000064DEPSRC = os.path.join(WORKDIR, 'third-party')
65DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000066
67# Location of the preferred SDK
Thomas Wouters89f507f2006-12-13 04:49:30 +000068SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
69#SDKPATH = "/"
Thomas Wouters477c8d52006-05-27 19:21:47 +000070
Thomas Wouters89f507f2006-12-13 04:49:30 +000071ARCHLIST = ('i386', 'ppc',)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000072
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000073# Source directory (asume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +000074SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +000075 os.path.dirname(
76 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000077 os.path.abspath(__file__
78 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +000079
Thomas Wouters89f507f2006-12-13 04:49:30 +000080USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +000081 Usage: build_python [options]
82
83 Options:
84 -? or -h: Show this message
85 -b DIR
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)
90""")% globals()
91
92
93# Instructions for building libraries that are necessary for building a
94# batteries included python.
Thomas Wouters89f507f2006-12-13 04:49:30 +000095LIBRARY_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +000096 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +000097 name="Bzip2 1.0.3",
98 url="http://www.bzip.org/1.0.3/bzip2-1.0.3.tar.gz",
99 configure=None,
100 install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
101 shellQuote(os.path.join(WORKDIR, 'libraries')),
102 ' -arch '.join(ARCHLIST),
103 SDKPATH,
104 ),
105 ),
106 dict(
107 name="ZLib 1.2.3",
108 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
109 configure=None,
110 install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
111 shellQuote(os.path.join(WORKDIR, 'libraries')),
112 ' -arch '.join(ARCHLIST),
113 SDKPATH,
114 ),
115 ),
116 dict(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000117 # 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" ,
120 patchlevel='0',
121 patches=[
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',
128 ]
129 ),
130
131 dict(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000132 name="SQLite 3.6.3",
133 url="http://www.sqlite.org/sqlite-3.6.3.tar.gz",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000134 checksum='93f742986e8bc2dfa34792e16df017a6feccf3a2',
135 configure_pre=[
136 '--enable-threadsafe',
137 '--enable-tempstore',
138 '--enable-shared=no',
139 '--enable-static=yes',
140 '--disable-tcl',
141 ]
142 ),
143
144 dict(
145 name="NCurses 5.5",
146 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
147 configure_pre=[
148 "--without-cxx",
149 "--without-ada",
150 "--without-progs",
151 "--without-curses-h",
152 "--enable-shared",
153 "--with-shared",
154 "--datadir=/usr/share",
155 "--sysconfdir=/etc",
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(),),
160 "--enable-termcap",
161 ],
162 patches=[
163 "ncurses-5.5.patch",
164 ],
165 useLDFlags=False,
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')),
169 getVersion(),
170 ),
171 ),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000172]
173
174
175# Instructions for building packages inside the .mpkg.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000176PKG_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +0000177 dict(
178 name="PythonFramework",
179 long_name="Python Framework",
180 source="/Library/Frameworks/Python.framework",
181 readme="""\
182 This package installs Python.framework, that is the python
183 interpreter and the standard library. This also includes Python
184 wrappers for lots of Mac OS X API's.
185 """,
186 postflight="scripts/postflight.framework",
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000187 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000188 ),
189 dict(
190 name="PythonApplications",
191 long_name="GUI Applications",
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000192 source="/Applications/Python %(VER)s",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000193 readme="""\
Thomas Wouters89f507f2006-12-13 04:49:30 +0000194 This package installs IDLE (an interactive Python IDE),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000195 Python Launcher and Build Applet (create application bundles
196 from python scripts).
197
198 It also installs a number of examples and demos.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000199 """,
200 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000201 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000202 ),
203 dict(
204 name="PythonUnixTools",
205 long_name="UNIX command-line tools",
206 source="/usr/local/bin",
207 readme="""\
208 This package installs the unix tools in /usr/local/bin for
209 compatibility with older releases of MacPython. This package
210 is not necessary to use MacPython.
211 """,
212 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000213 selected='unselected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000214 ),
215 dict(
216 name="PythonDocumentation",
217 long_name="Python Documentation",
218 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
219 source="/pydocs",
220 readme="""\
221 This package installs the python documentation at a location
222 that is useable for pydoc and IDLE. If you have installed Xcode
223 it will also install a link to the documentation in
224 /Developer/Documentation/Python
225 """,
226 postflight="scripts/postflight.documentation",
227 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000228 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000229 ),
230 dict(
231 name="PythonProfileChanges",
232 long_name="Shell profile updater",
233 readme="""\
234 This packages updates your shell profile to make sure that
235 the MacPython tools are found by your shell in preference of
236 the system provided Python tools.
237
238 If you don't install this package you'll have to add
239 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
240 to your PATH by hand.
241 """,
242 postflight="scripts/postflight.patch-profile",
243 topdir="/Library/Frameworks/Python.framework",
244 source="/empty-dir",
245 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000246 selected='unselected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000247 ),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000248 dict(
249 name="PythonSystemFixes",
250 long_name="Fix system Python",
251 readme="""\
252 This package updates the system python installation on
253 Mac OS X 10.3 to ensure that you can build new python extensions
Thomas Wouters89f507f2006-12-13 04:49:30 +0000254 using that copy of python after installing this version.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000255 """,
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000256 postflight="../Tools/fixapplepython23.py",
257 topdir="/Library/Frameworks/Python.framework",
258 source="/empty-dir",
259 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000260 selected='unselected',
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000261 )
Thomas Wouters477c8d52006-05-27 19:21:47 +0000262]
263
Thomas Wouters477c8d52006-05-27 19:21:47 +0000264def fatal(msg):
265 """
266 A fatal error, bail out.
267 """
268 sys.stderr.write('FATAL: ')
269 sys.stderr.write(msg)
270 sys.stderr.write('\n')
271 sys.exit(1)
272
273def fileContents(fn):
274 """
275 Return the contents of the named file
276 """
277 return open(fn, 'rb').read()
278
279def runCommand(commandline):
280 """
281 Run a command and raise RuntimeError if it fails. Output is surpressed
282 unless the command fails.
283 """
284 fd = os.popen(commandline, 'r')
285 data = fd.read()
286 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000287 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000288 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000289 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000290
291 if VERBOSE:
292 sys.stdout.write(data); sys.stdout.flush()
293
294def captureCommand(commandline):
295 fd = os.popen(commandline, 'r')
296 data = fd.read()
297 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000298 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000299 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000300 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000301
302 return data
303
304def checkEnvironment():
305 """
306 Check that we're running on a supported system.
307 """
308
309 if platform.system() != 'Darwin':
310 fatal("This script should be run on a Mac OS X 10.4 system")
311
312 if platform.release() <= '8.':
313 fatal("This script should be run on a Mac OS X 10.4 system")
314
315 if not os.path.exists(SDKPATH):
316 fatal("Please install the latest version of Xcode and the %s SDK"%(
317 os.path.basename(SDKPATH[:-4])))
318
319
320
Thomas Wouters89f507f2006-12-13 04:49:30 +0000321def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000322 """
323 Parse arguments and update global settings.
324 """
325 global WORKDIR, DEPSRC, SDKPATH, SRCDIR
326
327 if args is None:
328 args = sys.argv[1:]
329
330 try:
331 options, args = getopt.getopt(args, '?hb',
332 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir='])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000333 except getopt.error, msg:
334 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000335 sys.exit(1)
336
337 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000338 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000339 sys.exit(1)
340
341 for k, v in options:
342 if k in ('-h', '-?'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000343 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000344 sys.exit(0)
345
346 elif k in ('-d', '--build-dir'):
347 WORKDIR=v
348
349 elif k in ('--third-party',):
350 DEPSRC=v
351
352 elif k in ('--sdk-path',):
353 SDKPATH=v
354
355 elif k in ('--src-dir',):
356 SRCDIR=v
357
358 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000359 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360
361 SRCDIR=os.path.abspath(SRCDIR)
362 WORKDIR=os.path.abspath(WORKDIR)
363 SDKPATH=os.path.abspath(SDKPATH)
364 DEPSRC=os.path.abspath(DEPSRC)
365
Benjamin Peterson623918e2008-12-20 22:50:25 +0000366 print "Settings:"
367 print " * Source directory:", SRCDIR
368 print " * Build directory: ", WORKDIR
369 print " * SDK location: ", SDKPATH
370 print " * third-party source:", DEPSRC
371 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000372
373
374
375
376def extractArchive(builddir, archiveName):
377 """
378 Extract a source archive into 'builddir'. Returns the path of the
379 extracted archive.
380
381 XXX: This function assumes that archives contain a toplevel directory
382 that is has the same name as the basename of the archive. This is
383 save enough for anything we use.
384 """
385 curdir = os.getcwd()
386 try:
387 os.chdir(builddir)
388 if archiveName.endswith('.tar.gz'):
389 retval = os.path.basename(archiveName[:-7])
390 if os.path.exists(retval):
391 shutil.rmtree(retval)
392 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
393
394 elif archiveName.endswith('.tar.bz2'):
395 retval = os.path.basename(archiveName[:-8])
396 if os.path.exists(retval):
397 shutil.rmtree(retval)
398 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
399
400 elif archiveName.endswith('.tar'):
401 retval = os.path.basename(archiveName[:-4])
402 if os.path.exists(retval):
403 shutil.rmtree(retval)
404 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
405
406 elif archiveName.endswith('.zip'):
407 retval = os.path.basename(archiveName[:-4])
408 if os.path.exists(retval):
409 shutil.rmtree(retval)
410 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
411
412 data = fp.read()
413 xit = fp.close()
414 if xit is not None:
415 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000416 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000417
418 return os.path.join(builddir, retval)
419
420 finally:
421 os.chdir(curdir)
422
423KNOWNSIZES = {
424 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
425 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
426}
427
428def downloadURL(url, fname):
429 """
430 Download the contents of the url into the file.
431 """
432 try:
433 size = os.path.getsize(fname)
434 except OSError:
435 pass
436 else:
437 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000438 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000439 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000440 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000441 fpOut = open(fname, 'wb')
442 block = fpIn.read(10240)
443 try:
444 while block:
445 fpOut.write(block)
446 block = fpIn.read(10240)
447 fpIn.close()
448 fpOut.close()
449 except:
450 try:
451 os.unlink(fname)
452 except:
453 pass
454
455def buildRecipe(recipe, basedir, archList):
456 """
457 Build software using a recipe. This function does the
458 'configure;make;make install' dance for C software, with a possibility
459 to customize this process, basically a poor-mans DarwinPorts.
460 """
461 curdir = os.getcwd()
462
463 name = recipe['name']
464 url = recipe['url']
465 configure = recipe.get('configure', './configure')
466 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
467 shellQuote(basedir)))
468
469 archiveName = os.path.split(url)[-1]
470 sourceArchive = os.path.join(DEPSRC, archiveName)
471
472 if not os.path.exists(DEPSRC):
473 os.mkdir(DEPSRC)
474
475
476 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000477 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478
479 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000480 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000481 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000482 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000483
Benjamin Peterson623918e2008-12-20 22:50:25 +0000484 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000485 buildDir=os.path.join(WORKDIR, '_bld')
486 if not os.path.exists(buildDir):
487 os.mkdir(buildDir)
488
489 workDir = extractArchive(buildDir, sourceArchive)
490 os.chdir(workDir)
491 if 'buildDir' in recipe:
492 os.chdir(recipe['buildDir'])
493
494
495 for fn in recipe.get('patches', ()):
496 if fn.startswith('http://'):
497 # Download the patch before applying it.
498 path = os.path.join(DEPSRC, os.path.basename(fn))
499 downloadURL(fn, path)
500 fn = path
501
502 fn = os.path.join(curdir, fn)
503 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
504 shellQuote(fn),))
505
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000506 if configure is not None:
507 configure_args = [
508 "--prefix=/usr/local",
509 "--enable-static",
510 "--disable-shared",
511 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
512 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000514 if 'configure_pre' in recipe:
515 args = list(recipe['configure_pre'])
516 if '--disable-static' in args:
517 configure_args.remove('--enable-static')
518 if '--enable-shared' in args:
519 configure_args.remove('--disable-shared')
520 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000521
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000522 if recipe.get('useLDFlags', 1):
523 configure_args.extend([
524 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
525 ' -arch '.join(archList),
526 shellQuote(SDKPATH)[1:-1],
527 shellQuote(basedir)[1:-1],),
528 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000529 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000530 shellQuote(basedir)[1:-1],
531 ' -arch '.join(archList)),
532 ])
533 else:
534 configure_args.extend([
535 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
536 ' -arch '.join(archList),
537 shellQuote(SDKPATH)[1:-1],
538 shellQuote(basedir)[1:-1],),
539 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000540
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000541 if 'configure_post' in recipe:
542 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000544 configure_args.insert(0, configure)
545 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000546
Benjamin Peterson623918e2008-12-20 22:50:25 +0000547 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000548 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000549
Benjamin Peterson623918e2008-12-20 22:50:25 +0000550 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000551 runCommand('{ ' + install + ' ;} 2>&1')
552
Benjamin Peterson623918e2008-12-20 22:50:25 +0000553 print "Done %s"%(name,)
554 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555
556 os.chdir(curdir)
557
558def buildLibraries():
559 """
560 Build our dependencies into $WORKDIR/libraries/usr/local
561 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000562 print ""
563 print "Building required libraries"
564 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000565 universal = os.path.join(WORKDIR, 'libraries')
566 os.mkdir(universal)
567 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
568 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
569
570 for recipe in LIBRARY_RECIPES:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000571 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000572
573
574
575def buildPythonDocs():
576 # This stores the documentation as Resources/English.lproj/Docuentation
577 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000578 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000579 rootDir = os.path.join(WORKDIR, '_root')
580 version = getVersion()
581 docdir = os.path.join(rootDir, 'pydocs')
582
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000583 novername = 'python-docs-html.tar.bz2'
Thomas Wouters477c8d52006-05-27 19:21:47 +0000584 name = 'html-%s.tar.bz2'%(getFullVersion(),)
585 sourceArchive = os.path.join(DEPSRC, name)
586 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000587 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000588
589 else:
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000590 print "Downloading %s"%(novername,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000591 downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000592 getFullVersion(), novername), sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000593 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000594
595 extractArchive(os.path.dirname(docdir), sourceArchive)
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000596
Thomas Wouters477c8d52006-05-27 19:21:47 +0000597 os.rename(
598 os.path.join(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000599 os.path.dirname(docdir), 'python-docs-html'),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000600 docdir)
601
602
603def buildPython():
Benjamin Peterson623918e2008-12-20 22:50:25 +0000604 print "Building a universal python"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000605
606 buildDir = os.path.join(WORKDIR, '_bld', 'python')
607 rootDir = os.path.join(WORKDIR, '_root')
608
609 if os.path.exists(buildDir):
610 shutil.rmtree(buildDir)
611 if os.path.exists(rootDir):
612 shutil.rmtree(rootDir)
613 os.mkdir(buildDir)
614 os.mkdir(rootDir)
615 os.mkdir(os.path.join(rootDir, 'empty-dir'))
616 curdir = os.getcwd()
617 os.chdir(buildDir)
618
619 # Not sure if this is still needed, the original build script
620 # claims that parts of the install assume python.exe exists.
621 os.symlink('python', os.path.join(buildDir, 'python.exe'))
622
623 # Extract the version from the configure file, needed to calculate
624 # several paths.
625 version = getVersion()
626
Benjamin Peterson623918e2008-12-20 22:50:25 +0000627 print "Running configure..."
Thomas Wouters477c8d52006-05-27 19:21:47 +0000628 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"%(
629 shellQuote(os.path.join(SRCDIR, 'configure')),
630 shellQuote(SDKPATH), shellQuote(WORKDIR)[1:-1],
631 shellQuote(WORKDIR)[1:-1]))
632
Benjamin Peterson623918e2008-12-20 22:50:25 +0000633 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 runCommand("make")
635
Benjamin Peterson623918e2008-12-20 22:50:25 +0000636 print "Running make frameworkinstall"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637 runCommand("make frameworkinstall DESTDIR=%s"%(
638 shellQuote(rootDir)))
639
Benjamin Peterson623918e2008-12-20 22:50:25 +0000640 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000641 runCommand("make frameworkinstallextras DESTDIR=%s"%(
642 shellQuote(rootDir)))
643
Benjamin Peterson623918e2008-12-20 22:50:25 +0000644 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000645 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
646 runCommand("mv %s/* %s"%(
647 shellQuote(os.path.join(
648 WORKDIR, 'libraries', 'Library', 'Frameworks',
649 'Python.framework', 'Versions', getVersion(),
650 'lib')),
651 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
652 'Python.framework', 'Versions', getVersion(),
653 'lib'))))
654
Benjamin Peterson623918e2008-12-20 22:50:25 +0000655 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000656 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000657 gid = grp.getgrnam('admin').gr_gid
658
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000659
660
Thomas Wouters477c8d52006-05-27 19:21:47 +0000661 for dirpath, dirnames, filenames in os.walk(frmDir):
662 for dn in dirnames:
663 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000664 os.chown(os.path.join(dirpath, dn), -1, gid)
665
Thomas Wouters477c8d52006-05-27 19:21:47 +0000666
667 for fn in filenames:
668 if os.path.islink(fn):
669 continue
670
671 # "chmod g+w $fn"
672 p = os.path.join(dirpath, fn)
673 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000674 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
675 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000676
677 # We added some directories to the search path during the configure
678 # phase. Remove those because those directories won't be there on
679 # the end-users system.
680 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
681 'Versions', version, 'lib', 'python%s'%(version,),
682 'config', 'Makefile')
683 fp = open(path, 'r')
684 data = fp.read()
685 fp.close()
686
687 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
688 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
689 fp = open(path, 'w')
690 fp.write(data)
691 fp.close()
692
693 # Add symlinks in /usr/local/bin, using relative links
694 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
695 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
696 'Python.framework', 'Versions', version, 'bin')
697 if os.path.exists(usr_local_bin):
698 shutil.rmtree(usr_local_bin)
699 os.makedirs(usr_local_bin)
700 for fn in os.listdir(
701 os.path.join(frmDir, 'Versions', version, 'bin')):
702 os.symlink(os.path.join(to_framework, fn),
703 os.path.join(usr_local_bin, fn))
704
705 os.chdir(curdir)
706
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000707 # Remove the 'Current' link, that way we don't accidently mess with an already installed
708 # version of python
709 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework', 'Versions', 'Current'))
710
711
Thomas Wouters477c8d52006-05-27 19:21:47 +0000712
713
714def patchFile(inPath, outPath):
715 data = fileContents(inPath)
716 data = data.replace('$FULL_VERSION', getFullVersion())
717 data = data.replace('$VERSION', getVersion())
718 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later')
719 data = data.replace('$ARCHITECTURES', "i386, ppc")
720 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000721
722 # This one is not handy as a template variable
723 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000724 fp = open(outPath, 'wb')
725 fp.write(data)
726 fp.close()
727
728def patchScript(inPath, outPath):
729 data = fileContents(inPath)
730 data = data.replace('@PYVER@', getVersion())
731 fp = open(outPath, 'wb')
732 fp.write(data)
733 fp.close()
734 os.chmod(outPath, 0755)
735
736
737
738def packageFromRecipe(targetDir, recipe):
739 curdir = os.getcwd()
740 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000741 # The major version (such as 2.5) is included in the package name
742 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000743 # common.
744 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000745 srcdir = recipe.get('source')
746 pkgroot = recipe.get('topdir', srcdir)
747 postflight = recipe.get('postflight')
748 readme = textwrap.dedent(recipe['readme'])
749 isRequired = recipe.get('required', True)
750
Benjamin Peterson623918e2008-12-20 22:50:25 +0000751 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000752
753 # Substitute some variables
754 textvars = dict(
755 VER=getVersion(),
756 FULLVER=getFullVersion(),
757 )
758 readme = readme % textvars
759
760 if pkgroot is not None:
761 pkgroot = pkgroot % textvars
762 else:
763 pkgroot = '/'
764
765 if srcdir is not None:
766 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
767 srcdir = srcdir % textvars
768
769 if postflight is not None:
770 postflight = os.path.abspath(postflight)
771
772 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
773 os.makedirs(packageContents)
774
775 if srcdir is not None:
776 os.chdir(srcdir)
777 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
778 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
779 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
780
781 fn = os.path.join(packageContents, 'PkgInfo')
782 fp = open(fn, 'w')
783 fp.write('pmkrpkg1')
784 fp.close()
785
786 rsrcDir = os.path.join(packageContents, "Resources")
787 os.mkdir(rsrcDir)
788 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
789 fp.write(readme)
790 fp.close()
791
792 if postflight is not None:
793 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
794
795 vers = getFullVersion()
796 major, minor = map(int, getVersion().split('.', 2))
797 pl = Plist(
798 CFBundleGetInfoString="MacPython.%s %s"%(pkgname, vers,),
799 CFBundleIdentifier='org.python.MacPython.%s'%(pkgname,),
800 CFBundleName='MacPython.%s'%(pkgname,),
801 CFBundleShortVersionString=vers,
802 IFMajorVersion=major,
803 IFMinorVersion=minor,
804 IFPkgFormatVersion=0.10000000149011612,
805 IFPkgFlagAllowBackRev=False,
806 IFPkgFlagAuthorizationAction="RootAuthorization",
807 IFPkgFlagDefaultLocation=pkgroot,
808 IFPkgFlagFollowLinks=True,
809 IFPkgFlagInstallFat=True,
810 IFPkgFlagIsRequired=isRequired,
811 IFPkgFlagOverwritePermissions=False,
812 IFPkgFlagRelocatable=False,
813 IFPkgFlagRestartAction="NoRestart",
814 IFPkgFlagRootVolumeOnly=True,
815 IFPkgFlagUpdateInstalledLangauges=False,
816 )
817 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
818
819 pl = Plist(
820 IFPkgDescriptionDescription=readme,
821 IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)),
822 IFPkgDescriptionVersion=vers,
823 )
824 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
825
826 finally:
827 os.chdir(curdir)
828
829
830def makeMpkgPlist(path):
831
832 vers = getFullVersion()
833 major, minor = map(int, getVersion().split('.', 2))
834
835 pl = Plist(
836 CFBundleGetInfoString="MacPython %s"%(vers,),
837 CFBundleIdentifier='org.python.MacPython',
838 CFBundleName='MacPython',
839 CFBundleShortVersionString=vers,
840 IFMajorVersion=major,
841 IFMinorVersion=minor,
842 IFPkgFlagComponentDirectory="Contents/Packages",
843 IFPkgFlagPackageList=[
844 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000845 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000846 IFPkgFlagPackageSelection=item['selected'],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000847 )
848 for item in PKG_RECIPES
849 ],
850 IFPkgFormatVersion=0.10000000149011612,
851 IFPkgFlagBackgroundScaling="proportional",
852 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000853 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000854 )
855
856 writePlist(pl, path)
857
858
859def buildInstaller():
860
861 # Zap all compiled files
862 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
863 for fn in filenames:
864 if fn.endswith('.pyc') or fn.endswith('.pyo'):
865 os.unlink(os.path.join(dirpath, fn))
866
867 outdir = os.path.join(WORKDIR, 'installer')
868 if os.path.exists(outdir):
869 shutil.rmtree(outdir)
870 os.mkdir(outdir)
871
872 pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents')
873 pkgcontents = os.path.join(pkgroot, 'Packages')
874 os.makedirs(pkgcontents)
875 for recipe in PKG_RECIPES:
876 packageFromRecipe(pkgcontents, recipe)
877
878 rsrcDir = os.path.join(pkgroot, 'Resources')
879
880 fn = os.path.join(pkgroot, 'PkgInfo')
881 fp = open(fn, 'w')
882 fp.write('pmkrpkg1')
883 fp.close()
884
885 os.mkdir(rsrcDir)
886
887 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
888 pl = Plist(
889 IFPkgDescriptionTitle="Universal MacPython",
890 IFPkgDescriptionVersion=getVersion(),
891 )
892
893 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
894 for fn in os.listdir('resources'):
895 if fn == '.svn': continue
896 if fn.endswith('.jpg'):
897 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
898 else:
899 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
900
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000901 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000902
903
904def installSize(clear=False, _saved=[]):
905 if clear:
906 del _saved[:]
907 if not _saved:
908 data = captureCommand("du -ks %s"%(
909 shellQuote(os.path.join(WORKDIR, '_root'))))
910 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
911 return _saved[0]
912
913
914def buildDMG():
915 """
Thomas Wouters89f507f2006-12-13 04:49:30 +0000916 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000917 """
918 outdir = os.path.join(WORKDIR, 'diskimage')
919 if os.path.exists(outdir):
920 shutil.rmtree(outdir)
921
922 imagepath = os.path.join(outdir,
923 'python-%s-macosx'%(getFullVersion(),))
924 if INCLUDE_TIMESTAMP:
925 imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3])
926 imagepath = imagepath + '.dmg'
927
928 os.mkdir(outdir)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000929 runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000930 getFullVersion(),
931 shellQuote(os.path.join(WORKDIR, 'installer')),
932 shellQuote(imagepath)))
933
934 return imagepath
935
936
937def setIcon(filePath, icnsPath):
938 """
939 Set the custom icon for the specified file or directory.
940
941 For a directory the icon data is written in a file named 'Icon\r' inside
942 the directory. For both files and directories write the icon as an 'icns'
943 resource. Furthermore set kHasCustomIcon in the finder flags for filePath.
944 """
945 ref, isDirectory = Carbon.File.FSPathMakeRef(icnsPath)
946 icon = Carbon.Icn.ReadIconFile(ref)
947 del ref
948
949 #
950 # Open the resource fork of the target, to add the icon later on.
951 # For directories we use the file 'Icon\r' inside the directory.
952 #
953
954 ref, isDirectory = Carbon.File.FSPathMakeRef(filePath)
955
956 if isDirectory:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000957 # There is a problem with getting this into the pax(1) archive,
958 # just ignore directory icons for now.
959 return
960
Thomas Wouters477c8d52006-05-27 19:21:47 +0000961 tmpPath = os.path.join(filePath, "Icon\r")
962 if not os.path.exists(tmpPath):
963 fp = open(tmpPath, 'w')
964 fp.close()
965
966 tmpRef, _ = Carbon.File.FSPathMakeRef(tmpPath)
967 spec = Carbon.File.FSSpec(tmpRef)
968
969 else:
970 spec = Carbon.File.FSSpec(ref)
971
972 try:
973 Carbon.Res.HCreateResFile(*spec.as_tuple())
974 except MacOS.Error:
975 pass
976
977 # Try to create the resource fork again, this will avoid problems
978 # when adding an icon to a directory. I have no idea why this helps,
979 # but without this adding the icon to a directory will fail sometimes.
980 try:
981 Carbon.Res.HCreateResFile(*spec.as_tuple())
982 except MacOS.Error:
983 pass
984
985 refNum = Carbon.Res.FSpOpenResFile(spec, fsRdWrPerm)
986
987 Carbon.Res.UseResFile(refNum)
988
989 # Check if there already is an icon, remove it if there is.
990 try:
991 h = Carbon.Res.Get1Resource('icns', kCustomIconResource)
992 except MacOS.Error:
993 pass
994
995 else:
996 h.RemoveResource()
997 del h
998
999 # Add the icon to the resource for of the target
1000 res = Carbon.Res.Resource(icon)
1001 res.AddResource('icns', kCustomIconResource, '')
1002 res.WriteResource()
1003 res.DetachResource()
1004 Carbon.Res.CloseResFile(refNum)
1005
1006 # And now set the kHasCustomIcon property for the target. Annoyingly,
1007 # python doesn't seem to have bindings for the API that is needed for
1008 # this. Cop out and call SetFile
1009 os.system("/Developer/Tools/SetFile -a C %s"%(
1010 shellQuote(filePath),))
1011
1012 if isDirectory:
1013 os.system('/Developer/Tools/SetFile -a V %s'%(
1014 shellQuote(tmpPath),
1015 ))
1016
1017def main():
1018 # First parse options and check if we can perform our work
1019 parseOptions()
1020 checkEnvironment()
1021
1022 os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
1023
1024 if os.path.exists(WORKDIR):
1025 shutil.rmtree(WORKDIR)
1026 os.mkdir(WORKDIR)
1027
1028 # Then build third-party libraries such as sleepycat DB4.
1029 buildLibraries()
1030
1031 # Now build python itself
1032 buildPython()
1033 buildPythonDocs()
1034 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001035 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001036 patchFile("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001037 os.chmod(fn, 0755)
1038
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001039 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001040 getVersion(),))
1041 os.chmod(folder, 0755)
1042 setIcon(folder, "../Icons/Python Folder.icns")
1043
1044 # Create the installer
1045 buildInstaller()
1046
1047 # And copy the readme into the directory containing the installer
1048 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1049
1050 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001051 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001052
1053 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001054 print >> fp, "# BUILD INFO"
1055 print >> fp, "# Date:", time.ctime()
1056 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001057 fp.close()
1058
1059 # Custom icon for the DMG, shown when the DMG is mounted.
1060 shutil.copy("../Icons/Disk Image.icns",
1061 os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))
1062 os.system("/Developer/Tools/SetFile -a C %s"%(
1063 os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")))
1064
1065
1066 # And copy it to a DMG
1067 buildDMG()
1068
1069
1070if __name__ == "__main__":
1071 main()