blob: dbb968e4e73c2aba0b183b06132a4197dd9816a8 [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 Oussoren209d4c32009-09-29 19:34:13 +000064
65### There are some issues with the SDK selection below here,
66### The resulting binary doesn't work on all platforms that
67### it should. Always default to the 10.4u SDK until that
68### isue is resolved.
69###
70##if int(os.uname()[2].split('.')[0]) == 8:
71## # Explicitly use the 10.4u (universal) SDK when
72## # building on 10.4, the system headers are not
73## # useable for a universal build
74## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
75##else:
76## SDKPATH = "/"
77
78SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000079
Ronald Oussoren508282e2009-03-30 19:34:51 +000080universal_opts_map = { '32-bit': ('i386', 'ppc',),
81 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +000082 'intel': ('i386', 'x86_64'),
83 '3-way': ('ppc', 'i386', 'x86_64'),
84 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
85default_target_map = {
86 '64-bit': '10.5',
87 '3-way': '10.5',
88 'intel': '10.5',
89 'all': '10.5',
90}
Ronald Oussoren508282e2009-03-30 19:34:51 +000091
92UNIVERSALOPTS = tuple(universal_opts_map.keys())
93
94UNIVERSALARCHS = '32-bit'
95
96ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +000097
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000098# Source directory (asume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +000099SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000100 os.path.dirname(
101 os.path.dirname(
102 os.path.abspath(__file__
103 ))))
104
Ronald Oussoren508282e2009-03-30 19:34:51 +0000105# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
106DEPTARGET = '10.3'
107
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000108target_cc_map = {
109 '10.3': 'gcc-4.0',
110 '10.4': 'gcc-4.0',
111 '10.5': 'gcc-4.0',
112 '10.6': 'gcc-4.2',
113}
114
115CC = target_cc_map[DEPTARGET]
116
Ronald Oussoren158ad592006-11-07 16:00:34 +0000117USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000118 Usage: build_python [options]
119
120 Options:
121 -? or -h: Show this message
122 -b DIR
123 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
124 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
125 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
126 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000127 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
128 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000129""")% globals()
130
131
132# Instructions for building libraries that are necessary for building a
133# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000134# [The recipes are defined here for convenience but instantiated later after
135# command line options have been processed.]
136def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000137 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000138
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000139 if DEPTARGET < '10.5':
140 result.extend([
141 dict(
142 name="Bzip2 1.0.5",
143 url="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
144 checksum='3c15a0c8d1d3ee1c46a1634d00617b1a',
145 configure=None,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000146 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
147 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000148 shellQuote(os.path.join(WORKDIR, 'libraries')),
149 ' -arch '.join(ARCHLIST),
150 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000151 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000152 ),
153 dict(
154 name="ZLib 1.2.3",
155 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
156 checksum='debc62758716a169df9f62e6ab2bc634',
157 configure=None,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000158 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
159 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000160 shellQuote(os.path.join(WORKDIR, 'libraries')),
161 ' -arch '.join(ARCHLIST),
162 SDKPATH,
163 ),
164 ),
165 dict(
166 # Note that GNU readline is GPL'd software
167 name="GNU Readline 5.1.4",
168 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
169 checksum='7ee5a692db88b30ca48927a13fd60e46',
170 patchlevel='0',
171 patches=[
172 # The readline maintainers don't do actual micro releases, but
173 # just ship a set of patches.
174 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
175 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
176 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
177 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
178 ]
179 ),
180 dict(
181 name="SQLite 3.6.11",
182 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
183 checksum='7ebb099696ab76cc6ff65dd496d17858',
184 configure_pre=[
185 '--enable-threadsafe',
186 '--enable-tempstore',
187 '--enable-shared=no',
188 '--enable-static=yes',
189 '--disable-tcl',
190 ]
191 ),
192 dict(
193 name="NCurses 5.5",
194 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
195 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
196 configure_pre=[
197 "--without-cxx",
198 "--without-ada",
199 "--without-progs",
200 "--without-curses-h",
201 "--enable-shared",
202 "--with-shared",
203 "--datadir=/usr/share",
204 "--sysconfdir=/etc",
205 "--sharedstatedir=/usr/com",
206 "--with-terminfo-dirs=/usr/share/terminfo",
207 "--with-default-terminfo-dir=/usr/share/terminfo",
208 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
209 "--enable-termcap",
210 ],
211 patches=[
212 "ncurses-5.5.patch",
213 ],
214 useLDFlags=False,
215 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
216 shellQuote(os.path.join(WORKDIR, 'libraries')),
217 shellQuote(os.path.join(WORKDIR, 'libraries')),
218 getVersion(),
219 ),
220 ),
221 ])
222
223 result.extend([
Ronald Oussoren508282e2009-03-30 19:34:51 +0000224 dict(
225 name="Sleepycat DB 4.7.25",
226 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
227 checksum='ec2b87e833779681a0c3a814aa71359e',
228 buildDir="build_unix",
229 configure="../dist/configure",
230 configure_pre=[
231 '--includedir=/usr/local/include/db4',
232 ]
233 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000234 ])
235
236 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000237
238
239# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000240def pkg_recipes():
241 result = [
242 dict(
243 name="PythonFramework",
244 long_name="Python Framework",
245 source="/Library/Frameworks/Python.framework",
246 readme="""\
247 This package installs Python.framework, that is the python
248 interpreter and the standard library. This also includes Python
249 wrappers for lots of Mac OS X API's.
250 """,
251 postflight="scripts/postflight.framework",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000252 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000253 ),
254 dict(
255 name="PythonApplications",
256 long_name="GUI Applications",
257 source="/Applications/Python %(VER)s",
258 readme="""\
259 This package installs IDLE (an interactive Python IDE),
260 Python Launcher and Build Applet (create application bundles
261 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000262
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000263 It also installs a number of examples and demos.
264 """,
265 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000266 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000267 ),
268 dict(
269 name="PythonUnixTools",
270 long_name="UNIX command-line tools",
271 source="/usr/local/bin",
272 readme="""\
273 This package installs the unix tools in /usr/local/bin for
274 compatibility with older releases of Python. This package
275 is not necessary to use Python.
276 """,
277 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000278 selected='unselected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000279 ),
280 dict(
281 name="PythonDocumentation",
282 long_name="Python Documentation",
283 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
284 source="/pydocs",
285 readme="""\
286 This package installs the python documentation at a location
287 that is useable for pydoc and IDLE. If you have installed Xcode
288 it will also install a link to the documentation in
289 /Developer/Documentation/Python
290 """,
291 postflight="scripts/postflight.documentation",
292 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000293 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000294 ),
295 dict(
296 name="PythonProfileChanges",
297 long_name="Shell profile updater",
298 readme="""\
299 This packages updates your shell profile to make sure that
300 the Python tools are found by your shell in preference of
301 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000302
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000303 If you don't install this package you'll have to add
304 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
305 to your PATH by hand.
306 """,
307 postflight="scripts/postflight.patch-profile",
308 topdir="/Library/Frameworks/Python.framework",
309 source="/empty-dir",
310 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000311 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000312 ),
313 ]
314
315 if DEPTARGET < '10.4':
316 result.append(
317 dict(
318 name="PythonSystemFixes",
319 long_name="Fix system Python",
320 readme="""\
321 This package updates the system python installation on
322 Mac OS X 10.3 to ensure that you can build new python extensions
323 using that copy of python after installing this version.
324 """,
325 postflight="../Tools/fixapplepython23.py",
326 topdir="/Library/Frameworks/Python.framework",
327 source="/empty-dir",
328 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000329 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000330 )
331 )
332 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000333
334def fatal(msg):
335 """
336 A fatal error, bail out.
337 """
338 sys.stderr.write('FATAL: ')
339 sys.stderr.write(msg)
340 sys.stderr.write('\n')
341 sys.exit(1)
342
343def fileContents(fn):
344 """
345 Return the contents of the named file
346 """
347 return open(fn, 'rb').read()
348
349def runCommand(commandline):
350 """
351 Run a command and raise RuntimeError if it fails. Output is surpressed
352 unless the command fails.
353 """
354 fd = os.popen(commandline, 'r')
355 data = fd.read()
356 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000357 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000358 sys.stdout.write(data)
359 raise RuntimeError, "command failed: %s"%(commandline,)
360
361 if VERBOSE:
362 sys.stdout.write(data); sys.stdout.flush()
363
364def captureCommand(commandline):
365 fd = os.popen(commandline, 'r')
366 data = fd.read()
367 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000368 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000369 sys.stdout.write(data)
370 raise RuntimeError, "command failed: %s"%(commandline,)
371
372 return data
373
374def checkEnvironment():
375 """
376 Check that we're running on a supported system.
377 """
378
379 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000380 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000381
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000382 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000383 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000384
385 if not os.path.exists(SDKPATH):
386 fatal("Please install the latest version of Xcode and the %s SDK"%(
387 os.path.basename(SDKPATH[:-4])))
388
389
390
Ronald Oussoren158ad592006-11-07 16:00:34 +0000391def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000392 """
393 Parse arguments and update global settings.
394 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000395 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000396 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000397
398 if args is None:
399 args = sys.argv[1:]
400
401 try:
402 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000403 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
404 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000405 except getopt.error, msg:
406 print msg
407 sys.exit(1)
408
409 if args:
410 print "Additional arguments"
411 sys.exit(1)
412
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000413 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000414 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000415 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000416 print USAGE
417 sys.exit(0)
418
419 elif k in ('-d', '--build-dir'):
420 WORKDIR=v
421
422 elif k in ('--third-party',):
423 DEPSRC=v
424
425 elif k in ('--sdk-path',):
426 SDKPATH=v
427
428 elif k in ('--src-dir',):
429 SRCDIR=v
430
Ronald Oussoren508282e2009-03-30 19:34:51 +0000431 elif k in ('--dep-target', ):
432 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000433 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000434
435 elif k in ('--universal-archs', ):
436 if v in UNIVERSALOPTS:
437 UNIVERSALARCHS = v
438 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000439 if deptarget is None:
440 # Select alternate default deployment
441 # target
442 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000443 else:
444 raise NotImplementedError, v
445
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000446 else:
447 raise NotImplementedError, k
448
449 SRCDIR=os.path.abspath(SRCDIR)
450 WORKDIR=os.path.abspath(WORKDIR)
451 SDKPATH=os.path.abspath(SDKPATH)
452 DEPSRC=os.path.abspath(DEPSRC)
453
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000454 CC=target_cc_map[DEPTARGET]
455
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000456 print "Settings:"
457 print " * Source directory:", SRCDIR
458 print " * Build directory: ", WORKDIR
459 print " * SDK location: ", SDKPATH
Ronald Oussoren508282e2009-03-30 19:34:51 +0000460 print " * Third-party source:", DEPSRC
461 print " * Deployment target:", DEPTARGET
462 print " * Universal architectures:", ARCHLIST
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000463 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000464 print ""
465
466
467
468
469def extractArchive(builddir, archiveName):
470 """
471 Extract a source archive into 'builddir'. Returns the path of the
472 extracted archive.
473
474 XXX: This function assumes that archives contain a toplevel directory
475 that is has the same name as the basename of the archive. This is
476 save enough for anything we use.
477 """
478 curdir = os.getcwd()
479 try:
480 os.chdir(builddir)
481 if archiveName.endswith('.tar.gz'):
482 retval = os.path.basename(archiveName[:-7])
483 if os.path.exists(retval):
484 shutil.rmtree(retval)
485 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
486
487 elif archiveName.endswith('.tar.bz2'):
488 retval = os.path.basename(archiveName[:-8])
489 if os.path.exists(retval):
490 shutil.rmtree(retval)
491 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
492
493 elif archiveName.endswith('.tar'):
494 retval = os.path.basename(archiveName[:-4])
495 if os.path.exists(retval):
496 shutil.rmtree(retval)
497 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
498
499 elif archiveName.endswith('.zip'):
500 retval = os.path.basename(archiveName[:-4])
501 if os.path.exists(retval):
502 shutil.rmtree(retval)
503 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
504
505 data = fp.read()
506 xit = fp.close()
507 if xit is not None:
508 sys.stdout.write(data)
509 raise RuntimeError, "Cannot extract %s"%(archiveName,)
510
511 return os.path.join(builddir, retval)
512
513 finally:
514 os.chdir(curdir)
515
516KNOWNSIZES = {
517 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
518 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
519}
520
521def downloadURL(url, fname):
522 """
523 Download the contents of the url into the file.
524 """
525 try:
526 size = os.path.getsize(fname)
527 except OSError:
528 pass
529 else:
530 if KNOWNSIZES.get(url) == size:
531 print "Using existing file for", url
532 return
533 fpIn = urllib2.urlopen(url)
534 fpOut = open(fname, 'wb')
535 block = fpIn.read(10240)
536 try:
537 while block:
538 fpOut.write(block)
539 block = fpIn.read(10240)
540 fpIn.close()
541 fpOut.close()
542 except:
543 try:
544 os.unlink(fname)
545 except:
546 pass
547
548def buildRecipe(recipe, basedir, archList):
549 """
550 Build software using a recipe. This function does the
551 'configure;make;make install' dance for C software, with a possibility
552 to customize this process, basically a poor-mans DarwinPorts.
553 """
554 curdir = os.getcwd()
555
556 name = recipe['name']
557 url = recipe['url']
558 configure = recipe.get('configure', './configure')
559 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
560 shellQuote(basedir)))
561
562 archiveName = os.path.split(url)[-1]
563 sourceArchive = os.path.join(DEPSRC, archiveName)
564
565 if not os.path.exists(DEPSRC):
566 os.mkdir(DEPSRC)
567
568
569 if os.path.exists(sourceArchive):
570 print "Using local copy of %s"%(name,)
571
572 else:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000573 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000574 print "Downloading %s"%(name,)
575 downloadURL(url, sourceArchive)
576 print "Archive for %s stored as %s"%(name, sourceArchive)
577
578 print "Extracting archive for %s"%(name,)
579 buildDir=os.path.join(WORKDIR, '_bld')
580 if not os.path.exists(buildDir):
581 os.mkdir(buildDir)
582
583 workDir = extractArchive(buildDir, sourceArchive)
584 os.chdir(workDir)
585 if 'buildDir' in recipe:
586 os.chdir(recipe['buildDir'])
587
588
589 for fn in recipe.get('patches', ()):
590 if fn.startswith('http://'):
591 # Download the patch before applying it.
592 path = os.path.join(DEPSRC, os.path.basename(fn))
593 downloadURL(fn, path)
594 fn = path
595
596 fn = os.path.join(curdir, fn)
597 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
598 shellQuote(fn),))
599
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000600 if configure is not None:
601 configure_args = [
602 "--prefix=/usr/local",
603 "--enable-static",
604 "--disable-shared",
605 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
606 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000607
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000608 if 'configure_pre' in recipe:
609 args = list(recipe['configure_pre'])
610 if '--disable-static' in args:
611 configure_args.remove('--enable-static')
612 if '--enable-shared' in args:
613 configure_args.remove('--disable-shared')
614 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000615
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000616 if recipe.get('useLDFlags', 1):
617 configure_args.extend([
618 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
619 ' -arch '.join(archList),
620 shellQuote(SDKPATH)[1:-1],
621 shellQuote(basedir)[1:-1],),
622 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000623 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000624 shellQuote(basedir)[1:-1],
625 ' -arch '.join(archList)),
626 ])
627 else:
628 configure_args.extend([
629 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
630 ' -arch '.join(archList),
631 shellQuote(SDKPATH)[1:-1],
632 shellQuote(basedir)[1:-1],),
633 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000634
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000635 if 'configure_post' in recipe:
636 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000637
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000638 configure_args.insert(0, configure)
639 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000640
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000641 print "Running configure for %s"%(name,)
642 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000643
644 print "Running install for %s"%(name,)
645 runCommand('{ ' + install + ' ;} 2>&1')
646
647 print "Done %s"%(name,)
648 print ""
649
650 os.chdir(curdir)
651
652def buildLibraries():
653 """
654 Build our dependencies into $WORKDIR/libraries/usr/local
655 """
656 print ""
657 print "Building required libraries"
658 print ""
659 universal = os.path.join(WORKDIR, 'libraries')
660 os.mkdir(universal)
661 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
662 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
663
Ronald Oussoren508282e2009-03-30 19:34:51 +0000664 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000665 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000666
667
668
669def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000670 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000671 # inside the framwork. pydoc and IDLE will pick it up there.
672 print "Install python documentation"
673 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000674 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000675 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000676 curDir = os.getcwd()
677 os.chdir(buildDir)
678 runCommand('make update')
679 runCommand('make html')
680 os.chdir(curDir)
681 if not os.path.exists(docdir):
682 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000683 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000684
685
686def buildPython():
Ronald Oussoren508282e2009-03-30 19:34:51 +0000687 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000688
689 buildDir = os.path.join(WORKDIR, '_bld', 'python')
690 rootDir = os.path.join(WORKDIR, '_root')
691
692 if os.path.exists(buildDir):
693 shutil.rmtree(buildDir)
694 if os.path.exists(rootDir):
695 shutil.rmtree(rootDir)
696 os.mkdir(buildDir)
697 os.mkdir(rootDir)
698 os.mkdir(os.path.join(rootDir, 'empty-dir'))
699 curdir = os.getcwd()
700 os.chdir(buildDir)
701
702 # Not sure if this is still needed, the original build script
703 # claims that parts of the install assume python.exe exists.
704 os.symlink('python', os.path.join(buildDir, 'python.exe'))
705
706 # Extract the version from the configure file, needed to calculate
707 # several paths.
708 version = getVersion()
709
Ronald Oussoren008af852009-03-30 20:02:08 +0000710 # Since the extra libs are not in their installed framework location
711 # during the build, augment the library path so that the interpreter
712 # will find them during its extension import sanity checks.
713 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
714 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000715 print "Running configure..."
Ronald Oussoren508282e2009-03-30 19:34:51 +0000716 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
717 "--with-universal-archs=%s "
718 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
719 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
720 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
721 UNIVERSALARCHS,
722 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000723 shellQuote(WORKDIR)[1:-1]))
724
725 print "Running make"
726 runCommand("make")
727
Ronald Oussorenaa560962006-11-07 15:53:38 +0000728 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000729 runCommand("make frameworkinstall DESTDIR=%s"%(
730 shellQuote(rootDir)))
731
Ronald Oussorenaa560962006-11-07 15:53:38 +0000732 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000733 runCommand("make frameworkinstallextras DESTDIR=%s"%(
734 shellQuote(rootDir)))
735
Ronald Oussoren008af852009-03-30 20:02:08 +0000736 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000737 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000738 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
739 runCommand("mv %s/* %s"%(
740 shellQuote(os.path.join(
741 WORKDIR, 'libraries', 'Library', 'Frameworks',
742 'Python.framework', 'Versions', getVersion(),
743 'lib')),
744 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
745 'Python.framework', 'Versions', getVersion(),
746 'lib'))))
747
748 print "Fix file modes"
749 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000750 gid = grp.getgrnam('admin').gr_gid
751
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000752 for dirpath, dirnames, filenames in os.walk(frmDir):
753 for dn in dirnames:
754 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000755 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000756
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000757
758 for fn in filenames:
759 if os.path.islink(fn):
760 continue
761
762 # "chmod g+w $fn"
763 p = os.path.join(dirpath, fn)
764 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000765 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
766 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000767
768 # We added some directories to the search path during the configure
769 # phase. Remove those because those directories won't be there on
770 # the end-users system.
771 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
772 'Versions', version, 'lib', 'python%s'%(version,),
773 'config', 'Makefile')
774 fp = open(path, 'r')
775 data = fp.read()
776 fp.close()
777
778 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
779 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
780 fp = open(path, 'w')
781 fp.write(data)
782 fp.close()
783
784 # Add symlinks in /usr/local/bin, using relative links
785 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
786 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
787 'Python.framework', 'Versions', version, 'bin')
788 if os.path.exists(usr_local_bin):
789 shutil.rmtree(usr_local_bin)
790 os.makedirs(usr_local_bin)
791 for fn in os.listdir(
792 os.path.join(frmDir, 'Versions', version, 'bin')):
793 os.symlink(os.path.join(to_framework, fn),
794 os.path.join(usr_local_bin, fn))
795
796 os.chdir(curdir)
797
798
799
800def patchFile(inPath, outPath):
801 data = fileContents(inPath)
802 data = data.replace('$FULL_VERSION', getFullVersion())
803 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +0000804 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000805 data = data.replace('$ARCHITECTURES', "i386, ppc")
806 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000807
808 # This one is not handy as a template variable
809 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000810 fp = open(outPath, 'wb')
811 fp.write(data)
812 fp.close()
813
814def patchScript(inPath, outPath):
815 data = fileContents(inPath)
816 data = data.replace('@PYVER@', getVersion())
817 fp = open(outPath, 'wb')
818 fp.write(data)
819 fp.close()
820 os.chmod(outPath, 0755)
821
822
823
824def packageFromRecipe(targetDir, recipe):
825 curdir = os.getcwd()
826 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000827 # The major version (such as 2.5) is included in the package name
828 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000829 # common.
830 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000831 srcdir = recipe.get('source')
832 pkgroot = recipe.get('topdir', srcdir)
833 postflight = recipe.get('postflight')
834 readme = textwrap.dedent(recipe['readme'])
835 isRequired = recipe.get('required', True)
836
837 print "- building package %s"%(pkgname,)
838
839 # Substitute some variables
840 textvars = dict(
841 VER=getVersion(),
842 FULLVER=getFullVersion(),
843 )
844 readme = readme % textvars
845
846 if pkgroot is not None:
847 pkgroot = pkgroot % textvars
848 else:
849 pkgroot = '/'
850
851 if srcdir is not None:
852 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
853 srcdir = srcdir % textvars
854
855 if postflight is not None:
856 postflight = os.path.abspath(postflight)
857
858 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
859 os.makedirs(packageContents)
860
861 if srcdir is not None:
862 os.chdir(srcdir)
863 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
864 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
865 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
866
867 fn = os.path.join(packageContents, 'PkgInfo')
868 fp = open(fn, 'w')
869 fp.write('pmkrpkg1')
870 fp.close()
871
872 rsrcDir = os.path.join(packageContents, "Resources")
873 os.mkdir(rsrcDir)
874 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
875 fp.write(readme)
876 fp.close()
877
878 if postflight is not None:
879 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
880
881 vers = getFullVersion()
882 major, minor = map(int, getVersion().split('.', 2))
883 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000884 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
885 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
886 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000887 CFBundleShortVersionString=vers,
888 IFMajorVersion=major,
889 IFMinorVersion=minor,
890 IFPkgFormatVersion=0.10000000149011612,
891 IFPkgFlagAllowBackRev=False,
892 IFPkgFlagAuthorizationAction="RootAuthorization",
893 IFPkgFlagDefaultLocation=pkgroot,
894 IFPkgFlagFollowLinks=True,
895 IFPkgFlagInstallFat=True,
896 IFPkgFlagIsRequired=isRequired,
897 IFPkgFlagOverwritePermissions=False,
898 IFPkgFlagRelocatable=False,
899 IFPkgFlagRestartAction="NoRestart",
900 IFPkgFlagRootVolumeOnly=True,
901 IFPkgFlagUpdateInstalledLangauges=False,
902 )
903 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
904
905 pl = Plist(
906 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000907 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000908 IFPkgDescriptionVersion=vers,
909 )
910 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
911
912 finally:
913 os.chdir(curdir)
914
915
916def makeMpkgPlist(path):
917
918 vers = getFullVersion()
919 major, minor = map(int, getVersion().split('.', 2))
920
921 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000922 CFBundleGetInfoString="Python %s"%(vers,),
923 CFBundleIdentifier='org.python.Python',
924 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000925 CFBundleShortVersionString=vers,
926 IFMajorVersion=major,
927 IFMinorVersion=minor,
928 IFPkgFlagComponentDirectory="Contents/Packages",
929 IFPkgFlagPackageList=[
930 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000931 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000932 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000933 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000934 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000935 ],
936 IFPkgFormatVersion=0.10000000149011612,
937 IFPkgFlagBackgroundScaling="proportional",
938 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000939 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000940 )
941
942 writePlist(pl, path)
943
944
945def buildInstaller():
946
947 # Zap all compiled files
948 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
949 for fn in filenames:
950 if fn.endswith('.pyc') or fn.endswith('.pyo'):
951 os.unlink(os.path.join(dirpath, fn))
952
953 outdir = os.path.join(WORKDIR, 'installer')
954 if os.path.exists(outdir):
955 shutil.rmtree(outdir)
956 os.mkdir(outdir)
957
Ronald Oussoren508282e2009-03-30 19:34:51 +0000958 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000959 pkgcontents = os.path.join(pkgroot, 'Packages')
960 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000961 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000962 packageFromRecipe(pkgcontents, recipe)
963
964 rsrcDir = os.path.join(pkgroot, 'Resources')
965
966 fn = os.path.join(pkgroot, 'PkgInfo')
967 fp = open(fn, 'w')
968 fp.write('pmkrpkg1')
969 fp.close()
970
971 os.mkdir(rsrcDir)
972
973 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
974 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000975 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000976 IFPkgDescriptionVersion=getVersion(),
977 )
978
979 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
980 for fn in os.listdir('resources'):
981 if fn == '.svn': continue
982 if fn.endswith('.jpg'):
983 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
984 else:
985 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
986
Ronald Oussorenc5555542006-06-11 20:24:45 +0000987 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000988
989
990def installSize(clear=False, _saved=[]):
991 if clear:
992 del _saved[:]
993 if not _saved:
994 data = captureCommand("du -ks %s"%(
995 shellQuote(os.path.join(WORKDIR, '_root'))))
996 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
997 return _saved[0]
998
999
1000def buildDMG():
1001 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001002 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001003 """
1004 outdir = os.path.join(WORKDIR, 'diskimage')
1005 if os.path.exists(outdir):
1006 shutil.rmtree(outdir)
1007
1008 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001009 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001010 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001011 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001012 imagepath = imagepath + '.dmg'
1013
1014 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001015 volname='Python %s'%(getFullVersion())
1016 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1017 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001018 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001019 shellQuote(imagepath + ".tmp.dmg" )))
1020
1021
1022 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1023 os.mkdir(os.path.join(WORKDIR, "mnt"))
1024 runCommand("hdiutil attach %s -mountroot %s"%(
1025 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1026
1027 # Custom icon for the DMG, shown when the DMG is mounted.
1028 shutil.copy("../Icons/Disk Image.icns",
1029 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1030 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1031 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1032
1033 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1034
1035 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1036 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1037 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1038 setIcon(imagepath, "../Icons/Disk Image.icns")
1039
1040 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001041
1042 return imagepath
1043
1044
1045def setIcon(filePath, icnsPath):
1046 """
1047 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001048 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001049
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001050 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1051 dirPath = os.path.dirname(__file__)
1052 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1053 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1054 # to connections to the window server.
1055 if not os.path.exists('seticon.app/Contents/MacOS'):
1056 os.makedirs('seticon.app/Contents/MacOS')
1057 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1058 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001059
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001060 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1061 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001062
1063def main():
1064 # First parse options and check if we can perform our work
1065 parseOptions()
1066 checkEnvironment()
1067
Ronald Oussoren508282e2009-03-30 19:34:51 +00001068 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001069 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001070
1071 if os.path.exists(WORKDIR):
1072 shutil.rmtree(WORKDIR)
1073 os.mkdir(WORKDIR)
1074
1075 # Then build third-party libraries such as sleepycat DB4.
1076 buildLibraries()
1077
1078 # Now build python itself
1079 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001080
1081 # And then build the documentation
1082 # Remove the Deployment Target from the shell
1083 # environment, it's no longer needed and
1084 # an unexpected build target can cause problems
1085 # when Sphinx and its dependencies need to
1086 # be (re-)installed.
1087 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001088 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001089
1090
1091 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001092 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001093 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001094 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001095
Benjamin Petersonc3104762008-10-03 11:52:06 +00001096 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001097 getVersion(),))
1098 os.chmod(folder, 0755)
1099 setIcon(folder, "../Icons/Python Folder.icns")
1100
1101 # Create the installer
1102 buildInstaller()
1103
1104 # And copy the readme into the directory containing the installer
1105 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1106
1107 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001108 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001109
1110 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1111 print >> fp, "# BUILD INFO"
1112 print >> fp, "# Date:", time.ctime()
1113 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1114 fp.close()
1115
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001116 # And copy it to a DMG
1117 buildDMG()
1118
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001119if __name__ == "__main__":
1120 main()