blob: 8f58dbbdac986a1bf806c3e603639c2a96a6f7de [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
Ronald Oussoren1943f862009-03-30 19:39:14 +00005work. 64-bit or four-way universal builds require at least OS X 10.5 and
6the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +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"""
Benjamin Peterson623918e2008-12-20 22:50:25 +000013import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Thomas Wouters89f507f2006-12-13 04:49:30 +000014import grp
Thomas Wouters477c8d52006-05-27 19:21:47 +000015
Thomas Wouters89f507f2006-12-13 04:49:30 +000016INCLUDE_TIMESTAMP = 1
17VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000018
19from plistlib import Plist
20
21import MacOS
Thomas Wouters477c8d52006-05-27 19:21:47 +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 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000032 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +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]
Benjamin Petersonf7239562010-03-19 21:48:54 +000043 raise RuntimeError, "Cannot find variable %s" % variable[:-1]
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
45def getVersion():
46 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
47
Benjamin Petersonf7239562010-03-19 21:48:54 +000048def getVersionTuple():
49 return tuple([int(n) for n in getVersion().split('.')])
50
Thomas Wouters477c8d52006-05-27 19:21:47 +000051def getFullVersion():
52 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
53 for ln in open(fn):
54 if 'PY_VERSION' in ln:
55 return ln.split()[-1][1:-1]
Benjamin Peterson623918e2008-12-20 22:50:25 +000056 raise RuntimeError, "Cannot find full version??"
Thomas Wouters477c8d52006-05-27 19:21:47 +000057
Thomas Wouters89f507f2006-12-13 04:49:30 +000058# The directory we'll use to create the build (will be erased and recreated)
59WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000060
Thomas Wouters89f507f2006-12-13 04:49:30 +000061# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000062# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000063DEPSRC = os.path.join(WORKDIR, 'third-party')
64DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000065
66# Location of the preferred SDK
Benjamin Petersonf7239562010-03-19 21:48:54 +000067
68### There are some issues with the SDK selection below here,
69### The resulting binary doesn't work on all platforms that
70### it should. Always default to the 10.4u SDK until that
71### isue is resolved.
72###
73##if int(os.uname()[2].split('.')[0]) == 8:
74## # Explicitly use the 10.4u (universal) SDK when
75## # building on 10.4, the system headers are not
76## # useable for a universal build
77## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
78##else:
79## SDKPATH = "/"
80
Thomas Wouters89f507f2006-12-13 04:49:30 +000081SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +000082
Ronald Oussoren1943f862009-03-30 19:39:14 +000083universal_opts_map = { '32-bit': ('i386', 'ppc',),
84 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersonf7239562010-03-19 21:48:54 +000085 'intel': ('i386', 'x86_64'),
86 '3-way': ('ppc', 'i386', 'x86_64'),
87 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
88default_target_map = {
89 '64-bit': '10.5',
90 '3-way': '10.5',
91 'intel': '10.5',
92 'all': '10.5',
93}
Ronald Oussoren1943f862009-03-30 19:39:14 +000094
95UNIVERSALOPTS = tuple(universal_opts_map.keys())
96
97UNIVERSALARCHS = '32-bit'
98
99ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000101# Source directory (asume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000102SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000103 os.path.dirname(
104 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000105 os.path.abspath(__file__
106 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000107
Ronald Oussoren1943f862009-03-30 19:39:14 +0000108# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
109DEPTARGET = '10.3'
110
Benjamin Petersonf7239562010-03-19 21:48:54 +0000111target_cc_map = {
112 '10.3': 'gcc-4.0',
113 '10.4': 'gcc-4.0',
114 '10.5': 'gcc-4.0',
115 '10.6': 'gcc-4.2',
116}
117
118CC = target_cc_map[DEPTARGET]
119
120PYTHON_3 = getVersionTuple() >= (3, 0)
121
Thomas Wouters89f507f2006-12-13 04:49:30 +0000122USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000123 Usage: build_python [options]
124
125 Options:
126 -? or -h: Show this message
127 -b DIR
128 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
129 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
130 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
131 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000132 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
133 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000134""")% globals()
135
136
137# Instructions for building libraries that are necessary for building a
138# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000139# [The recipes are defined here for convenience but instantiated later after
140# command line options have been processed.]
141def library_recipes():
Benjamin Petersonf7239562010-03-19 21:48:54 +0000142 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143
Benjamin Petersonf7239562010-03-19 21:48:54 +0000144 if DEPTARGET < '10.5':
145 result.extend([
146 dict(
147 name="Bzip2 1.0.5",
148 url="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
149 checksum='3c15a0c8d1d3ee1c46a1634d00617b1a',
150 configure=None,
151 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
152 CC,
153 shellQuote(os.path.join(WORKDIR, 'libraries')),
154 ' -arch '.join(ARCHLIST),
155 SDKPATH,
156 ),
157 ),
158 dict(
159 name="ZLib 1.2.3",
160 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
161 checksum='debc62758716a169df9f62e6ab2bc634',
162 configure=None,
163 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
164 CC,
165 shellQuote(os.path.join(WORKDIR, 'libraries')),
166 ' -arch '.join(ARCHLIST),
167 SDKPATH,
168 ),
169 ),
170 dict(
171 # Note that GNU readline is GPL'd software
172 name="GNU Readline 5.1.4",
173 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
174 checksum='7ee5a692db88b30ca48927a13fd60e46',
175 patchlevel='0',
176 patches=[
177 # The readline maintainers don't do actual micro releases, but
178 # just ship a set of patches.
179 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
180 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
181 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
182 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
183 ]
184 ),
185 dict(
186 name="SQLite 3.6.11",
187 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
188 checksum='7ebb099696ab76cc6ff65dd496d17858',
189 configure_pre=[
190 '--enable-threadsafe',
191 '--enable-tempstore',
192 '--enable-shared=no',
193 '--enable-static=yes',
194 '--disable-tcl',
195 ]
196 ),
197 dict(
198 name="NCurses 5.5",
199 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
200 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
201 configure_pre=[
202 "--without-cxx",
203 "--without-ada",
204 "--without-progs",
205 "--without-curses-h",
206 "--enable-shared",
207 "--with-shared",
208 "--datadir=/usr/share",
209 "--sysconfdir=/etc",
210 "--sharedstatedir=/usr/com",
211 "--with-terminfo-dirs=/usr/share/terminfo",
212 "--with-default-terminfo-dir=/usr/share/terminfo",
213 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
214 "--enable-termcap",
215 ],
216 patches=[
217 "ncurses-5.5.patch",
218 ],
219 useLDFlags=False,
220 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
221 shellQuote(os.path.join(WORKDIR, 'libraries')),
222 shellQuote(os.path.join(WORKDIR, 'libraries')),
223 getVersion(),
224 ),
225 ),
226 ])
227
228 result.extend([
Ronald Oussoren1943f862009-03-30 19:39:14 +0000229 dict(
Benjamin Petersonf7239562010-03-19 21:48:54 +0000230 name="Sleepycat DB 4.7.25",
231 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
232 checksum='ec2b87e833779681a0c3a814aa71359e',
233 buildDir="build_unix",
234 configure="../dist/configure",
Ronald Oussoren1943f862009-03-30 19:39:14 +0000235 configure_pre=[
Benjamin Petersonf7239562010-03-19 21:48:54 +0000236 '--includedir=/usr/local/include/db4',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000237 ]
238 ),
Benjamin Petersonf7239562010-03-19 21:48:54 +0000239 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000240
Benjamin Petersonf7239562010-03-19 21:48:54 +0000241 return result
242
Thomas Wouters477c8d52006-05-27 19:21:47 +0000243
Thomas Wouters477c8d52006-05-27 19:21:47 +0000244# Instructions for building packages inside the .mpkg.
Benjamin Petersonf7239562010-03-19 21:48:54 +0000245def pkg_recipes():
246 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
247 result = [
248 dict(
249 name="PythonFramework",
250 long_name="Python Framework",
251 source="/Library/Frameworks/Python.framework",
252 readme="""\
253 This package installs Python.framework, that is the python
254 interpreter and the standard library. This also includes Python
255 wrappers for lots of Mac OS X API's.
256 """,
257 postflight="scripts/postflight.framework",
258 selected='selected',
259 ),
260 dict(
261 name="PythonApplications",
262 long_name="GUI Applications",
263 source="/Applications/Python %(VER)s",
264 readme="""\
265 This package installs IDLE (an interactive Python IDE),
266 Python Launcher and Build Applet (create application bundles
267 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000268
Benjamin Petersonf7239562010-03-19 21:48:54 +0000269 It also installs a number of examples and demos.
270 """,
271 required=False,
272 selected='selected',
273 ),
274 dict(
275 name="PythonUnixTools",
276 long_name="UNIX command-line tools",
277 source="/usr/local/bin",
278 readme="""\
279 This package installs the unix tools in /usr/local/bin for
280 compatibility with older releases of Python. This package
281 is not necessary to use Python.
282 """,
283 required=False,
284 selected='selected',
285 ),
286 dict(
287 name="PythonDocumentation",
288 long_name="Python Documentation",
289 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
290 source="/pydocs",
291 readme="""\
292 This package installs the python documentation at a location
293 that is useable for pydoc and IDLE. If you have installed Xcode
294 it will also install a link to the documentation in
295 /Developer/Documentation/Python
296 """,
297 postflight="scripts/postflight.documentation",
298 required=False,
299 selected='selected',
300 ),
301 dict(
302 name="PythonProfileChanges",
303 long_name="Shell profile updater",
304 readme="""\
305 This packages updates your shell profile to make sure that
306 the Python tools are found by your shell in preference of
307 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000308
Benjamin Petersonf7239562010-03-19 21:48:54 +0000309 If you don't install this package you'll have to add
310 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
311 to your PATH by hand.
312 """,
313 postflight="scripts/postflight.patch-profile",
314 topdir="/Library/Frameworks/Python.framework",
315 source="/empty-dir",
316 required=False,
317 selected=unselected_for_python3,
318 ),
319 ]
320
321 if DEPTARGET < '10.4':
322 result.append(
323 dict(
324 name="PythonSystemFixes",
325 long_name="Fix system Python",
326 readme="""\
327 This package updates the system python installation on
328 Mac OS X 10.3 to ensure that you can build new python extensions
329 using that copy of python after installing this version.
330 """,
331 postflight="../Tools/fixapplepython23.py",
332 topdir="/Library/Frameworks/Python.framework",
333 source="/empty-dir",
334 required=False,
335 selected=unselected_for_python3,
336 )
337 )
338 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000339
Thomas Wouters477c8d52006-05-27 19:21:47 +0000340def fatal(msg):
341 """
342 A fatal error, bail out.
343 """
344 sys.stderr.write('FATAL: ')
345 sys.stderr.write(msg)
346 sys.stderr.write('\n')
347 sys.exit(1)
348
349def fileContents(fn):
350 """
351 Return the contents of the named file
352 """
353 return open(fn, 'rb').read()
354
355def runCommand(commandline):
356 """
357 Run a command and raise RuntimeError if it fails. Output is surpressed
358 unless the command fails.
359 """
360 fd = os.popen(commandline, 'r')
361 data = fd.read()
362 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000363 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000364 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000365 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000366
367 if VERBOSE:
368 sys.stdout.write(data); sys.stdout.flush()
369
370def captureCommand(commandline):
371 fd = os.popen(commandline, 'r')
372 data = fd.read()
373 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000374 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000375 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000376 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000377
378 return data
379
380def checkEnvironment():
381 """
382 Check that we're running on a supported system.
383 """
384
385 if platform.system() != 'Darwin':
Benjamin Petersonf7239562010-03-19 21:48:54 +0000386 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000387
Benjamin Petersonf7239562010-03-19 21:48:54 +0000388 if int(platform.release().split('.')[0]) < 8:
389 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000390
391 if not os.path.exists(SDKPATH):
392 fatal("Please install the latest version of Xcode and the %s SDK"%(
393 os.path.basename(SDKPATH[:-4])))
394
395
396
Thomas Wouters89f507f2006-12-13 04:49:30 +0000397def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000398 """
399 Parse arguments and update global settings.
400 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000401 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +0000402 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000403
404 if args is None:
405 args = sys.argv[1:]
406
407 try:
408 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000409 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
410 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000411 except getopt.error, msg:
412 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000413 sys.exit(1)
414
415 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000416 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000417 sys.exit(1)
418
Benjamin Petersonf7239562010-03-19 21:48:54 +0000419 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000420 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000421 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000422 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000423 sys.exit(0)
424
425 elif k in ('-d', '--build-dir'):
426 WORKDIR=v
427
428 elif k in ('--third-party',):
429 DEPSRC=v
430
431 elif k in ('--sdk-path',):
432 SDKPATH=v
433
434 elif k in ('--src-dir',):
435 SRCDIR=v
436
Ronald Oussoren1943f862009-03-30 19:39:14 +0000437 elif k in ('--dep-target', ):
438 DEPTARGET=v
Benjamin Petersonf7239562010-03-19 21:48:54 +0000439 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000440
441 elif k in ('--universal-archs', ):
442 if v in UNIVERSALOPTS:
443 UNIVERSALARCHS = v
444 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersonf7239562010-03-19 21:48:54 +0000445 if deptarget is None:
446 # Select alternate default deployment
447 # target
448 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000449 else:
450 raise NotImplementedError, v
451
Thomas Wouters477c8d52006-05-27 19:21:47 +0000452 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000453 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000454
455 SRCDIR=os.path.abspath(SRCDIR)
456 WORKDIR=os.path.abspath(WORKDIR)
457 SDKPATH=os.path.abspath(SDKPATH)
458 DEPSRC=os.path.abspath(DEPSRC)
459
Benjamin Petersonf7239562010-03-19 21:48:54 +0000460 CC=target_cc_map[DEPTARGET]
461
Benjamin Peterson623918e2008-12-20 22:50:25 +0000462 print "Settings:"
463 print " * Source directory:", SRCDIR
464 print " * Build directory: ", WORKDIR
465 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000466 print " * Third-party source:", DEPSRC
467 print " * Deployment target:", DEPTARGET
468 print " * Universal architectures:", ARCHLIST
Benjamin Petersonf7239562010-03-19 21:48:54 +0000469 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000470 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000471
472
473
474
475def extractArchive(builddir, archiveName):
476 """
477 Extract a source archive into 'builddir'. Returns the path of the
478 extracted archive.
479
480 XXX: This function assumes that archives contain a toplevel directory
481 that is has the same name as the basename of the archive. This is
482 save enough for anything we use.
483 """
484 curdir = os.getcwd()
485 try:
486 os.chdir(builddir)
487 if archiveName.endswith('.tar.gz'):
488 retval = os.path.basename(archiveName[:-7])
489 if os.path.exists(retval):
490 shutil.rmtree(retval)
491 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
492
493 elif archiveName.endswith('.tar.bz2'):
494 retval = os.path.basename(archiveName[:-8])
495 if os.path.exists(retval):
496 shutil.rmtree(retval)
497 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
498
499 elif archiveName.endswith('.tar'):
500 retval = os.path.basename(archiveName[:-4])
501 if os.path.exists(retval):
502 shutil.rmtree(retval)
503 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
504
505 elif archiveName.endswith('.zip'):
506 retval = os.path.basename(archiveName[:-4])
507 if os.path.exists(retval):
508 shutil.rmtree(retval)
509 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
510
511 data = fp.read()
512 xit = fp.close()
513 if xit is not None:
514 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000515 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000516
517 return os.path.join(builddir, retval)
518
519 finally:
520 os.chdir(curdir)
521
522KNOWNSIZES = {
523 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
524 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
525}
526
527def downloadURL(url, fname):
528 """
529 Download the contents of the url into the file.
530 """
531 try:
532 size = os.path.getsize(fname)
533 except OSError:
534 pass
535 else:
536 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000537 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000538 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000539 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000540 fpOut = open(fname, 'wb')
541 block = fpIn.read(10240)
542 try:
543 while block:
544 fpOut.write(block)
545 block = fpIn.read(10240)
546 fpIn.close()
547 fpOut.close()
548 except:
549 try:
550 os.unlink(fname)
551 except:
552 pass
553
554def buildRecipe(recipe, basedir, archList):
555 """
556 Build software using a recipe. This function does the
557 'configure;make;make install' dance for C software, with a possibility
558 to customize this process, basically a poor-mans DarwinPorts.
559 """
560 curdir = os.getcwd()
561
562 name = recipe['name']
563 url = recipe['url']
564 configure = recipe.get('configure', './configure')
565 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
566 shellQuote(basedir)))
567
568 archiveName = os.path.split(url)[-1]
569 sourceArchive = os.path.join(DEPSRC, archiveName)
570
571 if not os.path.exists(DEPSRC):
572 os.mkdir(DEPSRC)
573
574
575 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000576 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000577
578 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000579 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000580 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000581 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000582 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000583
Benjamin Peterson623918e2008-12-20 22:50:25 +0000584 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000585 buildDir=os.path.join(WORKDIR, '_bld')
586 if not os.path.exists(buildDir):
587 os.mkdir(buildDir)
588
589 workDir = extractArchive(buildDir, sourceArchive)
590 os.chdir(workDir)
591 if 'buildDir' in recipe:
592 os.chdir(recipe['buildDir'])
593
594
595 for fn in recipe.get('patches', ()):
596 if fn.startswith('http://'):
597 # Download the patch before applying it.
598 path = os.path.join(DEPSRC, os.path.basename(fn))
599 downloadURL(fn, path)
600 fn = path
601
602 fn = os.path.join(curdir, fn)
603 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
604 shellQuote(fn),))
605
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000606 if configure is not None:
607 configure_args = [
608 "--prefix=/usr/local",
609 "--enable-static",
610 "--disable-shared",
611 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
612 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000613
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000614 if 'configure_pre' in recipe:
615 args = list(recipe['configure_pre'])
616 if '--disable-static' in args:
617 configure_args.remove('--enable-static')
618 if '--enable-shared' in args:
619 configure_args.remove('--disable-shared')
620 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000621
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000622 if recipe.get('useLDFlags', 1):
623 configure_args.extend([
624 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
625 ' -arch '.join(archList),
626 shellQuote(SDKPATH)[1:-1],
627 shellQuote(basedir)[1:-1],),
628 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000629 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000630 shellQuote(basedir)[1:-1],
631 ' -arch '.join(archList)),
632 ])
633 else:
634 configure_args.extend([
635 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
636 ' -arch '.join(archList),
637 shellQuote(SDKPATH)[1:-1],
638 shellQuote(basedir)[1:-1],),
639 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000640
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000641 if 'configure_post' in recipe:
642 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000643
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000644 configure_args.insert(0, configure)
645 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000646
Benjamin Peterson623918e2008-12-20 22:50:25 +0000647 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000648 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000649
Benjamin Peterson623918e2008-12-20 22:50:25 +0000650 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000651 runCommand('{ ' + install + ' ;} 2>&1')
652
Benjamin Peterson623918e2008-12-20 22:50:25 +0000653 print "Done %s"%(name,)
654 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000655
656 os.chdir(curdir)
657
658def buildLibraries():
659 """
660 Build our dependencies into $WORKDIR/libraries/usr/local
661 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000662 print ""
663 print "Building required libraries"
664 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665 universal = os.path.join(WORKDIR, 'libraries')
666 os.mkdir(universal)
667 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
668 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
669
Ronald Oussoren1943f862009-03-30 19:39:14 +0000670 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000671 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000672
673
674
675def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000676 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000678 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000679 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000680 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000681 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000682 curDir = os.getcwd()
683 os.chdir(buildDir)
684 runCommand('make update')
685 runCommand('make html')
686 os.chdir(curDir)
Benjamin Petersonf7239562010-03-19 21:48:54 +0000687 if not os.path.exists(docdir):
688 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000689 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000690
691
692def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000693 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000694
695 buildDir = os.path.join(WORKDIR, '_bld', 'python')
696 rootDir = os.path.join(WORKDIR, '_root')
697
698 if os.path.exists(buildDir):
699 shutil.rmtree(buildDir)
700 if os.path.exists(rootDir):
701 shutil.rmtree(rootDir)
702 os.mkdir(buildDir)
703 os.mkdir(rootDir)
704 os.mkdir(os.path.join(rootDir, 'empty-dir'))
705 curdir = os.getcwd()
706 os.chdir(buildDir)
707
708 # Not sure if this is still needed, the original build script
709 # claims that parts of the install assume python.exe exists.
710 os.symlink('python', os.path.join(buildDir, 'python.exe'))
711
712 # Extract the version from the configure file, needed to calculate
713 # several paths.
714 version = getVersion()
715
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000716 # Since the extra libs are not in their installed framework location
717 # during the build, augment the library path so that the interpreter
718 # will find them during its extension import sanity checks.
719 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
720 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000721 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000722 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersonf7239562010-03-19 21:48:54 +0000723 "--with-universal-archs=%s "
724 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000725 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
726 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
727 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
728 UNIVERSALARCHS,
Benjamin Petersonf7239562010-03-19 21:48:54 +0000729 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000730 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000731 shellQuote(WORKDIR)[1:-1]))
732
Benjamin Peterson623918e2008-12-20 22:50:25 +0000733 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734 runCommand("make")
735
Benjamin Petersonf7239562010-03-19 21:48:54 +0000736 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000737 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000738 shellQuote(rootDir)))
739
Benjamin Peterson623918e2008-12-20 22:50:25 +0000740 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000741 runCommand("make frameworkinstallextras DESTDIR=%s"%(
742 shellQuote(rootDir)))
743
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000744 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000745 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000746 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
747 runCommand("mv %s/* %s"%(
748 shellQuote(os.path.join(
749 WORKDIR, 'libraries', 'Library', 'Frameworks',
750 'Python.framework', 'Versions', getVersion(),
751 'lib')),
752 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
753 'Python.framework', 'Versions', getVersion(),
754 'lib'))))
755
Benjamin Peterson623918e2008-12-20 22:50:25 +0000756 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000757 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000758 gid = grp.getgrnam('admin').gr_gid
759
Thomas Wouters477c8d52006-05-27 19:21:47 +0000760 for dirpath, dirnames, filenames in os.walk(frmDir):
761 for dn in dirnames:
762 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000763 os.chown(os.path.join(dirpath, dn), -1, gid)
764
Thomas Wouters477c8d52006-05-27 19:21:47 +0000765
766 for fn in filenames:
767 if os.path.islink(fn):
768 continue
769
770 # "chmod g+w $fn"
771 p = os.path.join(dirpath, fn)
772 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000773 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
774 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000775
776 # We added some directories to the search path during the configure
777 # phase. Remove those because those directories won't be there on
778 # the end-users system.
779 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
780 'Versions', version, 'lib', 'python%s'%(version,),
781 'config', 'Makefile')
782 fp = open(path, 'r')
783 data = fp.read()
784 fp.close()
785
786 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
787 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
788 fp = open(path, 'w')
789 fp.write(data)
790 fp.close()
791
792 # Add symlinks in /usr/local/bin, using relative links
793 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
794 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
795 'Python.framework', 'Versions', version, 'bin')
796 if os.path.exists(usr_local_bin):
797 shutil.rmtree(usr_local_bin)
798 os.makedirs(usr_local_bin)
799 for fn in os.listdir(
800 os.path.join(frmDir, 'Versions', version, 'bin')):
801 os.symlink(os.path.join(to_framework, fn),
802 os.path.join(usr_local_bin, fn))
803
804 os.chdir(curdir)
805
Benjamin Petersonf7239562010-03-19 21:48:54 +0000806 if PYTHON_3:
807 # Remove the 'Current' link, that way we don't accidently mess
808 # with an already installed version of python 2
809 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
810 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000811
812def patchFile(inPath, outPath):
813 data = fileContents(inPath)
814 data = data.replace('$FULL_VERSION', getFullVersion())
815 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000816 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000817 data = data.replace('$ARCHITECTURES', "i386, ppc")
818 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000819
820 # This one is not handy as a template variable
821 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000822 fp = open(outPath, 'wb')
823 fp.write(data)
824 fp.close()
825
826def patchScript(inPath, outPath):
827 data = fileContents(inPath)
828 data = data.replace('@PYVER@', getVersion())
829 fp = open(outPath, 'wb')
830 fp.write(data)
831 fp.close()
832 os.chmod(outPath, 0755)
833
834
835
836def packageFromRecipe(targetDir, recipe):
837 curdir = os.getcwd()
838 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000839 # The major version (such as 2.5) is included in the package name
840 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000841 # common.
842 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000843 srcdir = recipe.get('source')
844 pkgroot = recipe.get('topdir', srcdir)
845 postflight = recipe.get('postflight')
846 readme = textwrap.dedent(recipe['readme'])
847 isRequired = recipe.get('required', True)
848
Benjamin Peterson623918e2008-12-20 22:50:25 +0000849 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000850
851 # Substitute some variables
852 textvars = dict(
853 VER=getVersion(),
854 FULLVER=getFullVersion(),
855 )
856 readme = readme % textvars
857
858 if pkgroot is not None:
859 pkgroot = pkgroot % textvars
860 else:
861 pkgroot = '/'
862
863 if srcdir is not None:
864 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
865 srcdir = srcdir % textvars
866
867 if postflight is not None:
868 postflight = os.path.abspath(postflight)
869
870 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
871 os.makedirs(packageContents)
872
873 if srcdir is not None:
874 os.chdir(srcdir)
875 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
876 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
877 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
878
879 fn = os.path.join(packageContents, 'PkgInfo')
880 fp = open(fn, 'w')
881 fp.write('pmkrpkg1')
882 fp.close()
883
884 rsrcDir = os.path.join(packageContents, "Resources")
885 os.mkdir(rsrcDir)
886 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
887 fp.write(readme)
888 fp.close()
889
890 if postflight is not None:
891 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
892
893 vers = getFullVersion()
894 major, minor = map(int, getVersion().split('.', 2))
895 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000896 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
897 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
898 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899 CFBundleShortVersionString=vers,
900 IFMajorVersion=major,
901 IFMinorVersion=minor,
902 IFPkgFormatVersion=0.10000000149011612,
903 IFPkgFlagAllowBackRev=False,
904 IFPkgFlagAuthorizationAction="RootAuthorization",
905 IFPkgFlagDefaultLocation=pkgroot,
906 IFPkgFlagFollowLinks=True,
907 IFPkgFlagInstallFat=True,
908 IFPkgFlagIsRequired=isRequired,
909 IFPkgFlagOverwritePermissions=False,
910 IFPkgFlagRelocatable=False,
911 IFPkgFlagRestartAction="NoRestart",
912 IFPkgFlagRootVolumeOnly=True,
913 IFPkgFlagUpdateInstalledLangauges=False,
914 )
915 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
916
917 pl = Plist(
918 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +0000919 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 IFPkgDescriptionVersion=vers,
921 )
922 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
923
924 finally:
925 os.chdir(curdir)
926
927
928def makeMpkgPlist(path):
929
930 vers = getFullVersion()
931 major, minor = map(int, getVersion().split('.', 2))
932
933 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000934 CFBundleGetInfoString="Python %s"%(vers,),
935 CFBundleIdentifier='org.python.Python',
936 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000937 CFBundleShortVersionString=vers,
938 IFMajorVersion=major,
939 IFMinorVersion=minor,
940 IFPkgFlagComponentDirectory="Contents/Packages",
941 IFPkgFlagPackageList=[
942 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000943 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersonf7239562010-03-19 21:48:54 +0000944 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000945 )
Benjamin Petersonf7239562010-03-19 21:48:54 +0000946 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000947 ],
948 IFPkgFormatVersion=0.10000000149011612,
949 IFPkgFlagBackgroundScaling="proportional",
950 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000951 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000952 )
953
954 writePlist(pl, path)
955
956
957def buildInstaller():
958
959 # Zap all compiled files
960 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
961 for fn in filenames:
962 if fn.endswith('.pyc') or fn.endswith('.pyo'):
963 os.unlink(os.path.join(dirpath, fn))
964
965 outdir = os.path.join(WORKDIR, 'installer')
966 if os.path.exists(outdir):
967 shutil.rmtree(outdir)
968 os.mkdir(outdir)
969
Ronald Oussoren1943f862009-03-30 19:39:14 +0000970 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000971 pkgcontents = os.path.join(pkgroot, 'Packages')
972 os.makedirs(pkgcontents)
Benjamin Petersonf7239562010-03-19 21:48:54 +0000973 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +0000974 packageFromRecipe(pkgcontents, recipe)
975
976 rsrcDir = os.path.join(pkgroot, 'Resources')
977
978 fn = os.path.join(pkgroot, 'PkgInfo')
979 fp = open(fn, 'w')
980 fp.write('pmkrpkg1')
981 fp.close()
982
983 os.mkdir(rsrcDir)
984
985 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
986 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000987 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 IFPkgDescriptionVersion=getVersion(),
989 )
990
991 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
992 for fn in os.listdir('resources'):
993 if fn == '.svn': continue
994 if fn.endswith('.jpg'):
995 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
996 else:
997 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
998
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000999 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001000
1001
1002def installSize(clear=False, _saved=[]):
1003 if clear:
1004 del _saved[:]
1005 if not _saved:
1006 data = captureCommand("du -ks %s"%(
1007 shellQuote(os.path.join(WORKDIR, '_root'))))
1008 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1009 return _saved[0]
1010
1011
1012def buildDMG():
1013 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001014 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001015 """
1016 outdir = os.path.join(WORKDIR, 'diskimage')
1017 if os.path.exists(outdir):
1018 shutil.rmtree(outdir)
1019
1020 imagepath = os.path.join(outdir,
Benjamin Petersonf7239562010-03-19 21:48:54 +00001021 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001022 if INCLUDE_TIMESTAMP:
Benjamin Petersonf7239562010-03-19 21:48:54 +00001023 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001024 imagepath = imagepath + '.dmg'
1025
1026 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001027 volname='Python %s'%(getFullVersion())
1028 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1029 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001030 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001031 shellQuote(imagepath + ".tmp.dmg" )))
1032
1033
1034 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1035 os.mkdir(os.path.join(WORKDIR, "mnt"))
1036 runCommand("hdiutil attach %s -mountroot %s"%(
1037 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1038
1039 # Custom icon for the DMG, shown when the DMG is mounted.
1040 shutil.copy("../Icons/Disk Image.icns",
1041 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1042 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1043 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1044
1045 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1046
1047 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1048 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1049 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1050 setIcon(imagepath, "../Icons/Disk Image.icns")
1051
1052 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001053
1054 return imagepath
1055
1056
1057def setIcon(filePath, icnsPath):
1058 """
1059 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001060 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001061
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001062 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1063 dirPath = os.path.dirname(__file__)
1064 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1065 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1066 # to connections to the window server.
1067 if not os.path.exists('seticon.app/Contents/MacOS'):
1068 os.makedirs('seticon.app/Contents/MacOS')
1069 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1070 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001071
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001072 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1073 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001074
1075def main():
1076 # First parse options and check if we can perform our work
1077 parseOptions()
1078 checkEnvironment()
1079
Ronald Oussoren1943f862009-03-30 19:39:14 +00001080 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +00001081 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001082
1083 if os.path.exists(WORKDIR):
1084 shutil.rmtree(WORKDIR)
1085 os.mkdir(WORKDIR)
1086
1087 # Then build third-party libraries such as sleepycat DB4.
1088 buildLibraries()
1089
1090 # Now build python itself
1091 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001092
1093 # And then build the documentation
1094 # Remove the Deployment Target from the shell
1095 # environment, it's no longer needed and
1096 # an unexpected build target can cause problems
1097 # when Sphinx and its dependencies need to
1098 # be (re-)installed.
1099 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001101
1102
1103 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001104 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001105 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001106 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001107
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001108 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001109 getVersion(),))
1110 os.chmod(folder, 0755)
1111 setIcon(folder, "../Icons/Python Folder.icns")
1112
1113 # Create the installer
1114 buildInstaller()
1115
1116 # And copy the readme into the directory containing the installer
1117 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1118
1119 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001120 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001121
1122 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001123 print >> fp, "# BUILD INFO"
1124 print >> fp, "# Date:", time.ctime()
1125 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001126 fp.close()
1127
Thomas Wouters477c8d52006-05-27 19:21:47 +00001128 # And copy it to a DMG
1129 buildDMG()
1130
Thomas Wouters477c8d52006-05-27 19:21:47 +00001131if __name__ == "__main__":
1132 main()