blob: d5da074cb4427ebcfd9e31d17c563b1d19271ec6 [file] [log] [blame]
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001#!/usr/bin/python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
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
Ronald Oussoren508282e2009-03-30 19:34:51 +00005work. 64-bit or four-way universal builds require at least OS X 10.5 and
6the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
8Please ensure that this script keeps working with Python 2.3, to avoid
9bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
10
11Usage: see USAGE variable in the script.
12"""
13import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Ronald Oussoren74d3eef2006-10-10 07:55:06 +000014import grp
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000015
Ronald Oussoren158ad592006-11-07 16:00:34 +000016INCLUDE_TIMESTAMP = 1
17VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000018
19from plistlib import Plist
20
21import MacOS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000022
23try:
24 from plistlib import writePlist
25except ImportError:
26 # We're run using python2.3
27 def writePlist(plist, path):
28 plist.write(path)
29
30def shellQuote(value):
31 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000032 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000033 a shell command.
34 """
35 return "'%s'"%(value.replace("'", "'\"'\"'"))
36
37def grepValue(fn, variable):
38 variable = variable + '='
39 for ln in open(fn, 'r'):
40 if ln.startswith(variable):
41 value = ln[len(variable):].strip()
42 return value[1:-1]
43
44def getVersion():
45 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
46
47def getFullVersion():
48 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
49 for ln in open(fn):
50 if 'PY_VERSION' in ln:
51 return ln.split()[-1][1:-1]
52
53 raise RuntimeError, "Cannot find full version??"
54
Ronald Oussorenaa560962006-11-07 15:53:38 +000055# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +000056WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000057
Ronald Oussorenaa560962006-11-07 15:53:38 +000058# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000059# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +000060DEPSRC = os.path.join(WORKDIR, 'third-party')
61DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000062
63# Location of the preferred SDK
Ronald Oussoren158ad592006-11-07 16:00:34 +000064SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
65#SDKPATH = "/"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000066
Ronald Oussoren508282e2009-03-30 19:34:51 +000067universal_opts_map = { '32-bit': ('i386', 'ppc',),
68 '64-bit': ('x86_64', 'ppc64',),
69 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
70
71UNIVERSALOPTS = tuple(universal_opts_map.keys())
72
73UNIVERSALARCHS = '32-bit'
74
75ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +000076
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000077# Source directory (asume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +000078SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000079 os.path.dirname(
80 os.path.dirname(
81 os.path.abspath(__file__
82 ))))
83
Ronald Oussoren508282e2009-03-30 19:34:51 +000084# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
85DEPTARGET = '10.3'
86
Ronald Oussoren158ad592006-11-07 16:00:34 +000087USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000088 Usage: build_python [options]
89
90 Options:
91 -? or -h: Show this message
92 -b DIR
93 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
94 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
95 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
96 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +000097 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
98 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000099""")% globals()
100
101
102# Instructions for building libraries that are necessary for building a
103# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000104# [The recipes are defined here for convenience but instantiated later after
105# command line options have been processed.]
106def library_recipes():
107 return [
108 dict(
109 name="Bzip2 1.0.4",
110 url="http://www.bzip.org/1.0.4/bzip2-1.0.4.tar.gz",
111 checksum='fc310b254f6ba5fbb5da018f04533688',
112 configure=None,
113 install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
114 shellQuote(os.path.join(WORKDIR, 'libraries')),
115 ' -arch '.join(ARCHLIST),
116 SDKPATH,
117 ),
118 ),
119 dict(
120 name="ZLib 1.2.3",
121 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
122 checksum='debc62758716a169df9f62e6ab2bc634',
123 configure=None,
124 install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
125 shellQuote(os.path.join(WORKDIR, 'libraries')),
126 ' -arch '.join(ARCHLIST),
127 SDKPATH,
128 ),
129 ),
130 dict(
131 # Note that GNU readline is GPL'd software
132 name="GNU Readline 5.1.4",
133 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
134 checksum='7ee5a692db88b30ca48927a13fd60e46',
135 patchlevel='0',
136 patches=[
137 # The readline maintainers don't do actual micro releases, but
138 # just ship a set of patches.
139 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
140 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
141 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
142 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
143 ]
144 ),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000145
Ronald Oussoren508282e2009-03-30 19:34:51 +0000146 dict(
147 name="SQLite 3.6.11",
148 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
149 checksum='7ebb099696ab76cc6ff65dd496d17858',
150 configure_pre=[
151 '--enable-threadsafe',
152 '--enable-tempstore',
153 '--enable-shared=no',
154 '--enable-static=yes',
155 '--disable-tcl',
156 ]
157 ),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000158
Ronald Oussoren508282e2009-03-30 19:34:51 +0000159 dict(
160 name="NCurses 5.5",
161 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
162 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
163 configure_pre=[
164 "--without-cxx",
165 "--without-ada",
166 "--without-progs",
167 "--without-curses-h",
168 "--enable-shared",
169 "--with-shared",
170 "--datadir=/usr/share",
171 "--sysconfdir=/etc",
172 "--sharedstatedir=/usr/com",
173 "--with-terminfo-dirs=/usr/share/terminfo",
174 "--with-default-terminfo-dir=/usr/share/terminfo",
175 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
176 "--enable-termcap",
177 ],
178 patches=[
179 "ncurses-5.5.patch",
180 ],
181 useLDFlags=False,
182 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
183 shellQuote(os.path.join(WORKDIR, 'libraries')),
184 shellQuote(os.path.join(WORKDIR, 'libraries')),
185 getVersion(),
186 ),
187 ),
188 dict(
189 name="Sleepycat DB 4.7.25",
190 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
191 checksum='ec2b87e833779681a0c3a814aa71359e',
192 buildDir="build_unix",
193 configure="../dist/configure",
194 configure_pre=[
195 '--includedir=/usr/local/include/db4',
196 ]
197 ),
198 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000199
200
201# Instructions for building packages inside the .mpkg.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000202PKG_RECIPES = [
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000203 dict(
204 name="PythonFramework",
205 long_name="Python Framework",
206 source="/Library/Frameworks/Python.framework",
207 readme="""\
208 This package installs Python.framework, that is the python
209 interpreter and the standard library. This also includes Python
210 wrappers for lots of Mac OS X API's.
211 """,
212 postflight="scripts/postflight.framework",
213 ),
214 dict(
215 name="PythonApplications",
216 long_name="GUI Applications",
Ronald Oussoren580c7fe2008-05-02 19:45:11 +0000217 source="/Applications/Python %(VER)s",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000218 readme="""\
Ronald Oussorenaa560962006-11-07 15:53:38 +0000219 This package installs IDLE (an interactive Python IDE),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000220 Python Launcher and Build Applet (create application bundles
221 from python scripts).
222
223 It also installs a number of examples and demos.
224 """,
225 required=False,
226 ),
227 dict(
228 name="PythonUnixTools",
229 long_name="UNIX command-line tools",
230 source="/usr/local/bin",
231 readme="""\
232 This package installs the unix tools in /usr/local/bin for
Ronald Oussoren508282e2009-03-30 19:34:51 +0000233 compatibility with older releases of Python. This package
234 is not necessary to use Python.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000235 """,
236 required=False,
237 ),
238 dict(
239 name="PythonDocumentation",
240 long_name="Python Documentation",
241 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
242 source="/pydocs",
243 readme="""\
244 This package installs the python documentation at a location
245 that is useable for pydoc and IDLE. If you have installed Xcode
246 it will also install a link to the documentation in
247 /Developer/Documentation/Python
248 """,
249 postflight="scripts/postflight.documentation",
250 required=False,
251 ),
252 dict(
253 name="PythonProfileChanges",
254 long_name="Shell profile updater",
255 readme="""\
256 This packages updates your shell profile to make sure that
Ronald Oussoren508282e2009-03-30 19:34:51 +0000257 the Python tools are found by your shell in preference of
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000258 the system provided Python tools.
259
260 If you don't install this package you'll have to add
261 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
262 to your PATH by hand.
263 """,
264 postflight="scripts/postflight.patch-profile",
265 topdir="/Library/Frameworks/Python.framework",
266 source="/empty-dir",
267 required=False,
268 ),
269 dict(
270 name="PythonSystemFixes",
271 long_name="Fix system Python",
272 readme="""\
Tim Petersae6a5a72006-06-07 20:40:06 +0000273 This package updates the system python installation on
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000274 Mac OS X 10.3 to ensure that you can build new python extensions
Ronald Oussorenaa560962006-11-07 15:53:38 +0000275 using that copy of python after installing this version.
Ronald Oussorenc5555542006-06-11 20:24:45 +0000276 """,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000277 postflight="../Tools/fixapplepython23.py",
278 topdir="/Library/Frameworks/Python.framework",
279 source="/empty-dir",
280 required=False,
281 )
282]
283
284def fatal(msg):
285 """
286 A fatal error, bail out.
287 """
288 sys.stderr.write('FATAL: ')
289 sys.stderr.write(msg)
290 sys.stderr.write('\n')
291 sys.exit(1)
292
293def fileContents(fn):
294 """
295 Return the contents of the named file
296 """
297 return open(fn, 'rb').read()
298
299def runCommand(commandline):
300 """
301 Run a command and raise RuntimeError if it fails. Output is surpressed
302 unless the command fails.
303 """
304 fd = os.popen(commandline, 'r')
305 data = fd.read()
306 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000307 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000308 sys.stdout.write(data)
309 raise RuntimeError, "command failed: %s"%(commandline,)
310
311 if VERBOSE:
312 sys.stdout.write(data); sys.stdout.flush()
313
314def captureCommand(commandline):
315 fd = os.popen(commandline, 'r')
316 data = fd.read()
317 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000318 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000319 sys.stdout.write(data)
320 raise RuntimeError, "command failed: %s"%(commandline,)
321
322 return data
323
324def checkEnvironment():
325 """
326 Check that we're running on a supported system.
327 """
328
329 if platform.system() != 'Darwin':
330 fatal("This script should be run on a Mac OS X 10.4 system")
331
332 if platform.release() <= '8.':
333 fatal("This script should be run on a Mac OS X 10.4 system")
334
335 if not os.path.exists(SDKPATH):
336 fatal("Please install the latest version of Xcode and the %s SDK"%(
337 os.path.basename(SDKPATH[:-4])))
338
339
340
Ronald Oussoren158ad592006-11-07 16:00:34 +0000341def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000342 """
343 Parse arguments and update global settings.
344 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000345 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
346 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000347
348 if args is None:
349 args = sys.argv[1:]
350
351 try:
352 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000353 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
354 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000355 except getopt.error, msg:
356 print msg
357 sys.exit(1)
358
359 if args:
360 print "Additional arguments"
361 sys.exit(1)
362
363 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000364 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000365 print USAGE
366 sys.exit(0)
367
368 elif k in ('-d', '--build-dir'):
369 WORKDIR=v
370
371 elif k in ('--third-party',):
372 DEPSRC=v
373
374 elif k in ('--sdk-path',):
375 SDKPATH=v
376
377 elif k in ('--src-dir',):
378 SRCDIR=v
379
Ronald Oussoren508282e2009-03-30 19:34:51 +0000380 elif k in ('--dep-target', ):
381 DEPTARGET=v
382
383 elif k in ('--universal-archs', ):
384 if v in UNIVERSALOPTS:
385 UNIVERSALARCHS = v
386 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
387 else:
388 raise NotImplementedError, v
389
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000390 else:
391 raise NotImplementedError, k
392
393 SRCDIR=os.path.abspath(SRCDIR)
394 WORKDIR=os.path.abspath(WORKDIR)
395 SDKPATH=os.path.abspath(SDKPATH)
396 DEPSRC=os.path.abspath(DEPSRC)
397
398 print "Settings:"
399 print " * Source directory:", SRCDIR
400 print " * Build directory: ", WORKDIR
401 print " * SDK location: ", SDKPATH
Ronald Oussoren508282e2009-03-30 19:34:51 +0000402 print " * Third-party source:", DEPSRC
403 print " * Deployment target:", DEPTARGET
404 print " * Universal architectures:", ARCHLIST
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000405 print ""
406
407
408
409
410def extractArchive(builddir, archiveName):
411 """
412 Extract a source archive into 'builddir'. Returns the path of the
413 extracted archive.
414
415 XXX: This function assumes that archives contain a toplevel directory
416 that is has the same name as the basename of the archive. This is
417 save enough for anything we use.
418 """
419 curdir = os.getcwd()
420 try:
421 os.chdir(builddir)
422 if archiveName.endswith('.tar.gz'):
423 retval = os.path.basename(archiveName[:-7])
424 if os.path.exists(retval):
425 shutil.rmtree(retval)
426 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
427
428 elif archiveName.endswith('.tar.bz2'):
429 retval = os.path.basename(archiveName[:-8])
430 if os.path.exists(retval):
431 shutil.rmtree(retval)
432 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
433
434 elif archiveName.endswith('.tar'):
435 retval = os.path.basename(archiveName[:-4])
436 if os.path.exists(retval):
437 shutil.rmtree(retval)
438 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
439
440 elif archiveName.endswith('.zip'):
441 retval = os.path.basename(archiveName[:-4])
442 if os.path.exists(retval):
443 shutil.rmtree(retval)
444 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
445
446 data = fp.read()
447 xit = fp.close()
448 if xit is not None:
449 sys.stdout.write(data)
450 raise RuntimeError, "Cannot extract %s"%(archiveName,)
451
452 return os.path.join(builddir, retval)
453
454 finally:
455 os.chdir(curdir)
456
457KNOWNSIZES = {
458 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
459 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
460}
461
462def downloadURL(url, fname):
463 """
464 Download the contents of the url into the file.
465 """
466 try:
467 size = os.path.getsize(fname)
468 except OSError:
469 pass
470 else:
471 if KNOWNSIZES.get(url) == size:
472 print "Using existing file for", url
473 return
474 fpIn = urllib2.urlopen(url)
475 fpOut = open(fname, 'wb')
476 block = fpIn.read(10240)
477 try:
478 while block:
479 fpOut.write(block)
480 block = fpIn.read(10240)
481 fpIn.close()
482 fpOut.close()
483 except:
484 try:
485 os.unlink(fname)
486 except:
487 pass
488
489def buildRecipe(recipe, basedir, archList):
490 """
491 Build software using a recipe. This function does the
492 'configure;make;make install' dance for C software, with a possibility
493 to customize this process, basically a poor-mans DarwinPorts.
494 """
495 curdir = os.getcwd()
496
497 name = recipe['name']
498 url = recipe['url']
499 configure = recipe.get('configure', './configure')
500 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
501 shellQuote(basedir)))
502
503 archiveName = os.path.split(url)[-1]
504 sourceArchive = os.path.join(DEPSRC, archiveName)
505
506 if not os.path.exists(DEPSRC):
507 os.mkdir(DEPSRC)
508
509
510 if os.path.exists(sourceArchive):
511 print "Using local copy of %s"%(name,)
512
513 else:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000514 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000515 print "Downloading %s"%(name,)
516 downloadURL(url, sourceArchive)
517 print "Archive for %s stored as %s"%(name, sourceArchive)
518
519 print "Extracting archive for %s"%(name,)
520 buildDir=os.path.join(WORKDIR, '_bld')
521 if not os.path.exists(buildDir):
522 os.mkdir(buildDir)
523
524 workDir = extractArchive(buildDir, sourceArchive)
525 os.chdir(workDir)
526 if 'buildDir' in recipe:
527 os.chdir(recipe['buildDir'])
528
529
530 for fn in recipe.get('patches', ()):
531 if fn.startswith('http://'):
532 # Download the patch before applying it.
533 path = os.path.join(DEPSRC, os.path.basename(fn))
534 downloadURL(fn, path)
535 fn = path
536
537 fn = os.path.join(curdir, fn)
538 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
539 shellQuote(fn),))
540
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000541 if configure is not None:
542 configure_args = [
543 "--prefix=/usr/local",
544 "--enable-static",
545 "--disable-shared",
546 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
547 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000548
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000549 if 'configure_pre' in recipe:
550 args = list(recipe['configure_pre'])
551 if '--disable-static' in args:
552 configure_args.remove('--enable-static')
553 if '--enable-shared' in args:
554 configure_args.remove('--disable-shared')
555 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000556
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000557 if recipe.get('useLDFlags', 1):
558 configure_args.extend([
559 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
560 ' -arch '.join(archList),
561 shellQuote(SDKPATH)[1:-1],
562 shellQuote(basedir)[1:-1],),
563 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000564 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000565 shellQuote(basedir)[1:-1],
566 ' -arch '.join(archList)),
567 ])
568 else:
569 configure_args.extend([
570 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
571 ' -arch '.join(archList),
572 shellQuote(SDKPATH)[1:-1],
573 shellQuote(basedir)[1:-1],),
574 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000575
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000576 if 'configure_post' in recipe:
577 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000578
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000579 configure_args.insert(0, configure)
580 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000581
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000582 print "Running configure for %s"%(name,)
583 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000584
585 print "Running install for %s"%(name,)
586 runCommand('{ ' + install + ' ;} 2>&1')
587
588 print "Done %s"%(name,)
589 print ""
590
591 os.chdir(curdir)
592
593def buildLibraries():
594 """
595 Build our dependencies into $WORKDIR/libraries/usr/local
596 """
597 print ""
598 print "Building required libraries"
599 print ""
600 universal = os.path.join(WORKDIR, 'libraries')
601 os.mkdir(universal)
602 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
603 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
604
Ronald Oussoren508282e2009-03-30 19:34:51 +0000605 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000606 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000607
608
609
610def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000611 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000612 # inside the framwork. pydoc and IDLE will pick it up there.
613 print "Install python documentation"
614 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000615 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000616 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000617 curDir = os.getcwd()
618 os.chdir(buildDir)
619 runCommand('make update')
620 runCommand('make html')
621 os.chdir(curDir)
622 if not os.path.exists(docdir):
623 os.mkdir(docdir)
624 os.rename(os.path.join(buildDir, 'build', 'html'),
625 os.path.join(docdir, 'python-docs-html'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000626
627
628def buildPython():
Ronald Oussoren508282e2009-03-30 19:34:51 +0000629 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000630
631 buildDir = os.path.join(WORKDIR, '_bld', 'python')
632 rootDir = os.path.join(WORKDIR, '_root')
633
634 if os.path.exists(buildDir):
635 shutil.rmtree(buildDir)
636 if os.path.exists(rootDir):
637 shutil.rmtree(rootDir)
638 os.mkdir(buildDir)
639 os.mkdir(rootDir)
640 os.mkdir(os.path.join(rootDir, 'empty-dir'))
641 curdir = os.getcwd()
642 os.chdir(buildDir)
643
644 # Not sure if this is still needed, the original build script
645 # claims that parts of the install assume python.exe exists.
646 os.symlink('python', os.path.join(buildDir, 'python.exe'))
647
648 # Extract the version from the configure file, needed to calculate
649 # several paths.
650 version = getVersion()
651
Ronald Oussoren008af852009-03-30 20:02:08 +0000652 # Since the extra libs are not in their installed framework location
653 # during the build, augment the library path so that the interpreter
654 # will find them during its extension import sanity checks.
655 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
656 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000657 print "Running configure..."
Ronald Oussoren508282e2009-03-30 19:34:51 +0000658 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
659 "--with-universal-archs=%s "
660 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
661 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
662 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
663 UNIVERSALARCHS,
664 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000665 shellQuote(WORKDIR)[1:-1]))
666
667 print "Running make"
668 runCommand("make")
669
Ronald Oussorenaa560962006-11-07 15:53:38 +0000670 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000671 runCommand("make frameworkinstall DESTDIR=%s"%(
672 shellQuote(rootDir)))
673
Ronald Oussorenaa560962006-11-07 15:53:38 +0000674 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000675 runCommand("make frameworkinstallextras DESTDIR=%s"%(
676 shellQuote(rootDir)))
677
Ronald Oussoren008af852009-03-30 20:02:08 +0000678 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000679 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000680 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
681 runCommand("mv %s/* %s"%(
682 shellQuote(os.path.join(
683 WORKDIR, 'libraries', 'Library', 'Frameworks',
684 'Python.framework', 'Versions', getVersion(),
685 'lib')),
686 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
687 'Python.framework', 'Versions', getVersion(),
688 'lib'))))
689
690 print "Fix file modes"
691 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000692 gid = grp.getgrnam('admin').gr_gid
693
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000694 for dirpath, dirnames, filenames in os.walk(frmDir):
695 for dn in dirnames:
696 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000697 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000698
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000699
700 for fn in filenames:
701 if os.path.islink(fn):
702 continue
703
704 # "chmod g+w $fn"
705 p = os.path.join(dirpath, fn)
706 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000707 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
708 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000709
710 # We added some directories to the search path during the configure
711 # phase. Remove those because those directories won't be there on
712 # the end-users system.
713 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
714 'Versions', version, 'lib', 'python%s'%(version,),
715 'config', 'Makefile')
716 fp = open(path, 'r')
717 data = fp.read()
718 fp.close()
719
720 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
721 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
722 fp = open(path, 'w')
723 fp.write(data)
724 fp.close()
725
726 # Add symlinks in /usr/local/bin, using relative links
727 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
728 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
729 'Python.framework', 'Versions', version, 'bin')
730 if os.path.exists(usr_local_bin):
731 shutil.rmtree(usr_local_bin)
732 os.makedirs(usr_local_bin)
733 for fn in os.listdir(
734 os.path.join(frmDir, 'Versions', version, 'bin')):
735 os.symlink(os.path.join(to_framework, fn),
736 os.path.join(usr_local_bin, fn))
737
738 os.chdir(curdir)
739
740
741
742def patchFile(inPath, outPath):
743 data = fileContents(inPath)
744 data = data.replace('$FULL_VERSION', getFullVersion())
745 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +0000746 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000747 data = data.replace('$ARCHITECTURES', "i386, ppc")
748 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000749
750 # This one is not handy as a template variable
751 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000752 fp = open(outPath, 'wb')
753 fp.write(data)
754 fp.close()
755
756def patchScript(inPath, outPath):
757 data = fileContents(inPath)
758 data = data.replace('@PYVER@', getVersion())
759 fp = open(outPath, 'wb')
760 fp.write(data)
761 fp.close()
762 os.chmod(outPath, 0755)
763
764
765
766def packageFromRecipe(targetDir, recipe):
767 curdir = os.getcwd()
768 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000769 # The major version (such as 2.5) is included in the package name
770 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000771 # common.
772 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000773 srcdir = recipe.get('source')
774 pkgroot = recipe.get('topdir', srcdir)
775 postflight = recipe.get('postflight')
776 readme = textwrap.dedent(recipe['readme'])
777 isRequired = recipe.get('required', True)
778
779 print "- building package %s"%(pkgname,)
780
781 # Substitute some variables
782 textvars = dict(
783 VER=getVersion(),
784 FULLVER=getFullVersion(),
785 )
786 readme = readme % textvars
787
788 if pkgroot is not None:
789 pkgroot = pkgroot % textvars
790 else:
791 pkgroot = '/'
792
793 if srcdir is not None:
794 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
795 srcdir = srcdir % textvars
796
797 if postflight is not None:
798 postflight = os.path.abspath(postflight)
799
800 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
801 os.makedirs(packageContents)
802
803 if srcdir is not None:
804 os.chdir(srcdir)
805 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
806 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
807 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
808
809 fn = os.path.join(packageContents, 'PkgInfo')
810 fp = open(fn, 'w')
811 fp.write('pmkrpkg1')
812 fp.close()
813
814 rsrcDir = os.path.join(packageContents, "Resources")
815 os.mkdir(rsrcDir)
816 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
817 fp.write(readme)
818 fp.close()
819
820 if postflight is not None:
821 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
822
823 vers = getFullVersion()
824 major, minor = map(int, getVersion().split('.', 2))
825 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000826 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
827 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
828 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000829 CFBundleShortVersionString=vers,
830 IFMajorVersion=major,
831 IFMinorVersion=minor,
832 IFPkgFormatVersion=0.10000000149011612,
833 IFPkgFlagAllowBackRev=False,
834 IFPkgFlagAuthorizationAction="RootAuthorization",
835 IFPkgFlagDefaultLocation=pkgroot,
836 IFPkgFlagFollowLinks=True,
837 IFPkgFlagInstallFat=True,
838 IFPkgFlagIsRequired=isRequired,
839 IFPkgFlagOverwritePermissions=False,
840 IFPkgFlagRelocatable=False,
841 IFPkgFlagRestartAction="NoRestart",
842 IFPkgFlagRootVolumeOnly=True,
843 IFPkgFlagUpdateInstalledLangauges=False,
844 )
845 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
846
847 pl = Plist(
848 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000849 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000850 IFPkgDescriptionVersion=vers,
851 )
852 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
853
854 finally:
855 os.chdir(curdir)
856
857
858def makeMpkgPlist(path):
859
860 vers = getFullVersion()
861 major, minor = map(int, getVersion().split('.', 2))
862
863 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000864 CFBundleGetInfoString="Python %s"%(vers,),
865 CFBundleIdentifier='org.python.Python',
866 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000867 CFBundleShortVersionString=vers,
868 IFMajorVersion=major,
869 IFMinorVersion=minor,
870 IFPkgFlagComponentDirectory="Contents/Packages",
871 IFPkgFlagPackageList=[
872 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000873 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000874 IFPkgFlagPackageSelection='selected'
875 )
876 for item in PKG_RECIPES
877 ],
878 IFPkgFormatVersion=0.10000000149011612,
879 IFPkgFlagBackgroundScaling="proportional",
880 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000881 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000882 )
883
884 writePlist(pl, path)
885
886
887def buildInstaller():
888
889 # Zap all compiled files
890 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
891 for fn in filenames:
892 if fn.endswith('.pyc') or fn.endswith('.pyo'):
893 os.unlink(os.path.join(dirpath, fn))
894
895 outdir = os.path.join(WORKDIR, 'installer')
896 if os.path.exists(outdir):
897 shutil.rmtree(outdir)
898 os.mkdir(outdir)
899
Ronald Oussoren508282e2009-03-30 19:34:51 +0000900 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000901 pkgcontents = os.path.join(pkgroot, 'Packages')
902 os.makedirs(pkgcontents)
903 for recipe in PKG_RECIPES:
904 packageFromRecipe(pkgcontents, recipe)
905
906 rsrcDir = os.path.join(pkgroot, 'Resources')
907
908 fn = os.path.join(pkgroot, 'PkgInfo')
909 fp = open(fn, 'w')
910 fp.write('pmkrpkg1')
911 fp.close()
912
913 os.mkdir(rsrcDir)
914
915 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
916 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000917 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000918 IFPkgDescriptionVersion=getVersion(),
919 )
920
921 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
922 for fn in os.listdir('resources'):
923 if fn == '.svn': continue
924 if fn.endswith('.jpg'):
925 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
926 else:
927 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
928
Ronald Oussorenc5555542006-06-11 20:24:45 +0000929 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000930
931
932def installSize(clear=False, _saved=[]):
933 if clear:
934 del _saved[:]
935 if not _saved:
936 data = captureCommand("du -ks %s"%(
937 shellQuote(os.path.join(WORKDIR, '_root'))))
938 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
939 return _saved[0]
940
941
942def buildDMG():
943 """
Ronald Oussorenaa560962006-11-07 15:53:38 +0000944 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000945 """
946 outdir = os.path.join(WORKDIR, 'diskimage')
947 if os.path.exists(outdir):
948 shutil.rmtree(outdir)
949
950 imagepath = os.path.join(outdir,
951 'python-%s-macosx'%(getFullVersion(),))
952 if INCLUDE_TIMESTAMP:
953 imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3])
954 imagepath = imagepath + '.dmg'
955
956 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000957 volname='Python %s'%(getFullVersion())
958 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
959 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000960 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +0000961 shellQuote(imagepath + ".tmp.dmg" )))
962
963
964 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
965 os.mkdir(os.path.join(WORKDIR, "mnt"))
966 runCommand("hdiutil attach %s -mountroot %s"%(
967 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
968
969 # Custom icon for the DMG, shown when the DMG is mounted.
970 shutil.copy("../Icons/Disk Image.icns",
971 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
972 runCommand("/Developer/Tools/SetFile -a C %s/"%(
973 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
974
975 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
976
977 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
978 runCommand("hdiutil convert %s -format UDZO -o %s"%(
979 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
980 setIcon(imagepath, "../Icons/Disk Image.icns")
981
982 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000983
984 return imagepath
985
986
987def setIcon(filePath, icnsPath):
988 """
989 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000990 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000991
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000992 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
993 dirPath = os.path.dirname(__file__)
994 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
995 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
996 # to connections to the window server.
997 if not os.path.exists('seticon.app/Contents/MacOS'):
998 os.makedirs('seticon.app/Contents/MacOS')
999 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1000 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001001
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001002 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1003 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001004
1005def main():
1006 # First parse options and check if we can perform our work
1007 parseOptions()
1008 checkEnvironment()
1009
Ronald Oussoren508282e2009-03-30 19:34:51 +00001010 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001011
1012 if os.path.exists(WORKDIR):
1013 shutil.rmtree(WORKDIR)
1014 os.mkdir(WORKDIR)
1015
1016 # Then build third-party libraries such as sleepycat DB4.
1017 buildLibraries()
1018
1019 # Now build python itself
1020 buildPython()
1021 buildPythonDocs()
1022 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001023 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001024 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001025
Benjamin Petersonc3104762008-10-03 11:52:06 +00001026 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001027 getVersion(),))
1028 os.chmod(folder, 0755)
1029 setIcon(folder, "../Icons/Python Folder.icns")
1030
1031 # Create the installer
1032 buildInstaller()
1033
1034 # And copy the readme into the directory containing the installer
1035 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1036
1037 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001038 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001039
1040 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1041 print >> fp, "# BUILD INFO"
1042 print >> fp, "# Date:", time.ctime()
1043 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1044 fp.close()
1045
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001046
1047
1048 # And copy it to a DMG
1049 buildDMG()
1050
1051
1052if __name__ == "__main__":
1053 main()