blob: 688834e3d506af20c5500be346b3dd66c156f57f [file] [log] [blame]
Ned Deily7d9cf832010-11-27 16:42:15 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
4It requires at least Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK for
532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
11Python 2.4.
Thomas Wouters477c8d52006-05-27 19:21:47 +000012
13Usage: see USAGE variable in the script.
14"""
Ned Deily7d9cf832010-11-27 16:42:15 -080015import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
16try:
17 import urllib2 as urllib_request
18except ImportError:
19 import urllib.request as urllib_request
20
21STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
22 | stat.S_IRGRP | stat.S_IXGRP
23 | stat.S_IROTH | stat.S_IXOTH )
24
25STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
26 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
27 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000028
Thomas Wouters89f507f2006-12-13 04:49:30 +000029INCLUDE_TIMESTAMP = 1
30VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000031
32from plistlib import Plist
33
Thomas Wouters477c8d52006-05-27 19:21:47 +000034try:
35 from plistlib import writePlist
36except ImportError:
37 # We're run using python2.3
38 def writePlist(plist, path):
39 plist.write(path)
40
41def shellQuote(value):
42 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000043 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000044 a shell command.
45 """
46 return "'%s'"%(value.replace("'", "'\"'\"'"))
47
48def grepValue(fn, variable):
49 variable = variable + '='
50 for ln in open(fn, 'r'):
51 if ln.startswith(variable):
52 value = ln[len(variable):].strip()
53 return value[1:-1]
Ned Deily7d9cf832010-11-27 16:42:15 -080054 raise RuntimeError("Cannot find variable %s" % variable[:-1])
55
56_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000057
58def getVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080059 global _cache_getVersion
60 if _cache_getVersion is None:
61 _cache_getVersion = grepValue(
62 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
63 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000064
Benjamin Petersond9b7d482010-03-19 21:42:45 +000065def getVersionTuple():
66 return tuple([int(n) for n in getVersion().split('.')])
67
Ned Deily7d9cf832010-11-27 16:42:15 -080068def getVersionMajorMinor():
69 return tuple([int(n) for n in getVersion().split('.', 2)])
70
71_cache_getFullVersion = None
72
Thomas Wouters477c8d52006-05-27 19:21:47 +000073def getFullVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080074 global _cache_getFullVersion
75 if _cache_getFullVersion is not None:
76 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000077 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
78 for ln in open(fn):
79 if 'PY_VERSION' in ln:
Ned Deily7d9cf832010-11-27 16:42:15 -080080 _cache_getFullVersion = ln.split()[-1][1:-1]
81 return _cache_getFullVersion
82 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000083
Thomas Wouters89f507f2006-12-13 04:49:30 +000084# The directory we'll use to create the build (will be erased and recreated)
85WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000086
Thomas Wouters89f507f2006-12-13 04:49:30 +000087# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000088# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000089DEPSRC = os.path.join(WORKDIR, 'third-party')
90DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000091
92# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +000093
94### There are some issues with the SDK selection below here,
95### The resulting binary doesn't work on all platforms that
96### it should. Always default to the 10.4u SDK until that
97### isue is resolved.
98###
99##if int(os.uname()[2].split('.')[0]) == 8:
100## # Explicitly use the 10.4u (universal) SDK when
101## # building on 10.4, the system headers are not
102## # useable for a universal build
103## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
104##else:
105## SDKPATH = "/"
106
Thomas Wouters89f507f2006-12-13 04:49:30 +0000107SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000108
Ronald Oussoren1943f862009-03-30 19:39:14 +0000109universal_opts_map = { '32-bit': ('i386', 'ppc',),
110 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000111 'intel': ('i386', 'x86_64'),
112 '3-way': ('ppc', 'i386', 'x86_64'),
113 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
114default_target_map = {
115 '64-bit': '10.5',
116 '3-way': '10.5',
117 'intel': '10.5',
118 'all': '10.5',
119}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000120
121UNIVERSALOPTS = tuple(universal_opts_map.keys())
122
123UNIVERSALARCHS = '32-bit'
124
125ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126
Ezio Melotti42da6632011-03-15 05:18:48 +0200127# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000128SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000129 os.path.dirname(
130 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000131 os.path.abspath(__file__
132 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000133
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
135DEPTARGET = '10.3'
136
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000137target_cc_map = {
138 '10.3': 'gcc-4.0',
139 '10.4': 'gcc-4.0',
Ned Deily1bc276d2012-06-24 01:27:51 -0700140 '10.5': 'gcc-4.2',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000141 '10.6': 'gcc-4.2',
Ned Deily1bc276d2012-06-24 01:27:51 -0700142 '10.7': 'clang',
143 '10.8': 'clang',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000144}
145
146CC = target_cc_map[DEPTARGET]
147
148PYTHON_3 = getVersionTuple() >= (3, 0)
149
Thomas Wouters89f507f2006-12-13 04:49:30 +0000150USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000151 Usage: build_python [options]
152
153 Options:
154 -? or -h: Show this message
155 -b DIR
156 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
157 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
158 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
159 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000160 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
161 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000162""")% globals()
163
Ned Deily2910a7b2012-07-30 02:35:58 -0700164# Dict of object file names with shared library names to check after building.
165# This is to ensure that we ended up dynamically linking with the shared
166# library paths and versions we expected. For example:
167# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
168# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
169# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
170EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000171
172# Instructions for building libraries that are necessary for building a
173# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000174# [The recipes are defined here for convenience but instantiated later after
175# command line options have been processed.]
176def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000177 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000178
Ned Deily4d4c0ee2012-04-01 00:17:33 -0700179 result.extend([
180 dict(
181 name="XZ 5.0.3",
182 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
183 checksum='fefe52f9ecd521de2a8ce38c21a27574',
184 configure_pre=[
185 '--disable-dependency-tracking',
186 ]
187 )
188 ])
189
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000190 if DEPTARGET < '10.5':
191 result.extend([
192 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000193 name="Bzip2 1.0.6",
194 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
195 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000196 configure=None,
197 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
198 CC,
199 shellQuote(os.path.join(WORKDIR, 'libraries')),
200 ' -arch '.join(ARCHLIST),
201 SDKPATH,
202 ),
203 ),
204 dict(
205 name="ZLib 1.2.3",
206 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
207 checksum='debc62758716a169df9f62e6ab2bc634',
208 configure=None,
209 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
210 CC,
211 shellQuote(os.path.join(WORKDIR, 'libraries')),
212 ' -arch '.join(ARCHLIST),
213 SDKPATH,
214 ),
215 ),
216 dict(
217 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000218 name="GNU Readline 6.1.2",
219 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
220 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000221 patchlevel='0',
222 patches=[
223 # The readline maintainers don't do actual micro releases, but
224 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000225 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
226 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000227 ]
228 ),
229 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000230 name="SQLite 3.7.4",
231 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
232 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
233 configure_env=('CFLAGS="-Os'
234 ' -DSQLITE_ENABLE_FTS3'
235 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
236 ' -DSQLITE_ENABLE_RTREE'
237 ' -DSQLITE_TCL=0'
238 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000239 configure_pre=[
240 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000241 '--enable-shared=no',
242 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000243 '--disable-readline',
244 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000245 ]
246 ),
247 dict(
248 name="NCurses 5.5",
249 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
250 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
251 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000252 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000253 "--without-cxx",
254 "--without-ada",
255 "--without-progs",
256 "--without-curses-h",
257 "--enable-shared",
258 "--with-shared",
259 "--datadir=/usr/share",
260 "--sysconfdir=/etc",
261 "--sharedstatedir=/usr/com",
262 "--with-terminfo-dirs=/usr/share/terminfo",
263 "--with-default-terminfo-dir=/usr/share/terminfo",
264 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
265 "--enable-termcap",
266 ],
267 patches=[
268 "ncurses-5.5.patch",
269 ],
270 useLDFlags=False,
271 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
272 shellQuote(os.path.join(WORKDIR, 'libraries')),
273 shellQuote(os.path.join(WORKDIR, 'libraries')),
274 getVersion(),
275 ),
276 ),
277 ])
278
Ned Deily4f7ff782011-01-15 05:29:12 +0000279 if not PYTHON_3:
280 result.extend([
281 dict(
282 name="Sleepycat DB 4.7.25",
283 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
284 checksum='ec2b87e833779681a0c3a814aa71359e',
285 buildDir="build_unix",
286 configure="../dist/configure",
287 configure_pre=[
288 '--includedir=/usr/local/include/db4',
289 ]
290 ),
291 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000292
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000293 return result
294
Thomas Wouters477c8d52006-05-27 19:21:47 +0000295
Thomas Wouters477c8d52006-05-27 19:21:47 +0000296# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000297def pkg_recipes():
298 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
299 result = [
300 dict(
301 name="PythonFramework",
302 long_name="Python Framework",
303 source="/Library/Frameworks/Python.framework",
304 readme="""\
305 This package installs Python.framework, that is the python
306 interpreter and the standard library. This also includes Python
307 wrappers for lots of Mac OS X API's.
308 """,
309 postflight="scripts/postflight.framework",
310 selected='selected',
311 ),
312 dict(
313 name="PythonApplications",
314 long_name="GUI Applications",
315 source="/Applications/Python %(VER)s",
316 readme="""\
317 This package installs IDLE (an interactive Python IDE),
318 Python Launcher and Build Applet (create application bundles
319 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000320
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000321 It also installs a number of examples and demos.
322 """,
323 required=False,
324 selected='selected',
325 ),
326 dict(
327 name="PythonUnixTools",
328 long_name="UNIX command-line tools",
329 source="/usr/local/bin",
330 readme="""\
331 This package installs the unix tools in /usr/local/bin for
332 compatibility with older releases of Python. This package
333 is not necessary to use Python.
334 """,
335 required=False,
336 selected='selected',
337 ),
338 dict(
339 name="PythonDocumentation",
340 long_name="Python Documentation",
341 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
342 source="/pydocs",
343 readme="""\
344 This package installs the python documentation at a location
345 that is useable for pydoc and IDLE. If you have installed Xcode
346 it will also install a link to the documentation in
347 /Developer/Documentation/Python
348 """,
349 postflight="scripts/postflight.documentation",
350 required=False,
351 selected='selected',
352 ),
353 dict(
354 name="PythonProfileChanges",
355 long_name="Shell profile updater",
356 readme="""\
357 This packages updates your shell profile to make sure that
358 the Python tools are found by your shell in preference of
359 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000361 If you don't install this package you'll have to add
362 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
363 to your PATH by hand.
364 """,
365 postflight="scripts/postflight.patch-profile",
366 topdir="/Library/Frameworks/Python.framework",
367 source="/empty-dir",
368 required=False,
369 selected=unselected_for_python3,
370 ),
371 ]
372
Ned Deily430d7a32012-06-24 00:19:31 -0700373 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000374 result.append(
375 dict(
376 name="PythonSystemFixes",
377 long_name="Fix system Python",
378 readme="""\
379 This package updates the system python installation on
380 Mac OS X 10.3 to ensure that you can build new python extensions
381 using that copy of python after installing this version.
382 """,
383 postflight="../Tools/fixapplepython23.py",
384 topdir="/Library/Frameworks/Python.framework",
385 source="/empty-dir",
386 required=False,
387 selected=unselected_for_python3,
388 )
389 )
390 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000391
Thomas Wouters477c8d52006-05-27 19:21:47 +0000392def fatal(msg):
393 """
394 A fatal error, bail out.
395 """
396 sys.stderr.write('FATAL: ')
397 sys.stderr.write(msg)
398 sys.stderr.write('\n')
399 sys.exit(1)
400
401def fileContents(fn):
402 """
403 Return the contents of the named file
404 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800405 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000406
407def runCommand(commandline):
408 """
Ezio Melotti13925002011-03-16 11:05:33 +0200409 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000410 unless the command fails.
411 """
412 fd = os.popen(commandline, 'r')
413 data = fd.read()
414 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000415 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000416 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800417 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000418
419 if VERBOSE:
420 sys.stdout.write(data); sys.stdout.flush()
421
422def captureCommand(commandline):
423 fd = os.popen(commandline, 'r')
424 data = fd.read()
425 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000426 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000427 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800428 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000429
430 return data
431
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000432def getTclTkVersion(configfile, versionline):
433 """
434 search Tcl or Tk configuration file for version line
435 """
436 try:
437 f = open(configfile, "r")
438 except:
439 fatal("Framework configuration file not found: %s" % configfile)
440
441 for l in f:
442 if l.startswith(versionline):
443 f.close()
444 return l
445
446 fatal("Version variable %s not found in framework configuration file: %s"
447 % (versionline, configfile))
448
Thomas Wouters477c8d52006-05-27 19:21:47 +0000449def checkEnvironment():
450 """
451 Check that we're running on a supported system.
452 """
453
Ned Deilye59e4c52011-01-29 18:56:28 +0000454 if sys.version_info[0:2] < (2, 4):
455 fatal("This script must be run with Python 2.4 or later")
456
Thomas Wouters477c8d52006-05-27 19:21:47 +0000457 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000458 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000459
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000460 if int(platform.release().split('.')[0]) < 8:
461 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000462
463 if not os.path.exists(SDKPATH):
464 fatal("Please install the latest version of Xcode and the %s SDK"%(
465 os.path.basename(SDKPATH[:-4])))
466
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000467 # Because we only support dynamic load of only one major/minor version of
468 # Tcl/Tk, ensure:
469 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily2910a7b2012-07-30 02:35:58 -0700470 # higher than the Apple-supplied system version in
471 # SDKROOT/System/Library/Frameworks
472 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
473 # in) SDKROOT/Library/Frameworks with the same version as the system
474 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000475
Ned Deily2910a7b2012-07-30 02:35:58 -0700476 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000477 for framework in ['Tcl', 'Tk']:
Ned Deily2910a7b2012-07-30 02:35:58 -0700478 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000479 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily2910a7b2012-07-30 02:35:58 -0700480 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000481 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily2910a7b2012-07-30 02:35:58 -0700482 frameworks[framework] = os.readlink(sysfw)
483 if not os.path.exists(libfw):
484 fatal("Please install a link to a current %s %s as %s so "
485 "the user can override the system framework."
486 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000487 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000488 fatal("Version of %s must match %s" % (libfw, sysfw) )
489 if os.path.exists(usrfw):
490 fatal("Please rename %s to avoid possible dynamic load issues."
491 % usrfw)
492
Ned Deily2910a7b2012-07-30 02:35:58 -0700493 if frameworks['Tcl'] != frameworks['Tk']:
494 fatal("The Tcl and Tk frameworks are not the same version.")
495
496 # add files to check after build
497 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
498 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
499 % frameworks['Tcl'],
500 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
501 % frameworks['Tk'],
502 ]
503
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000504 # Remove inherited environment variables which might influence build
505 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
506 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
507 for ev in list(os.environ):
508 for prefix in environ_var_prefixes:
509 if ev.startswith(prefix) :
Ned Deily7d9cf832010-11-27 16:42:15 -0800510 print("INFO: deleting environment variable %s=%s" % (
511 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000512 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000514 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
Ned Deily7d9cf832010-11-27 16:42:15 -0800515 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000516
Thomas Wouters477c8d52006-05-27 19:21:47 +0000517
Thomas Wouters89f507f2006-12-13 04:49:30 +0000518def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000519 """
520 Parse arguments and update global settings.
521 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000522 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000523 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000524
525 if args is None:
526 args = sys.argv[1:]
527
528 try:
529 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000530 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
531 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily7d9cf832010-11-27 16:42:15 -0800532 except getopt.GetoptError:
533 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000534 sys.exit(1)
535
536 if args:
Ned Deily7d9cf832010-11-27 16:42:15 -0800537 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000538 sys.exit(1)
539
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000540 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000541 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000542 if k in ('-h', '-?', '--help'):
Ned Deily7d9cf832010-11-27 16:42:15 -0800543 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000544 sys.exit(0)
545
546 elif k in ('-d', '--build-dir'):
547 WORKDIR=v
548
549 elif k in ('--third-party',):
550 DEPSRC=v
551
552 elif k in ('--sdk-path',):
553 SDKPATH=v
554
555 elif k in ('--src-dir',):
556 SRCDIR=v
557
Ronald Oussoren1943f862009-03-30 19:39:14 +0000558 elif k in ('--dep-target', ):
559 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000560 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000561
562 elif k in ('--universal-archs', ):
563 if v in UNIVERSALOPTS:
564 UNIVERSALARCHS = v
565 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000566 if deptarget is None:
567 # Select alternate default deployment
568 # target
569 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000570 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800571 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000572
Thomas Wouters477c8d52006-05-27 19:21:47 +0000573 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800574 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000575
576 SRCDIR=os.path.abspath(SRCDIR)
577 WORKDIR=os.path.abspath(WORKDIR)
578 SDKPATH=os.path.abspath(SDKPATH)
579 DEPSRC=os.path.abspath(DEPSRC)
580
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000581 CC=target_cc_map[DEPTARGET]
582
Ned Deily7d9cf832010-11-27 16:42:15 -0800583 print("Settings:")
584 print(" * Source directory:", SRCDIR)
585 print(" * Build directory: ", WORKDIR)
586 print(" * SDK location: ", SDKPATH)
587 print(" * Third-party source:", DEPSRC)
588 print(" * Deployment target:", DEPTARGET)
589 print(" * Universal architectures:", ARCHLIST)
590 print(" * C compiler:", CC)
591 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000592
593
594
595
596def extractArchive(builddir, archiveName):
597 """
598 Extract a source archive into 'builddir'. Returns the path of the
599 extracted archive.
600
601 XXX: This function assumes that archives contain a toplevel directory
602 that is has the same name as the basename of the archive. This is
603 save enough for anything we use.
604 """
605 curdir = os.getcwd()
606 try:
607 os.chdir(builddir)
608 if archiveName.endswith('.tar.gz'):
609 retval = os.path.basename(archiveName[:-7])
610 if os.path.exists(retval):
611 shutil.rmtree(retval)
612 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
613
614 elif archiveName.endswith('.tar.bz2'):
615 retval = os.path.basename(archiveName[:-8])
616 if os.path.exists(retval):
617 shutil.rmtree(retval)
618 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
619
620 elif archiveName.endswith('.tar'):
621 retval = os.path.basename(archiveName[:-4])
622 if os.path.exists(retval):
623 shutil.rmtree(retval)
624 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
625
626 elif archiveName.endswith('.zip'):
627 retval = os.path.basename(archiveName[:-4])
628 if os.path.exists(retval):
629 shutil.rmtree(retval)
630 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
631
632 data = fp.read()
633 xit = fp.close()
634 if xit is not None:
635 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800636 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637
638 return os.path.join(builddir, retval)
639
640 finally:
641 os.chdir(curdir)
642
643KNOWNSIZES = {
644 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
645 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
646}
647
648def downloadURL(url, fname):
649 """
650 Download the contents of the url into the file.
651 """
652 try:
653 size = os.path.getsize(fname)
654 except OSError:
655 pass
656 else:
657 if KNOWNSIZES.get(url) == size:
Ned Deily7d9cf832010-11-27 16:42:15 -0800658 print("Using existing file for", url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000659 return
Ned Deily7d9cf832010-11-27 16:42:15 -0800660 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000661 fpOut = open(fname, 'wb')
662 block = fpIn.read(10240)
663 try:
664 while block:
665 fpOut.write(block)
666 block = fpIn.read(10240)
667 fpIn.close()
668 fpOut.close()
669 except:
670 try:
671 os.unlink(fname)
672 except:
673 pass
674
675def buildRecipe(recipe, basedir, archList):
676 """
677 Build software using a recipe. This function does the
678 'configure;make;make install' dance for C software, with a possibility
679 to customize this process, basically a poor-mans DarwinPorts.
680 """
681 curdir = os.getcwd()
682
683 name = recipe['name']
684 url = recipe['url']
685 configure = recipe.get('configure', './configure')
686 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
687 shellQuote(basedir)))
688
689 archiveName = os.path.split(url)[-1]
690 sourceArchive = os.path.join(DEPSRC, archiveName)
691
692 if not os.path.exists(DEPSRC):
693 os.mkdir(DEPSRC)
694
695
696 if os.path.exists(sourceArchive):
Ned Deily7d9cf832010-11-27 16:42:15 -0800697 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000698
699 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800700 print("Did not find local copy of %s"%(name,))
701 print("Downloading %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000702 downloadURL(url, sourceArchive)
Ned Deily7d9cf832010-11-27 16:42:15 -0800703 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704
Ned Deily7d9cf832010-11-27 16:42:15 -0800705 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000706 buildDir=os.path.join(WORKDIR, '_bld')
707 if not os.path.exists(buildDir):
708 os.mkdir(buildDir)
709
710 workDir = extractArchive(buildDir, sourceArchive)
711 os.chdir(workDir)
712 if 'buildDir' in recipe:
713 os.chdir(recipe['buildDir'])
714
715
716 for fn in recipe.get('patches', ()):
717 if fn.startswith('http://'):
718 # Download the patch before applying it.
719 path = os.path.join(DEPSRC, os.path.basename(fn))
720 downloadURL(fn, path)
721 fn = path
722
723 fn = os.path.join(curdir, fn)
724 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
725 shellQuote(fn),))
726
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000727 if configure is not None:
728 configure_args = [
729 "--prefix=/usr/local",
730 "--enable-static",
731 "--disable-shared",
732 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
733 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000735 if 'configure_pre' in recipe:
736 args = list(recipe['configure_pre'])
737 if '--disable-static' in args:
738 configure_args.remove('--enable-static')
739 if '--enable-shared' in args:
740 configure_args.remove('--disable-shared')
741 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000742
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000743 if recipe.get('useLDFlags', 1):
744 configure_args.extend([
745 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
746 ' -arch '.join(archList),
747 shellQuote(SDKPATH)[1:-1],
748 shellQuote(basedir)[1:-1],),
749 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000750 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000751 shellQuote(basedir)[1:-1],
752 ' -arch '.join(archList)),
753 ])
754 else:
755 configure_args.extend([
756 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
757 ' -arch '.join(archList),
758 shellQuote(SDKPATH)[1:-1],
759 shellQuote(basedir)[1:-1],),
760 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000761
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000762 if 'configure_post' in recipe:
763 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000764
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000765 configure_args.insert(0, configure)
766 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000767
Ned Deily4f7ff782011-01-15 05:29:12 +0000768 if 'configure_env' in recipe:
769 configure_args.insert(0, recipe['configure_env'])
770
Ned Deily7d9cf832010-11-27 16:42:15 -0800771 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000772 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000773
Ned Deily7d9cf832010-11-27 16:42:15 -0800774 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000775 runCommand('{ ' + install + ' ;} 2>&1')
776
Ned Deily7d9cf832010-11-27 16:42:15 -0800777 print("Done %s"%(name,))
778 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000779
780 os.chdir(curdir)
781
782def buildLibraries():
783 """
784 Build our dependencies into $WORKDIR/libraries/usr/local
785 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800786 print("")
787 print("Building required libraries")
788 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000789 universal = os.path.join(WORKDIR, 'libraries')
790 os.mkdir(universal)
791 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
792 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
793
Ronald Oussoren1943f862009-03-30 19:39:14 +0000794 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000795 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000796
797
798
799def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000800 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000801 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily7d9cf832010-11-27 16:42:15 -0800802 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000803 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000804 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000805 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000806 curDir = os.getcwd()
807 os.chdir(buildDir)
808 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000809 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000810 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000811 if not os.path.exists(docdir):
812 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000813 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000814
815
816def buildPython():
Ned Deily7d9cf832010-11-27 16:42:15 -0800817 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818
819 buildDir = os.path.join(WORKDIR, '_bld', 'python')
820 rootDir = os.path.join(WORKDIR, '_root')
821
822 if os.path.exists(buildDir):
823 shutil.rmtree(buildDir)
824 if os.path.exists(rootDir):
825 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000826 os.makedirs(buildDir)
827 os.makedirs(rootDir)
828 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000829 curdir = os.getcwd()
830 os.chdir(buildDir)
831
832 # Not sure if this is still needed, the original build script
833 # claims that parts of the install assume python.exe exists.
834 os.symlink('python', os.path.join(buildDir, 'python.exe'))
835
836 # Extract the version from the configure file, needed to calculate
837 # several paths.
838 version = getVersion()
839
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000840 # Since the extra libs are not in their installed framework location
841 # during the build, augment the library path so that the interpreter
842 # will find them during its extension import sanity checks.
843 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
844 'libraries', 'usr', 'local', 'lib')
Ned Deily7d9cf832010-11-27 16:42:15 -0800845 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000846 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000847 "--with-universal-archs=%s "
848 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000849 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
850 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
851 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
852 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000853 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000854 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000855 shellQuote(WORKDIR)[1:-1]))
856
Ned Deily7d9cf832010-11-27 16:42:15 -0800857 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000858 runCommand("make")
859
Ned Deily7d9cf832010-11-27 16:42:15 -0800860 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000861 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000862 shellQuote(rootDir)))
863
Ned Deily7d9cf832010-11-27 16:42:15 -0800864 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000865 runCommand("make frameworkinstallextras DESTDIR=%s"%(
866 shellQuote(rootDir)))
867
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000868 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily7d9cf832010-11-27 16:42:15 -0800869 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000870 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
871 runCommand("mv %s/* %s"%(
872 shellQuote(os.path.join(
873 WORKDIR, 'libraries', 'Library', 'Frameworks',
874 'Python.framework', 'Versions', getVersion(),
875 'lib')),
876 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
877 'Python.framework', 'Versions', getVersion(),
878 'lib'))))
879
Ned Deily7d9cf832010-11-27 16:42:15 -0800880 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000881 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000882 gid = grp.getgrnam('admin').gr_gid
883
Ned Deily2910a7b2012-07-30 02:35:58 -0700884 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +0000885 for dirpath, dirnames, filenames in os.walk(frmDir):
886 for dn in dirnames:
Ned Deily7d9cf832010-11-27 16:42:15 -0800887 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000888 os.chown(os.path.join(dirpath, dn), -1, gid)
889
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890 for fn in filenames:
891 if os.path.islink(fn):
892 continue
893
894 # "chmod g+w $fn"
895 p = os.path.join(dirpath, fn)
896 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000897 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
898 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899
Ned Deily2910a7b2012-07-30 02:35:58 -0700900 if fn in EXPECTED_SHARED_LIBS:
901 # check to see that this file was linked with the
902 # expected library path and version
903 data = captureCommand("otool -L %s" % shellQuote(p))
904 for sl in EXPECTED_SHARED_LIBS[fn]:
905 if ("\t%s " % sl) not in data:
906 print("Expected shared lib %s was not linked with %s"
907 % (sl, p))
908 shared_lib_error = True
909
910 if shared_lib_error:
911 fatal("Unexpected shared library errors.")
912
Ned Deilye59e4c52011-01-29 18:56:28 +0000913 if PYTHON_3:
914 LDVERSION=None
915 VERSION=None
916 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000917
Ned Deilye59e4c52011-01-29 18:56:28 +0000918 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000919 for ln in fp:
920 if ln.startswith('VERSION='):
921 VERSION=ln.split()[1]
922 if ln.startswith('ABIFLAGS='):
923 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000924 if ln.startswith('LDVERSION='):
925 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000926 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000927
Ned Deilye59e4c52011-01-29 18:56:28 +0000928 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
929 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
930 config_suffix = '-' + LDVERSION
931 else:
932 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000933
Thomas Wouters477c8d52006-05-27 19:21:47 +0000934 # We added some directories to the search path during the configure
935 # phase. Remove those because those directories won't be there on
Ned Deilya606aef2012-07-21 10:48:09 -0700936 # the end-users system. Also remove the directories from _sysconfigdata.py
937 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000938
Ned Deilya606aef2012-07-21 10:48:09 -0700939 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
940 'Python.framework', 'Versions',
941 version, 'lib', 'python%s'%(version,))
942 paths = [os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile'),
943 os.path.join(path_to_lib, '_sysconfigdata.py')]
944 for path in paths:
945 if not os.path.exists(path):
946 continue
947 fp = open(path, 'r')
948 data = fp.read()
949 fp.close()
950
Ned Deily2c80e122012-07-22 00:46:46 -0700951 data = data.replace(' -L%s/libraries/usr/local/lib'%(WORKDIR,), '')
952 data = data.replace(' -I%s/libraries/usr/local/include'%(WORKDIR,), '')
Ned Deilya606aef2012-07-21 10:48:09 -0700953 fp = open(path, 'w')
954 fp.write(data)
955 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000956
957 # Add symlinks in /usr/local/bin, using relative links
958 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
959 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
960 'Python.framework', 'Versions', version, 'bin')
961 if os.path.exists(usr_local_bin):
962 shutil.rmtree(usr_local_bin)
963 os.makedirs(usr_local_bin)
964 for fn in os.listdir(
965 os.path.join(frmDir, 'Versions', version, 'bin')):
966 os.symlink(os.path.join(to_framework, fn),
967 os.path.join(usr_local_bin, fn))
968
969 os.chdir(curdir)
970
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000971 if PYTHON_3:
972 # Remove the 'Current' link, that way we don't accidently mess
973 # with an already installed version of python 2
974 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
975 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000976
977def patchFile(inPath, outPath):
978 data = fileContents(inPath)
979 data = data.replace('$FULL_VERSION', getFullVersion())
980 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000981 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000982 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000983 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000984
985 # This one is not handy as a template variable
986 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily7d9cf832010-11-27 16:42:15 -0800987 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 fp.write(data)
989 fp.close()
990
991def patchScript(inPath, outPath):
992 data = fileContents(inPath)
993 data = data.replace('@PYVER@', getVersion())
Ned Deily7d9cf832010-11-27 16:42:15 -0800994 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000995 fp.write(data)
996 fp.close()
Ned Deily7d9cf832010-11-27 16:42:15 -0800997 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000998
999
1000
1001def packageFromRecipe(targetDir, recipe):
1002 curdir = os.getcwd()
1003 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001004 # The major version (such as 2.5) is included in the package name
1005 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001006 # common.
1007 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001008 srcdir = recipe.get('source')
1009 pkgroot = recipe.get('topdir', srcdir)
1010 postflight = recipe.get('postflight')
1011 readme = textwrap.dedent(recipe['readme'])
1012 isRequired = recipe.get('required', True)
1013
Ned Deily7d9cf832010-11-27 16:42:15 -08001014 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001015
1016 # Substitute some variables
1017 textvars = dict(
1018 VER=getVersion(),
1019 FULLVER=getFullVersion(),
1020 )
1021 readme = readme % textvars
1022
1023 if pkgroot is not None:
1024 pkgroot = pkgroot % textvars
1025 else:
1026 pkgroot = '/'
1027
1028 if srcdir is not None:
1029 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1030 srcdir = srcdir % textvars
1031
1032 if postflight is not None:
1033 postflight = os.path.abspath(postflight)
1034
1035 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1036 os.makedirs(packageContents)
1037
1038 if srcdir is not None:
1039 os.chdir(srcdir)
1040 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1041 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1042 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1043
1044 fn = os.path.join(packageContents, 'PkgInfo')
1045 fp = open(fn, 'w')
1046 fp.write('pmkrpkg1')
1047 fp.close()
1048
1049 rsrcDir = os.path.join(packageContents, "Resources")
1050 os.mkdir(rsrcDir)
1051 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1052 fp.write(readme)
1053 fp.close()
1054
1055 if postflight is not None:
1056 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1057
1058 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001059 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001060 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001061 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1062 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1063 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001064 CFBundleShortVersionString=vers,
1065 IFMajorVersion=major,
1066 IFMinorVersion=minor,
1067 IFPkgFormatVersion=0.10000000149011612,
1068 IFPkgFlagAllowBackRev=False,
1069 IFPkgFlagAuthorizationAction="RootAuthorization",
1070 IFPkgFlagDefaultLocation=pkgroot,
1071 IFPkgFlagFollowLinks=True,
1072 IFPkgFlagInstallFat=True,
1073 IFPkgFlagIsRequired=isRequired,
1074 IFPkgFlagOverwritePermissions=False,
1075 IFPkgFlagRelocatable=False,
1076 IFPkgFlagRestartAction="NoRestart",
1077 IFPkgFlagRootVolumeOnly=True,
1078 IFPkgFlagUpdateInstalledLangauges=False,
1079 )
1080 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1081
1082 pl = Plist(
1083 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001084 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001085 IFPkgDescriptionVersion=vers,
1086 )
1087 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1088
1089 finally:
1090 os.chdir(curdir)
1091
1092
1093def makeMpkgPlist(path):
1094
1095 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001096 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001097
1098 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001099 CFBundleGetInfoString="Python %s"%(vers,),
1100 CFBundleIdentifier='org.python.Python',
1101 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001102 CFBundleShortVersionString=vers,
1103 IFMajorVersion=major,
1104 IFMinorVersion=minor,
1105 IFPkgFlagComponentDirectory="Contents/Packages",
1106 IFPkgFlagPackageList=[
1107 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001108 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001109 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001110 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001111 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001112 ],
1113 IFPkgFormatVersion=0.10000000149011612,
1114 IFPkgFlagBackgroundScaling="proportional",
1115 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001116 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001117 )
1118
1119 writePlist(pl, path)
1120
1121
1122def buildInstaller():
1123
1124 # Zap all compiled files
1125 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1126 for fn in filenames:
1127 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1128 os.unlink(os.path.join(dirpath, fn))
1129
1130 outdir = os.path.join(WORKDIR, 'installer')
1131 if os.path.exists(outdir):
1132 shutil.rmtree(outdir)
1133 os.mkdir(outdir)
1134
Ronald Oussoren1943f862009-03-30 19:39:14 +00001135 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001136 pkgcontents = os.path.join(pkgroot, 'Packages')
1137 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001138 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001139 packageFromRecipe(pkgcontents, recipe)
1140
1141 rsrcDir = os.path.join(pkgroot, 'Resources')
1142
1143 fn = os.path.join(pkgroot, 'PkgInfo')
1144 fp = open(fn, 'w')
1145 fp.write('pmkrpkg1')
1146 fp.close()
1147
1148 os.mkdir(rsrcDir)
1149
1150 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1151 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001152 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001153 IFPkgDescriptionVersion=getVersion(),
1154 )
1155
1156 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1157 for fn in os.listdir('resources'):
1158 if fn == '.svn': continue
1159 if fn.endswith('.jpg'):
1160 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1161 else:
1162 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1163
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001164 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001165
1166
1167def installSize(clear=False, _saved=[]):
1168 if clear:
1169 del _saved[:]
1170 if not _saved:
1171 data = captureCommand("du -ks %s"%(
1172 shellQuote(os.path.join(WORKDIR, '_root'))))
1173 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1174 return _saved[0]
1175
1176
1177def buildDMG():
1178 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001179 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001180 """
1181 outdir = os.path.join(WORKDIR, 'diskimage')
1182 if os.path.exists(outdir):
1183 shutil.rmtree(outdir)
1184
1185 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001186 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001187 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001188 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001189 imagepath = imagepath + '.dmg'
1190
1191 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001192 volname='Python %s'%(getFullVersion())
1193 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1194 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001195 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001196 shellQuote(imagepath + ".tmp.dmg" )))
1197
1198
1199 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1200 os.mkdir(os.path.join(WORKDIR, "mnt"))
1201 runCommand("hdiutil attach %s -mountroot %s"%(
1202 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1203
1204 # Custom icon for the DMG, shown when the DMG is mounted.
1205 shutil.copy("../Icons/Disk Image.icns",
1206 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1207 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1208 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1209
1210 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1211
1212 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1213 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1214 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1215 setIcon(imagepath, "../Icons/Disk Image.icns")
1216
1217 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001218
1219 return imagepath
1220
1221
1222def setIcon(filePath, icnsPath):
1223 """
1224 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001225 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001226
Ronald Oussoren70050672010-04-30 15:00:26 +00001227 dirPath = os.path.normpath(os.path.dirname(__file__))
1228 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001229 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1230 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1231 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001232 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1233 if not os.path.exists(appPath):
1234 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001235 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1236 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001237
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001238 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1239 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001240
1241def main():
1242 # First parse options and check if we can perform our work
1243 parseOptions()
1244 checkEnvironment()
1245
Ronald Oussoren1943f862009-03-30 19:39:14 +00001246 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001247 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001248
1249 if os.path.exists(WORKDIR):
1250 shutil.rmtree(WORKDIR)
1251 os.mkdir(WORKDIR)
1252
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001253 os.environ['LC_ALL'] = 'C'
1254
Thomas Wouters477c8d52006-05-27 19:21:47 +00001255 # Then build third-party libraries such as sleepycat DB4.
1256 buildLibraries()
1257
1258 # Now build python itself
1259 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001260
1261 # And then build the documentation
1262 # Remove the Deployment Target from the shell
1263 # environment, it's no longer needed and
1264 # an unexpected build target can cause problems
1265 # when Sphinx and its dependencies need to
1266 # be (re-)installed.
1267 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001268 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001269
1270
1271 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001272 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001273 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001274 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001275
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001276 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001277 getVersion(),))
Ned Deily7d9cf832010-11-27 16:42:15 -08001278 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001279 setIcon(folder, "../Icons/Python Folder.icns")
1280
1281 # Create the installer
1282 buildInstaller()
1283
1284 # And copy the readme into the directory containing the installer
1285 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1286
1287 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001288 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001289
1290 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily7d9cf832010-11-27 16:42:15 -08001291 fp.write("# BUILD INFO\n")
1292 fp.write("# Date: %s\n" % time.ctime())
1293 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001294 fp.close()
1295
Thomas Wouters477c8d52006-05-27 19:21:47 +00001296 # And copy it to a DMG
1297 buildDMG()
1298
Thomas Wouters477c8d52006-05-27 19:21:47 +00001299if __name__ == "__main__":
1300 main()