blob: ec7b8c39e372ea97d56c2e84ca9ff418c606116c [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
Ronald Oussorena1078192010-04-18 15:25:17 +0000380def getTclTkVersion(configfile, versionline):
381 """
382 search Tcl or Tk configuration file for version line
383 """
384 try:
385 f = open(configfile, "r")
386 except:
387 fatal("Framework configuration file not found: %s" % configfile)
388
389 for l in f:
390 if l.startswith(versionline):
391 f.close()
392 return l
393
394 fatal("Version variable %s not found in framework configuration file: %s"
395 % (versionline, configfile))
396
Thomas Wouters477c8d52006-05-27 19:21:47 +0000397def checkEnvironment():
398 """
399 Check that we're running on a supported system.
400 """
401
402 if platform.system() != 'Darwin':
Benjamin Petersonf7239562010-03-19 21:48:54 +0000403 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000404
Benjamin Petersonf7239562010-03-19 21:48:54 +0000405 if int(platform.release().split('.')[0]) < 8:
406 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000407
408 if not os.path.exists(SDKPATH):
409 fatal("Please install the latest version of Xcode and the %s SDK"%(
410 os.path.basename(SDKPATH[:-4])))
411
Ronald Oussorena1078192010-04-18 15:25:17 +0000412 # Because we only support dynamic load of only one major/minor version of
413 # Tcl/Tk, ensure:
414 # 1. there are no user-installed frameworks of Tcl/Tk with version
415 # higher than the Apple-supplied system version
416 # 2. there is a user-installed framework in /Library/Frameworks with the
417 # same version as the system version. This allows users to choose
418 # to install a newer patch level.
419
420 for framework in ['Tcl', 'Tk']:
421 fw = dict(lower=framework.lower(),
422 upper=framework.upper(),
423 cap=framework.capitalize())
424 fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
425 sysfw = os.path.join('/System', fwpth)
426 libfw = os.path.join('/', fwpth)
427 usrfw = os.path.join(os.getenv('HOME'), fwpth)
428 version = "%(upper)s_VERSION" % fw
429 if getTclTkVersion(libfw, version) != getTclTkVersion(sysfw, version):
430 fatal("Version of %s must match %s" % (libfw, sysfw) )
431 if os.path.exists(usrfw):
432 fatal("Please rename %s to avoid possible dynamic load issues."
433 % usrfw)
434
435 # Remove inherited environment variables which might influence build
436 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
437 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
438 for ev in list(os.environ):
439 for prefix in environ_var_prefixes:
440 if ev.startswith(prefix) :
441 print "INFO: deleting environment variable %s=%s" % (
442 ev, os.environ[ev])
443 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000444
445
Thomas Wouters89f507f2006-12-13 04:49:30 +0000446def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000447 """
448 Parse arguments and update global settings.
449 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000450 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +0000451 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000452
453 if args is None:
454 args = sys.argv[1:]
455
456 try:
457 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000458 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
459 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000460 except getopt.error, msg:
461 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000462 sys.exit(1)
463
464 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000465 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000466 sys.exit(1)
467
Benjamin Petersonf7239562010-03-19 21:48:54 +0000468 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000469 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000470 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000471 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000472 sys.exit(0)
473
474 elif k in ('-d', '--build-dir'):
475 WORKDIR=v
476
477 elif k in ('--third-party',):
478 DEPSRC=v
479
480 elif k in ('--sdk-path',):
481 SDKPATH=v
482
483 elif k in ('--src-dir',):
484 SRCDIR=v
485
Ronald Oussoren1943f862009-03-30 19:39:14 +0000486 elif k in ('--dep-target', ):
487 DEPTARGET=v
Benjamin Petersonf7239562010-03-19 21:48:54 +0000488 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000489
490 elif k in ('--universal-archs', ):
491 if v in UNIVERSALOPTS:
492 UNIVERSALARCHS = v
493 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersonf7239562010-03-19 21:48:54 +0000494 if deptarget is None:
495 # Select alternate default deployment
496 # target
497 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000498 else:
499 raise NotImplementedError, v
500
Thomas Wouters477c8d52006-05-27 19:21:47 +0000501 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000502 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000503
504 SRCDIR=os.path.abspath(SRCDIR)
505 WORKDIR=os.path.abspath(WORKDIR)
506 SDKPATH=os.path.abspath(SDKPATH)
507 DEPSRC=os.path.abspath(DEPSRC)
508
Benjamin Petersonf7239562010-03-19 21:48:54 +0000509 CC=target_cc_map[DEPTARGET]
510
Benjamin Peterson623918e2008-12-20 22:50:25 +0000511 print "Settings:"
512 print " * Source directory:", SRCDIR
513 print " * Build directory: ", WORKDIR
514 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000515 print " * Third-party source:", DEPSRC
516 print " * Deployment target:", DEPTARGET
517 print " * Universal architectures:", ARCHLIST
Benjamin Petersonf7239562010-03-19 21:48:54 +0000518 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000519 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000520
521
522
523
524def extractArchive(builddir, archiveName):
525 """
526 Extract a source archive into 'builddir'. Returns the path of the
527 extracted archive.
528
529 XXX: This function assumes that archives contain a toplevel directory
530 that is has the same name as the basename of the archive. This is
531 save enough for anything we use.
532 """
533 curdir = os.getcwd()
534 try:
535 os.chdir(builddir)
536 if archiveName.endswith('.tar.gz'):
537 retval = os.path.basename(archiveName[:-7])
538 if os.path.exists(retval):
539 shutil.rmtree(retval)
540 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
541
542 elif archiveName.endswith('.tar.bz2'):
543 retval = os.path.basename(archiveName[:-8])
544 if os.path.exists(retval):
545 shutil.rmtree(retval)
546 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
547
548 elif archiveName.endswith('.tar'):
549 retval = os.path.basename(archiveName[:-4])
550 if os.path.exists(retval):
551 shutil.rmtree(retval)
552 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
553
554 elif archiveName.endswith('.zip'):
555 retval = os.path.basename(archiveName[:-4])
556 if os.path.exists(retval):
557 shutil.rmtree(retval)
558 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
559
560 data = fp.read()
561 xit = fp.close()
562 if xit is not None:
563 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000564 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000565
566 return os.path.join(builddir, retval)
567
568 finally:
569 os.chdir(curdir)
570
571KNOWNSIZES = {
572 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
573 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
574}
575
576def downloadURL(url, fname):
577 """
578 Download the contents of the url into the file.
579 """
580 try:
581 size = os.path.getsize(fname)
582 except OSError:
583 pass
584 else:
585 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000586 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000587 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000588 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000589 fpOut = open(fname, 'wb')
590 block = fpIn.read(10240)
591 try:
592 while block:
593 fpOut.write(block)
594 block = fpIn.read(10240)
595 fpIn.close()
596 fpOut.close()
597 except:
598 try:
599 os.unlink(fname)
600 except:
601 pass
602
603def buildRecipe(recipe, basedir, archList):
604 """
605 Build software using a recipe. This function does the
606 'configure;make;make install' dance for C software, with a possibility
607 to customize this process, basically a poor-mans DarwinPorts.
608 """
609 curdir = os.getcwd()
610
611 name = recipe['name']
612 url = recipe['url']
613 configure = recipe.get('configure', './configure')
614 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
615 shellQuote(basedir)))
616
617 archiveName = os.path.split(url)[-1]
618 sourceArchive = os.path.join(DEPSRC, archiveName)
619
620 if not os.path.exists(DEPSRC):
621 os.mkdir(DEPSRC)
622
623
624 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000625 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000626
627 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000628 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000629 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000630 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000631 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632
Benjamin Peterson623918e2008-12-20 22:50:25 +0000633 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 buildDir=os.path.join(WORKDIR, '_bld')
635 if not os.path.exists(buildDir):
636 os.mkdir(buildDir)
637
638 workDir = extractArchive(buildDir, sourceArchive)
639 os.chdir(workDir)
640 if 'buildDir' in recipe:
641 os.chdir(recipe['buildDir'])
642
643
644 for fn in recipe.get('patches', ()):
645 if fn.startswith('http://'):
646 # Download the patch before applying it.
647 path = os.path.join(DEPSRC, os.path.basename(fn))
648 downloadURL(fn, path)
649 fn = path
650
651 fn = os.path.join(curdir, fn)
652 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
653 shellQuote(fn),))
654
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000655 if configure is not None:
656 configure_args = [
657 "--prefix=/usr/local",
658 "--enable-static",
659 "--disable-shared",
660 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
661 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000662
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000663 if 'configure_pre' in recipe:
664 args = list(recipe['configure_pre'])
665 if '--disable-static' in args:
666 configure_args.remove('--enable-static')
667 if '--enable-shared' in args:
668 configure_args.remove('--disable-shared')
669 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000670
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000671 if recipe.get('useLDFlags', 1):
672 configure_args.extend([
673 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
674 ' -arch '.join(archList),
675 shellQuote(SDKPATH)[1:-1],
676 shellQuote(basedir)[1:-1],),
677 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000678 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000679 shellQuote(basedir)[1:-1],
680 ' -arch '.join(archList)),
681 ])
682 else:
683 configure_args.extend([
684 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
685 ' -arch '.join(archList),
686 shellQuote(SDKPATH)[1:-1],
687 shellQuote(basedir)[1:-1],),
688 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000689
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000690 if 'configure_post' in recipe:
691 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000692
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000693 configure_args.insert(0, configure)
694 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000695
Benjamin Peterson623918e2008-12-20 22:50:25 +0000696 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000697 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000698
Benjamin Peterson623918e2008-12-20 22:50:25 +0000699 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000700 runCommand('{ ' + install + ' ;} 2>&1')
701
Benjamin Peterson623918e2008-12-20 22:50:25 +0000702 print "Done %s"%(name,)
703 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704
705 os.chdir(curdir)
706
707def buildLibraries():
708 """
709 Build our dependencies into $WORKDIR/libraries/usr/local
710 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000711 print ""
712 print "Building required libraries"
713 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000714 universal = os.path.join(WORKDIR, 'libraries')
715 os.mkdir(universal)
716 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
717 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
718
Ronald Oussoren1943f862009-03-30 19:39:14 +0000719 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000720 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000721
722
723
724def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000725 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000726 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000727 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000728 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000729 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000730 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000731 curDir = os.getcwd()
732 os.chdir(buildDir)
733 runCommand('make update')
734 runCommand('make html')
735 os.chdir(curDir)
Benjamin Petersonf7239562010-03-19 21:48:54 +0000736 if not os.path.exists(docdir):
737 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000738 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739
740
741def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000742 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743
744 buildDir = os.path.join(WORKDIR, '_bld', 'python')
745 rootDir = os.path.join(WORKDIR, '_root')
746
747 if os.path.exists(buildDir):
748 shutil.rmtree(buildDir)
749 if os.path.exists(rootDir):
750 shutil.rmtree(rootDir)
751 os.mkdir(buildDir)
752 os.mkdir(rootDir)
753 os.mkdir(os.path.join(rootDir, 'empty-dir'))
754 curdir = os.getcwd()
755 os.chdir(buildDir)
756
757 # Not sure if this is still needed, the original build script
758 # claims that parts of the install assume python.exe exists.
759 os.symlink('python', os.path.join(buildDir, 'python.exe'))
760
761 # Extract the version from the configure file, needed to calculate
762 # several paths.
763 version = getVersion()
764
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000765 # Since the extra libs are not in their installed framework location
766 # during the build, augment the library path so that the interpreter
767 # will find them during its extension import sanity checks.
768 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
769 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000770 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000771 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersonf7239562010-03-19 21:48:54 +0000772 "--with-universal-archs=%s "
773 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000774 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
775 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
776 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
777 UNIVERSALARCHS,
Benjamin Petersonf7239562010-03-19 21:48:54 +0000778 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000779 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000780 shellQuote(WORKDIR)[1:-1]))
781
Benjamin Peterson623918e2008-12-20 22:50:25 +0000782 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783 runCommand("make")
784
Benjamin Petersonf7239562010-03-19 21:48:54 +0000785 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000786 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000787 shellQuote(rootDir)))
788
Benjamin Peterson623918e2008-12-20 22:50:25 +0000789 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000790 runCommand("make frameworkinstallextras DESTDIR=%s"%(
791 shellQuote(rootDir)))
792
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000793 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000794 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000795 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
796 runCommand("mv %s/* %s"%(
797 shellQuote(os.path.join(
798 WORKDIR, 'libraries', 'Library', 'Frameworks',
799 'Python.framework', 'Versions', getVersion(),
800 'lib')),
801 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
802 'Python.framework', 'Versions', getVersion(),
803 'lib'))))
804
Benjamin Peterson623918e2008-12-20 22:50:25 +0000805 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000806 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000807 gid = grp.getgrnam('admin').gr_gid
808
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 for dirpath, dirnames, filenames in os.walk(frmDir):
810 for dn in dirnames:
811 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000812 os.chown(os.path.join(dirpath, dn), -1, gid)
813
Thomas Wouters477c8d52006-05-27 19:21:47 +0000814
815 for fn in filenames:
816 if os.path.islink(fn):
817 continue
818
819 # "chmod g+w $fn"
820 p = os.path.join(dirpath, fn)
821 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000822 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
823 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000824
825 # We added some directories to the search path during the configure
826 # phase. Remove those because those directories won't be there on
827 # the end-users system.
828 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
829 'Versions', version, 'lib', 'python%s'%(version,),
830 'config', 'Makefile')
831 fp = open(path, 'r')
832 data = fp.read()
833 fp.close()
834
835 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
836 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
837 fp = open(path, 'w')
838 fp.write(data)
839 fp.close()
840
841 # Add symlinks in /usr/local/bin, using relative links
842 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
843 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
844 'Python.framework', 'Versions', version, 'bin')
845 if os.path.exists(usr_local_bin):
846 shutil.rmtree(usr_local_bin)
847 os.makedirs(usr_local_bin)
848 for fn in os.listdir(
849 os.path.join(frmDir, 'Versions', version, 'bin')):
850 os.symlink(os.path.join(to_framework, fn),
851 os.path.join(usr_local_bin, fn))
852
853 os.chdir(curdir)
854
Benjamin Petersonf7239562010-03-19 21:48:54 +0000855 if PYTHON_3:
856 # Remove the 'Current' link, that way we don't accidently mess
857 # with an already installed version of python 2
858 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
859 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000860
861def patchFile(inPath, outPath):
862 data = fileContents(inPath)
863 data = data.replace('$FULL_VERSION', getFullVersion())
864 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000865 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000866 data = data.replace('$ARCHITECTURES', "i386, ppc")
867 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000868
869 # This one is not handy as a template variable
870 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000871 fp = open(outPath, 'wb')
872 fp.write(data)
873 fp.close()
874
875def patchScript(inPath, outPath):
876 data = fileContents(inPath)
877 data = data.replace('@PYVER@', getVersion())
878 fp = open(outPath, 'wb')
879 fp.write(data)
880 fp.close()
881 os.chmod(outPath, 0755)
882
883
884
885def packageFromRecipe(targetDir, recipe):
886 curdir = os.getcwd()
887 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000888 # The major version (such as 2.5) is included in the package name
889 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000890 # common.
891 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000892 srcdir = recipe.get('source')
893 pkgroot = recipe.get('topdir', srcdir)
894 postflight = recipe.get('postflight')
895 readme = textwrap.dedent(recipe['readme'])
896 isRequired = recipe.get('required', True)
897
Benjamin Peterson623918e2008-12-20 22:50:25 +0000898 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899
900 # Substitute some variables
901 textvars = dict(
902 VER=getVersion(),
903 FULLVER=getFullVersion(),
904 )
905 readme = readme % textvars
906
907 if pkgroot is not None:
908 pkgroot = pkgroot % textvars
909 else:
910 pkgroot = '/'
911
912 if srcdir is not None:
913 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
914 srcdir = srcdir % textvars
915
916 if postflight is not None:
917 postflight = os.path.abspath(postflight)
918
919 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
920 os.makedirs(packageContents)
921
922 if srcdir is not None:
923 os.chdir(srcdir)
924 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
925 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
926 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
927
928 fn = os.path.join(packageContents, 'PkgInfo')
929 fp = open(fn, 'w')
930 fp.write('pmkrpkg1')
931 fp.close()
932
933 rsrcDir = os.path.join(packageContents, "Resources")
934 os.mkdir(rsrcDir)
935 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
936 fp.write(readme)
937 fp.close()
938
939 if postflight is not None:
940 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
941
942 vers = getFullVersion()
943 major, minor = map(int, getVersion().split('.', 2))
944 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000945 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
946 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
947 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000948 CFBundleShortVersionString=vers,
949 IFMajorVersion=major,
950 IFMinorVersion=minor,
951 IFPkgFormatVersion=0.10000000149011612,
952 IFPkgFlagAllowBackRev=False,
953 IFPkgFlagAuthorizationAction="RootAuthorization",
954 IFPkgFlagDefaultLocation=pkgroot,
955 IFPkgFlagFollowLinks=True,
956 IFPkgFlagInstallFat=True,
957 IFPkgFlagIsRequired=isRequired,
958 IFPkgFlagOverwritePermissions=False,
959 IFPkgFlagRelocatable=False,
960 IFPkgFlagRestartAction="NoRestart",
961 IFPkgFlagRootVolumeOnly=True,
962 IFPkgFlagUpdateInstalledLangauges=False,
963 )
964 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
965
966 pl = Plist(
967 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +0000968 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000969 IFPkgDescriptionVersion=vers,
970 )
971 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
972
973 finally:
974 os.chdir(curdir)
975
976
977def makeMpkgPlist(path):
978
979 vers = getFullVersion()
980 major, minor = map(int, getVersion().split('.', 2))
981
982 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000983 CFBundleGetInfoString="Python %s"%(vers,),
984 CFBundleIdentifier='org.python.Python',
985 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000986 CFBundleShortVersionString=vers,
987 IFMajorVersion=major,
988 IFMinorVersion=minor,
989 IFPkgFlagComponentDirectory="Contents/Packages",
990 IFPkgFlagPackageList=[
991 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000992 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersonf7239562010-03-19 21:48:54 +0000993 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000994 )
Benjamin Petersonf7239562010-03-19 21:48:54 +0000995 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000996 ],
997 IFPkgFormatVersion=0.10000000149011612,
998 IFPkgFlagBackgroundScaling="proportional",
999 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001000 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001001 )
1002
1003 writePlist(pl, path)
1004
1005
1006def buildInstaller():
1007
1008 # Zap all compiled files
1009 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1010 for fn in filenames:
1011 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1012 os.unlink(os.path.join(dirpath, fn))
1013
1014 outdir = os.path.join(WORKDIR, 'installer')
1015 if os.path.exists(outdir):
1016 shutil.rmtree(outdir)
1017 os.mkdir(outdir)
1018
Ronald Oussoren1943f862009-03-30 19:39:14 +00001019 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001020 pkgcontents = os.path.join(pkgroot, 'Packages')
1021 os.makedirs(pkgcontents)
Benjamin Petersonf7239562010-03-19 21:48:54 +00001022 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001023 packageFromRecipe(pkgcontents, recipe)
1024
1025 rsrcDir = os.path.join(pkgroot, 'Resources')
1026
1027 fn = os.path.join(pkgroot, 'PkgInfo')
1028 fp = open(fn, 'w')
1029 fp.write('pmkrpkg1')
1030 fp.close()
1031
1032 os.mkdir(rsrcDir)
1033
1034 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1035 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001036 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001037 IFPkgDescriptionVersion=getVersion(),
1038 )
1039
1040 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1041 for fn in os.listdir('resources'):
1042 if fn == '.svn': continue
1043 if fn.endswith('.jpg'):
1044 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1045 else:
1046 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1047
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001048 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001049
1050
1051def installSize(clear=False, _saved=[]):
1052 if clear:
1053 del _saved[:]
1054 if not _saved:
1055 data = captureCommand("du -ks %s"%(
1056 shellQuote(os.path.join(WORKDIR, '_root'))))
1057 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1058 return _saved[0]
1059
1060
1061def buildDMG():
1062 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001063 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001064 """
1065 outdir = os.path.join(WORKDIR, 'diskimage')
1066 if os.path.exists(outdir):
1067 shutil.rmtree(outdir)
1068
1069 imagepath = os.path.join(outdir,
Benjamin Petersonf7239562010-03-19 21:48:54 +00001070 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001071 if INCLUDE_TIMESTAMP:
Benjamin Petersonf7239562010-03-19 21:48:54 +00001072 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073 imagepath = imagepath + '.dmg'
1074
1075 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001076 volname='Python %s'%(getFullVersion())
1077 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1078 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001079 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001080 shellQuote(imagepath + ".tmp.dmg" )))
1081
1082
1083 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1084 os.mkdir(os.path.join(WORKDIR, "mnt"))
1085 runCommand("hdiutil attach %s -mountroot %s"%(
1086 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1087
1088 # Custom icon for the DMG, shown when the DMG is mounted.
1089 shutil.copy("../Icons/Disk Image.icns",
1090 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1091 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1092 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1093
1094 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1095
1096 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1097 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1098 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1099 setIcon(imagepath, "../Icons/Disk Image.icns")
1100
1101 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001102
1103 return imagepath
1104
1105
1106def setIcon(filePath, icnsPath):
1107 """
1108 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001109 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001110
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001111 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1112 dirPath = os.path.dirname(__file__)
1113 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1114 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1115 # to connections to the window server.
1116 if not os.path.exists('seticon.app/Contents/MacOS'):
1117 os.makedirs('seticon.app/Contents/MacOS')
1118 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1119 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001120
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001121 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1122 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001123
1124def main():
1125 # First parse options and check if we can perform our work
1126 parseOptions()
1127 checkEnvironment()
1128
Ronald Oussoren1943f862009-03-30 19:39:14 +00001129 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +00001130 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001131
1132 if os.path.exists(WORKDIR):
1133 shutil.rmtree(WORKDIR)
1134 os.mkdir(WORKDIR)
1135
Ronald Oussorena1078192010-04-18 15:25:17 +00001136 os.environ['LC_ALL'] = 'C'
1137
Thomas Wouters477c8d52006-05-27 19:21:47 +00001138 # Then build third-party libraries such as sleepycat DB4.
1139 buildLibraries()
1140
1141 # Now build python itself
1142 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001143
1144 # And then build the documentation
1145 # Remove the Deployment Target from the shell
1146 # environment, it's no longer needed and
1147 # an unexpected build target can cause problems
1148 # when Sphinx and its dependencies need to
1149 # be (re-)installed.
1150 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001151 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001152
1153
1154 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001155 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001156 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001157 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001158
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001159 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001160 getVersion(),))
1161 os.chmod(folder, 0755)
1162 setIcon(folder, "../Icons/Python Folder.icns")
1163
1164 # Create the installer
1165 buildInstaller()
1166
1167 # And copy the readme into the directory containing the installer
1168 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1169
1170 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001171 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001172
1173 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001174 print >> fp, "# BUILD INFO"
1175 print >> fp, "# Date:", time.ctime()
1176 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001177 fp.close()
1178
Thomas Wouters477c8d52006-05-27 19:21:47 +00001179 # And copy it to a DMG
1180 buildDMG()
1181
Thomas Wouters477c8d52006-05-27 19:21:47 +00001182if __name__ == "__main__":
1183 main()