blob: e1263087fc61deaf07a480237cab02530e4afdb7 [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
Ronald Oussoren8e963942010-04-20 06:37:54 +0000445 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
446 print "Setting default PATH: %s"%(os.environ['PATH'])
447
Thomas Wouters477c8d52006-05-27 19:21:47 +0000448
Thomas Wouters89f507f2006-12-13 04:49:30 +0000449def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000450 """
451 Parse arguments and update global settings.
452 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000453 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +0000454 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000455
456 if args is None:
457 args = sys.argv[1:]
458
459 try:
460 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000461 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
462 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000463 except getopt.error, msg:
464 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000465 sys.exit(1)
466
467 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000468 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000469 sys.exit(1)
470
Benjamin Petersonf7239562010-03-19 21:48:54 +0000471 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000472 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000473 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000474 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000475 sys.exit(0)
476
477 elif k in ('-d', '--build-dir'):
478 WORKDIR=v
479
480 elif k in ('--third-party',):
481 DEPSRC=v
482
483 elif k in ('--sdk-path',):
484 SDKPATH=v
485
486 elif k in ('--src-dir',):
487 SRCDIR=v
488
Ronald Oussoren1943f862009-03-30 19:39:14 +0000489 elif k in ('--dep-target', ):
490 DEPTARGET=v
Benjamin Petersonf7239562010-03-19 21:48:54 +0000491 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000492
493 elif k in ('--universal-archs', ):
494 if v in UNIVERSALOPTS:
495 UNIVERSALARCHS = v
496 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersonf7239562010-03-19 21:48:54 +0000497 if deptarget is None:
498 # Select alternate default deployment
499 # target
500 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000501 else:
502 raise NotImplementedError, v
503
Thomas Wouters477c8d52006-05-27 19:21:47 +0000504 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000505 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000506
507 SRCDIR=os.path.abspath(SRCDIR)
508 WORKDIR=os.path.abspath(WORKDIR)
509 SDKPATH=os.path.abspath(SDKPATH)
510 DEPSRC=os.path.abspath(DEPSRC)
511
Benjamin Petersonf7239562010-03-19 21:48:54 +0000512 CC=target_cc_map[DEPTARGET]
513
Benjamin Peterson623918e2008-12-20 22:50:25 +0000514 print "Settings:"
515 print " * Source directory:", SRCDIR
516 print " * Build directory: ", WORKDIR
517 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000518 print " * Third-party source:", DEPSRC
519 print " * Deployment target:", DEPTARGET
520 print " * Universal architectures:", ARCHLIST
Benjamin Petersonf7239562010-03-19 21:48:54 +0000521 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000522 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000523
524
525
526
527def extractArchive(builddir, archiveName):
528 """
529 Extract a source archive into 'builddir'. Returns the path of the
530 extracted archive.
531
532 XXX: This function assumes that archives contain a toplevel directory
533 that is has the same name as the basename of the archive. This is
534 save enough for anything we use.
535 """
536 curdir = os.getcwd()
537 try:
538 os.chdir(builddir)
539 if archiveName.endswith('.tar.gz'):
540 retval = os.path.basename(archiveName[:-7])
541 if os.path.exists(retval):
542 shutil.rmtree(retval)
543 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
544
545 elif archiveName.endswith('.tar.bz2'):
546 retval = os.path.basename(archiveName[:-8])
547 if os.path.exists(retval):
548 shutil.rmtree(retval)
549 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
550
551 elif archiveName.endswith('.tar'):
552 retval = os.path.basename(archiveName[:-4])
553 if os.path.exists(retval):
554 shutil.rmtree(retval)
555 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
556
557 elif archiveName.endswith('.zip'):
558 retval = os.path.basename(archiveName[:-4])
559 if os.path.exists(retval):
560 shutil.rmtree(retval)
561 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
562
563 data = fp.read()
564 xit = fp.close()
565 if xit is not None:
566 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000567 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000568
569 return os.path.join(builddir, retval)
570
571 finally:
572 os.chdir(curdir)
573
574KNOWNSIZES = {
575 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
576 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
577}
578
579def downloadURL(url, fname):
580 """
581 Download the contents of the url into the file.
582 """
583 try:
584 size = os.path.getsize(fname)
585 except OSError:
586 pass
587 else:
588 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000589 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000590 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000591 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000592 fpOut = open(fname, 'wb')
593 block = fpIn.read(10240)
594 try:
595 while block:
596 fpOut.write(block)
597 block = fpIn.read(10240)
598 fpIn.close()
599 fpOut.close()
600 except:
601 try:
602 os.unlink(fname)
603 except:
604 pass
605
606def buildRecipe(recipe, basedir, archList):
607 """
608 Build software using a recipe. This function does the
609 'configure;make;make install' dance for C software, with a possibility
610 to customize this process, basically a poor-mans DarwinPorts.
611 """
612 curdir = os.getcwd()
613
614 name = recipe['name']
615 url = recipe['url']
616 configure = recipe.get('configure', './configure')
617 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
618 shellQuote(basedir)))
619
620 archiveName = os.path.split(url)[-1]
621 sourceArchive = os.path.join(DEPSRC, archiveName)
622
623 if not os.path.exists(DEPSRC):
624 os.mkdir(DEPSRC)
625
626
627 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000628 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000629
630 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000631 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000632 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000633 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000634 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000635
Benjamin Peterson623918e2008-12-20 22:50:25 +0000636 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637 buildDir=os.path.join(WORKDIR, '_bld')
638 if not os.path.exists(buildDir):
639 os.mkdir(buildDir)
640
641 workDir = extractArchive(buildDir, sourceArchive)
642 os.chdir(workDir)
643 if 'buildDir' in recipe:
644 os.chdir(recipe['buildDir'])
645
646
647 for fn in recipe.get('patches', ()):
648 if fn.startswith('http://'):
649 # Download the patch before applying it.
650 path = os.path.join(DEPSRC, os.path.basename(fn))
651 downloadURL(fn, path)
652 fn = path
653
654 fn = os.path.join(curdir, fn)
655 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
656 shellQuote(fn),))
657
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000658 if configure is not None:
659 configure_args = [
660 "--prefix=/usr/local",
661 "--enable-static",
662 "--disable-shared",
663 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
664 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000666 if 'configure_pre' in recipe:
667 args = list(recipe['configure_pre'])
668 if '--disable-static' in args:
669 configure_args.remove('--enable-static')
670 if '--enable-shared' in args:
671 configure_args.remove('--disable-shared')
672 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000673
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000674 if recipe.get('useLDFlags', 1):
675 configure_args.extend([
676 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
677 ' -arch '.join(archList),
678 shellQuote(SDKPATH)[1:-1],
679 shellQuote(basedir)[1:-1],),
680 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000681 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000682 shellQuote(basedir)[1:-1],
683 ' -arch '.join(archList)),
684 ])
685 else:
686 configure_args.extend([
687 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
688 ' -arch '.join(archList),
689 shellQuote(SDKPATH)[1:-1],
690 shellQuote(basedir)[1:-1],),
691 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000692
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000693 if 'configure_post' in recipe:
694 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000695
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000696 configure_args.insert(0, configure)
697 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000698
Benjamin Peterson623918e2008-12-20 22:50:25 +0000699 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000700 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000701
Benjamin Peterson623918e2008-12-20 22:50:25 +0000702 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000703 runCommand('{ ' + install + ' ;} 2>&1')
704
Benjamin Peterson623918e2008-12-20 22:50:25 +0000705 print "Done %s"%(name,)
706 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000707
708 os.chdir(curdir)
709
710def buildLibraries():
711 """
712 Build our dependencies into $WORKDIR/libraries/usr/local
713 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000714 print ""
715 print "Building required libraries"
716 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000717 universal = os.path.join(WORKDIR, 'libraries')
718 os.mkdir(universal)
719 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
720 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
721
Ronald Oussoren1943f862009-03-30 19:39:14 +0000722 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000723 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000724
725
726
727def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000728 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000729 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000730 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000731 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000732 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000733 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000734 curDir = os.getcwd()
735 os.chdir(buildDir)
736 runCommand('make update')
Martin v. Löwis621522b2010-04-22 13:18:09 +0000737 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000738 os.chdir(curDir)
Benjamin Petersonf7239562010-03-19 21:48:54 +0000739 if not os.path.exists(docdir):
740 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000741 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000742
743
744def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000745 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000746
747 buildDir = os.path.join(WORKDIR, '_bld', 'python')
748 rootDir = os.path.join(WORKDIR, '_root')
749
750 if os.path.exists(buildDir):
751 shutil.rmtree(buildDir)
752 if os.path.exists(rootDir):
753 shutil.rmtree(rootDir)
754 os.mkdir(buildDir)
755 os.mkdir(rootDir)
756 os.mkdir(os.path.join(rootDir, 'empty-dir'))
757 curdir = os.getcwd()
758 os.chdir(buildDir)
759
760 # Not sure if this is still needed, the original build script
761 # claims that parts of the install assume python.exe exists.
762 os.symlink('python', os.path.join(buildDir, 'python.exe'))
763
764 # Extract the version from the configure file, needed to calculate
765 # several paths.
766 version = getVersion()
767
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000768 # Since the extra libs are not in their installed framework location
769 # during the build, augment the library path so that the interpreter
770 # will find them during its extension import sanity checks.
771 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
772 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000773 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000774 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersonf7239562010-03-19 21:48:54 +0000775 "--with-universal-archs=%s "
776 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000777 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
778 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
779 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
780 UNIVERSALARCHS,
Benjamin Petersonf7239562010-03-19 21:48:54 +0000781 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000782 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783 shellQuote(WORKDIR)[1:-1]))
784
Benjamin Peterson623918e2008-12-20 22:50:25 +0000785 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000786 runCommand("make")
787
Benjamin Petersonf7239562010-03-19 21:48:54 +0000788 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000789 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000790 shellQuote(rootDir)))
791
Benjamin Peterson623918e2008-12-20 22:50:25 +0000792 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000793 runCommand("make frameworkinstallextras DESTDIR=%s"%(
794 shellQuote(rootDir)))
795
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000796 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000797 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000798 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
799 runCommand("mv %s/* %s"%(
800 shellQuote(os.path.join(
801 WORKDIR, 'libraries', 'Library', 'Frameworks',
802 'Python.framework', 'Versions', getVersion(),
803 'lib')),
804 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
805 'Python.framework', 'Versions', getVersion(),
806 'lib'))))
807
Benjamin Peterson623918e2008-12-20 22:50:25 +0000808 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000810 gid = grp.getgrnam('admin').gr_gid
811
Thomas Wouters477c8d52006-05-27 19:21:47 +0000812 for dirpath, dirnames, filenames in os.walk(frmDir):
813 for dn in dirnames:
814 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000815 os.chown(os.path.join(dirpath, dn), -1, gid)
816
Thomas Wouters477c8d52006-05-27 19:21:47 +0000817
818 for fn in filenames:
819 if os.path.islink(fn):
820 continue
821
822 # "chmod g+w $fn"
823 p = os.path.join(dirpath, fn)
824 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000825 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
826 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827
828 # We added some directories to the search path during the configure
829 # phase. Remove those because those directories won't be there on
830 # the end-users system.
831 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
832 'Versions', version, 'lib', 'python%s'%(version,),
833 'config', 'Makefile')
834 fp = open(path, 'r')
835 data = fp.read()
836 fp.close()
837
838 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
839 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
840 fp = open(path, 'w')
841 fp.write(data)
842 fp.close()
843
844 # Add symlinks in /usr/local/bin, using relative links
845 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
846 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
847 'Python.framework', 'Versions', version, 'bin')
848 if os.path.exists(usr_local_bin):
849 shutil.rmtree(usr_local_bin)
850 os.makedirs(usr_local_bin)
851 for fn in os.listdir(
852 os.path.join(frmDir, 'Versions', version, 'bin')):
853 os.symlink(os.path.join(to_framework, fn),
854 os.path.join(usr_local_bin, fn))
855
856 os.chdir(curdir)
857
Benjamin Petersonf7239562010-03-19 21:48:54 +0000858 if PYTHON_3:
859 # Remove the 'Current' link, that way we don't accidently mess
860 # with an already installed version of python 2
861 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
862 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000863
864def patchFile(inPath, outPath):
865 data = fileContents(inPath)
866 data = data.replace('$FULL_VERSION', getFullVersion())
867 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000868 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000869 data = data.replace('$ARCHITECTURES', "i386, ppc")
870 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000871
872 # This one is not handy as a template variable
873 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000874 fp = open(outPath, 'wb')
875 fp.write(data)
876 fp.close()
877
878def patchScript(inPath, outPath):
879 data = fileContents(inPath)
880 data = data.replace('@PYVER@', getVersion())
881 fp = open(outPath, 'wb')
882 fp.write(data)
883 fp.close()
884 os.chmod(outPath, 0755)
885
886
887
888def packageFromRecipe(targetDir, recipe):
889 curdir = os.getcwd()
890 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000891 # The major version (such as 2.5) is included in the package name
892 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000893 # common.
894 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000895 srcdir = recipe.get('source')
896 pkgroot = recipe.get('topdir', srcdir)
897 postflight = recipe.get('postflight')
898 readme = textwrap.dedent(recipe['readme'])
899 isRequired = recipe.get('required', True)
900
Benjamin Peterson623918e2008-12-20 22:50:25 +0000901 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000902
903 # Substitute some variables
904 textvars = dict(
905 VER=getVersion(),
906 FULLVER=getFullVersion(),
907 )
908 readme = readme % textvars
909
910 if pkgroot is not None:
911 pkgroot = pkgroot % textvars
912 else:
913 pkgroot = '/'
914
915 if srcdir is not None:
916 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
917 srcdir = srcdir % textvars
918
919 if postflight is not None:
920 postflight = os.path.abspath(postflight)
921
922 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
923 os.makedirs(packageContents)
924
925 if srcdir is not None:
926 os.chdir(srcdir)
927 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
928 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
929 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
930
931 fn = os.path.join(packageContents, 'PkgInfo')
932 fp = open(fn, 'w')
933 fp.write('pmkrpkg1')
934 fp.close()
935
936 rsrcDir = os.path.join(packageContents, "Resources")
937 os.mkdir(rsrcDir)
938 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
939 fp.write(readme)
940 fp.close()
941
942 if postflight is not None:
943 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
944
945 vers = getFullVersion()
946 major, minor = map(int, getVersion().split('.', 2))
947 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000948 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
949 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
950 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000951 CFBundleShortVersionString=vers,
952 IFMajorVersion=major,
953 IFMinorVersion=minor,
954 IFPkgFormatVersion=0.10000000149011612,
955 IFPkgFlagAllowBackRev=False,
956 IFPkgFlagAuthorizationAction="RootAuthorization",
957 IFPkgFlagDefaultLocation=pkgroot,
958 IFPkgFlagFollowLinks=True,
959 IFPkgFlagInstallFat=True,
960 IFPkgFlagIsRequired=isRequired,
961 IFPkgFlagOverwritePermissions=False,
962 IFPkgFlagRelocatable=False,
963 IFPkgFlagRestartAction="NoRestart",
964 IFPkgFlagRootVolumeOnly=True,
965 IFPkgFlagUpdateInstalledLangauges=False,
966 )
967 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
968
969 pl = Plist(
970 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +0000971 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000972 IFPkgDescriptionVersion=vers,
973 )
974 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
975
976 finally:
977 os.chdir(curdir)
978
979
980def makeMpkgPlist(path):
981
982 vers = getFullVersion()
983 major, minor = map(int, getVersion().split('.', 2))
984
985 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000986 CFBundleGetInfoString="Python %s"%(vers,),
987 CFBundleIdentifier='org.python.Python',
988 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +0000989 CFBundleShortVersionString=vers,
990 IFMajorVersion=major,
991 IFMinorVersion=minor,
992 IFPkgFlagComponentDirectory="Contents/Packages",
993 IFPkgFlagPackageList=[
994 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000995 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersonf7239562010-03-19 21:48:54 +0000996 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000997 )
Benjamin Petersonf7239562010-03-19 21:48:54 +0000998 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000999 ],
1000 IFPkgFormatVersion=0.10000000149011612,
1001 IFPkgFlagBackgroundScaling="proportional",
1002 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001003 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001004 )
1005
1006 writePlist(pl, path)
1007
1008
1009def buildInstaller():
1010
1011 # Zap all compiled files
1012 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1013 for fn in filenames:
1014 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1015 os.unlink(os.path.join(dirpath, fn))
1016
1017 outdir = os.path.join(WORKDIR, 'installer')
1018 if os.path.exists(outdir):
1019 shutil.rmtree(outdir)
1020 os.mkdir(outdir)
1021
Ronald Oussoren1943f862009-03-30 19:39:14 +00001022 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001023 pkgcontents = os.path.join(pkgroot, 'Packages')
1024 os.makedirs(pkgcontents)
Benjamin Petersonf7239562010-03-19 21:48:54 +00001025 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001026 packageFromRecipe(pkgcontents, recipe)
1027
1028 rsrcDir = os.path.join(pkgroot, 'Resources')
1029
1030 fn = os.path.join(pkgroot, 'PkgInfo')
1031 fp = open(fn, 'w')
1032 fp.write('pmkrpkg1')
1033 fp.close()
1034
1035 os.mkdir(rsrcDir)
1036
1037 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1038 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001039 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001040 IFPkgDescriptionVersion=getVersion(),
1041 )
1042
1043 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1044 for fn in os.listdir('resources'):
1045 if fn == '.svn': continue
1046 if fn.endswith('.jpg'):
1047 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1048 else:
1049 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1050
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001051 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001052
1053
1054def installSize(clear=False, _saved=[]):
1055 if clear:
1056 del _saved[:]
1057 if not _saved:
1058 data = captureCommand("du -ks %s"%(
1059 shellQuote(os.path.join(WORKDIR, '_root'))))
1060 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1061 return _saved[0]
1062
1063
1064def buildDMG():
1065 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001066 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001067 """
1068 outdir = os.path.join(WORKDIR, 'diskimage')
1069 if os.path.exists(outdir):
1070 shutil.rmtree(outdir)
1071
1072 imagepath = os.path.join(outdir,
Benjamin Petersonf7239562010-03-19 21:48:54 +00001073 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001074 if INCLUDE_TIMESTAMP:
Benjamin Petersonf7239562010-03-19 21:48:54 +00001075 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076 imagepath = imagepath + '.dmg'
1077
1078 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001079 volname='Python %s'%(getFullVersion())
1080 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1081 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001082 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001083 shellQuote(imagepath + ".tmp.dmg" )))
1084
1085
1086 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1087 os.mkdir(os.path.join(WORKDIR, "mnt"))
1088 runCommand("hdiutil attach %s -mountroot %s"%(
1089 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1090
1091 # Custom icon for the DMG, shown when the DMG is mounted.
1092 shutil.copy("../Icons/Disk Image.icns",
1093 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1094 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1095 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1096
1097 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1098
1099 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1100 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1101 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1102 setIcon(imagepath, "../Icons/Disk Image.icns")
1103
1104 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001105
1106 return imagepath
1107
1108
1109def setIcon(filePath, icnsPath):
1110 """
1111 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001112 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001113
Ronald Oussoren72a8b7d2010-04-30 15:01:23 +00001114 dirPath = os.path.normpath(os.path.dirname(__file__))
1115 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001116 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1117 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1118 # to connections to the window server.
Ronald Oussoren72a8b7d2010-04-30 15:01:23 +00001119 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1120 if not os.path.exists(appPath):
1121 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001122 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1123 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001124
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001125 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1126 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001127
1128def main():
1129 # First parse options and check if we can perform our work
1130 parseOptions()
1131 checkEnvironment()
1132
Ronald Oussoren1943f862009-03-30 19:39:14 +00001133 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersonf7239562010-03-19 21:48:54 +00001134 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001135
1136 if os.path.exists(WORKDIR):
1137 shutil.rmtree(WORKDIR)
1138 os.mkdir(WORKDIR)
1139
Ronald Oussorena1078192010-04-18 15:25:17 +00001140 os.environ['LC_ALL'] = 'C'
1141
Thomas Wouters477c8d52006-05-27 19:21:47 +00001142 # Then build third-party libraries such as sleepycat DB4.
1143 buildLibraries()
1144
1145 # Now build python itself
1146 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001147
1148 # And then build the documentation
1149 # Remove the Deployment Target from the shell
1150 # environment, it's no longer needed and
1151 # an unexpected build target can cause problems
1152 # when Sphinx and its dependencies need to
1153 # be (re-)installed.
1154 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001155 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001156
1157
1158 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001160 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001161 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001162
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001163 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164 getVersion(),))
1165 os.chmod(folder, 0755)
1166 setIcon(folder, "../Icons/Python Folder.icns")
1167
1168 # Create the installer
1169 buildInstaller()
1170
1171 # And copy the readme into the directory containing the installer
1172 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1173
1174 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001175 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001176
1177 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001178 print >> fp, "# BUILD INFO"
1179 print >> fp, "# Date:", time.ctime()
1180 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001181 fp.close()
1182
Thomas Wouters477c8d52006-05-27 19:21:47 +00001183 # And copy it to a DMG
1184 buildDMG()
1185
Thomas Wouters477c8d52006-05-27 19:21:47 +00001186if __name__ == "__main__":
1187 main()