blob: ad672997370f94b4e8f18404a9c8541a6a5d68d9 [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 Deilyaa6a2122013-11-23 03:30:11 -0800196# Disable for now
Ned Deily7e60f512014-04-07 12:10:21 -0700197 if False: # if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 5)):
Ned Deily5b3582c2013-10-25 00:41:46 -0700198 result.extend([
199 dict(
200 name="Tcl 8.5.15",
201 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
202 checksum='f3df162f92c69b254079c4d0af7a690f',
203 buildDir="unix",
204 configure_pre=[
205 '--enable-shared',
206 '--enable-threads',
207 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
208 ],
209 useLDFlags=False,
210 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
211 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
212 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
213 },
214 ),
215 dict(
216 name="Tk 8.5.15",
217 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
218 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700219 patches=[
220 "issue19373_tk_8_5_15_source.patch",
221 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700222 buildDir="unix",
223 configure_pre=[
224 '--enable-aqua',
225 '--enable-shared',
226 '--enable-threads',
227 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
228 ],
229 useLDFlags=False,
230 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'%{
231 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
232 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
233 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
234 },
235 ),
236 ])
237
Ned Deily4a96a372013-01-29 00:08:32 -0800238 if getVersionTuple() >= (3, 3):
239 result.extend([
240 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800241 name="XZ 5.0.5",
242 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
243 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deily4a96a372013-01-29 00:08:32 -0800244 configure_pre=[
245 '--disable-dependency-tracking',
246 ]
247 ),
248 ])
249
250 result.extend([
251 dict(
252 name="NCurses 5.9",
253 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
254 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
255 configure_pre=[
256 "--enable-widec",
257 "--without-cxx",
258 "--without-cxx-binding",
259 "--without-ada",
260 "--without-curses-h",
261 "--enable-shared",
262 "--with-shared",
263 "--without-debug",
264 "--without-normal",
265 "--without-tests",
266 "--without-manpages",
267 "--datadir=/usr/share",
268 "--sysconfdir=/etc",
269 "--sharedstatedir=/usr/com",
270 "--with-terminfo-dirs=/usr/share/terminfo",
271 "--with-default-terminfo-dir=/usr/share/terminfo",
272 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
273 ],
274 patchscripts=[
275 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
276 "f54bf02a349f96a7c4f0d00922f3a0d4"),
277 ],
278 useLDFlags=False,
279 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
280 shellQuote(os.path.join(WORKDIR, 'libraries')),
281 shellQuote(os.path.join(WORKDIR, 'libraries')),
282 getVersion(),
283 ),
284 ),
285 dict(
Ned Deilyc1195c72014-03-01 14:00:46 -0800286 name="SQLite 3.8.3.1",
287 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
288 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deily4a96a372013-01-29 00:08:32 -0800289 extra_cflags=('-Os '
290 '-DSQLITE_ENABLE_FTS4 '
291 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
292 '-DSQLITE_ENABLE_RTREE '
293 '-DSQLITE_TCL=0 '
294 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
295 configure_pre=[
296 '--enable-threadsafe',
297 '--enable-shared=no',
298 '--enable-static=yes',
299 '--disable-readline',
300 '--disable-dependency-tracking',
301 ]
302 ),
303 ])
304
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000305 if DEPTARGET < '10.5':
306 result.extend([
307 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000308 name="Bzip2 1.0.6",
309 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
310 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000311 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800312 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
313 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000314 shellQuote(os.path.join(WORKDIR, 'libraries')),
315 ' -arch '.join(ARCHLIST),
316 SDKPATH,
317 ),
318 ),
319 dict(
320 name="ZLib 1.2.3",
321 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
322 checksum='debc62758716a169df9f62e6ab2bc634',
323 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800324 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
325 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000326 shellQuote(os.path.join(WORKDIR, 'libraries')),
327 ' -arch '.join(ARCHLIST),
328 SDKPATH,
329 ),
330 ),
331 dict(
332 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000333 name="GNU Readline 6.1.2",
334 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
335 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000336 patchlevel='0',
337 patches=[
338 # The readline maintainers don't do actual micro releases, but
339 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800340 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
341 'c642f2e84d820884b0bf9fd176bc6c3f'),
342 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
343 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000344 ]
345 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000346 ])
347
Ned Deily4f7ff782011-01-15 05:29:12 +0000348 if not PYTHON_3:
349 result.extend([
350 dict(
351 name="Sleepycat DB 4.7.25",
352 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
353 checksum='ec2b87e833779681a0c3a814aa71359e',
354 buildDir="build_unix",
355 configure="../dist/configure",
356 configure_pre=[
357 '--includedir=/usr/local/include/db4',
358 ]
359 ),
360 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000361
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000362 return result
363
Thomas Wouters477c8d52006-05-27 19:21:47 +0000364
Thomas Wouters477c8d52006-05-27 19:21:47 +0000365# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000366def pkg_recipes():
367 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deily41ab6c32013-11-22 22:25:43 -0800368 unselected_for_lt_python34 = ('selected', 'unselected')[getVersionTuple() < (3, 4)]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000369 result = [
370 dict(
371 name="PythonFramework",
372 long_name="Python Framework",
373 source="/Library/Frameworks/Python.framework",
374 readme="""\
375 This package installs Python.framework, that is the python
376 interpreter and the standard library. This also includes Python
377 wrappers for lots of Mac OS X API's.
378 """,
379 postflight="scripts/postflight.framework",
380 selected='selected',
381 ),
382 dict(
383 name="PythonApplications",
384 long_name="GUI Applications",
385 source="/Applications/Python %(VER)s",
386 readme="""\
387 This package installs IDLE (an interactive Python IDE),
388 Python Launcher and Build Applet (create application bundles
389 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000390
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000391 It also installs a number of examples and demos.
392 """,
393 required=False,
394 selected='selected',
395 ),
396 dict(
397 name="PythonUnixTools",
398 long_name="UNIX command-line tools",
399 source="/usr/local/bin",
400 readme="""\
401 This package installs the unix tools in /usr/local/bin for
402 compatibility with older releases of Python. This package
403 is not necessary to use Python.
404 """,
405 required=False,
406 selected='selected',
407 ),
408 dict(
409 name="PythonDocumentation",
410 long_name="Python Documentation",
411 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
412 source="/pydocs",
413 readme="""\
414 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800415 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000416 """,
417 postflight="scripts/postflight.documentation",
418 required=False,
419 selected='selected',
420 ),
421 dict(
422 name="PythonProfileChanges",
423 long_name="Shell profile updater",
424 readme="""\
425 This packages updates your shell profile to make sure that
426 the Python tools are found by your shell in preference of
427 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000428
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000429 If you don't install this package you'll have to add
430 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
431 to your PATH by hand.
432 """,
433 postflight="scripts/postflight.patch-profile",
434 topdir="/Library/Frameworks/Python.framework",
435 source="/empty-dir",
436 required=False,
Ned Deily41ab6c32013-11-22 22:25:43 -0800437 selected=unselected_for_lt_python34,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000438 ),
439 ]
440
Ned Deily41ab6c32013-11-22 22:25:43 -0800441 if getVersionTuple() >= (3, 4):
442 result.append(
443 dict(
444 name="PythonInstallPip",
445 long_name="Install or upgrade pip",
446 readme="""\
447 This package installs (or upgrades from an earlier version)
448 pip, a tool for installing and managing Python packages.
449 """,
450 postflight="scripts/postflight.ensurepip",
451 topdir="/Library/Frameworks/Python.framework",
452 source="/empty-dir",
453 required=False,
454 selected='selected',
455 )
456 )
457
Ned Deily4a96a372013-01-29 00:08:32 -0800458 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000459 result.append(
460 dict(
461 name="PythonSystemFixes",
462 long_name="Fix system Python",
463 readme="""\
464 This package updates the system python installation on
465 Mac OS X 10.3 to ensure that you can build new python extensions
466 using that copy of python after installing this version.
467 """,
468 postflight="../Tools/fixapplepython23.py",
469 topdir="/Library/Frameworks/Python.framework",
470 source="/empty-dir",
471 required=False,
472 selected=unselected_for_python3,
473 )
474 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800475
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000476 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478def fatal(msg):
479 """
480 A fatal error, bail out.
481 """
482 sys.stderr.write('FATAL: ')
483 sys.stderr.write(msg)
484 sys.stderr.write('\n')
485 sys.exit(1)
486
487def fileContents(fn):
488 """
489 Return the contents of the named file
490 """
Ned Deily4a96a372013-01-29 00:08:32 -0800491 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000492
493def runCommand(commandline):
494 """
Ezio Melotti13925002011-03-16 11:05:33 +0200495 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000496 unless the command fails.
497 """
498 fd = os.popen(commandline, 'r')
499 data = fd.read()
500 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000501 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000502 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800503 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000504
505 if VERBOSE:
506 sys.stdout.write(data); sys.stdout.flush()
507
508def captureCommand(commandline):
509 fd = os.popen(commandline, 'r')
510 data = fd.read()
511 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000512 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800514 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000515
516 return data
517
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000518def getTclTkVersion(configfile, versionline):
519 """
520 search Tcl or Tk configuration file for version line
521 """
522 try:
523 f = open(configfile, "r")
524 except:
525 fatal("Framework configuration file not found: %s" % configfile)
526
527 for l in f:
528 if l.startswith(versionline):
529 f.close()
530 return l
531
532 fatal("Version variable %s not found in framework configuration file: %s"
533 % (versionline, configfile))
534
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535def checkEnvironment():
536 """
537 Check that we're running on a supported system.
538 """
539
Ned Deilye59e4c52011-01-29 18:56:28 +0000540 if sys.version_info[0:2] < (2, 4):
541 fatal("This script must be run with Python 2.4 or later")
542
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000544 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000545
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000546 if int(platform.release().split('.')[0]) < 8:
547 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548
549 if not os.path.exists(SDKPATH):
550 fatal("Please install the latest version of Xcode and the %s SDK"%(
551 os.path.basename(SDKPATH[:-4])))
552
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000553 # Because we only support dynamic load of only one major/minor version of
554 # Tcl/Tk, ensure:
555 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800556 # higher than the Apple-supplied system version in
557 # SDKROOT/System/Library/Frameworks
558 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
559 # in) SDKROOT/Library/Frameworks with the same version as the system
560 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000561
Ned Deily4a96a372013-01-29 00:08:32 -0800562 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000563 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800564 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000565 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800566 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000567 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800568 frameworks[framework] = os.readlink(sysfw)
569 if not os.path.exists(libfw):
570 fatal("Please install a link to a current %s %s as %s so "
571 "the user can override the system framework."
572 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000573 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000574 fatal("Version of %s must match %s" % (libfw, sysfw) )
575 if os.path.exists(usrfw):
576 fatal("Please rename %s to avoid possible dynamic load issues."
577 % usrfw)
578
Ned Deily4a96a372013-01-29 00:08:32 -0800579 if frameworks['Tcl'] != frameworks['Tk']:
580 fatal("The Tcl and Tk frameworks are not the same version.")
581
582 # add files to check after build
583 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
584 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
585 % frameworks['Tcl'],
586 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
587 % frameworks['Tk'],
588 ]
589
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000590 # Remove inherited environment variables which might influence build
591 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
592 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
593 for ev in list(os.environ):
594 for prefix in environ_var_prefixes:
595 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800596 print("INFO: deleting environment variable %s=%s" % (
597 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000598 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000599
Ned Deily4a96a372013-01-29 00:08:32 -0800600 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
601 if 'SDK_TOOLS_BIN' in os.environ:
602 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
603 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
604 # add its fixed location here if it exists
605 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
606 if os.path.isdir(OLD_DEVELOPER_TOOLS):
607 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
608 os.environ['PATH'] = base_path
609 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deily7e60f512014-04-07 12:10:21 -0700610 # Ensure ws have access to hg and to sphinx-build.
611 # You may have to create links in /usr/bin for them.
612 runCommand('hg --version')
613 runCommand('sphinx-build --version')
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000614
Thomas Wouters477c8d52006-05-27 19:21:47 +0000615
Thomas Wouters89f507f2006-12-13 04:49:30 +0000616def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000617 """
618 Parse arguments and update global settings.
619 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000620 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800621 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000622
623 if args is None:
624 args = sys.argv[1:]
625
626 try:
627 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000628 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
629 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800630 except getopt.GetoptError:
631 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632 sys.exit(1)
633
634 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800635 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000636 sys.exit(1)
637
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000638 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000639 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000640 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800641 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000642 sys.exit(0)
643
644 elif k in ('-d', '--build-dir'):
645 WORKDIR=v
646
647 elif k in ('--third-party',):
648 DEPSRC=v
649
650 elif k in ('--sdk-path',):
651 SDKPATH=v
652
653 elif k in ('--src-dir',):
654 SRCDIR=v
655
Ronald Oussoren1943f862009-03-30 19:39:14 +0000656 elif k in ('--dep-target', ):
657 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000658 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000659
660 elif k in ('--universal-archs', ):
661 if v in UNIVERSALOPTS:
662 UNIVERSALARCHS = v
663 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000664 if deptarget is None:
665 # Select alternate default deployment
666 # target
667 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000668 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800669 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000670
Thomas Wouters477c8d52006-05-27 19:21:47 +0000671 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800672 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000673
674 SRCDIR=os.path.abspath(SRCDIR)
675 WORKDIR=os.path.abspath(WORKDIR)
676 SDKPATH=os.path.abspath(SDKPATH)
677 DEPSRC=os.path.abspath(DEPSRC)
678
Ned Deily4a96a372013-01-29 00:08:32 -0800679 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000680
Ned Deily4a96a372013-01-29 00:08:32 -0800681 print("Settings:")
682 print(" * Source directory:", SRCDIR)
683 print(" * Build directory: ", WORKDIR)
684 print(" * SDK location: ", SDKPATH)
685 print(" * Third-party source:", DEPSRC)
686 print(" * Deployment target:", DEPTARGET)
687 print(" * Universal architectures:", ARCHLIST)
688 print(" * C compiler:", CC)
689 print(" * C++ compiler:", CXX)
690 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000691
692
693
694
695def extractArchive(builddir, archiveName):
696 """
697 Extract a source archive into 'builddir'. Returns the path of the
698 extracted archive.
699
700 XXX: This function assumes that archives contain a toplevel directory
701 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700702 safe enough for almost anything we use. Unfortunately, it does not
703 work for current Tcl and Tk source releases where the basename of
704 the archive ends with "-src" but the uncompressed directory does not.
705 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000706 """
707 curdir = os.getcwd()
708 try:
709 os.chdir(builddir)
710 if archiveName.endswith('.tar.gz'):
711 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700712 if ((retval.startswith('tcl') or retval.startswith('tk'))
713 and retval.endswith('-src')):
714 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000715 if os.path.exists(retval):
716 shutil.rmtree(retval)
717 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
718
719 elif archiveName.endswith('.tar.bz2'):
720 retval = os.path.basename(archiveName[:-8])
721 if os.path.exists(retval):
722 shutil.rmtree(retval)
723 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
724
725 elif archiveName.endswith('.tar'):
726 retval = os.path.basename(archiveName[:-4])
727 if os.path.exists(retval):
728 shutil.rmtree(retval)
729 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
730
731 elif archiveName.endswith('.zip'):
732 retval = os.path.basename(archiveName[:-4])
733 if os.path.exists(retval):
734 shutil.rmtree(retval)
735 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
736
737 data = fp.read()
738 xit = fp.close()
739 if xit is not None:
740 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800741 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000742
743 return os.path.join(builddir, retval)
744
745 finally:
746 os.chdir(curdir)
747
Thomas Wouters477c8d52006-05-27 19:21:47 +0000748def downloadURL(url, fname):
749 """
750 Download the contents of the url into the file.
751 """
Ned Deily4a96a372013-01-29 00:08:32 -0800752 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000753 fpOut = open(fname, 'wb')
754 block = fpIn.read(10240)
755 try:
756 while block:
757 fpOut.write(block)
758 block = fpIn.read(10240)
759 fpIn.close()
760 fpOut.close()
761 except:
762 try:
763 os.unlink(fname)
764 except:
765 pass
766
Ned Deily4a96a372013-01-29 00:08:32 -0800767def verifyThirdPartyFile(url, checksum, fname):
768 """
769 Download file from url to filename fname if it does not already exist.
770 Abort if file contents does not match supplied md5 checksum.
771 """
772 name = os.path.basename(fname)
773 if os.path.exists(fname):
774 print("Using local copy of %s"%(name,))
775 else:
776 print("Did not find local copy of %s"%(name,))
777 print("Downloading %s"%(name,))
778 downloadURL(url, fname)
779 print("Archive for %s stored as %s"%(name, fname))
780 if os.system(
781 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
782 % (shellQuote(fname), checksum) ):
783 fatal('MD5 checksum mismatch for file %s' % fname)
784
Thomas Wouters477c8d52006-05-27 19:21:47 +0000785def buildRecipe(recipe, basedir, archList):
786 """
787 Build software using a recipe. This function does the
788 'configure;make;make install' dance for C software, with a possibility
789 to customize this process, basically a poor-mans DarwinPorts.
790 """
791 curdir = os.getcwd()
792
793 name = recipe['name']
794 url = recipe['url']
795 configure = recipe.get('configure', './configure')
796 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
797 shellQuote(basedir)))
798
799 archiveName = os.path.split(url)[-1]
800 sourceArchive = os.path.join(DEPSRC, archiveName)
801
802 if not os.path.exists(DEPSRC):
803 os.mkdir(DEPSRC)
804
Ned Deily4a96a372013-01-29 00:08:32 -0800805 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
806 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000807 buildDir=os.path.join(WORKDIR, '_bld')
808 if not os.path.exists(buildDir):
809 os.mkdir(buildDir)
810
811 workDir = extractArchive(buildDir, sourceArchive)
812 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000813
Ned Deily4a96a372013-01-29 00:08:32 -0800814 for patch in recipe.get('patches', ()):
815 if isinstance(patch, tuple):
816 url, checksum = patch
817 fn = os.path.join(DEPSRC, os.path.basename(url))
818 verifyThirdPartyFile(url, checksum, fn)
819 else:
820 # patch is a file in the source directory
821 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000822 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
823 shellQuote(fn),))
824
Ned Deily4a96a372013-01-29 00:08:32 -0800825 for patchscript in recipe.get('patchscripts', ()):
826 if isinstance(patchscript, tuple):
827 url, checksum = patchscript
828 fn = os.path.join(DEPSRC, os.path.basename(url))
829 verifyThirdPartyFile(url, checksum, fn)
830 else:
831 # patch is a file in the source directory
832 fn = os.path.join(curdir, patchscript)
833 if fn.endswith('.bz2'):
834 runCommand('bunzip2 -fk %s' % shellQuote(fn))
835 fn = fn[:-4]
836 runCommand('sh %s' % shellQuote(fn))
837 os.unlink(fn)
838
Ned Deily94764b22013-10-27 19:49:29 -0700839 if 'buildDir' in recipe:
840 os.chdir(recipe['buildDir'])
841
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000842 if configure is not None:
843 configure_args = [
844 "--prefix=/usr/local",
845 "--enable-static",
846 "--disable-shared",
847 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
848 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000849
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000850 if 'configure_pre' in recipe:
851 args = list(recipe['configure_pre'])
852 if '--disable-static' in args:
853 configure_args.remove('--enable-static')
854 if '--enable-shared' in args:
855 configure_args.remove('--disable-shared')
856 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000857
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000858 if recipe.get('useLDFlags', 1):
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],),
Ned Deily4a96a372013-01-29 00:08:32 -0800867 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
868 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000869 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000870 shellQuote(basedir)[1:-1],
871 ' -arch '.join(archList)),
872 ])
873 else:
874 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800875 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
876 "-I%s/usr/local/include"%(
877 recipe.get('extra_cflags', ''),
878 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000879 ' -arch '.join(archList),
880 shellQuote(SDKPATH)[1:-1],
881 shellQuote(basedir)[1:-1],),
882 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000883
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000884 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800885 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000886
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000887 configure_args.insert(0, configure)
888 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000889
Ned Deily4a96a372013-01-29 00:08:32 -0800890 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000891 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000892
Ned Deily4a96a372013-01-29 00:08:32 -0800893 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000894 runCommand('{ ' + install + ' ;} 2>&1')
895
Ned Deily4a96a372013-01-29 00:08:32 -0800896 print("Done %s"%(name,))
897 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000898
899 os.chdir(curdir)
900
901def buildLibraries():
902 """
903 Build our dependencies into $WORKDIR/libraries/usr/local
904 """
Ned Deily4a96a372013-01-29 00:08:32 -0800905 print("")
906 print("Building required libraries")
907 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000908 universal = os.path.join(WORKDIR, 'libraries')
909 os.mkdir(universal)
910 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
911 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
912
Ronald Oussoren1943f862009-03-30 19:39:14 +0000913 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000914 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000915
916
917
918def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000919 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800921 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000922 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000923 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000924 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000925 curDir = os.getcwd()
926 os.chdir(buildDir)
Ned Deily7e60f512014-04-07 12:10:21 -0700927 runCommand('make clean')
928 # Assume sphinx-build is on our PATH, checked in checkEnvironment
929 runCommand('make html')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000930 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000931 if not os.path.exists(docdir):
932 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000933 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000934
935
936def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800937 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000938
939 buildDir = os.path.join(WORKDIR, '_bld', 'python')
940 rootDir = os.path.join(WORKDIR, '_root')
941
942 if os.path.exists(buildDir):
943 shutil.rmtree(buildDir)
944 if os.path.exists(rootDir):
945 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000946 os.makedirs(buildDir)
947 os.makedirs(rootDir)
948 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000949 curdir = os.getcwd()
950 os.chdir(buildDir)
951
952 # Not sure if this is still needed, the original build script
953 # claims that parts of the install assume python.exe exists.
954 os.symlink('python', os.path.join(buildDir, 'python.exe'))
955
956 # Extract the version from the configure file, needed to calculate
957 # several paths.
958 version = getVersion()
959
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000960 # Since the extra libs are not in their installed framework location
961 # during the build, augment the library path so that the interpreter
962 # will find them during its extension import sanity checks.
963 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
964 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800965 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000966 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000967 "--with-universal-archs=%s "
968 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800969 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000970 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700971 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000972 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
973 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000974 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily41ab6c32013-11-22 22:25:43 -0800975 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000976 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000977 shellQuote(WORKDIR)[1:-1]))
978
Ned Deily4a96a372013-01-29 00:08:32 -0800979 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000980 runCommand("make")
981
Ned Deily4a96a372013-01-29 00:08:32 -0800982 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000983 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000984 shellQuote(rootDir)))
985
Ned Deily4a96a372013-01-29 00:08:32 -0800986 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000987 runCommand("make frameworkinstallextras DESTDIR=%s"%(
988 shellQuote(rootDir)))
989
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000990 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800991 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
993 runCommand("mv %s/* %s"%(
994 shellQuote(os.path.join(
995 WORKDIR, 'libraries', 'Library', 'Frameworks',
996 'Python.framework', 'Versions', getVersion(),
997 'lib')),
998 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
999 'Python.framework', 'Versions', getVersion(),
1000 'lib'))))
1001
Ned Deily050fcd52013-10-26 03:16:44 -07001002 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1003 'Python.framework', 'Versions',
1004 version, 'lib', 'python%s'%(version,))
1005
Ned Deily4a96a372013-01-29 00:08:32 -08001006 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001007 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001008 gid = grp.getgrnam('admin').gr_gid
1009
Ned Deily4a96a372013-01-29 00:08:32 -08001010 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001011 for dirpath, dirnames, filenames in os.walk(frmDir):
1012 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001013 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001014 os.chown(os.path.join(dirpath, dn), -1, gid)
1015
Thomas Wouters477c8d52006-05-27 19:21:47 +00001016 for fn in filenames:
1017 if os.path.islink(fn):
1018 continue
1019
1020 # "chmod g+w $fn"
1021 p = os.path.join(dirpath, fn)
1022 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001023 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1024 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001025
Ned Deily4a96a372013-01-29 00:08:32 -08001026 if fn in EXPECTED_SHARED_LIBS:
1027 # check to see that this file was linked with the
1028 # expected library path and version
1029 data = captureCommand("otool -L %s" % shellQuote(p))
1030 for sl in EXPECTED_SHARED_LIBS[fn]:
1031 if ("\t%s " % sl) not in data:
1032 print("Expected shared lib %s was not linked with %s"
1033 % (sl, p))
1034 shared_lib_error = True
1035
1036 if shared_lib_error:
1037 fatal("Unexpected shared library errors.")
1038
Ned Deilye59e4c52011-01-29 18:56:28 +00001039 if PYTHON_3:
1040 LDVERSION=None
1041 VERSION=None
1042 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001043
Ned Deilye59e4c52011-01-29 18:56:28 +00001044 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001045 for ln in fp:
1046 if ln.startswith('VERSION='):
1047 VERSION=ln.split()[1]
1048 if ln.startswith('ABIFLAGS='):
1049 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001050 if ln.startswith('LDVERSION='):
1051 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001052 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001053
Ned Deilye59e4c52011-01-29 18:56:28 +00001054 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1055 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1056 config_suffix = '-' + LDVERSION
1057 else:
1058 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001059
Thomas Wouters477c8d52006-05-27 19:21:47 +00001060 # We added some directories to the search path during the configure
1061 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001062 # the end-users system. Also remove the directories from _sysconfigdata.py
1063 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001064
Ned Deilya4f6b002013-10-25 00:47:38 -07001065 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1066 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1067
Ned Deilya4f6b002013-10-25 00:47:38 -07001068 # fix Makefile
1069 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1070 fp = open(path, 'r')
1071 data = fp.read()
1072 fp.close()
1073
1074 for p in (include_path, lib_path):
1075 data = data.replace(" " + p, '')
1076 data = data.replace(p + " ", '')
1077
1078 fp = open(path, 'w')
1079 fp.write(data)
1080 fp.close()
1081
1082 # fix _sysconfigdata if it exists
1083 #
1084 # TODO: make this more robust! test_sysconfig_module of
1085 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1086 # the output from get_config_var in both sysconfig and
1087 # distutils.sysconfig is exactly the same for both CFLAGS and
1088 # LDFLAGS. The fixing up is now complicated by the pretty
1089 # printing in _sysconfigdata.py. Also, we are using the
1090 # pprint from the Python running the installer build which
1091 # may not cosmetically format the same as the pprint in the Python
1092 # being built (and which is used to originally generate
1093 # _sysconfigdata.py).
1094
1095 import pprint
1096 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1097 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001098 fp = open(path, 'r')
1099 data = fp.read()
1100 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001101 # create build_time_vars dict
1102 exec(data)
1103 vars = {}
1104 for k, v in build_time_vars.items():
1105 if type(v) == type(''):
1106 for p in (include_path, lib_path):
1107 v = v.replace(' ' + p, '')
1108 v = v.replace(p + ' ', '')
1109 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001110
Ned Deily4a96a372013-01-29 00:08:32 -08001111 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001112 # duplicated from sysconfig._generate_posix_vars()
1113 fp.write('# system configuration generated and used by'
1114 ' the sysconfig module\n')
1115 fp.write('build_time_vars = ')
1116 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001117 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001118
1119 # Add symlinks in /usr/local/bin, using relative links
1120 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1121 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1122 'Python.framework', 'Versions', version, 'bin')
1123 if os.path.exists(usr_local_bin):
1124 shutil.rmtree(usr_local_bin)
1125 os.makedirs(usr_local_bin)
1126 for fn in os.listdir(
1127 os.path.join(frmDir, 'Versions', version, 'bin')):
1128 os.symlink(os.path.join(to_framework, fn),
1129 os.path.join(usr_local_bin, fn))
1130
1131 os.chdir(curdir)
1132
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001133 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001134 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001135 # with an already installed version of python 2
1136 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1137 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001138
1139def patchFile(inPath, outPath):
1140 data = fileContents(inPath)
1141 data = data.replace('$FULL_VERSION', getFullVersion())
1142 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001143 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001144 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001145 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001146
1147 # This one is not handy as a template variable
1148 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001149 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001150 fp.write(data)
1151 fp.close()
1152
1153def patchScript(inPath, outPath):
1154 data = fileContents(inPath)
1155 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001156 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 fp.write(data)
1158 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001159 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001160
1161
1162
1163def packageFromRecipe(targetDir, recipe):
1164 curdir = os.getcwd()
1165 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001166 # The major version (such as 2.5) is included in the package name
1167 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001168 # common.
1169 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001170 srcdir = recipe.get('source')
1171 pkgroot = recipe.get('topdir', srcdir)
1172 postflight = recipe.get('postflight')
1173 readme = textwrap.dedent(recipe['readme'])
1174 isRequired = recipe.get('required', True)
1175
Ned Deily4a96a372013-01-29 00:08:32 -08001176 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001177
1178 # Substitute some variables
1179 textvars = dict(
1180 VER=getVersion(),
1181 FULLVER=getFullVersion(),
1182 )
1183 readme = readme % textvars
1184
1185 if pkgroot is not None:
1186 pkgroot = pkgroot % textvars
1187 else:
1188 pkgroot = '/'
1189
1190 if srcdir is not None:
1191 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1192 srcdir = srcdir % textvars
1193
1194 if postflight is not None:
1195 postflight = os.path.abspath(postflight)
1196
1197 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1198 os.makedirs(packageContents)
1199
1200 if srcdir is not None:
1201 os.chdir(srcdir)
1202 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1203 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1204 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1205
1206 fn = os.path.join(packageContents, 'PkgInfo')
1207 fp = open(fn, 'w')
1208 fp.write('pmkrpkg1')
1209 fp.close()
1210
1211 rsrcDir = os.path.join(packageContents, "Resources")
1212 os.mkdir(rsrcDir)
1213 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1214 fp.write(readme)
1215 fp.close()
1216
1217 if postflight is not None:
1218 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1219
1220 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001221 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001222 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001223 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1224 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1225 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001226 CFBundleShortVersionString=vers,
1227 IFMajorVersion=major,
1228 IFMinorVersion=minor,
1229 IFPkgFormatVersion=0.10000000149011612,
1230 IFPkgFlagAllowBackRev=False,
1231 IFPkgFlagAuthorizationAction="RootAuthorization",
1232 IFPkgFlagDefaultLocation=pkgroot,
1233 IFPkgFlagFollowLinks=True,
1234 IFPkgFlagInstallFat=True,
1235 IFPkgFlagIsRequired=isRequired,
1236 IFPkgFlagOverwritePermissions=False,
1237 IFPkgFlagRelocatable=False,
1238 IFPkgFlagRestartAction="NoRestart",
1239 IFPkgFlagRootVolumeOnly=True,
1240 IFPkgFlagUpdateInstalledLangauges=False,
1241 )
1242 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1243
1244 pl = Plist(
1245 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001246 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001247 IFPkgDescriptionVersion=vers,
1248 )
1249 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1250
1251 finally:
1252 os.chdir(curdir)
1253
1254
1255def makeMpkgPlist(path):
1256
1257 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001258 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001259
1260 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001261 CFBundleGetInfoString="Python %s"%(vers,),
1262 CFBundleIdentifier='org.python.Python',
1263 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001264 CFBundleShortVersionString=vers,
1265 IFMajorVersion=major,
1266 IFMinorVersion=minor,
1267 IFPkgFlagComponentDirectory="Contents/Packages",
1268 IFPkgFlagPackageList=[
1269 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001270 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001271 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001272 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001273 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001274 ],
1275 IFPkgFormatVersion=0.10000000149011612,
1276 IFPkgFlagBackgroundScaling="proportional",
1277 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001278 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001279 )
1280
1281 writePlist(pl, path)
1282
1283
1284def buildInstaller():
1285
1286 # Zap all compiled files
1287 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1288 for fn in filenames:
1289 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1290 os.unlink(os.path.join(dirpath, fn))
1291
1292 outdir = os.path.join(WORKDIR, 'installer')
1293 if os.path.exists(outdir):
1294 shutil.rmtree(outdir)
1295 os.mkdir(outdir)
1296
Ronald Oussoren1943f862009-03-30 19:39:14 +00001297 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001298 pkgcontents = os.path.join(pkgroot, 'Packages')
1299 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001300 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001301 packageFromRecipe(pkgcontents, recipe)
1302
1303 rsrcDir = os.path.join(pkgroot, 'Resources')
1304
1305 fn = os.path.join(pkgroot, 'PkgInfo')
1306 fp = open(fn, 'w')
1307 fp.write('pmkrpkg1')
1308 fp.close()
1309
1310 os.mkdir(rsrcDir)
1311
1312 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1313 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001314 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001315 IFPkgDescriptionVersion=getVersion(),
1316 )
1317
1318 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1319 for fn in os.listdir('resources'):
1320 if fn == '.svn': continue
1321 if fn.endswith('.jpg'):
1322 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1323 else:
1324 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1325
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001326 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001327
1328
1329def installSize(clear=False, _saved=[]):
1330 if clear:
1331 del _saved[:]
1332 if not _saved:
1333 data = captureCommand("du -ks %s"%(
1334 shellQuote(os.path.join(WORKDIR, '_root'))))
1335 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1336 return _saved[0]
1337
1338
1339def buildDMG():
1340 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001341 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001342 """
1343 outdir = os.path.join(WORKDIR, 'diskimage')
1344 if os.path.exists(outdir):
1345 shutil.rmtree(outdir)
1346
1347 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001348 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001349 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001350 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001351 imagepath = imagepath + '.dmg'
1352
1353 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001354 volname='Python %s'%(getFullVersion())
1355 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1356 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001357 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001358 shellQuote(imagepath + ".tmp.dmg" )))
1359
1360
1361 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1362 os.mkdir(os.path.join(WORKDIR, "mnt"))
1363 runCommand("hdiutil attach %s -mountroot %s"%(
1364 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1365
1366 # Custom icon for the DMG, shown when the DMG is mounted.
1367 shutil.copy("../Icons/Disk Image.icns",
1368 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001369 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001370 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1371
1372 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1373
1374 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1375 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1376 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1377 setIcon(imagepath, "../Icons/Disk Image.icns")
1378
1379 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001380
1381 return imagepath
1382
1383
1384def setIcon(filePath, icnsPath):
1385 """
1386 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001387 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001388
Ronald Oussoren70050672010-04-30 15:00:26 +00001389 dirPath = os.path.normpath(os.path.dirname(__file__))
1390 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001391 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1392 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1393 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001394 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1395 if not os.path.exists(appPath):
1396 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001397 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1398 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001399
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001400 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1401 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001402
1403def main():
1404 # First parse options and check if we can perform our work
1405 parseOptions()
1406 checkEnvironment()
1407
Ronald Oussoren1943f862009-03-30 19:39:14 +00001408 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001409 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001410 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411
1412 if os.path.exists(WORKDIR):
1413 shutil.rmtree(WORKDIR)
1414 os.mkdir(WORKDIR)
1415
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001416 os.environ['LC_ALL'] = 'C'
1417
Thomas Wouters477c8d52006-05-27 19:21:47 +00001418 # Then build third-party libraries such as sleepycat DB4.
1419 buildLibraries()
1420
1421 # Now build python itself
1422 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001423
1424 # And then build the documentation
1425 # Remove the Deployment Target from the shell
1426 # environment, it's no longer needed and
1427 # an unexpected build target can cause problems
1428 # when Sphinx and its dependencies need to
1429 # be (re-)installed.
1430 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001431 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001432
1433
1434 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001435 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001436 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001437 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001438
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001439 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001440 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001441 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001442 setIcon(folder, "../Icons/Python Folder.icns")
1443
1444 # Create the installer
1445 buildInstaller()
1446
1447 # And copy the readme into the directory containing the installer
1448 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1449
1450 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001451 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452
1453 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001454 fp.write("# BUILD INFO\n")
1455 fp.write("# Date: %s\n" % time.ctime())
1456 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001457 fp.close()
1458
Thomas Wouters477c8d52006-05-27 19:21:47 +00001459 # And copy it to a DMG
1460 buildDMG()
1461
Thomas Wouters477c8d52006-05-27 19:21:47 +00001462if __name__ == "__main__":
1463 main()