blob: 4823246de949045675a65d58f8209dde04f1a6c6 [file] [log] [blame]
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001#!/usr/bin/python
Thomas Wouters477c8d52006-05-27 19:21:47 +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
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
Thomas Wouters477c8d52006-05-27 19:21:47 +000021
22try:
23 from plistlib import writePlist
24except ImportError:
25 # We're run using python2.3
26 def writePlist(plist, path):
27 plist.write(path)
28
29def shellQuote(value):
30 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000031 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000032 a shell command.
33 """
34 return "'%s'"%(value.replace("'", "'\"'\"'"))
35
36def grepValue(fn, variable):
37 variable = variable + '='
38 for ln in open(fn, 'r'):
39 if ln.startswith(variable):
40 value = ln[len(variable):].strip()
41 return value[1:-1]
42
43def getVersion():
44 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
45
46def getFullVersion():
47 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
48 for ln in open(fn):
49 if 'PY_VERSION' in ln:
50 return ln.split()[-1][1:-1]
51
Benjamin Peterson623918e2008-12-20 22:50:25 +000052 raise RuntimeError, "Cannot find full version??"
Thomas Wouters477c8d52006-05-27 19:21:47 +000053
Thomas Wouters89f507f2006-12-13 04:49:30 +000054# The directory we'll use to create the build (will be erased and recreated)
55WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000056
Thomas Wouters89f507f2006-12-13 04:49:30 +000057# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000058# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000059DEPSRC = os.path.join(WORKDIR, 'third-party')
60DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000061
62# Location of the preferred SDK
Thomas Wouters89f507f2006-12-13 04:49:30 +000063SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
64#SDKPATH = "/"
Thomas Wouters477c8d52006-05-27 19:21:47 +000065
Thomas Wouters89f507f2006-12-13 04:49:30 +000066ARCHLIST = ('i386', 'ppc',)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000067
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000068# Source directory (asume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +000069SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +000070 os.path.dirname(
71 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000072 os.path.abspath(__file__
73 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +000074
Thomas Wouters89f507f2006-12-13 04:49:30 +000075USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +000076 Usage: build_python [options]
77
78 Options:
79 -? or -h: Show this message
80 -b DIR
81 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
82 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
83 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
84 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
85""")% globals()
86
87
88# Instructions for building libraries that are necessary for building a
89# batteries included python.
Thomas Wouters89f507f2006-12-13 04:49:30 +000090LIBRARY_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +000091 dict(
Ronald Oussoren207b4c22009-03-30 17:20:30 +000092 name="Bzip2 1.0.4",
93 url="http://www.bzip.org/1.0.4/bzip2-1.0.4.tar.gz",
94 checksum='fc310b254f6ba5fbb5da018f04533688',
Thomas Wouters0e3f5912006-08-11 14:57:12 +000095 configure=None,
96 install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
97 shellQuote(os.path.join(WORKDIR, 'libraries')),
98 ' -arch '.join(ARCHLIST),
99 SDKPATH,
100 ),
101 ),
102 dict(
103 name="ZLib 1.2.3",
104 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000105 checksum='debc62758716a169df9f62e6ab2bc634',
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000106 configure=None,
107 install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
108 shellQuote(os.path.join(WORKDIR, 'libraries')),
109 ' -arch '.join(ARCHLIST),
110 SDKPATH,
111 ),
112 ),
113 dict(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000114 # Note that GNU readline is GPL'd software
115 name="GNU Readline 5.1.4",
116 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000117 checksum='7ee5a692db88b30ca48927a13fd60e46',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000118 patchlevel='0',
119 patches=[
120 # The readline maintainers don't do actual micro releases, but
121 # just ship a set of patches.
122 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
123 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
124 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
125 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
126 ]
127 ),
128
129 dict(
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000130 name="SQLite 3.6.11",
131 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
132 checksum='7ebb099696ab76cc6ff65dd496d17858',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000133 configure_pre=[
134 '--enable-threadsafe',
135 '--enable-tempstore',
136 '--enable-shared=no',
137 '--enable-static=yes',
138 '--disable-tcl',
139 ]
140 ),
141
142 dict(
143 name="NCurses 5.5",
144 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000145 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000146 configure_pre=[
147 "--without-cxx",
148 "--without-ada",
149 "--without-progs",
150 "--without-curses-h",
151 "--enable-shared",
152 "--with-shared",
153 "--datadir=/usr/share",
154 "--sysconfdir=/etc",
155 "--sharedstatedir=/usr/com",
156 "--with-terminfo-dirs=/usr/share/terminfo",
157 "--with-default-terminfo-dir=/usr/share/terminfo",
158 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
159 "--enable-termcap",
160 ],
161 patches=[
162 "ncurses-5.5.patch",
163 ],
164 useLDFlags=False,
165 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
166 shellQuote(os.path.join(WORKDIR, 'libraries')),
167 shellQuote(os.path.join(WORKDIR, 'libraries')),
168 getVersion(),
169 ),
170 ),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000171]
172
173
174# Instructions for building packages inside the .mpkg.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000175PKG_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +0000176 dict(
177 name="PythonFramework",
178 long_name="Python Framework",
179 source="/Library/Frameworks/Python.framework",
180 readme="""\
181 This package installs Python.framework, that is the python
182 interpreter and the standard library. This also includes Python
183 wrappers for lots of Mac OS X API's.
184 """,
185 postflight="scripts/postflight.framework",
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000186 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000187 ),
188 dict(
189 name="PythonApplications",
190 long_name="GUI Applications",
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000191 source="/Applications/Python %(VER)s",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000192 readme="""\
Thomas Wouters89f507f2006-12-13 04:49:30 +0000193 This package installs IDLE (an interactive Python IDE),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000194 Python Launcher and Build Applet (create application bundles
195 from python scripts).
196
197 It also installs a number of examples and demos.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000198 """,
199 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000200 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000201 ),
202 dict(
203 name="PythonUnixTools",
204 long_name="UNIX command-line tools",
205 source="/usr/local/bin",
206 readme="""\
207 This package installs the unix tools in /usr/local/bin for
208 compatibility with older releases of MacPython. This package
209 is not necessary to use MacPython.
210 """,
211 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000212 selected='unselected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000213 ),
214 dict(
215 name="PythonDocumentation",
216 long_name="Python Documentation",
217 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
218 source="/pydocs",
219 readme="""\
220 This package installs the python documentation at a location
221 that is useable for pydoc and IDLE. If you have installed Xcode
222 it will also install a link to the documentation in
223 /Developer/Documentation/Python
224 """,
225 postflight="scripts/postflight.documentation",
226 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000227 selected='selected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000228 ),
229 dict(
230 name="PythonProfileChanges",
231 long_name="Shell profile updater",
232 readme="""\
233 This packages updates your shell profile to make sure that
234 the MacPython tools are found by your shell in preference of
235 the system provided Python tools.
236
237 If you don't install this package you'll have to add
238 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
239 to your PATH by hand.
240 """,
241 postflight="scripts/postflight.patch-profile",
242 topdir="/Library/Frameworks/Python.framework",
243 source="/empty-dir",
244 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000245 selected='unselected',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000246 ),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000247 dict(
248 name="PythonSystemFixes",
249 long_name="Fix system Python",
250 readme="""\
251 This package updates the system python installation on
252 Mac OS X 10.3 to ensure that you can build new python extensions
Thomas Wouters89f507f2006-12-13 04:49:30 +0000253 using that copy of python after installing this version.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000254 """,
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000255 postflight="../Tools/fixapplepython23.py",
256 topdir="/Library/Frameworks/Python.framework",
257 source="/empty-dir",
258 required=False,
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000259 selected='unselected',
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000260 )
Thomas Wouters477c8d52006-05-27 19:21:47 +0000261]
262
Thomas Wouters477c8d52006-05-27 19:21:47 +0000263def fatal(msg):
264 """
265 A fatal error, bail out.
266 """
267 sys.stderr.write('FATAL: ')
268 sys.stderr.write(msg)
269 sys.stderr.write('\n')
270 sys.exit(1)
271
272def fileContents(fn):
273 """
274 Return the contents of the named file
275 """
276 return open(fn, 'rb').read()
277
278def runCommand(commandline):
279 """
280 Run a command and raise RuntimeError if it fails. Output is surpressed
281 unless the command fails.
282 """
283 fd = os.popen(commandline, 'r')
284 data = fd.read()
285 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000286 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000287 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000288 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000289
290 if VERBOSE:
291 sys.stdout.write(data); sys.stdout.flush()
292
293def captureCommand(commandline):
294 fd = os.popen(commandline, 'r')
295 data = fd.read()
296 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000297 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000298 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000299 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000300
301 return data
302
303def checkEnvironment():
304 """
305 Check that we're running on a supported system.
306 """
307
308 if platform.system() != 'Darwin':
309 fatal("This script should be run on a Mac OS X 10.4 system")
310
311 if platform.release() <= '8.':
312 fatal("This script should be run on a Mac OS X 10.4 system")
313
314 if not os.path.exists(SDKPATH):
315 fatal("Please install the latest version of Xcode and the %s SDK"%(
316 os.path.basename(SDKPATH[:-4])))
317
318
319
Thomas Wouters89f507f2006-12-13 04:49:30 +0000320def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000321 """
322 Parse arguments and update global settings.
323 """
324 global WORKDIR, DEPSRC, SDKPATH, SRCDIR
325
326 if args is None:
327 args = sys.argv[1:]
328
329 try:
330 options, args = getopt.getopt(args, '?hb',
331 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir='])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000332 except getopt.error, msg:
333 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000334 sys.exit(1)
335
336 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000337 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000338 sys.exit(1)
339
340 for k, v in options:
341 if k in ('-h', '-?'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000342 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000343 sys.exit(0)
344
345 elif k in ('-d', '--build-dir'):
346 WORKDIR=v
347
348 elif k in ('--third-party',):
349 DEPSRC=v
350
351 elif k in ('--sdk-path',):
352 SDKPATH=v
353
354 elif k in ('--src-dir',):
355 SRCDIR=v
356
357 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000358 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000359
360 SRCDIR=os.path.abspath(SRCDIR)
361 WORKDIR=os.path.abspath(WORKDIR)
362 SDKPATH=os.path.abspath(SDKPATH)
363 DEPSRC=os.path.abspath(DEPSRC)
364
Benjamin Peterson623918e2008-12-20 22:50:25 +0000365 print "Settings:"
366 print " * Source directory:", SRCDIR
367 print " * Build directory: ", WORKDIR
368 print " * SDK location: ", SDKPATH
369 print " * third-party source:", DEPSRC
370 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000371
372
373
374
375def extractArchive(builddir, archiveName):
376 """
377 Extract a source archive into 'builddir'. Returns the path of the
378 extracted archive.
379
380 XXX: This function assumes that archives contain a toplevel directory
381 that is has the same name as the basename of the archive. This is
382 save enough for anything we use.
383 """
384 curdir = os.getcwd()
385 try:
386 os.chdir(builddir)
387 if archiveName.endswith('.tar.gz'):
388 retval = os.path.basename(archiveName[:-7])
389 if os.path.exists(retval):
390 shutil.rmtree(retval)
391 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
392
393 elif archiveName.endswith('.tar.bz2'):
394 retval = os.path.basename(archiveName[:-8])
395 if os.path.exists(retval):
396 shutil.rmtree(retval)
397 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
398
399 elif archiveName.endswith('.tar'):
400 retval = os.path.basename(archiveName[:-4])
401 if os.path.exists(retval):
402 shutil.rmtree(retval)
403 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
404
405 elif archiveName.endswith('.zip'):
406 retval = os.path.basename(archiveName[:-4])
407 if os.path.exists(retval):
408 shutil.rmtree(retval)
409 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
410
411 data = fp.read()
412 xit = fp.close()
413 if xit is not None:
414 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000415 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000416
417 return os.path.join(builddir, retval)
418
419 finally:
420 os.chdir(curdir)
421
422KNOWNSIZES = {
423 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
424 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
425}
426
427def downloadURL(url, fname):
428 """
429 Download the contents of the url into the file.
430 """
431 try:
432 size = os.path.getsize(fname)
433 except OSError:
434 pass
435 else:
436 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000437 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000438 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000439 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000440 fpOut = open(fname, 'wb')
441 block = fpIn.read(10240)
442 try:
443 while block:
444 fpOut.write(block)
445 block = fpIn.read(10240)
446 fpIn.close()
447 fpOut.close()
448 except:
449 try:
450 os.unlink(fname)
451 except:
452 pass
453
454def buildRecipe(recipe, basedir, archList):
455 """
456 Build software using a recipe. This function does the
457 'configure;make;make install' dance for C software, with a possibility
458 to customize this process, basically a poor-mans DarwinPorts.
459 """
460 curdir = os.getcwd()
461
462 name = recipe['name']
463 url = recipe['url']
464 configure = recipe.get('configure', './configure')
465 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
466 shellQuote(basedir)))
467
468 archiveName = os.path.split(url)[-1]
469 sourceArchive = os.path.join(DEPSRC, archiveName)
470
471 if not os.path.exists(DEPSRC):
472 os.mkdir(DEPSRC)
473
474
475 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000476 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477
478 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000479 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000480 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000481 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482
Benjamin Peterson623918e2008-12-20 22:50:25 +0000483 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484 buildDir=os.path.join(WORKDIR, '_bld')
485 if not os.path.exists(buildDir):
486 os.mkdir(buildDir)
487
488 workDir = extractArchive(buildDir, sourceArchive)
489 os.chdir(workDir)
490 if 'buildDir' in recipe:
491 os.chdir(recipe['buildDir'])
492
493
494 for fn in recipe.get('patches', ()):
495 if fn.startswith('http://'):
496 # Download the patch before applying it.
497 path = os.path.join(DEPSRC, os.path.basename(fn))
498 downloadURL(fn, path)
499 fn = path
500
501 fn = os.path.join(curdir, fn)
502 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
503 shellQuote(fn),))
504
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000505 if configure is not None:
506 configure_args = [
507 "--prefix=/usr/local",
508 "--enable-static",
509 "--disable-shared",
510 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
511 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000512
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000513 if 'configure_pre' in recipe:
514 args = list(recipe['configure_pre'])
515 if '--disable-static' in args:
516 configure_args.remove('--enable-static')
517 if '--enable-shared' in args:
518 configure_args.remove('--disable-shared')
519 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000520
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000521 if recipe.get('useLDFlags', 1):
522 configure_args.extend([
523 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
524 ' -arch '.join(archList),
525 shellQuote(SDKPATH)[1:-1],
526 shellQuote(basedir)[1:-1],),
527 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000528 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000529 shellQuote(basedir)[1:-1],
530 ' -arch '.join(archList)),
531 ])
532 else:
533 configure_args.extend([
534 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
535 ' -arch '.join(archList),
536 shellQuote(SDKPATH)[1:-1],
537 shellQuote(basedir)[1:-1],),
538 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000539
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000540 if 'configure_post' in recipe:
541 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000542
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000543 configure_args.insert(0, configure)
544 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000545
Benjamin Peterson623918e2008-12-20 22:50:25 +0000546 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548
Benjamin Peterson623918e2008-12-20 22:50:25 +0000549 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000550 runCommand('{ ' + install + ' ;} 2>&1')
551
Benjamin Peterson623918e2008-12-20 22:50:25 +0000552 print "Done %s"%(name,)
553 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000554
555 os.chdir(curdir)
556
557def buildLibraries():
558 """
559 Build our dependencies into $WORKDIR/libraries/usr/local
560 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000561 print ""
562 print "Building required libraries"
563 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000564 universal = os.path.join(WORKDIR, 'libraries')
565 os.mkdir(universal)
566 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
567 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
568
569 for recipe in LIBRARY_RECIPES:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000570 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000571
572
573
574def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000575 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000576 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000577 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000578 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000579 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000580 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000581 curDir = os.getcwd()
582 os.chdir(buildDir)
583 runCommand('make update')
584 runCommand('make html')
585 os.chdir(curDir)
586 if not os.path.exists(docdir):
587 os.mkdir(docdir)
588 os.rename(os.path.join(buildDir, 'build', 'html'),
589 os.path.join(docdir, 'python-docs-html'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000590
591
592def buildPython():
Benjamin Peterson623918e2008-12-20 22:50:25 +0000593 print "Building a universal python"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000594
595 buildDir = os.path.join(WORKDIR, '_bld', 'python')
596 rootDir = os.path.join(WORKDIR, '_root')
597
598 if os.path.exists(buildDir):
599 shutil.rmtree(buildDir)
600 if os.path.exists(rootDir):
601 shutil.rmtree(rootDir)
602 os.mkdir(buildDir)
603 os.mkdir(rootDir)
604 os.mkdir(os.path.join(rootDir, 'empty-dir'))
605 curdir = os.getcwd()
606 os.chdir(buildDir)
607
608 # Not sure if this is still needed, the original build script
609 # claims that parts of the install assume python.exe exists.
610 os.symlink('python', os.path.join(buildDir, 'python.exe'))
611
612 # Extract the version from the configure file, needed to calculate
613 # several paths.
614 version = getVersion()
615
Benjamin Peterson623918e2008-12-20 22:50:25 +0000616 print "Running configure..."
Thomas Wouters477c8d52006-05-27 19:21:47 +0000617 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"%(
618 shellQuote(os.path.join(SRCDIR, 'configure')),
619 shellQuote(SDKPATH), shellQuote(WORKDIR)[1:-1],
620 shellQuote(WORKDIR)[1:-1]))
621
Benjamin Peterson623918e2008-12-20 22:50:25 +0000622 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000623 runCommand("make")
624
Benjamin Peterson623918e2008-12-20 22:50:25 +0000625 print "Running make frameworkinstall"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000626 runCommand("make frameworkinstall DESTDIR=%s"%(
627 shellQuote(rootDir)))
628
Benjamin Peterson623918e2008-12-20 22:50:25 +0000629 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000630 runCommand("make frameworkinstallextras DESTDIR=%s"%(
631 shellQuote(rootDir)))
632
Benjamin Peterson623918e2008-12-20 22:50:25 +0000633 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
635 runCommand("mv %s/* %s"%(
636 shellQuote(os.path.join(
637 WORKDIR, 'libraries', 'Library', 'Frameworks',
638 'Python.framework', 'Versions', getVersion(),
639 'lib')),
640 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
641 'Python.framework', 'Versions', getVersion(),
642 'lib'))))
643
Benjamin Peterson623918e2008-12-20 22:50:25 +0000644 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000645 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000646 gid = grp.getgrnam('admin').gr_gid
647
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000648
649
Thomas Wouters477c8d52006-05-27 19:21:47 +0000650 for dirpath, dirnames, filenames in os.walk(frmDir):
651 for dn in dirnames:
652 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000653 os.chown(os.path.join(dirpath, dn), -1, gid)
654
Thomas Wouters477c8d52006-05-27 19:21:47 +0000655
656 for fn in filenames:
657 if os.path.islink(fn):
658 continue
659
660 # "chmod g+w $fn"
661 p = os.path.join(dirpath, fn)
662 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000663 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
664 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665
666 # We added some directories to the search path during the configure
667 # phase. Remove those because those directories won't be there on
668 # the end-users system.
669 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
670 'Versions', version, 'lib', 'python%s'%(version,),
671 'config', 'Makefile')
672 fp = open(path, 'r')
673 data = fp.read()
674 fp.close()
675
676 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
677 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
678 fp = open(path, 'w')
679 fp.write(data)
680 fp.close()
681
682 # Add symlinks in /usr/local/bin, using relative links
683 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
684 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
685 'Python.framework', 'Versions', version, 'bin')
686 if os.path.exists(usr_local_bin):
687 shutil.rmtree(usr_local_bin)
688 os.makedirs(usr_local_bin)
689 for fn in os.listdir(
690 os.path.join(frmDir, 'Versions', version, 'bin')):
691 os.symlink(os.path.join(to_framework, fn),
692 os.path.join(usr_local_bin, fn))
693
694 os.chdir(curdir)
695
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000696 # Remove the 'Current' link, that way we don't accidently mess with an already installed
697 # version of python
698 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework', 'Versions', 'Current'))
699
700
Thomas Wouters477c8d52006-05-27 19:21:47 +0000701
702
703def patchFile(inPath, outPath):
704 data = fileContents(inPath)
705 data = data.replace('$FULL_VERSION', getFullVersion())
706 data = data.replace('$VERSION', getVersion())
707 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later')
708 data = data.replace('$ARCHITECTURES', "i386, ppc")
709 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000710
711 # This one is not handy as a template variable
712 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000713 fp = open(outPath, 'wb')
714 fp.write(data)
715 fp.close()
716
717def patchScript(inPath, outPath):
718 data = fileContents(inPath)
719 data = data.replace('@PYVER@', getVersion())
720 fp = open(outPath, 'wb')
721 fp.write(data)
722 fp.close()
723 os.chmod(outPath, 0755)
724
725
726
727def packageFromRecipe(targetDir, recipe):
728 curdir = os.getcwd()
729 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000730 # The major version (such as 2.5) is included in the package name
731 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000732 # common.
733 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734 srcdir = recipe.get('source')
735 pkgroot = recipe.get('topdir', srcdir)
736 postflight = recipe.get('postflight')
737 readme = textwrap.dedent(recipe['readme'])
738 isRequired = recipe.get('required', True)
739
Benjamin Peterson623918e2008-12-20 22:50:25 +0000740 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000741
742 # Substitute some variables
743 textvars = dict(
744 VER=getVersion(),
745 FULLVER=getFullVersion(),
746 )
747 readme = readme % textvars
748
749 if pkgroot is not None:
750 pkgroot = pkgroot % textvars
751 else:
752 pkgroot = '/'
753
754 if srcdir is not None:
755 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
756 srcdir = srcdir % textvars
757
758 if postflight is not None:
759 postflight = os.path.abspath(postflight)
760
761 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
762 os.makedirs(packageContents)
763
764 if srcdir is not None:
765 os.chdir(srcdir)
766 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
767 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
768 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
769
770 fn = os.path.join(packageContents, 'PkgInfo')
771 fp = open(fn, 'w')
772 fp.write('pmkrpkg1')
773 fp.close()
774
775 rsrcDir = os.path.join(packageContents, "Resources")
776 os.mkdir(rsrcDir)
777 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
778 fp.write(readme)
779 fp.close()
780
781 if postflight is not None:
782 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
783
784 vers = getFullVersion()
785 major, minor = map(int, getVersion().split('.', 2))
786 pl = Plist(
787 CFBundleGetInfoString="MacPython.%s %s"%(pkgname, vers,),
788 CFBundleIdentifier='org.python.MacPython.%s'%(pkgname,),
789 CFBundleName='MacPython.%s'%(pkgname,),
790 CFBundleShortVersionString=vers,
791 IFMajorVersion=major,
792 IFMinorVersion=minor,
793 IFPkgFormatVersion=0.10000000149011612,
794 IFPkgFlagAllowBackRev=False,
795 IFPkgFlagAuthorizationAction="RootAuthorization",
796 IFPkgFlagDefaultLocation=pkgroot,
797 IFPkgFlagFollowLinks=True,
798 IFPkgFlagInstallFat=True,
799 IFPkgFlagIsRequired=isRequired,
800 IFPkgFlagOverwritePermissions=False,
801 IFPkgFlagRelocatable=False,
802 IFPkgFlagRestartAction="NoRestart",
803 IFPkgFlagRootVolumeOnly=True,
804 IFPkgFlagUpdateInstalledLangauges=False,
805 )
806 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
807
808 pl = Plist(
809 IFPkgDescriptionDescription=readme,
810 IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)),
811 IFPkgDescriptionVersion=vers,
812 )
813 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
814
815 finally:
816 os.chdir(curdir)
817
818
819def makeMpkgPlist(path):
820
821 vers = getFullVersion()
822 major, minor = map(int, getVersion().split('.', 2))
823
824 pl = Plist(
825 CFBundleGetInfoString="MacPython %s"%(vers,),
826 CFBundleIdentifier='org.python.MacPython',
827 CFBundleName='MacPython',
828 CFBundleShortVersionString=vers,
829 IFMajorVersion=major,
830 IFMinorVersion=minor,
831 IFPkgFlagComponentDirectory="Contents/Packages",
832 IFPkgFlagPackageList=[
833 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000834 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren360e98c2008-12-30 14:16:51 +0000835 IFPkgFlagPackageSelection=item['selected'],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000836 )
837 for item in PKG_RECIPES
838 ],
839 IFPkgFormatVersion=0.10000000149011612,
840 IFPkgFlagBackgroundScaling="proportional",
841 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000842 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000843 )
844
845 writePlist(pl, path)
846
847
848def buildInstaller():
849
850 # Zap all compiled files
851 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
852 for fn in filenames:
853 if fn.endswith('.pyc') or fn.endswith('.pyo'):
854 os.unlink(os.path.join(dirpath, fn))
855
856 outdir = os.path.join(WORKDIR, 'installer')
857 if os.path.exists(outdir):
858 shutil.rmtree(outdir)
859 os.mkdir(outdir)
860
861 pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents')
862 pkgcontents = os.path.join(pkgroot, 'Packages')
863 os.makedirs(pkgcontents)
864 for recipe in PKG_RECIPES:
865 packageFromRecipe(pkgcontents, recipe)
866
867 rsrcDir = os.path.join(pkgroot, 'Resources')
868
869 fn = os.path.join(pkgroot, 'PkgInfo')
870 fp = open(fn, 'w')
871 fp.write('pmkrpkg1')
872 fp.close()
873
874 os.mkdir(rsrcDir)
875
876 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
877 pl = Plist(
878 IFPkgDescriptionTitle="Universal MacPython",
879 IFPkgDescriptionVersion=getVersion(),
880 )
881
882 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
883 for fn in os.listdir('resources'):
884 if fn == '.svn': continue
885 if fn.endswith('.jpg'):
886 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
887 else:
888 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
889
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000890 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000891
892
893def installSize(clear=False, _saved=[]):
894 if clear:
895 del _saved[:]
896 if not _saved:
897 data = captureCommand("du -ks %s"%(
898 shellQuote(os.path.join(WORKDIR, '_root'))))
899 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
900 return _saved[0]
901
902
903def buildDMG():
904 """
Thomas Wouters89f507f2006-12-13 04:49:30 +0000905 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000906 """
907 outdir = os.path.join(WORKDIR, 'diskimage')
908 if os.path.exists(outdir):
909 shutil.rmtree(outdir)
910
911 imagepath = os.path.join(outdir,
912 'python-%s-macosx'%(getFullVersion(),))
913 if INCLUDE_TIMESTAMP:
914 imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3])
915 imagepath = imagepath + '.dmg'
916
917 os.mkdir(outdir)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000918 runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000919 getFullVersion(),
920 shellQuote(os.path.join(WORKDIR, 'installer')),
921 shellQuote(imagepath)))
922
923 return imagepath
924
925
926def setIcon(filePath, icnsPath):
927 """
928 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929 """
Thomas Wouters477c8d52006-05-27 19:21:47 +0000930
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000931 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
932 dirPath = os.path.dirname(__file__)
933 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
934 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
935 # to connections to the window server.
936 if not os.path.exists('seticon.app/Contents/MacOS'):
937 os.makedirs('seticon.app/Contents/MacOS')
938 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
939 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000940
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000941 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
942 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000943
944def main():
945 # First parse options and check if we can perform our work
946 parseOptions()
947 checkEnvironment()
948
949 os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
950
951 if os.path.exists(WORKDIR):
952 shutil.rmtree(WORKDIR)
953 os.mkdir(WORKDIR)
954
955 # Then build third-party libraries such as sleepycat DB4.
956 buildLibraries()
957
958 # Now build python itself
959 buildPython()
960 buildPythonDocs()
961 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000962 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +0000963 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000964
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000965 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000966 getVersion(),))
967 os.chmod(folder, 0755)
968 setIcon(folder, "../Icons/Python Folder.icns")
969
970 # Create the installer
971 buildInstaller()
972
973 # And copy the readme into the directory containing the installer
974 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
975
976 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000977 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000978
979 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000980 print >> fp, "# BUILD INFO"
981 print >> fp, "# Date:", time.ctime()
982 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +0000983 fp.close()
984
985 # Custom icon for the DMG, shown when the DMG is mounted.
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000986 # XXX: Code is diabled because it doesn't actually work :-(
987# shutil.copy("../Icons/Disk Image.icns",
988# os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))
989# os.system("/Developer/Tools/SetFile -a C %s"%(
990# os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")))
991 setIcon(os.path.join(WORKDIR, "installer"), "../Icons/Disk Image.icns")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992
993
994 # And copy it to a DMG
995 buildDMG()
996
997
998if __name__ == "__main__":
999 main()