blob: 0859b3aa068d11b0d945a249981bb3bbe04d3ae7 [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++'),
Ned Deilyac25ca12013-10-18 20:41:16 -0700158 '10.9': ('clang', 'clang++'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000159}
160
Ned Deily4a96a372013-01-29 00:08:32 -0800161CC, CXX = target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000162
163PYTHON_3 = getVersionTuple() >= (3, 0)
164
Thomas Wouters89f507f2006-12-13 04:49:30 +0000165USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000166 Usage: build_python [options]
167
168 Options:
169 -? or -h: Show this message
170 -b DIR
171 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
172 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
173 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
174 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000175 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
176 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000177""")% globals()
178
Ned Deily4a96a372013-01-29 00:08:32 -0800179# Dict of object file names with shared library names to check after building.
180# This is to ensure that we ended up dynamically linking with the shared
181# library paths and versions we expected. For example:
182# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
183# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
184# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
185EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000186
187# Instructions for building libraries that are necessary for building a
188# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000189# [The recipes are defined here for convenience but instantiated later after
190# command line options have been processed.]
191def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000192 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000193
Ned Deily4a96a372013-01-29 00:08:32 -0800194 LT_10_5 = bool(DEPTARGET < '10.5')
195
Ned Deily5b3582c2013-10-25 00:41:46 -0700196 if DEPTARGET > '10.5':
197 result.extend([
198 dict(
199 name="Tcl 8.5.15",
200 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
201 checksum='f3df162f92c69b254079c4d0af7a690f',
202 buildDir="unix",
203 configure_pre=[
204 '--enable-shared',
205 '--enable-threads',
206 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
207 ],
208 useLDFlags=False,
209 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
210 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
211 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
212 },
213 ),
214 dict(
215 name="Tk 8.5.15",
216 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
217 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700218 patches=[
219 "issue19373_tk_8_5_15_source.patch",
220 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700221 buildDir="unix",
222 configure_pre=[
223 '--enable-aqua',
224 '--enable-shared',
225 '--enable-threads',
226 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
227 ],
228 useLDFlags=False,
229 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'%{
230 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
231 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
232 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
233 },
234 ),
235 ])
236
Ned Deily4a96a372013-01-29 00:08:32 -0800237 if getVersionTuple() >= (3, 3):
238 result.extend([
239 dict(
240 name="XZ 5.0.3",
241 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
242 checksum='fefe52f9ecd521de2a8ce38c21a27574',
243 configure_pre=[
244 '--disable-dependency-tracking',
245 ]
246 ),
247 ])
248
249 result.extend([
250 dict(
251 name="NCurses 5.9",
252 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
253 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
254 configure_pre=[
255 "--enable-widec",
256 "--without-cxx",
257 "--without-cxx-binding",
258 "--without-ada",
259 "--without-curses-h",
260 "--enable-shared",
261 "--with-shared",
262 "--without-debug",
263 "--without-normal",
264 "--without-tests",
265 "--without-manpages",
266 "--datadir=/usr/share",
267 "--sysconfdir=/etc",
268 "--sharedstatedir=/usr/com",
269 "--with-terminfo-dirs=/usr/share/terminfo",
270 "--with-default-terminfo-dir=/usr/share/terminfo",
271 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
272 ],
273 patchscripts=[
274 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
275 "f54bf02a349f96a7c4f0d00922f3a0d4"),
276 ],
277 useLDFlags=False,
278 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
279 shellQuote(os.path.join(WORKDIR, 'libraries')),
280 shellQuote(os.path.join(WORKDIR, 'libraries')),
281 getVersion(),
282 ),
283 ),
284 dict(
285 name="SQLite 3.7.13",
286 url="http://www.sqlite.org/sqlite-autoconf-3071300.tar.gz",
287 checksum='c97df403e8a3d5b67bb408fcd6aabd8e',
288 extra_cflags=('-Os '
289 '-DSQLITE_ENABLE_FTS4 '
290 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
291 '-DSQLITE_ENABLE_RTREE '
292 '-DSQLITE_TCL=0 '
293 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
294 configure_pre=[
295 '--enable-threadsafe',
296 '--enable-shared=no',
297 '--enable-static=yes',
298 '--disable-readline',
299 '--disable-dependency-tracking',
300 ]
301 ),
302 ])
303
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000304 if DEPTARGET < '10.5':
305 result.extend([
306 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000307 name="Bzip2 1.0.6",
308 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
309 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000310 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800311 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
312 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000313 shellQuote(os.path.join(WORKDIR, 'libraries')),
314 ' -arch '.join(ARCHLIST),
315 SDKPATH,
316 ),
317 ),
318 dict(
319 name="ZLib 1.2.3",
320 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
321 checksum='debc62758716a169df9f62e6ab2bc634',
322 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800323 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
324 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000325 shellQuote(os.path.join(WORKDIR, 'libraries')),
326 ' -arch '.join(ARCHLIST),
327 SDKPATH,
328 ),
329 ),
330 dict(
331 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000332 name="GNU Readline 6.1.2",
333 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
334 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000335 patchlevel='0',
336 patches=[
337 # The readline maintainers don't do actual micro releases, but
338 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800339 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
340 'c642f2e84d820884b0bf9fd176bc6c3f'),
341 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
342 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000343 ]
344 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000345 ])
346
Ned Deily4f7ff782011-01-15 05:29:12 +0000347 if not PYTHON_3:
348 result.extend([
349 dict(
350 name="Sleepycat DB 4.7.25",
351 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
352 checksum='ec2b87e833779681a0c3a814aa71359e',
353 buildDir="build_unix",
354 configure="../dist/configure",
355 configure_pre=[
356 '--includedir=/usr/local/include/db4',
357 ]
358 ),
359 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000361 return result
362
Thomas Wouters477c8d52006-05-27 19:21:47 +0000363
Thomas Wouters477c8d52006-05-27 19:21:47 +0000364# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000365def pkg_recipes():
366 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
367 result = [
368 dict(
369 name="PythonFramework",
370 long_name="Python Framework",
371 source="/Library/Frameworks/Python.framework",
372 readme="""\
373 This package installs Python.framework, that is the python
374 interpreter and the standard library. This also includes Python
375 wrappers for lots of Mac OS X API's.
376 """,
377 postflight="scripts/postflight.framework",
378 selected='selected',
379 ),
380 dict(
381 name="PythonApplications",
382 long_name="GUI Applications",
383 source="/Applications/Python %(VER)s",
384 readme="""\
385 This package installs IDLE (an interactive Python IDE),
386 Python Launcher and Build Applet (create application bundles
387 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000388
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000389 It also installs a number of examples and demos.
390 """,
391 required=False,
392 selected='selected',
393 ),
394 dict(
395 name="PythonUnixTools",
396 long_name="UNIX command-line tools",
397 source="/usr/local/bin",
398 readme="""\
399 This package installs the unix tools in /usr/local/bin for
400 compatibility with older releases of Python. This package
401 is not necessary to use Python.
402 """,
403 required=False,
404 selected='selected',
405 ),
406 dict(
407 name="PythonDocumentation",
408 long_name="Python Documentation",
409 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
410 source="/pydocs",
411 readme="""\
412 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800413 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000414 """,
415 postflight="scripts/postflight.documentation",
416 required=False,
417 selected='selected',
418 ),
419 dict(
420 name="PythonProfileChanges",
421 long_name="Shell profile updater",
422 readme="""\
423 This packages updates your shell profile to make sure that
424 the Python tools are found by your shell in preference of
425 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000426
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000427 If you don't install this package you'll have to add
428 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
429 to your PATH by hand.
430 """,
431 postflight="scripts/postflight.patch-profile",
432 topdir="/Library/Frameworks/Python.framework",
433 source="/empty-dir",
434 required=False,
435 selected=unselected_for_python3,
436 ),
437 ]
438
Ned Deily4a96a372013-01-29 00:08:32 -0800439 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000440 result.append(
441 dict(
442 name="PythonSystemFixes",
443 long_name="Fix system Python",
444 readme="""\
445 This package updates the system python installation on
446 Mac OS X 10.3 to ensure that you can build new python extensions
447 using that copy of python after installing this version.
448 """,
449 postflight="../Tools/fixapplepython23.py",
450 topdir="/Library/Frameworks/Python.framework",
451 source="/empty-dir",
452 required=False,
453 selected=unselected_for_python3,
454 )
455 )
456 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000457
Thomas Wouters477c8d52006-05-27 19:21:47 +0000458def fatal(msg):
459 """
460 A fatal error, bail out.
461 """
462 sys.stderr.write('FATAL: ')
463 sys.stderr.write(msg)
464 sys.stderr.write('\n')
465 sys.exit(1)
466
467def fileContents(fn):
468 """
469 Return the contents of the named file
470 """
Ned Deily4a96a372013-01-29 00:08:32 -0800471 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000472
473def runCommand(commandline):
474 """
Ezio Melotti13925002011-03-16 11:05:33 +0200475 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000476 unless the command fails.
477 """
478 fd = os.popen(commandline, 'r')
479 data = fd.read()
480 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000481 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800483 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484
485 if VERBOSE:
486 sys.stdout.write(data); sys.stdout.flush()
487
488def captureCommand(commandline):
489 fd = os.popen(commandline, 'r')
490 data = fd.read()
491 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000492 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000493 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800494 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000495
496 return data
497
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000498def getTclTkVersion(configfile, versionline):
499 """
500 search Tcl or Tk configuration file for version line
501 """
502 try:
503 f = open(configfile, "r")
504 except:
505 fatal("Framework configuration file not found: %s" % configfile)
506
507 for l in f:
508 if l.startswith(versionline):
509 f.close()
510 return l
511
512 fatal("Version variable %s not found in framework configuration file: %s"
513 % (versionline, configfile))
514
Thomas Wouters477c8d52006-05-27 19:21:47 +0000515def checkEnvironment():
516 """
517 Check that we're running on a supported system.
518 """
519
Ned Deilye59e4c52011-01-29 18:56:28 +0000520 if sys.version_info[0:2] < (2, 4):
521 fatal("This script must be run with Python 2.4 or later")
522
Thomas Wouters477c8d52006-05-27 19:21:47 +0000523 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000524 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000525
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000526 if int(platform.release().split('.')[0]) < 8:
527 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000528
529 if not os.path.exists(SDKPATH):
530 fatal("Please install the latest version of Xcode and the %s SDK"%(
531 os.path.basename(SDKPATH[:-4])))
532
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000533 # Because we only support dynamic load of only one major/minor version of
534 # Tcl/Tk, ensure:
535 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800536 # higher than the Apple-supplied system version in
537 # SDKROOT/System/Library/Frameworks
538 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
539 # in) SDKROOT/Library/Frameworks with the same version as the system
540 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000541
Ned Deily4a96a372013-01-29 00:08:32 -0800542 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000543 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800544 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000545 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800546 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000547 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800548 frameworks[framework] = os.readlink(sysfw)
549 if not os.path.exists(libfw):
550 fatal("Please install a link to a current %s %s as %s so "
551 "the user can override the system framework."
552 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000553 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000554 fatal("Version of %s must match %s" % (libfw, sysfw) )
555 if os.path.exists(usrfw):
556 fatal("Please rename %s to avoid possible dynamic load issues."
557 % usrfw)
558
Ned Deily4a96a372013-01-29 00:08:32 -0800559 if frameworks['Tcl'] != frameworks['Tk']:
560 fatal("The Tcl and Tk frameworks are not the same version.")
561
562 # add files to check after build
563 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
564 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
565 % frameworks['Tcl'],
566 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
567 % frameworks['Tk'],
568 ]
569
Ned Deily5b3582c2013-10-25 00:41:46 -0700570 # For 10.6+ builds, we build two versions of _tkinter:
Ned Deily050fcd52013-10-26 03:16:44 -0700571 # - the traditional version (renamed to _tkinter_library.so) linked
Ned Deily5b3582c2013-10-25 00:41:46 -0700572 # with /Library/Frameworks/{Tcl,Tk}.framework
Ned Deily050fcd52013-10-26 03:16:44 -0700573 # - the default version linked with our builtin copies of Tcl and Tk
Ned Deily5b3582c2013-10-25 00:41:46 -0700574 if DEPTARGET > '10.5':
Ned Deily050fcd52013-10-26 03:16:44 -0700575 EXPECTED_SHARED_LIBS['_tkinter_library.so'] = \
Ned Deily5b3582c2013-10-25 00:41:46 -0700576 EXPECTED_SHARED_LIBS['_tkinter.so']
577 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
578 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib"
579 % (getVersion(), frameworks['Tcl']),
580 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtk%s.dylib"
581 % (getVersion(), frameworks['Tk']),
582 ]
583
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000584 # Remove inherited environment variables which might influence build
585 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
586 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
587 for ev in list(os.environ):
588 for prefix in environ_var_prefixes:
589 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800590 print("INFO: deleting environment variable %s=%s" % (
591 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000592 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000593
Ned Deily4a96a372013-01-29 00:08:32 -0800594 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
595 if 'SDK_TOOLS_BIN' in os.environ:
596 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
597 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
598 # add its fixed location here if it exists
599 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
600 if os.path.isdir(OLD_DEVELOPER_TOOLS):
601 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
602 os.environ['PATH'] = base_path
603 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000604
Thomas Wouters477c8d52006-05-27 19:21:47 +0000605
Thomas Wouters89f507f2006-12-13 04:49:30 +0000606def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000607 """
608 Parse arguments and update global settings.
609 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000610 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800611 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000612
613 if args is None:
614 args = sys.argv[1:]
615
616 try:
617 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000618 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
619 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800620 except getopt.GetoptError:
621 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000622 sys.exit(1)
623
624 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800625 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000626 sys.exit(1)
627
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000628 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000629 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000630 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800631 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632 sys.exit(0)
633
634 elif k in ('-d', '--build-dir'):
635 WORKDIR=v
636
637 elif k in ('--third-party',):
638 DEPSRC=v
639
640 elif k in ('--sdk-path',):
641 SDKPATH=v
642
643 elif k in ('--src-dir',):
644 SRCDIR=v
645
Ronald Oussoren1943f862009-03-30 19:39:14 +0000646 elif k in ('--dep-target', ):
647 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000648 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000649
650 elif k in ('--universal-archs', ):
651 if v in UNIVERSALOPTS:
652 UNIVERSALARCHS = v
653 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000654 if deptarget is None:
655 # Select alternate default deployment
656 # target
657 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000658 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800659 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000660
Thomas Wouters477c8d52006-05-27 19:21:47 +0000661 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800662 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000663
664 SRCDIR=os.path.abspath(SRCDIR)
665 WORKDIR=os.path.abspath(WORKDIR)
666 SDKPATH=os.path.abspath(SDKPATH)
667 DEPSRC=os.path.abspath(DEPSRC)
668
Ned Deily4a96a372013-01-29 00:08:32 -0800669 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000670
Ned Deily4a96a372013-01-29 00:08:32 -0800671 print("Settings:")
672 print(" * Source directory:", SRCDIR)
673 print(" * Build directory: ", WORKDIR)
674 print(" * SDK location: ", SDKPATH)
675 print(" * Third-party source:", DEPSRC)
676 print(" * Deployment target:", DEPTARGET)
677 print(" * Universal architectures:", ARCHLIST)
678 print(" * C compiler:", CC)
679 print(" * C++ compiler:", CXX)
680 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000681
682
683
684
685def extractArchive(builddir, archiveName):
686 """
687 Extract a source archive into 'builddir'. Returns the path of the
688 extracted archive.
689
690 XXX: This function assumes that archives contain a toplevel directory
691 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700692 safe enough for almost anything we use. Unfortunately, it does not
693 work for current Tcl and Tk source releases where the basename of
694 the archive ends with "-src" but the uncompressed directory does not.
695 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000696 """
697 curdir = os.getcwd()
698 try:
699 os.chdir(builddir)
700 if archiveName.endswith('.tar.gz'):
701 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700702 if ((retval.startswith('tcl') or retval.startswith('tk'))
703 and retval.endswith('-src')):
704 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000705 if os.path.exists(retval):
706 shutil.rmtree(retval)
707 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
708
709 elif archiveName.endswith('.tar.bz2'):
710 retval = os.path.basename(archiveName[:-8])
711 if os.path.exists(retval):
712 shutil.rmtree(retval)
713 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
714
715 elif archiveName.endswith('.tar'):
716 retval = os.path.basename(archiveName[:-4])
717 if os.path.exists(retval):
718 shutil.rmtree(retval)
719 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
720
721 elif archiveName.endswith('.zip'):
722 retval = os.path.basename(archiveName[:-4])
723 if os.path.exists(retval):
724 shutil.rmtree(retval)
725 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
726
727 data = fp.read()
728 xit = fp.close()
729 if xit is not None:
730 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800731 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000732
733 return os.path.join(builddir, retval)
734
735 finally:
736 os.chdir(curdir)
737
Thomas Wouters477c8d52006-05-27 19:21:47 +0000738def downloadURL(url, fname):
739 """
740 Download the contents of the url into the file.
741 """
Ned Deily4a96a372013-01-29 00:08:32 -0800742 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743 fpOut = open(fname, 'wb')
744 block = fpIn.read(10240)
745 try:
746 while block:
747 fpOut.write(block)
748 block = fpIn.read(10240)
749 fpIn.close()
750 fpOut.close()
751 except:
752 try:
753 os.unlink(fname)
754 except:
755 pass
756
Ned Deily4a96a372013-01-29 00:08:32 -0800757def verifyThirdPartyFile(url, checksum, fname):
758 """
759 Download file from url to filename fname if it does not already exist.
760 Abort if file contents does not match supplied md5 checksum.
761 """
762 name = os.path.basename(fname)
763 if os.path.exists(fname):
764 print("Using local copy of %s"%(name,))
765 else:
766 print("Did not find local copy of %s"%(name,))
767 print("Downloading %s"%(name,))
768 downloadURL(url, fname)
769 print("Archive for %s stored as %s"%(name, fname))
770 if os.system(
771 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
772 % (shellQuote(fname), checksum) ):
773 fatal('MD5 checksum mismatch for file %s' % fname)
774
Thomas Wouters477c8d52006-05-27 19:21:47 +0000775def buildRecipe(recipe, basedir, archList):
776 """
777 Build software using a recipe. This function does the
778 'configure;make;make install' dance for C software, with a possibility
779 to customize this process, basically a poor-mans DarwinPorts.
780 """
781 curdir = os.getcwd()
782
783 name = recipe['name']
784 url = recipe['url']
785 configure = recipe.get('configure', './configure')
786 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
787 shellQuote(basedir)))
788
789 archiveName = os.path.split(url)[-1]
790 sourceArchive = os.path.join(DEPSRC, archiveName)
791
792 if not os.path.exists(DEPSRC):
793 os.mkdir(DEPSRC)
794
Ned Deily4a96a372013-01-29 00:08:32 -0800795 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
796 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000797 buildDir=os.path.join(WORKDIR, '_bld')
798 if not os.path.exists(buildDir):
799 os.mkdir(buildDir)
800
801 workDir = extractArchive(buildDir, sourceArchive)
802 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000803
Ned Deily4a96a372013-01-29 00:08:32 -0800804 for patch in recipe.get('patches', ()):
805 if isinstance(patch, tuple):
806 url, checksum = patch
807 fn = os.path.join(DEPSRC, os.path.basename(url))
808 verifyThirdPartyFile(url, checksum, fn)
809 else:
810 # patch is a file in the source directory
811 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000812 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
813 shellQuote(fn),))
814
Ned Deily4a96a372013-01-29 00:08:32 -0800815 for patchscript in recipe.get('patchscripts', ()):
816 if isinstance(patchscript, tuple):
817 url, checksum = patchscript
818 fn = os.path.join(DEPSRC, os.path.basename(url))
819 verifyThirdPartyFile(url, checksum, fn)
820 else:
821 # patch is a file in the source directory
822 fn = os.path.join(curdir, patchscript)
823 if fn.endswith('.bz2'):
824 runCommand('bunzip2 -fk %s' % shellQuote(fn))
825 fn = fn[:-4]
826 runCommand('sh %s' % shellQuote(fn))
827 os.unlink(fn)
828
Ned Deily94764b22013-10-27 19:49:29 -0700829 if 'buildDir' in recipe:
830 os.chdir(recipe['buildDir'])
831
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000832 if configure is not None:
833 configure_args = [
834 "--prefix=/usr/local",
835 "--enable-static",
836 "--disable-shared",
837 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
838 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000839
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000840 if 'configure_pre' in recipe:
841 args = list(recipe['configure_pre'])
842 if '--disable-static' in args:
843 configure_args.remove('--enable-static')
844 if '--enable-shared' in args:
845 configure_args.remove('--disable-shared')
846 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000847
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000848 if recipe.get('useLDFlags', 1):
849 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800850 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
851 "-I%s/usr/local/include"%(
852 recipe.get('extra_cflags', ''),
853 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000854 ' -arch '.join(archList),
855 shellQuote(SDKPATH)[1:-1],
856 shellQuote(basedir)[1:-1],),
Ned Deily4a96a372013-01-29 00:08:32 -0800857 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
858 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000859 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000860 shellQuote(basedir)[1:-1],
861 ' -arch '.join(archList)),
862 ])
863 else:
864 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800865 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
866 "-I%s/usr/local/include"%(
867 recipe.get('extra_cflags', ''),
868 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000869 ' -arch '.join(archList),
870 shellQuote(SDKPATH)[1:-1],
871 shellQuote(basedir)[1:-1],),
872 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000873
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000874 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800875 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000876
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000877 configure_args.insert(0, configure)
878 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000879
Ned Deily4a96a372013-01-29 00:08:32 -0800880 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000881 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000882
Ned Deily4a96a372013-01-29 00:08:32 -0800883 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000884 runCommand('{ ' + install + ' ;} 2>&1')
885
Ned Deily4a96a372013-01-29 00:08:32 -0800886 print("Done %s"%(name,))
887 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000888
889 os.chdir(curdir)
890
891def buildLibraries():
892 """
893 Build our dependencies into $WORKDIR/libraries/usr/local
894 """
Ned Deily4a96a372013-01-29 00:08:32 -0800895 print("")
896 print("Building required libraries")
897 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000898 universal = os.path.join(WORKDIR, 'libraries')
899 os.mkdir(universal)
900 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
901 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
902
Ronald Oussoren1943f862009-03-30 19:39:14 +0000903 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000904 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905
906
907
908def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000909 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000910 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800911 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000912 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000913 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000914 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000915 curDir = os.getcwd()
916 os.chdir(buildDir)
917 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000918 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000919 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000920 if not os.path.exists(docdir):
921 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000922 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000923
924
925def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800926 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000927
928 buildDir = os.path.join(WORKDIR, '_bld', 'python')
929 rootDir = os.path.join(WORKDIR, '_root')
930
931 if os.path.exists(buildDir):
932 shutil.rmtree(buildDir)
933 if os.path.exists(rootDir):
934 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000935 os.makedirs(buildDir)
936 os.makedirs(rootDir)
937 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000938 curdir = os.getcwd()
939 os.chdir(buildDir)
940
941 # Not sure if this is still needed, the original build script
942 # claims that parts of the install assume python.exe exists.
943 os.symlink('python', os.path.join(buildDir, 'python.exe'))
944
945 # Extract the version from the configure file, needed to calculate
946 # several paths.
947 version = getVersion()
948
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000949 # Since the extra libs are not in their installed framework location
950 # during the build, augment the library path so that the interpreter
951 # will find them during its extension import sanity checks.
952 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
953 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800954 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000955 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000956 "--with-universal-archs=%s "
957 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000958 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700959 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000960 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
961 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000962 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000963 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000964 shellQuote(WORKDIR)[1:-1]))
965
Ned Deily4a96a372013-01-29 00:08:32 -0800966 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000967 runCommand("make")
968
Ned Deily5b3582c2013-10-25 00:41:46 -0700969 # For deployment targets of 10.6 and higher, we build our own version
970 # of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is
971 # out-of-date and has critical bugs. Save the _tkinter.so that was
972 # linked with /Library/Frameworks/{Tck,Tk}.framework and build
Ned Deily050fcd52013-10-26 03:16:44 -0700973 # another _tkinter.so linked with our builtin Tcl and Tk libs.
Ned Deily5b3582c2013-10-25 00:41:46 -0700974 if DEPTARGET > '10.5':
975 runCommand("find build -name '_tkinter.so' "
Ned Deily050fcd52013-10-26 03:16:44 -0700976 " -execdir mv '{}' _tkinter_library.so \;")
977 print("Running make to build builtin _tkinter")
Ned Deily5b3582c2013-10-25 00:41:46 -0700978 runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' "
979 "TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%(
980 shellQuote(WORKDIR)[1:-1],
981 shellQuote(WORKDIR)[1:-1]))
Ned Deily050fcd52013-10-26 03:16:44 -0700982 # make a copy which will be moved to lib-tkinter later
Ned Deily5b3582c2013-10-25 00:41:46 -0700983 runCommand("find build -name '_tkinter.so' "
Ned Deily050fcd52013-10-26 03:16:44 -0700984 " -execdir cp -p '{}' _tkinter_builtin.so \;")
Ned Deily5b3582c2013-10-25 00:41:46 -0700985
Ned Deily4a96a372013-01-29 00:08:32 -0800986 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000987 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 shellQuote(rootDir)))
989
Ned Deily4a96a372013-01-29 00:08:32 -0800990 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000991 runCommand("make frameworkinstallextras DESTDIR=%s"%(
992 shellQuote(rootDir)))
993
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000994 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800995 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000996 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
997 runCommand("mv %s/* %s"%(
998 shellQuote(os.path.join(
999 WORKDIR, 'libraries', 'Library', 'Frameworks',
1000 'Python.framework', 'Versions', getVersion(),
1001 'lib')),
1002 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1003 'Python.framework', 'Versions', getVersion(),
1004 'lib'))))
1005
Ned Deily050fcd52013-10-26 03:16:44 -07001006 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1007 'Python.framework', 'Versions',
1008 version, 'lib', 'python%s'%(version,))
1009
1010 # If we made multiple versions of _tkinter, move them to
1011 # their own directories under python lib. This allows
1012 # users to select which to import by manipulating sys.path
1013 # directly or with PYTHONPATH.
1014
1015 if DEPTARGET > '10.5':
1016 TKINTERS = ['builtin', 'library']
1017 tkinter_moves = [('_tkinter_' + tkn + '.so',
1018 os.path.join(path_to_lib, 'lib-tkinter', tkn))
1019 for tkn in TKINTERS]
1020 # Create the destination directories under lib-tkinter.
1021 # The permissions and uid/gid will be fixed up next.
1022 for tkm in tkinter_moves:
1023 os.makedirs(tkm[1])
1024
Ned Deily4a96a372013-01-29 00:08:32 -08001025 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001026 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001027 gid = grp.getgrnam('admin').gr_gid
1028
Ned Deily4a96a372013-01-29 00:08:32 -08001029 shared_lib_error = False
Ned Deily050fcd52013-10-26 03:16:44 -07001030 moves_list = []
Thomas Wouters477c8d52006-05-27 19:21:47 +00001031 for dirpath, dirnames, filenames in os.walk(frmDir):
1032 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001033 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001034 os.chown(os.path.join(dirpath, dn), -1, gid)
1035
Thomas Wouters477c8d52006-05-27 19:21:47 +00001036 for fn in filenames:
1037 if os.path.islink(fn):
1038 continue
1039
1040 # "chmod g+w $fn"
1041 p = os.path.join(dirpath, fn)
1042 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001043 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1044 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001045
Ned Deily4a96a372013-01-29 00:08:32 -08001046 if fn in EXPECTED_SHARED_LIBS:
1047 # check to see that this file was linked with the
1048 # expected library path and version
1049 data = captureCommand("otool -L %s" % shellQuote(p))
1050 for sl in EXPECTED_SHARED_LIBS[fn]:
1051 if ("\t%s " % sl) not in data:
1052 print("Expected shared lib %s was not linked with %s"
1053 % (sl, p))
1054 shared_lib_error = True
1055
Ned Deily050fcd52013-10-26 03:16:44 -07001056 # If this is a _tkinter variant, move it to its own directory
1057 # now that we have fixed its permissions and checked that it
1058 # was linked properly. The directory was created earlier.
1059 # The files are moved after the entire tree has been walked
1060 # since the shared library checking depends on the files
1061 # having unique names.
1062 if DEPTARGET > '10.5':
1063 for tkm in tkinter_moves:
1064 if fn == tkm[0]:
1065 moves_list.append(
1066 (p, os.path.join(tkm[1], '_tkinter.so')))
1067
Ned Deily4a96a372013-01-29 00:08:32 -08001068 if shared_lib_error:
1069 fatal("Unexpected shared library errors.")
1070
Ned Deily050fcd52013-10-26 03:16:44 -07001071 # Now do the moves.
1072 for ml in moves_list:
1073 shutil.move(ml[0], ml[1])
1074
Ned Deilye59e4c52011-01-29 18:56:28 +00001075 if PYTHON_3:
1076 LDVERSION=None
1077 VERSION=None
1078 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001079
Ned Deilye59e4c52011-01-29 18:56:28 +00001080 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001081 for ln in fp:
1082 if ln.startswith('VERSION='):
1083 VERSION=ln.split()[1]
1084 if ln.startswith('ABIFLAGS='):
1085 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001086 if ln.startswith('LDVERSION='):
1087 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001088 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001089
Ned Deilye59e4c52011-01-29 18:56:28 +00001090 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1091 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1092 config_suffix = '-' + LDVERSION
1093 else:
1094 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001095
Thomas Wouters477c8d52006-05-27 19:21:47 +00001096 # We added some directories to the search path during the configure
1097 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001098 # the end-users system. Also remove the directories from _sysconfigdata.py
1099 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100
Ned Deilya4f6b002013-10-25 00:47:38 -07001101 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1102 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1103
Ned Deilya4f6b002013-10-25 00:47:38 -07001104 # fix Makefile
1105 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1106 fp = open(path, 'r')
1107 data = fp.read()
1108 fp.close()
1109
1110 for p in (include_path, lib_path):
1111 data = data.replace(" " + p, '')
1112 data = data.replace(p + " ", '')
1113
1114 fp = open(path, 'w')
1115 fp.write(data)
1116 fp.close()
1117
1118 # fix _sysconfigdata if it exists
1119 #
1120 # TODO: make this more robust! test_sysconfig_module of
1121 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1122 # the output from get_config_var in both sysconfig and
1123 # distutils.sysconfig is exactly the same for both CFLAGS and
1124 # LDFLAGS. The fixing up is now complicated by the pretty
1125 # printing in _sysconfigdata.py. Also, we are using the
1126 # pprint from the Python running the installer build which
1127 # may not cosmetically format the same as the pprint in the Python
1128 # being built (and which is used to originally generate
1129 # _sysconfigdata.py).
1130
1131 import pprint
1132 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1133 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001134 fp = open(path, 'r')
1135 data = fp.read()
1136 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001137 # create build_time_vars dict
1138 exec(data)
1139 vars = {}
1140 for k, v in build_time_vars.items():
1141 if type(v) == type(''):
1142 for p in (include_path, lib_path):
1143 v = v.replace(' ' + p, '')
1144 v = v.replace(p + ' ', '')
1145 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001146
Ned Deily4a96a372013-01-29 00:08:32 -08001147 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001148 # duplicated from sysconfig._generate_posix_vars()
1149 fp.write('# system configuration generated and used by'
1150 ' the sysconfig module\n')
1151 fp.write('build_time_vars = ')
1152 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001153 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154
1155 # Add symlinks in /usr/local/bin, using relative links
1156 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1157 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1158 'Python.framework', 'Versions', version, 'bin')
1159 if os.path.exists(usr_local_bin):
1160 shutil.rmtree(usr_local_bin)
1161 os.makedirs(usr_local_bin)
1162 for fn in os.listdir(
1163 os.path.join(frmDir, 'Versions', version, 'bin')):
1164 os.symlink(os.path.join(to_framework, fn),
1165 os.path.join(usr_local_bin, fn))
1166
1167 os.chdir(curdir)
1168
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001169 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001170 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001171 # with an already installed version of python 2
1172 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1173 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001174
1175def patchFile(inPath, outPath):
1176 data = fileContents(inPath)
1177 data = data.replace('$FULL_VERSION', getFullVersion())
1178 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001179 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001180 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001181 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001182
1183 # This one is not handy as a template variable
1184 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001185 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001186 fp.write(data)
1187 fp.close()
1188
1189def patchScript(inPath, outPath):
1190 data = fileContents(inPath)
1191 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001192 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001193 fp.write(data)
1194 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001195 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001196
1197
1198
1199def packageFromRecipe(targetDir, recipe):
1200 curdir = os.getcwd()
1201 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001202 # The major version (such as 2.5) is included in the package name
1203 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001204 # common.
1205 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001206 srcdir = recipe.get('source')
1207 pkgroot = recipe.get('topdir', srcdir)
1208 postflight = recipe.get('postflight')
1209 readme = textwrap.dedent(recipe['readme'])
1210 isRequired = recipe.get('required', True)
1211
Ned Deily4a96a372013-01-29 00:08:32 -08001212 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001213
1214 # Substitute some variables
1215 textvars = dict(
1216 VER=getVersion(),
1217 FULLVER=getFullVersion(),
1218 )
1219 readme = readme % textvars
1220
1221 if pkgroot is not None:
1222 pkgroot = pkgroot % textvars
1223 else:
1224 pkgroot = '/'
1225
1226 if srcdir is not None:
1227 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1228 srcdir = srcdir % textvars
1229
1230 if postflight is not None:
1231 postflight = os.path.abspath(postflight)
1232
1233 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1234 os.makedirs(packageContents)
1235
1236 if srcdir is not None:
1237 os.chdir(srcdir)
1238 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1239 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1240 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1241
1242 fn = os.path.join(packageContents, 'PkgInfo')
1243 fp = open(fn, 'w')
1244 fp.write('pmkrpkg1')
1245 fp.close()
1246
1247 rsrcDir = os.path.join(packageContents, "Resources")
1248 os.mkdir(rsrcDir)
1249 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1250 fp.write(readme)
1251 fp.close()
1252
1253 if postflight is not None:
1254 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1255
1256 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001257 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001258 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001259 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1260 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1261 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001262 CFBundleShortVersionString=vers,
1263 IFMajorVersion=major,
1264 IFMinorVersion=minor,
1265 IFPkgFormatVersion=0.10000000149011612,
1266 IFPkgFlagAllowBackRev=False,
1267 IFPkgFlagAuthorizationAction="RootAuthorization",
1268 IFPkgFlagDefaultLocation=pkgroot,
1269 IFPkgFlagFollowLinks=True,
1270 IFPkgFlagInstallFat=True,
1271 IFPkgFlagIsRequired=isRequired,
1272 IFPkgFlagOverwritePermissions=False,
1273 IFPkgFlagRelocatable=False,
1274 IFPkgFlagRestartAction="NoRestart",
1275 IFPkgFlagRootVolumeOnly=True,
1276 IFPkgFlagUpdateInstalledLangauges=False,
1277 )
1278 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1279
1280 pl = Plist(
1281 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001282 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001283 IFPkgDescriptionVersion=vers,
1284 )
1285 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1286
1287 finally:
1288 os.chdir(curdir)
1289
1290
1291def makeMpkgPlist(path):
1292
1293 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001294 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001295
1296 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001297 CFBundleGetInfoString="Python %s"%(vers,),
1298 CFBundleIdentifier='org.python.Python',
1299 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001300 CFBundleShortVersionString=vers,
1301 IFMajorVersion=major,
1302 IFMinorVersion=minor,
1303 IFPkgFlagComponentDirectory="Contents/Packages",
1304 IFPkgFlagPackageList=[
1305 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001306 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001307 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001308 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001309 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001310 ],
1311 IFPkgFormatVersion=0.10000000149011612,
1312 IFPkgFlagBackgroundScaling="proportional",
1313 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001314 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001315 )
1316
1317 writePlist(pl, path)
1318
1319
1320def buildInstaller():
1321
1322 # Zap all compiled files
1323 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1324 for fn in filenames:
1325 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1326 os.unlink(os.path.join(dirpath, fn))
1327
1328 outdir = os.path.join(WORKDIR, 'installer')
1329 if os.path.exists(outdir):
1330 shutil.rmtree(outdir)
1331 os.mkdir(outdir)
1332
Ronald Oussoren1943f862009-03-30 19:39:14 +00001333 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001334 pkgcontents = os.path.join(pkgroot, 'Packages')
1335 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001336 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001337 packageFromRecipe(pkgcontents, recipe)
1338
1339 rsrcDir = os.path.join(pkgroot, 'Resources')
1340
1341 fn = os.path.join(pkgroot, 'PkgInfo')
1342 fp = open(fn, 'w')
1343 fp.write('pmkrpkg1')
1344 fp.close()
1345
1346 os.mkdir(rsrcDir)
1347
1348 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1349 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001350 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001351 IFPkgDescriptionVersion=getVersion(),
1352 )
1353
1354 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1355 for fn in os.listdir('resources'):
1356 if fn == '.svn': continue
1357 if fn.endswith('.jpg'):
1358 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1359 else:
1360 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1361
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001362 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001363
1364
1365def installSize(clear=False, _saved=[]):
1366 if clear:
1367 del _saved[:]
1368 if not _saved:
1369 data = captureCommand("du -ks %s"%(
1370 shellQuote(os.path.join(WORKDIR, '_root'))))
1371 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1372 return _saved[0]
1373
1374
1375def buildDMG():
1376 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001377 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001378 """
1379 outdir = os.path.join(WORKDIR, 'diskimage')
1380 if os.path.exists(outdir):
1381 shutil.rmtree(outdir)
1382
1383 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001384 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001385 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001386 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001387 imagepath = imagepath + '.dmg'
1388
1389 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001390 volname='Python %s'%(getFullVersion())
1391 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1392 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001393 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001394 shellQuote(imagepath + ".tmp.dmg" )))
1395
1396
1397 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1398 os.mkdir(os.path.join(WORKDIR, "mnt"))
1399 runCommand("hdiutil attach %s -mountroot %s"%(
1400 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1401
1402 # Custom icon for the DMG, shown when the DMG is mounted.
1403 shutil.copy("../Icons/Disk Image.icns",
1404 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001405 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001406 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1407
1408 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1409
1410 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1411 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1412 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1413 setIcon(imagepath, "../Icons/Disk Image.icns")
1414
1415 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001416
1417 return imagepath
1418
1419
1420def setIcon(filePath, icnsPath):
1421 """
1422 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001423 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001424
Ronald Oussoren70050672010-04-30 15:00:26 +00001425 dirPath = os.path.normpath(os.path.dirname(__file__))
1426 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001427 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1428 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1429 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001430 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1431 if not os.path.exists(appPath):
1432 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001433 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1434 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001435
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001436 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1437 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001438
1439def main():
1440 # First parse options and check if we can perform our work
1441 parseOptions()
1442 checkEnvironment()
1443
Ronald Oussoren1943f862009-03-30 19:39:14 +00001444 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001445 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001446 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001447
1448 if os.path.exists(WORKDIR):
1449 shutil.rmtree(WORKDIR)
1450 os.mkdir(WORKDIR)
1451
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001452 os.environ['LC_ALL'] = 'C'
1453
Thomas Wouters477c8d52006-05-27 19:21:47 +00001454 # Then build third-party libraries such as sleepycat DB4.
1455 buildLibraries()
1456
1457 # Now build python itself
1458 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001459
1460 # And then build the documentation
1461 # Remove the Deployment Target from the shell
1462 # environment, it's no longer needed and
1463 # an unexpected build target can cause problems
1464 # when Sphinx and its dependencies need to
1465 # be (re-)installed.
1466 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001467 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001468
1469
1470 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001471 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001472 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001473 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001474
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001475 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001476 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001477 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001478 setIcon(folder, "../Icons/Python Folder.icns")
1479
1480 # Create the installer
1481 buildInstaller()
1482
1483 # And copy the readme into the directory containing the installer
1484 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1485
1486 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001487 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001488
1489 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001490 fp.write("# BUILD INFO\n")
1491 fp.write("# Date: %s\n" % time.ctime())
1492 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001493 fp.close()
1494
Thomas Wouters477c8d52006-05-27 19:21:47 +00001495 # And copy it to a DMG
1496 buildDMG()
1497
Thomas Wouters477c8d52006-05-27 19:21:47 +00001498if __name__ == "__main__":
1499 main()