blob: 19ed8e889d4678d9cec04048b2b54fb946aa1328 [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -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.
Ned Deily4a96a372013-01-29 00:08:32 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-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
Ned Deily4a96a372013-01-29 00:08:32 -080013In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
14requires an installed version of hg and a third-party version of
15Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
16(for 10.6 or later) installed in /Library/Frameworks. When installed,
17the Python built by this script will attempt to dynamically link first to
18Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
19back to the ones in /System/Library/Framework. For the build, we recommend
20installing the most recent ActiveTcl 8.4 or 8.5 version.
21
2232-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
23and the installation of additional components, such as a newer Python
24(2.5 is needed for Python parser updates), hg, and svn (for the documentation
25build).
26
Thomas Wouters477c8d52006-05-27 19:21:47 +000027Usage: see USAGE variable in the script.
28"""
Ned Deily4a96a372013-01-29 00:08:32 -080029import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
30try:
31 import urllib2 as urllib_request
32except ImportError:
33 import urllib.request as urllib_request
34
35STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
36 | stat.S_IRGRP | stat.S_IXGRP
37 | stat.S_IROTH | stat.S_IXOTH )
38
39STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
40 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
41 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000042
Thomas Wouters89f507f2006-12-13 04:49:30 +000043INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000045
46from plistlib import Plist
47
Thomas Wouters477c8d52006-05-27 19:21:47 +000048try:
49 from plistlib import writePlist
50except ImportError:
51 # We're run using python2.3
52 def writePlist(plist, path):
53 plist.write(path)
54
55def shellQuote(value):
56 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000057 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000058 a shell command.
59 """
60 return "'%s'"%(value.replace("'", "'\"'\"'"))
61
62def grepValue(fn, variable):
63 variable = variable + '='
64 for ln in open(fn, 'r'):
65 if ln.startswith(variable):
66 value = ln[len(variable):].strip()
67 return value[1:-1]
Ned Deily4a96a372013-01-29 00:08:32 -080068 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000071
72def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080073 global _cache_getVersion
74 if _cache_getVersion is None:
75 _cache_getVersion = grepValue(
76 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
77 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
Benjamin Petersond9b7d482010-03-19 21:42:45 +000079def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
Ned Deily4a96a372013-01-29 00:08:32 -080082def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
Thomas Wouters477c8d52006-05-27 19:21:47 +000087def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080088 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000091 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080094 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000097
Thomas Wouters89f507f2006-12-13 04:49:30 +000098# The directory we'll use to create the build (will be erased and recreated)
99WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100
Thomas Wouters89f507f2006-12-13 04:49:30 +0000101# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000105
106# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000107
108### There are some issues with the SDK selection below here,
109### The resulting binary doesn't work on all platforms that
110### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300111### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000112###
113##if int(os.uname()[2].split('.')[0]) == 8:
114## # Explicitly use the 10.4u (universal) SDK when
115## # building on 10.4, the system headers are not
116## # useable for a universal build
117## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
118##else:
119## SDKPATH = "/"
120
Thomas Wouters89f507f2006-12-13 04:49:30 +0000121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000122
Ronald Oussoren1943f862009-03-30 19:39:14 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 'intel': ('i386', 'x86_64'),
126 '3-way': ('ppc', 'i386', 'x86_64'),
127 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
128default_target_map = {
129 '64-bit': '10.5',
130 '3-way': '10.5',
131 'intel': '10.5',
132 'all': '10.5',
133}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140
Ezio Melotti42da6632011-03-15 05:18:48 +0200141# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000142SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143 os.path.dirname(
144 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000145 os.path.abspath(__file__
146 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000147
Ronald Oussoren1943f862009-03-30 19:39:14 +0000148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000151target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800152 '10.3': ('gcc-4.0', 'g++-4.0'),
153 '10.4': ('gcc-4.0', 'g++-4.0'),
154 '10.5': ('gcc-4.2', 'g++-4.2'),
155 '10.6': ('gcc-4.2', 'g++-4.2'),
156 '10.7': ('clang', 'clang++'),
157 '10.8': ('clang', 'clang++'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000158}
159
Ned Deily4a96a372013-01-29 00:08:32 -0800160CC, CXX = target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000161
162PYTHON_3 = getVersionTuple() >= (3, 0)
163
Thomas Wouters89f507f2006-12-13 04:49:30 +0000164USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000165 Usage: build_python [options]
166
167 Options:
168 -? or -h: Show this message
169 -b DIR
170 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
171 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
172 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
173 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000174 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
175 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000176""")% globals()
177
Ned Deily4a96a372013-01-29 00:08:32 -0800178# Dict of object file names with shared library names to check after building.
179# This is to ensure that we ended up dynamically linking with the shared
180# library paths and versions we expected. For example:
181# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
182# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
183# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
184EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000185
186# Instructions for building libraries that are necessary for building a
187# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000188# [The recipes are defined here for convenience but instantiated later after
189# command line options have been processed.]
190def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000191 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000192
Ned Deily4a96a372013-01-29 00:08:32 -0800193 LT_10_5 = bool(DEPTARGET < '10.5')
194
Ned Deily981b6932013-09-06 01:18:36 -0700195 if DEPTARGET > '10.5':
196 result.extend([
197 dict(
198 name="Tcl 8.5.14",
199 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.14-src.tar.gz",
200 checksum='44b50e58ab45dd272f6714dce2129123',
201 buildDir="unix",
202 configure_pre=[
203 '--enable-shared',
204 '--enable-threads',
205 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
206 ],
207 useLDFlags=False,
208 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
209 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
210 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
211 },
212 ),
213 dict(
214 name="Tk 8.5.14",
215 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.14-src.tar.gz",
216 checksum='a9c48921b3688020470cd3a6dd4e867d',
217 buildDir="unix",
218 configure_pre=[
219 '--enable-aqua',
220 '--enable-shared',
221 '--enable-threads',
222 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
223 ],
224 useLDFlags=False,
225 install='make TCL_LIBRARY=%(TCL_LIBRARY)s TK_LIBRARY=%(TK_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s TK_LIBRARY=%(TK_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
226 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
227 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
228 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
229 },
230 ),
231 ])
232
Ned Deily4a96a372013-01-29 00:08:32 -0800233 if getVersionTuple() >= (3, 3):
234 result.extend([
235 dict(
236 name="XZ 5.0.3",
237 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
238 checksum='fefe52f9ecd521de2a8ce38c21a27574',
239 configure_pre=[
240 '--disable-dependency-tracking',
241 ]
242 ),
243 ])
244
245 result.extend([
246 dict(
247 name="NCurses 5.9",
248 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
249 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
250 configure_pre=[
251 "--enable-widec",
252 "--without-cxx",
253 "--without-cxx-binding",
254 "--without-ada",
255 "--without-curses-h",
256 "--enable-shared",
257 "--with-shared",
258 "--without-debug",
259 "--without-normal",
260 "--without-tests",
261 "--without-manpages",
262 "--datadir=/usr/share",
263 "--sysconfdir=/etc",
264 "--sharedstatedir=/usr/com",
265 "--with-terminfo-dirs=/usr/share/terminfo",
266 "--with-default-terminfo-dir=/usr/share/terminfo",
267 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
268 ],
269 patchscripts=[
270 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
271 "f54bf02a349f96a7c4f0d00922f3a0d4"),
272 ],
273 useLDFlags=False,
274 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
275 shellQuote(os.path.join(WORKDIR, 'libraries')),
276 shellQuote(os.path.join(WORKDIR, 'libraries')),
277 getVersion(),
278 ),
279 ),
280 dict(
281 name="SQLite 3.7.13",
282 url="http://www.sqlite.org/sqlite-autoconf-3071300.tar.gz",
283 checksum='c97df403e8a3d5b67bb408fcd6aabd8e',
284 extra_cflags=('-Os '
285 '-DSQLITE_ENABLE_FTS4 '
286 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
287 '-DSQLITE_ENABLE_RTREE '
288 '-DSQLITE_TCL=0 '
289 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
290 configure_pre=[
291 '--enable-threadsafe',
292 '--enable-shared=no',
293 '--enable-static=yes',
294 '--disable-readline',
295 '--disable-dependency-tracking',
296 ]
297 ),
298 ])
299
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000300 if DEPTARGET < '10.5':
301 result.extend([
302 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000303 name="Bzip2 1.0.6",
304 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
305 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000306 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800307 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
308 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000309 shellQuote(os.path.join(WORKDIR, 'libraries')),
310 ' -arch '.join(ARCHLIST),
311 SDKPATH,
312 ),
313 ),
314 dict(
315 name="ZLib 1.2.3",
316 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
317 checksum='debc62758716a169df9f62e6ab2bc634',
318 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800319 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
320 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000321 shellQuote(os.path.join(WORKDIR, 'libraries')),
322 ' -arch '.join(ARCHLIST),
323 SDKPATH,
324 ),
325 ),
326 dict(
327 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000328 name="GNU Readline 6.1.2",
329 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
330 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000331 patchlevel='0',
332 patches=[
333 # The readline maintainers don't do actual micro releases, but
334 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800335 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
336 'c642f2e84d820884b0bf9fd176bc6c3f'),
337 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
338 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000339 ]
340 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000341 ])
342
Ned Deily4f7ff782011-01-15 05:29:12 +0000343 if not PYTHON_3:
344 result.extend([
345 dict(
346 name="Sleepycat DB 4.7.25",
347 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
348 checksum='ec2b87e833779681a0c3a814aa71359e',
349 buildDir="build_unix",
350 configure="../dist/configure",
351 configure_pre=[
352 '--includedir=/usr/local/include/db4',
353 ]
354 ),
355 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000356
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000357 return result
358
Thomas Wouters477c8d52006-05-27 19:21:47 +0000359
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000361def pkg_recipes():
362 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
363 result = [
364 dict(
365 name="PythonFramework",
366 long_name="Python Framework",
367 source="/Library/Frameworks/Python.framework",
368 readme="""\
369 This package installs Python.framework, that is the python
370 interpreter and the standard library. This also includes Python
371 wrappers for lots of Mac OS X API's.
372 """,
373 postflight="scripts/postflight.framework",
374 selected='selected',
375 ),
376 dict(
377 name="PythonApplications",
378 long_name="GUI Applications",
379 source="/Applications/Python %(VER)s",
380 readme="""\
381 This package installs IDLE (an interactive Python IDE),
382 Python Launcher and Build Applet (create application bundles
383 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000384
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000385 It also installs a number of examples and demos.
386 """,
387 required=False,
388 selected='selected',
389 ),
390 dict(
391 name="PythonUnixTools",
392 long_name="UNIX command-line tools",
393 source="/usr/local/bin",
394 readme="""\
395 This package installs the unix tools in /usr/local/bin for
396 compatibility with older releases of Python. This package
397 is not necessary to use Python.
398 """,
399 required=False,
400 selected='selected',
401 ),
402 dict(
403 name="PythonDocumentation",
404 long_name="Python Documentation",
405 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
406 source="/pydocs",
407 readme="""\
408 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800409 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000410 """,
411 postflight="scripts/postflight.documentation",
412 required=False,
413 selected='selected',
414 ),
415 dict(
416 name="PythonProfileChanges",
417 long_name="Shell profile updater",
418 readme="""\
419 This packages updates your shell profile to make sure that
420 the Python tools are found by your shell in preference of
421 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000422
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000423 If you don't install this package you'll have to add
424 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
425 to your PATH by hand.
426 """,
427 postflight="scripts/postflight.patch-profile",
428 topdir="/Library/Frameworks/Python.framework",
429 source="/empty-dir",
430 required=False,
431 selected=unselected_for_python3,
432 ),
433 ]
434
Ned Deily4a96a372013-01-29 00:08:32 -0800435 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000436 result.append(
437 dict(
438 name="PythonSystemFixes",
439 long_name="Fix system Python",
440 readme="""\
441 This package updates the system python installation on
442 Mac OS X 10.3 to ensure that you can build new python extensions
443 using that copy of python after installing this version.
444 """,
445 postflight="../Tools/fixapplepython23.py",
446 topdir="/Library/Frameworks/Python.framework",
447 source="/empty-dir",
448 required=False,
449 selected=unselected_for_python3,
450 )
451 )
452 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000453
Thomas Wouters477c8d52006-05-27 19:21:47 +0000454def fatal(msg):
455 """
456 A fatal error, bail out.
457 """
458 sys.stderr.write('FATAL: ')
459 sys.stderr.write(msg)
460 sys.stderr.write('\n')
461 sys.exit(1)
462
463def fileContents(fn):
464 """
465 Return the contents of the named file
466 """
Ned Deily4a96a372013-01-29 00:08:32 -0800467 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000468
469def runCommand(commandline):
470 """
Ezio Melotti13925002011-03-16 11:05:33 +0200471 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000472 unless the command fails.
473 """
474 fd = os.popen(commandline, 'r')
475 data = fd.read()
476 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000477 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800479 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000480
481 if VERBOSE:
482 sys.stdout.write(data); sys.stdout.flush()
483
484def captureCommand(commandline):
485 fd = os.popen(commandline, 'r')
486 data = fd.read()
487 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000488 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000489 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800490 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000491
492 return data
493
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000494def getTclTkVersion(configfile, versionline):
495 """
496 search Tcl or Tk configuration file for version line
497 """
498 try:
499 f = open(configfile, "r")
500 except:
501 fatal("Framework configuration file not found: %s" % configfile)
502
503 for l in f:
504 if l.startswith(versionline):
505 f.close()
506 return l
507
508 fatal("Version variable %s not found in framework configuration file: %s"
509 % (versionline, configfile))
510
Thomas Wouters477c8d52006-05-27 19:21:47 +0000511def checkEnvironment():
512 """
513 Check that we're running on a supported system.
514 """
515
Ned Deilye59e4c52011-01-29 18:56:28 +0000516 if sys.version_info[0:2] < (2, 4):
517 fatal("This script must be run with Python 2.4 or later")
518
Thomas Wouters477c8d52006-05-27 19:21:47 +0000519 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000520 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000521
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000522 if int(platform.release().split('.')[0]) < 8:
523 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000524
525 if not os.path.exists(SDKPATH):
526 fatal("Please install the latest version of Xcode and the %s SDK"%(
527 os.path.basename(SDKPATH[:-4])))
528
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000529 # Because we only support dynamic load of only one major/minor version of
530 # Tcl/Tk, ensure:
531 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800532 # higher than the Apple-supplied system version in
533 # SDKROOT/System/Library/Frameworks
534 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
535 # in) SDKROOT/Library/Frameworks with the same version as the system
536 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000537
Ned Deily4a96a372013-01-29 00:08:32 -0800538 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000539 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800540 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000541 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800542 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000543 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800544 frameworks[framework] = os.readlink(sysfw)
545 if not os.path.exists(libfw):
546 fatal("Please install a link to a current %s %s as %s so "
547 "the user can override the system framework."
548 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000549 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000550 fatal("Version of %s must match %s" % (libfw, sysfw) )
551 if os.path.exists(usrfw):
552 fatal("Please rename %s to avoid possible dynamic load issues."
553 % usrfw)
554
Ned Deily4a96a372013-01-29 00:08:32 -0800555 if frameworks['Tcl'] != frameworks['Tk']:
556 fatal("The Tcl and Tk frameworks are not the same version.")
557
558 # add files to check after build
559 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
560 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
561 % frameworks['Tcl'],
562 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
563 % frameworks['Tk'],
564 ]
565
Ned Deily981b6932013-09-06 01:18:36 -0700566 # For 10.6+ builds, we build two versions of _tkinter:
567 # - the traditional version (renamed to _tkinter.so.framework) linked
568 # with /Library/Frameworks/{Tcl,Tk}.framework
569 # - the default version linked with our private copies of Tcl and Tk
570 if DEPTARGET > '10.5':
571 EXPECTED_SHARED_LIBS['_tkinter.so.framework'] = \
572 EXPECTED_SHARED_LIBS['_tkinter.so']
573 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
574 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib"
575 % (getVersion(), frameworks['Tcl']),
576 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtk%s.dylib"
577 % (getVersion(), frameworks['Tk']),
578 ]
579
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000580 # Remove inherited environment variables which might influence build
581 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
582 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
583 for ev in list(os.environ):
584 for prefix in environ_var_prefixes:
585 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800586 print("INFO: deleting environment variable %s=%s" % (
587 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000588 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000589
Ned Deily4a96a372013-01-29 00:08:32 -0800590 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
591 if 'SDK_TOOLS_BIN' in os.environ:
592 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
593 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
594 # add its fixed location here if it exists
595 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
596 if os.path.isdir(OLD_DEVELOPER_TOOLS):
597 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
598 os.environ['PATH'] = base_path
599 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000600
Thomas Wouters477c8d52006-05-27 19:21:47 +0000601
Thomas Wouters89f507f2006-12-13 04:49:30 +0000602def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000603 """
604 Parse arguments and update global settings.
605 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000606 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800607 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000608
609 if args is None:
610 args = sys.argv[1:]
611
612 try:
613 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000614 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
615 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800616 except getopt.GetoptError:
617 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000618 sys.exit(1)
619
620 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800621 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000622 sys.exit(1)
623
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000624 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000625 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000626 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800627 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000628 sys.exit(0)
629
630 elif k in ('-d', '--build-dir'):
631 WORKDIR=v
632
633 elif k in ('--third-party',):
634 DEPSRC=v
635
636 elif k in ('--sdk-path',):
637 SDKPATH=v
638
639 elif k in ('--src-dir',):
640 SRCDIR=v
641
Ronald Oussoren1943f862009-03-30 19:39:14 +0000642 elif k in ('--dep-target', ):
643 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000644 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000645
646 elif k in ('--universal-archs', ):
647 if v in UNIVERSALOPTS:
648 UNIVERSALARCHS = v
649 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000650 if deptarget is None:
651 # Select alternate default deployment
652 # target
653 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000654 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800655 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000656
Thomas Wouters477c8d52006-05-27 19:21:47 +0000657 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800658 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000659
660 SRCDIR=os.path.abspath(SRCDIR)
661 WORKDIR=os.path.abspath(WORKDIR)
662 SDKPATH=os.path.abspath(SDKPATH)
663 DEPSRC=os.path.abspath(DEPSRC)
664
Ned Deily4a96a372013-01-29 00:08:32 -0800665 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000666
Ned Deily4a96a372013-01-29 00:08:32 -0800667 print("Settings:")
668 print(" * Source directory:", SRCDIR)
669 print(" * Build directory: ", WORKDIR)
670 print(" * SDK location: ", SDKPATH)
671 print(" * Third-party source:", DEPSRC)
672 print(" * Deployment target:", DEPTARGET)
673 print(" * Universal architectures:", ARCHLIST)
674 print(" * C compiler:", CC)
675 print(" * C++ compiler:", CXX)
676 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677
678
679
680
681def extractArchive(builddir, archiveName):
682 """
683 Extract a source archive into 'builddir'. Returns the path of the
684 extracted archive.
685
686 XXX: This function assumes that archives contain a toplevel directory
687 that is has the same name as the basename of the archive. This is
Ned Deily981b6932013-09-06 01:18:36 -0700688 safe enough for almost anything we use. Unfortunately, it does not
689 work for current Tcl and Tk source releases where the basename of
690 the archive ends with "-src" but the uncompressed directory does not.
691 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000692 """
693 curdir = os.getcwd()
694 try:
695 os.chdir(builddir)
696 if archiveName.endswith('.tar.gz'):
697 retval = os.path.basename(archiveName[:-7])
Ned Deily981b6932013-09-06 01:18:36 -0700698 if ((retval.startswith('tcl') or retval.startswith('tk'))
699 and retval.endswith('-src')):
700 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000701 if os.path.exists(retval):
702 shutil.rmtree(retval)
703 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
704
705 elif archiveName.endswith('.tar.bz2'):
706 retval = os.path.basename(archiveName[:-8])
707 if os.path.exists(retval):
708 shutil.rmtree(retval)
709 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
710
711 elif archiveName.endswith('.tar'):
712 retval = os.path.basename(archiveName[:-4])
713 if os.path.exists(retval):
714 shutil.rmtree(retval)
715 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
716
717 elif archiveName.endswith('.zip'):
718 retval = os.path.basename(archiveName[:-4])
719 if os.path.exists(retval):
720 shutil.rmtree(retval)
721 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
722
723 data = fp.read()
724 xit = fp.close()
725 if xit is not None:
726 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800727 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000728
729 return os.path.join(builddir, retval)
730
731 finally:
732 os.chdir(curdir)
733
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734def downloadURL(url, fname):
735 """
736 Download the contents of the url into the file.
737 """
Ned Deily4a96a372013-01-29 00:08:32 -0800738 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739 fpOut = open(fname, 'wb')
740 block = fpIn.read(10240)
741 try:
742 while block:
743 fpOut.write(block)
744 block = fpIn.read(10240)
745 fpIn.close()
746 fpOut.close()
747 except:
748 try:
749 os.unlink(fname)
750 except:
751 pass
752
Ned Deily4a96a372013-01-29 00:08:32 -0800753def verifyThirdPartyFile(url, checksum, fname):
754 """
755 Download file from url to filename fname if it does not already exist.
756 Abort if file contents does not match supplied md5 checksum.
757 """
758 name = os.path.basename(fname)
759 if os.path.exists(fname):
760 print("Using local copy of %s"%(name,))
761 else:
762 print("Did not find local copy of %s"%(name,))
763 print("Downloading %s"%(name,))
764 downloadURL(url, fname)
765 print("Archive for %s stored as %s"%(name, fname))
766 if os.system(
767 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
768 % (shellQuote(fname), checksum) ):
769 fatal('MD5 checksum mismatch for file %s' % fname)
770
Thomas Wouters477c8d52006-05-27 19:21:47 +0000771def buildRecipe(recipe, basedir, archList):
772 """
773 Build software using a recipe. This function does the
774 'configure;make;make install' dance for C software, with a possibility
775 to customize this process, basically a poor-mans DarwinPorts.
776 """
777 curdir = os.getcwd()
778
779 name = recipe['name']
780 url = recipe['url']
781 configure = recipe.get('configure', './configure')
782 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
783 shellQuote(basedir)))
784
785 archiveName = os.path.split(url)[-1]
786 sourceArchive = os.path.join(DEPSRC, archiveName)
787
788 if not os.path.exists(DEPSRC):
789 os.mkdir(DEPSRC)
790
Ned Deily4a96a372013-01-29 00:08:32 -0800791 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
792 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000793 buildDir=os.path.join(WORKDIR, '_bld')
794 if not os.path.exists(buildDir):
795 os.mkdir(buildDir)
796
797 workDir = extractArchive(buildDir, sourceArchive)
798 os.chdir(workDir)
799 if 'buildDir' in recipe:
800 os.chdir(recipe['buildDir'])
801
Ned Deily4a96a372013-01-29 00:08:32 -0800802 for patch in recipe.get('patches', ()):
803 if isinstance(patch, tuple):
804 url, checksum = patch
805 fn = os.path.join(DEPSRC, os.path.basename(url))
806 verifyThirdPartyFile(url, checksum, fn)
807 else:
808 # patch is a file in the source directory
809 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000810 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
811 shellQuote(fn),))
812
Ned Deily4a96a372013-01-29 00:08:32 -0800813 for patchscript in recipe.get('patchscripts', ()):
814 if isinstance(patchscript, tuple):
815 url, checksum = patchscript
816 fn = os.path.join(DEPSRC, os.path.basename(url))
817 verifyThirdPartyFile(url, checksum, fn)
818 else:
819 # patch is a file in the source directory
820 fn = os.path.join(curdir, patchscript)
821 if fn.endswith('.bz2'):
822 runCommand('bunzip2 -fk %s' % shellQuote(fn))
823 fn = fn[:-4]
824 runCommand('sh %s' % shellQuote(fn))
825 os.unlink(fn)
826
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000827 if configure is not None:
828 configure_args = [
829 "--prefix=/usr/local",
830 "--enable-static",
831 "--disable-shared",
832 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
833 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000834
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000835 if 'configure_pre' in recipe:
836 args = list(recipe['configure_pre'])
837 if '--disable-static' in args:
838 configure_args.remove('--enable-static')
839 if '--enable-shared' in args:
840 configure_args.remove('--disable-shared')
841 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000842
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000843 if recipe.get('useLDFlags', 1):
844 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800845 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
846 "-I%s/usr/local/include"%(
847 recipe.get('extra_cflags', ''),
848 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000849 ' -arch '.join(archList),
850 shellQuote(SDKPATH)[1:-1],
851 shellQuote(basedir)[1:-1],),
Ned Deily4a96a372013-01-29 00:08:32 -0800852 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
853 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000854 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000855 shellQuote(basedir)[1:-1],
856 ' -arch '.join(archList)),
857 ])
858 else:
859 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800860 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
861 "-I%s/usr/local/include"%(
862 recipe.get('extra_cflags', ''),
863 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000864 ' -arch '.join(archList),
865 shellQuote(SDKPATH)[1:-1],
866 shellQuote(basedir)[1:-1],),
867 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000868
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000869 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800870 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000871
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000872 configure_args.insert(0, configure)
873 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000874
Ned Deily4a96a372013-01-29 00:08:32 -0800875 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000876 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000877
Ned Deily4a96a372013-01-29 00:08:32 -0800878 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000879 runCommand('{ ' + install + ' ;} 2>&1')
880
Ned Deily4a96a372013-01-29 00:08:32 -0800881 print("Done %s"%(name,))
882 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000883
884 os.chdir(curdir)
885
886def buildLibraries():
887 """
888 Build our dependencies into $WORKDIR/libraries/usr/local
889 """
Ned Deily4a96a372013-01-29 00:08:32 -0800890 print("")
891 print("Building required libraries")
892 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000893 universal = os.path.join(WORKDIR, 'libraries')
894 os.mkdir(universal)
895 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
896 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
897
Ronald Oussoren1943f862009-03-30 19:39:14 +0000898 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000899 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000900
901
902
903def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000904 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800906 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000907 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000908 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000909 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000910 curDir = os.getcwd()
911 os.chdir(buildDir)
912 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000913 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000914 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000915 if not os.path.exists(docdir):
916 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000917 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000918
919
920def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800921 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000922
923 buildDir = os.path.join(WORKDIR, '_bld', 'python')
924 rootDir = os.path.join(WORKDIR, '_root')
925
926 if os.path.exists(buildDir):
927 shutil.rmtree(buildDir)
928 if os.path.exists(rootDir):
929 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000930 os.makedirs(buildDir)
931 os.makedirs(rootDir)
932 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000933 curdir = os.getcwd()
934 os.chdir(buildDir)
935
936 # Not sure if this is still needed, the original build script
937 # claims that parts of the install assume python.exe exists.
938 os.symlink('python', os.path.join(buildDir, 'python.exe'))
939
940 # Extract the version from the configure file, needed to calculate
941 # several paths.
942 version = getVersion()
943
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000944 # Since the extra libs are not in their installed framework location
945 # during the build, augment the library path so that the interpreter
946 # will find them during its extension import sanity checks.
947 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
948 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800949 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000950 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000951 "--with-universal-archs=%s "
952 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000953 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
954 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
955 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
956 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000957 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000958 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000959 shellQuote(WORKDIR)[1:-1]))
960
Ned Deily4a96a372013-01-29 00:08:32 -0800961 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000962 runCommand("make")
963
Ned Deily981b6932013-09-06 01:18:36 -0700964 # For deployment targets of 10.6 and higher, we build our own version
965 # of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is
966 # out-of-date and has critical bugs. Save the _tkinter.so that was
967 # linked with /Library/Frameworks/{Tck,Tk}.framework and build
968 # another _tkinter.so linked with our private Tcl and Tk libs.
969 if DEPTARGET > '10.5':
970 runCommand("find build -name '_tkinter.so' "
971 " -execdir mv '{}' '{}'.framework \;")
972 print("Running make to rebuild _tkinter")
973 runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' "
974 "TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%(
975 shellQuote(WORKDIR)[1:-1],
976 shellQuote(WORKDIR)[1:-1]))
977 # make a backup copy, just in case
978 runCommand("find build -name '_tkinter.so' "
979 " -execdir cp -p '{}' '{}'.private \;")
980
Ned Deily4a96a372013-01-29 00:08:32 -0800981 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000982 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000983 shellQuote(rootDir)))
984
Ned Deily4a96a372013-01-29 00:08:32 -0800985 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000986 runCommand("make frameworkinstallextras DESTDIR=%s"%(
987 shellQuote(rootDir)))
988
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000989 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800990 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000991 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
992 runCommand("mv %s/* %s"%(
993 shellQuote(os.path.join(
994 WORKDIR, 'libraries', 'Library', 'Frameworks',
995 'Python.framework', 'Versions', getVersion(),
996 'lib')),
997 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
998 'Python.framework', 'Versions', getVersion(),
999 'lib'))))
1000
Ned Deily4a96a372013-01-29 00:08:32 -08001001 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001002 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001003 gid = grp.getgrnam('admin').gr_gid
1004
Ned Deily4a96a372013-01-29 00:08:32 -08001005 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001006 for dirpath, dirnames, filenames in os.walk(frmDir):
1007 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001008 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001009 os.chown(os.path.join(dirpath, dn), -1, gid)
1010
Thomas Wouters477c8d52006-05-27 19:21:47 +00001011 for fn in filenames:
1012 if os.path.islink(fn):
1013 continue
1014
1015 # "chmod g+w $fn"
1016 p = os.path.join(dirpath, fn)
1017 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001018 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1019 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001020
Ned Deily4a96a372013-01-29 00:08:32 -08001021 if fn in EXPECTED_SHARED_LIBS:
1022 # check to see that this file was linked with the
1023 # expected library path and version
1024 data = captureCommand("otool -L %s" % shellQuote(p))
1025 for sl in EXPECTED_SHARED_LIBS[fn]:
1026 if ("\t%s " % sl) not in data:
1027 print("Expected shared lib %s was not linked with %s"
1028 % (sl, p))
1029 shared_lib_error = True
1030
1031 if shared_lib_error:
1032 fatal("Unexpected shared library errors.")
1033
Ned Deilye59e4c52011-01-29 18:56:28 +00001034 if PYTHON_3:
1035 LDVERSION=None
1036 VERSION=None
1037 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001038
Ned Deilye59e4c52011-01-29 18:56:28 +00001039 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001040 for ln in fp:
1041 if ln.startswith('VERSION='):
1042 VERSION=ln.split()[1]
1043 if ln.startswith('ABIFLAGS='):
1044 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001045 if ln.startswith('LDVERSION='):
1046 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001047 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001048
Ned Deilye59e4c52011-01-29 18:56:28 +00001049 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1050 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1051 config_suffix = '-' + LDVERSION
1052 else:
1053 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001054
Thomas Wouters477c8d52006-05-27 19:21:47 +00001055 # We added some directories to the search path during the configure
1056 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001057 # the end-users system. Also remove the directories from _sysconfigdata.py
1058 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001059
Ned Deily362532b2013-08-01 15:39:47 -07001060 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1061 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1062
Ned Deily4a96a372013-01-29 00:08:32 -08001063 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1064 'Python.framework', 'Versions',
1065 version, 'lib', 'python%s'%(version,))
Ned Deily362532b2013-08-01 15:39:47 -07001066
1067 # fix Makefile
1068 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1069 fp = open(path, 'r')
1070 data = fp.read()
1071 fp.close()
1072
1073 for p in (include_path, lib_path):
1074 data = data.replace(" " + p, '')
1075 data = data.replace(p + " ", '')
1076
1077 fp = open(path, 'w')
1078 fp.write(data)
1079 fp.close()
1080
1081 # fix _sysconfigdata if it exists
1082 #
1083 # TODO: make this more robust! test_sysconfig_module of
1084 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1085 # the output from get_config_var in both sysconfig and
1086 # distutils.sysconfig is exactly the same for both CFLAGS and
1087 # LDFLAGS. The fixing up is now complicated by the pretty
1088 # printing in _sysconfigdata.py. Also, we are using the
1089 # pprint from the Python running the installer build which
1090 # may not cosmetically format the same as the pprint in the Python
1091 # being built (and which is used to originally generate
1092 # _sysconfigdata.py).
1093
1094 import pprint
1095 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1096 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001097 fp = open(path, 'r')
1098 data = fp.read()
1099 fp.close()
Ned Deily362532b2013-08-01 15:39:47 -07001100 # create build_time_vars dict
1101 exec(data)
1102 vars = {}
1103 for k, v in build_time_vars.items():
1104 if type(v) == type(''):
1105 for p in (include_path, lib_path):
1106 v = v.replace(' ' + p, '')
1107 v = v.replace(p + ' ', '')
1108 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001109
Ned Deily4a96a372013-01-29 00:08:32 -08001110 fp = open(path, 'w')
Ned Deily362532b2013-08-01 15:39:47 -07001111 # duplicated from sysconfig._generate_posix_vars()
1112 fp.write('# system configuration generated and used by'
1113 ' the sysconfig module\n')
1114 fp.write('build_time_vars = ')
1115 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001116 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001117
1118 # Add symlinks in /usr/local/bin, using relative links
1119 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1120 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1121 'Python.framework', 'Versions', version, 'bin')
1122 if os.path.exists(usr_local_bin):
1123 shutil.rmtree(usr_local_bin)
1124 os.makedirs(usr_local_bin)
1125 for fn in os.listdir(
1126 os.path.join(frmDir, 'Versions', version, 'bin')):
1127 os.symlink(os.path.join(to_framework, fn),
1128 os.path.join(usr_local_bin, fn))
1129
1130 os.chdir(curdir)
1131
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001132 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001133 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001134 # with an already installed version of python 2
1135 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1136 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001137
1138def patchFile(inPath, outPath):
1139 data = fileContents(inPath)
1140 data = data.replace('$FULL_VERSION', getFullVersion())
1141 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001142 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001143 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001144 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001145
1146 # This one is not handy as a template variable
1147 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001148 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001149 fp.write(data)
1150 fp.close()
1151
1152def patchScript(inPath, outPath):
1153 data = fileContents(inPath)
1154 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001155 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001156 fp.write(data)
1157 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001158 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159
1160
1161
1162def packageFromRecipe(targetDir, recipe):
1163 curdir = os.getcwd()
1164 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001165 # The major version (such as 2.5) is included in the package name
1166 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001167 # common.
1168 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001169 srcdir = recipe.get('source')
1170 pkgroot = recipe.get('topdir', srcdir)
1171 postflight = recipe.get('postflight')
1172 readme = textwrap.dedent(recipe['readme'])
1173 isRequired = recipe.get('required', True)
1174
Ned Deily4a96a372013-01-29 00:08:32 -08001175 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001176
1177 # Substitute some variables
1178 textvars = dict(
1179 VER=getVersion(),
1180 FULLVER=getFullVersion(),
1181 )
1182 readme = readme % textvars
1183
1184 if pkgroot is not None:
1185 pkgroot = pkgroot % textvars
1186 else:
1187 pkgroot = '/'
1188
1189 if srcdir is not None:
1190 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1191 srcdir = srcdir % textvars
1192
1193 if postflight is not None:
1194 postflight = os.path.abspath(postflight)
1195
1196 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1197 os.makedirs(packageContents)
1198
1199 if srcdir is not None:
1200 os.chdir(srcdir)
1201 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1202 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1203 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1204
1205 fn = os.path.join(packageContents, 'PkgInfo')
1206 fp = open(fn, 'w')
1207 fp.write('pmkrpkg1')
1208 fp.close()
1209
1210 rsrcDir = os.path.join(packageContents, "Resources")
1211 os.mkdir(rsrcDir)
1212 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1213 fp.write(readme)
1214 fp.close()
1215
1216 if postflight is not None:
1217 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1218
1219 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001220 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001221 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001222 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1223 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1224 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001225 CFBundleShortVersionString=vers,
1226 IFMajorVersion=major,
1227 IFMinorVersion=minor,
1228 IFPkgFormatVersion=0.10000000149011612,
1229 IFPkgFlagAllowBackRev=False,
1230 IFPkgFlagAuthorizationAction="RootAuthorization",
1231 IFPkgFlagDefaultLocation=pkgroot,
1232 IFPkgFlagFollowLinks=True,
1233 IFPkgFlagInstallFat=True,
1234 IFPkgFlagIsRequired=isRequired,
1235 IFPkgFlagOverwritePermissions=False,
1236 IFPkgFlagRelocatable=False,
1237 IFPkgFlagRestartAction="NoRestart",
1238 IFPkgFlagRootVolumeOnly=True,
1239 IFPkgFlagUpdateInstalledLangauges=False,
1240 )
1241 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1242
1243 pl = Plist(
1244 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001245 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001246 IFPkgDescriptionVersion=vers,
1247 )
1248 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1249
1250 finally:
1251 os.chdir(curdir)
1252
1253
1254def makeMpkgPlist(path):
1255
1256 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001257 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001258
1259 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001260 CFBundleGetInfoString="Python %s"%(vers,),
1261 CFBundleIdentifier='org.python.Python',
1262 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001263 CFBundleShortVersionString=vers,
1264 IFMajorVersion=major,
1265 IFMinorVersion=minor,
1266 IFPkgFlagComponentDirectory="Contents/Packages",
1267 IFPkgFlagPackageList=[
1268 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001269 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001270 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001271 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001272 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001273 ],
1274 IFPkgFormatVersion=0.10000000149011612,
1275 IFPkgFlagBackgroundScaling="proportional",
1276 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001277 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001278 )
1279
1280 writePlist(pl, path)
1281
1282
1283def buildInstaller():
1284
1285 # Zap all compiled files
1286 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1287 for fn in filenames:
1288 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1289 os.unlink(os.path.join(dirpath, fn))
1290
1291 outdir = os.path.join(WORKDIR, 'installer')
1292 if os.path.exists(outdir):
1293 shutil.rmtree(outdir)
1294 os.mkdir(outdir)
1295
Ronald Oussoren1943f862009-03-30 19:39:14 +00001296 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001297 pkgcontents = os.path.join(pkgroot, 'Packages')
1298 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001299 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001300 packageFromRecipe(pkgcontents, recipe)
1301
1302 rsrcDir = os.path.join(pkgroot, 'Resources')
1303
1304 fn = os.path.join(pkgroot, 'PkgInfo')
1305 fp = open(fn, 'w')
1306 fp.write('pmkrpkg1')
1307 fp.close()
1308
1309 os.mkdir(rsrcDir)
1310
1311 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1312 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001313 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001314 IFPkgDescriptionVersion=getVersion(),
1315 )
1316
1317 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1318 for fn in os.listdir('resources'):
1319 if fn == '.svn': continue
1320 if fn.endswith('.jpg'):
1321 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1322 else:
1323 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1324
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001325 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001326
1327
1328def installSize(clear=False, _saved=[]):
1329 if clear:
1330 del _saved[:]
1331 if not _saved:
1332 data = captureCommand("du -ks %s"%(
1333 shellQuote(os.path.join(WORKDIR, '_root'))))
1334 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1335 return _saved[0]
1336
1337
1338def buildDMG():
1339 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001340 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001341 """
1342 outdir = os.path.join(WORKDIR, 'diskimage')
1343 if os.path.exists(outdir):
1344 shutil.rmtree(outdir)
1345
1346 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001347 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001348 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001349 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001350 imagepath = imagepath + '.dmg'
1351
1352 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001353 volname='Python %s'%(getFullVersion())
1354 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1355 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001356 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001357 shellQuote(imagepath + ".tmp.dmg" )))
1358
1359
1360 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1361 os.mkdir(os.path.join(WORKDIR, "mnt"))
1362 runCommand("hdiutil attach %s -mountroot %s"%(
1363 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1364
1365 # Custom icon for the DMG, shown when the DMG is mounted.
1366 shutil.copy("../Icons/Disk Image.icns",
1367 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001368 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001369 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1370
1371 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1372
1373 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1374 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1375 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1376 setIcon(imagepath, "../Icons/Disk Image.icns")
1377
1378 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001379
1380 return imagepath
1381
1382
1383def setIcon(filePath, icnsPath):
1384 """
1385 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001386 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001387
Ronald Oussoren70050672010-04-30 15:00:26 +00001388 dirPath = os.path.normpath(os.path.dirname(__file__))
1389 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001390 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1391 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1392 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001393 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1394 if not os.path.exists(appPath):
1395 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001396 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1397 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001398
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001399 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1400 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001401
1402def main():
1403 # First parse options and check if we can perform our work
1404 parseOptions()
1405 checkEnvironment()
1406
Ronald Oussoren1943f862009-03-30 19:39:14 +00001407 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001408 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001409 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001410
1411 if os.path.exists(WORKDIR):
1412 shutil.rmtree(WORKDIR)
1413 os.mkdir(WORKDIR)
1414
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001415 os.environ['LC_ALL'] = 'C'
1416
Thomas Wouters477c8d52006-05-27 19:21:47 +00001417 # Then build third-party libraries such as sleepycat DB4.
1418 buildLibraries()
1419
1420 # Now build python itself
1421 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001422
1423 # And then build the documentation
1424 # Remove the Deployment Target from the shell
1425 # environment, it's no longer needed and
1426 # an unexpected build target can cause problems
1427 # when Sphinx and its dependencies need to
1428 # be (re-)installed.
1429 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001430 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001431
1432
1433 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001434 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001435 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001436 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001437
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001438 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001439 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001440 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001441 setIcon(folder, "../Icons/Python Folder.icns")
1442
1443 # Create the installer
1444 buildInstaller()
1445
1446 # And copy the readme into the directory containing the installer
1447 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1448
1449 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001450 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001451
1452 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001453 fp.write("# BUILD INFO\n")
1454 fp.write("# Date: %s\n" % time.ctime())
1455 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001456 fp.close()
1457
Thomas Wouters477c8d52006-05-27 19:21:47 +00001458 # And copy it to a DMG
1459 buildDMG()
1460
Thomas Wouters477c8d52006-05-27 19:21:47 +00001461if __name__ == "__main__":
1462 main()