blob: 8baa8c3dbb18c2ad784f7bc3fcd9be03ee5c722f [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
Ned Deily9978a932014-04-09 16:15:20 -070011Python 2.4. However, as of Python 3.4.1, Doc builds require an external
12sphinx-build and the current versions of Sphinx now require at least
13Python 2.6.
Thomas Wouters477c8d52006-05-27 19:21:47 +000014
Ned Deily4a96a372013-01-29 00:08:32 -080015In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
16requires an installed version of hg and a third-party version of
17Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
18(for 10.6 or later) installed in /Library/Frameworks. When installed,
19the Python built by this script will attempt to dynamically link first to
20Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
21back to the ones in /System/Library/Framework. For the build, we recommend
22installing the most recent ActiveTcl 8.4 or 8.5 version.
23
2432-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
25and the installation of additional components, such as a newer Python
Ned Deily9978a932014-04-09 16:15:20 -070026(2.5 is needed for Python parser updates), hg, and for the documentation
27build either svn (pre-3.4.1) or sphinx-build (3.4.1 and later).
Ned Deily4a96a372013-01-29 00:08:32 -080028
Thomas Wouters477c8d52006-05-27 19:21:47 +000029Usage: see USAGE variable in the script.
30"""
Ned Deily4a96a372013-01-29 00:08:32 -080031import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
32try:
33 import urllib2 as urllib_request
34except ImportError:
35 import urllib.request as urllib_request
36
37STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38 | stat.S_IRGRP | stat.S_IXGRP
39 | stat.S_IROTH | stat.S_IXOTH )
40
41STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
42 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
43 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
Thomas Wouters89f507f2006-12-13 04:49:30 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000047
48from plistlib import Plist
49
Thomas Wouters477c8d52006-05-27 19:21:47 +000050try:
51 from plistlib import writePlist
52except ImportError:
53 # We're run using python2.3
54 def writePlist(plist, path):
55 plist.write(path)
56
57def shellQuote(value):
58 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000059 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
Ned Deily5d3febf2014-12-13 00:17:46 -080065 """
66 Return the unquoted value of a variable from a file..
67 QUOTED_VALUE='quotes' -> str('quotes')
68 UNQUOTED_VALUE=noquotes -> str('noquotes')
69 """
Thomas Wouters477c8d52006-05-27 19:21:47 +000070 variable = variable + '='
71 for ln in open(fn, 'r'):
72 if ln.startswith(variable):
73 value = ln[len(variable):].strip()
Ned Deily5d3febf2014-12-13 00:17:46 -080074 return value.strip("\"'")
Ned Deily4a96a372013-01-29 00:08:32 -080075 raise RuntimeError("Cannot find variable %s" % variable[:-1])
76
77_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
79def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080080 global _cache_getVersion
81 if _cache_getVersion is None:
82 _cache_getVersion = grepValue(
83 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
84 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000085
Ned Deily4a96a372013-01-29 00:08:32 -080086def getVersionMajorMinor():
87 return tuple([int(n) for n in getVersion().split('.', 2)])
88
89_cache_getFullVersion = None
90
Thomas Wouters477c8d52006-05-27 19:21:47 +000091def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080092 global _cache_getFullVersion
93 if _cache_getFullVersion is not None:
94 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000095 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
96 for ln in open(fn):
97 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080098 _cache_getFullVersion = ln.split()[-1][1:-1]
99 return _cache_getFullVersion
100 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000101
Ned Deily5d3febf2014-12-13 00:17:46 -0800102FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
103FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
104
Thomas Wouters89f507f2006-12-13 04:49:30 +0000105# The directory we'll use to create the build (will be erased and recreated)
106WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000107
Thomas Wouters89f507f2006-12-13 04:49:30 +0000108# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000109# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000110DEPSRC = os.path.join(WORKDIR, 'third-party')
111DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000112
113# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000114
115### There are some issues with the SDK selection below here,
116### The resulting binary doesn't work on all platforms that
117### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300118### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000119###
120##if int(os.uname()[2].split('.')[0]) == 8:
121## # Explicitly use the 10.4u (universal) SDK when
122## # building on 10.4, the system headers are not
123## # useable for a universal build
124## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
125##else:
126## SDKPATH = "/"
127
Thomas Wouters89f507f2006-12-13 04:49:30 +0000128SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000129
Ronald Oussoren1943f862009-03-30 19:39:14 +0000130universal_opts_map = { '32-bit': ('i386', 'ppc',),
131 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000132 'intel': ('i386', 'x86_64'),
133 '3-way': ('ppc', 'i386', 'x86_64'),
134 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
135default_target_map = {
136 '64-bit': '10.5',
137 '3-way': '10.5',
138 'intel': '10.5',
139 'all': '10.5',
140}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000141
142UNIVERSALOPTS = tuple(universal_opts_map.keys())
143
144UNIVERSALARCHS = '32-bit'
145
146ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000147
Ezio Melotti42da6632011-03-15 05:18:48 +0200148# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000149SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000150 os.path.dirname(
151 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000152 os.path.abspath(__file__
153 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000154
Ronald Oussoren1943f862009-03-30 19:39:14 +0000155# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
156DEPTARGET = '10.3'
157
Ned Deily04cdfa12014-06-25 13:36:14 -0700158def getDeptargetTuple():
159 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
160
161def getTargetCompilers():
162 target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800163 '10.3': ('gcc-4.0', 'g++-4.0'),
164 '10.4': ('gcc-4.0', 'g++-4.0'),
165 '10.5': ('gcc-4.2', 'g++-4.2'),
166 '10.6': ('gcc-4.2', 'g++-4.2'),
Ned Deily04cdfa12014-06-25 13:36:14 -0700167 }
168 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000169
Ned Deily04cdfa12014-06-25 13:36:14 -0700170CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000171
Ned Deily5d3febf2014-12-13 00:17:46 -0800172PYTHON_3 = getVersionMajorMinor() >= (3, 0)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000173
Thomas Wouters89f507f2006-12-13 04:49:30 +0000174USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000175 Usage: build_python [options]
176
177 Options:
178 -? or -h: Show this message
179 -b DIR
180 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
181 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
182 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
183 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000184 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
185 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000186""")% globals()
187
Ned Deily4a96a372013-01-29 00:08:32 -0800188# Dict of object file names with shared library names to check after building.
189# This is to ensure that we ended up dynamically linking with the shared
190# library paths and versions we expected. For example:
191# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
192# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
193# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
194EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000195
Ned Deily5d3febf2014-12-13 00:17:46 -0800196# List of names of third party software built with this installer.
197# The names will be inserted into the rtf version of the License.
198THIRD_PARTY_LIBS = []
199
Thomas Wouters477c8d52006-05-27 19:21:47 +0000200# Instructions for building libraries that are necessary for building a
201# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000202# [The recipes are defined here for convenience but instantiated later after
203# command line options have been processed.]
204def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000205 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000206
Ned Deily04cdfa12014-06-25 13:36:14 -0700207 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800208
Ned Deily5d3febf2014-12-13 00:17:46 -0800209 if getDeptargetTuple() < (10, 6):
210 # The OpenSSL libs shipped with OS X 10.5 and earlier are
211 # hopelessly out-of-date and do not include Apple's tie-in to
212 # the root certificates in the user and system keychains via TEA
213 # that was introduced in OS X 10.6. Note that this applies to
214 # programs built and linked with a 10.5 SDK even when run on
215 # newer versions of OS X.
216 #
217 # Dealing with CAs is messy. For now, just supply a
218 # local libssl and libcrypto for the older installer variants
219 # (e.g. the python.org 10.5+ 32-bit-only installer) that use the
220 # same default ssl certfile location as the system libs do:
221 # /System/Library/OpenSSL/cert.pem
222 # Then at least TLS connections can be negotiated with sites that
223 # use sha-256 certs like python.org, assuming the proper CA certs
224 # have been supplied. The default CA cert management issues for
225 # 10.5 and earlier builds are the same as before, other than it is
226 # now more obvious with cert checking enabled by default in the
227 # standard library.
228 #
229 # For builds with 10.6+ SDKs, continue to use the deprecated but
230 # less out-of-date Apple 0.9.8 libs for now. While they are less
231 # secure than using an up-to-date 1.0.1 version, doing so
232 # avoids the big problems of forcing users to have to manage
233 # default CAs themselves, thanks to the Apple libs using private TEA
234 # APIs for cert validation from keychains if validation using the
235 # standard OpenSSL locations (/System/Library/OpenSSL, normally empty)
236 # fails.
237
238 result.extend([
239 dict(
Ned Deily63640942015-12-05 23:51:23 -0500240 name="OpenSSL 1.0.2e",
241 url="https://www.openssl.org/source/openssl-1.0.2e.tar.gz",
242 checksum='5262bfa25b60ed9de9f28d5d52d77fc5',
Ned Deily5d3febf2014-12-13 00:17:46 -0800243 patches=[
244 "openssl_sdk_makedepend.patch",
245 ],
246 buildrecipe=build_universal_openssl,
247 configure=None,
248 install=None,
249 ),
250 ])
251
Ned Deilyaa6a2122013-11-23 03:30:11 -0800252# Disable for now
Ned Deilyed730102014-11-14 18:55:05 -0800253 if False: # if getDeptargetTuple() > (10, 5):
Ned Deily5b3582c2013-10-25 00:41:46 -0700254 result.extend([
255 dict(
256 name="Tcl 8.5.15",
257 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
258 checksum='f3df162f92c69b254079c4d0af7a690f',
259 buildDir="unix",
260 configure_pre=[
261 '--enable-shared',
262 '--enable-threads',
263 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
264 ],
265 useLDFlags=False,
266 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
267 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
268 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
269 },
270 ),
271 dict(
272 name="Tk 8.5.15",
273 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
274 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700275 patches=[
276 "issue19373_tk_8_5_15_source.patch",
277 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700278 buildDir="unix",
279 configure_pre=[
280 '--enable-aqua',
281 '--enable-shared',
282 '--enable-threads',
283 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
284 ],
285 useLDFlags=False,
286 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'%{
287 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
288 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
289 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
290 },
291 ),
292 ])
293
Ned Deilyed730102014-11-14 18:55:05 -0800294 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800295 result.extend([
296 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800297 name="XZ 5.0.5",
298 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
299 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deily4a96a372013-01-29 00:08:32 -0800300 configure_pre=[
301 '--disable-dependency-tracking',
302 ]
303 ),
304 ])
305
306 result.extend([
307 dict(
308 name="NCurses 5.9",
309 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
310 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
311 configure_pre=[
312 "--enable-widec",
313 "--without-cxx",
314 "--without-cxx-binding",
315 "--without-ada",
316 "--without-curses-h",
317 "--enable-shared",
318 "--with-shared",
319 "--without-debug",
320 "--without-normal",
321 "--without-tests",
322 "--without-manpages",
323 "--datadir=/usr/share",
324 "--sysconfdir=/etc",
325 "--sharedstatedir=/usr/com",
326 "--with-terminfo-dirs=/usr/share/terminfo",
327 "--with-default-terminfo-dir=/usr/share/terminfo",
328 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
329 ],
330 patchscripts=[
331 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
332 "f54bf02a349f96a7c4f0d00922f3a0d4"),
333 ],
334 useLDFlags=False,
335 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
336 shellQuote(os.path.join(WORKDIR, 'libraries')),
337 shellQuote(os.path.join(WORKDIR, 'libraries')),
338 getVersion(),
339 ),
340 ),
341 dict(
Ned Deilybb1c0792015-07-28 23:22:23 -0700342 name="SQLite 3.8.11",
343 url="https://www.sqlite.org/2015/sqlite-autoconf-3081100.tar.gz",
344 checksum='77b451925121028befbddbf45ea2bc49',
Ned Deily4a96a372013-01-29 00:08:32 -0800345 extra_cflags=('-Os '
346 '-DSQLITE_ENABLE_FTS4 '
347 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
348 '-DSQLITE_ENABLE_RTREE '
349 '-DSQLITE_TCL=0 '
350 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
351 configure_pre=[
352 '--enable-threadsafe',
353 '--enable-shared=no',
354 '--enable-static=yes',
355 '--disable-readline',
356 '--disable-dependency-tracking',
357 ]
358 ),
359 ])
360
Ned Deily04cdfa12014-06-25 13:36:14 -0700361 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000362 result.extend([
363 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000364 name="Bzip2 1.0.6",
365 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
366 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000367 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800368 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
369 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000370 shellQuote(os.path.join(WORKDIR, 'libraries')),
371 ' -arch '.join(ARCHLIST),
372 SDKPATH,
373 ),
374 ),
375 dict(
376 name="ZLib 1.2.3",
377 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
378 checksum='debc62758716a169df9f62e6ab2bc634',
379 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800380 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
381 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000382 shellQuote(os.path.join(WORKDIR, 'libraries')),
383 ' -arch '.join(ARCHLIST),
384 SDKPATH,
385 ),
386 ),
387 dict(
388 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000389 name="GNU Readline 6.1.2",
390 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
391 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000392 patchlevel='0',
393 patches=[
394 # The readline maintainers don't do actual micro releases, but
395 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800396 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
397 'c642f2e84d820884b0bf9fd176bc6c3f'),
398 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
399 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000400 ]
401 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000402 ])
403
Ned Deily4f7ff782011-01-15 05:29:12 +0000404 if not PYTHON_3:
405 result.extend([
406 dict(
407 name="Sleepycat DB 4.7.25",
408 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
409 checksum='ec2b87e833779681a0c3a814aa71359e',
410 buildDir="build_unix",
411 configure="../dist/configure",
412 configure_pre=[
413 '--includedir=/usr/local/include/db4',
414 ]
415 ),
416 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000417
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000418 return result
419
Thomas Wouters477c8d52006-05-27 19:21:47 +0000420
Thomas Wouters477c8d52006-05-27 19:21:47 +0000421# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000422def pkg_recipes():
423 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
424 result = [
425 dict(
426 name="PythonFramework",
427 long_name="Python Framework",
428 source="/Library/Frameworks/Python.framework",
429 readme="""\
430 This package installs Python.framework, that is the python
431 interpreter and the standard library. This also includes Python
432 wrappers for lots of Mac OS X API's.
433 """,
434 postflight="scripts/postflight.framework",
435 selected='selected',
436 ),
437 dict(
438 name="PythonApplications",
439 long_name="GUI Applications",
440 source="/Applications/Python %(VER)s",
441 readme="""\
442 This package installs IDLE (an interactive Python IDE),
443 Python Launcher and Build Applet (create application bundles
444 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000445
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000446 It also installs a number of examples and demos.
447 """,
448 required=False,
449 selected='selected',
450 ),
451 dict(
452 name="PythonUnixTools",
453 long_name="UNIX command-line tools",
454 source="/usr/local/bin",
455 readme="""\
456 This package installs the unix tools in /usr/local/bin for
457 compatibility with older releases of Python. This package
458 is not necessary to use Python.
459 """,
460 required=False,
461 selected='selected',
462 ),
463 dict(
464 name="PythonDocumentation",
465 long_name="Python Documentation",
466 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
467 source="/pydocs",
468 readme="""\
469 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800470 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000471 """,
472 postflight="scripts/postflight.documentation",
473 required=False,
474 selected='selected',
475 ),
476 dict(
477 name="PythonProfileChanges",
478 long_name="Shell profile updater",
479 readme="""\
480 This packages updates your shell profile to make sure that
481 the Python tools are found by your shell in preference of
482 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000483
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000484 If you don't install this package you'll have to add
485 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
486 to your PATH by hand.
487 """,
488 postflight="scripts/postflight.patch-profile",
489 topdir="/Library/Frameworks/Python.framework",
490 source="/empty-dir",
491 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800492 selected='selected',
493 ),
494 dict(
495 name="PythonInstallPip",
496 long_name="Install or upgrade pip",
497 readme="""\
498 This package installs (or upgrades from an earlier version)
499 pip, a tool for installing and managing Python packages.
500 """,
501 postflight="scripts/postflight.ensurepip",
502 topdir="/Library/Frameworks/Python.framework",
503 source="/empty-dir",
504 required=False,
505 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000506 ),
507 ]
508
Ned Deily04cdfa12014-06-25 13:36:14 -0700509 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000510 result.append(
511 dict(
512 name="PythonSystemFixes",
513 long_name="Fix system Python",
514 readme="""\
515 This package updates the system python installation on
516 Mac OS X 10.3 to ensure that you can build new python extensions
517 using that copy of python after installing this version.
518 """,
519 postflight="../Tools/fixapplepython23.py",
520 topdir="/Library/Frameworks/Python.framework",
521 source="/empty-dir",
522 required=False,
523 selected=unselected_for_python3,
524 )
525 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800526
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000527 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000528
Thomas Wouters477c8d52006-05-27 19:21:47 +0000529def fatal(msg):
530 """
531 A fatal error, bail out.
532 """
533 sys.stderr.write('FATAL: ')
534 sys.stderr.write(msg)
535 sys.stderr.write('\n')
536 sys.exit(1)
537
538def fileContents(fn):
539 """
540 Return the contents of the named file
541 """
Ned Deily4a96a372013-01-29 00:08:32 -0800542 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543
544def runCommand(commandline):
545 """
Ezio Melotti13925002011-03-16 11:05:33 +0200546 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000547 unless the command fails.
548 """
549 fd = os.popen(commandline, 'r')
550 data = fd.read()
551 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000552 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000553 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800554 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555
556 if VERBOSE:
557 sys.stdout.write(data); sys.stdout.flush()
558
559def captureCommand(commandline):
560 fd = os.popen(commandline, 'r')
561 data = fd.read()
562 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000563 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000564 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800565 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000566
567 return data
568
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000569def getTclTkVersion(configfile, versionline):
570 """
571 search Tcl or Tk configuration file for version line
572 """
573 try:
574 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300575 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000576 fatal("Framework configuration file not found: %s" % configfile)
577
578 for l in f:
579 if l.startswith(versionline):
580 f.close()
581 return l
582
583 fatal("Version variable %s not found in framework configuration file: %s"
584 % (versionline, configfile))
585
Thomas Wouters477c8d52006-05-27 19:21:47 +0000586def checkEnvironment():
587 """
588 Check that we're running on a supported system.
589 """
590
Ned Deilye59e4c52011-01-29 18:56:28 +0000591 if sys.version_info[0:2] < (2, 4):
592 fatal("This script must be run with Python 2.4 or later")
593
Thomas Wouters477c8d52006-05-27 19:21:47 +0000594 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000595 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000596
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000597 if int(platform.release().split('.')[0]) < 8:
598 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000599
600 if not os.path.exists(SDKPATH):
601 fatal("Please install the latest version of Xcode and the %s SDK"%(
602 os.path.basename(SDKPATH[:-4])))
603
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000604 # Because we only support dynamic load of only one major/minor version of
605 # Tcl/Tk, ensure:
606 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800607 # higher than the Apple-supplied system version in
608 # SDKROOT/System/Library/Frameworks
609 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
610 # in) SDKROOT/Library/Frameworks with the same version as the system
611 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000612
Ned Deily4a96a372013-01-29 00:08:32 -0800613 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000614 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800615 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000616 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800617 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000618 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800619 frameworks[framework] = os.readlink(sysfw)
620 if not os.path.exists(libfw):
621 fatal("Please install a link to a current %s %s as %s so "
622 "the user can override the system framework."
623 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000624 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000625 fatal("Version of %s must match %s" % (libfw, sysfw) )
626 if os.path.exists(usrfw):
627 fatal("Please rename %s to avoid possible dynamic load issues."
628 % usrfw)
629
Ned Deily4a96a372013-01-29 00:08:32 -0800630 if frameworks['Tcl'] != frameworks['Tk']:
631 fatal("The Tcl and Tk frameworks are not the same version.")
632
633 # add files to check after build
634 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
635 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
636 % frameworks['Tcl'],
637 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
638 % frameworks['Tk'],
639 ]
640
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000641 # Remove inherited environment variables which might influence build
642 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
643 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
644 for ev in list(os.environ):
645 for prefix in environ_var_prefixes:
646 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800647 print("INFO: deleting environment variable %s=%s" % (
648 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000649 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000650
Ned Deily4a96a372013-01-29 00:08:32 -0800651 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
652 if 'SDK_TOOLS_BIN' in os.environ:
653 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
654 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
655 # add its fixed location here if it exists
656 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
657 if os.path.isdir(OLD_DEVELOPER_TOOLS):
658 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
659 os.environ['PATH'] = base_path
660 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deily7e60f512014-04-07 12:10:21 -0700661 # Ensure ws have access to hg and to sphinx-build.
662 # You may have to create links in /usr/bin for them.
663 runCommand('hg --version')
Ned Deily1ff32a92014-09-05 15:57:05 -0700664 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665
Thomas Wouters89f507f2006-12-13 04:49:30 +0000666def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000667 """
668 Parse arguments and update global settings.
669 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000670 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800671 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily5d3febf2014-12-13 00:17:46 -0800672 global FW_VERSION_PREFIX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000673
674 if args is None:
675 args = sys.argv[1:]
676
677 try:
678 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000679 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
680 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800681 except getopt.GetoptError:
682 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000683 sys.exit(1)
684
685 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800686 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000687 sys.exit(1)
688
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000689 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000690 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000691 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800692 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000693 sys.exit(0)
694
695 elif k in ('-d', '--build-dir'):
696 WORKDIR=v
697
698 elif k in ('--third-party',):
699 DEPSRC=v
700
701 elif k in ('--sdk-path',):
702 SDKPATH=v
703
704 elif k in ('--src-dir',):
705 SRCDIR=v
706
Ronald Oussoren1943f862009-03-30 19:39:14 +0000707 elif k in ('--dep-target', ):
708 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000709 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000710
711 elif k in ('--universal-archs', ):
712 if v in UNIVERSALOPTS:
713 UNIVERSALARCHS = v
714 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000715 if deptarget is None:
716 # Select alternate default deployment
717 # target
718 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000719 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800720 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000721
Thomas Wouters477c8d52006-05-27 19:21:47 +0000722 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800723 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000724
725 SRCDIR=os.path.abspath(SRCDIR)
726 WORKDIR=os.path.abspath(WORKDIR)
727 SDKPATH=os.path.abspath(SDKPATH)
728 DEPSRC=os.path.abspath(DEPSRC)
729
Ned Deily04cdfa12014-06-25 13:36:14 -0700730 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000731
Ned Deily5d3febf2014-12-13 00:17:46 -0800732 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
733
734 print("-- Settings:")
735 print(" * Source directory: %s" % SRCDIR)
736 print(" * Build directory: %s" % WORKDIR)
737 print(" * SDK location: %s" % SDKPATH)
738 print(" * Third-party source: %s" % DEPSRC)
739 print(" * Deployment target: %s" % DEPTARGET)
740 print(" * Universal archs: %s" % str(ARCHLIST))
741 print(" * C compiler: %s" % CC)
742 print(" * C++ compiler: %s" % CXX)
Ned Deily4a96a372013-01-29 00:08:32 -0800743 print("")
Ned Deily5d3febf2014-12-13 00:17:46 -0800744 print(" -- Building a Python %s framework at patch level %s"
745 % (getVersion(), getFullVersion()))
746 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000747
748def extractArchive(builddir, archiveName):
749 """
750 Extract a source archive into 'builddir'. Returns the path of the
751 extracted archive.
752
753 XXX: This function assumes that archives contain a toplevel directory
754 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700755 safe enough for almost anything we use. Unfortunately, it does not
756 work for current Tcl and Tk source releases where the basename of
757 the archive ends with "-src" but the uncompressed directory does not.
758 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000759 """
760 curdir = os.getcwd()
761 try:
762 os.chdir(builddir)
763 if archiveName.endswith('.tar.gz'):
764 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700765 if ((retval.startswith('tcl') or retval.startswith('tk'))
766 and retval.endswith('-src')):
767 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000768 if os.path.exists(retval):
769 shutil.rmtree(retval)
770 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
771
772 elif archiveName.endswith('.tar.bz2'):
773 retval = os.path.basename(archiveName[:-8])
774 if os.path.exists(retval):
775 shutil.rmtree(retval)
776 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
777
778 elif archiveName.endswith('.tar'):
779 retval = os.path.basename(archiveName[:-4])
780 if os.path.exists(retval):
781 shutil.rmtree(retval)
782 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
783
784 elif archiveName.endswith('.zip'):
785 retval = os.path.basename(archiveName[:-4])
786 if os.path.exists(retval):
787 shutil.rmtree(retval)
788 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
789
790 data = fp.read()
791 xit = fp.close()
792 if xit is not None:
793 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800794 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000795
796 return os.path.join(builddir, retval)
797
798 finally:
799 os.chdir(curdir)
800
Thomas Wouters477c8d52006-05-27 19:21:47 +0000801def downloadURL(url, fname):
802 """
803 Download the contents of the url into the file.
804 """
Ned Deily4a96a372013-01-29 00:08:32 -0800805 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000806 fpOut = open(fname, 'wb')
807 block = fpIn.read(10240)
808 try:
809 while block:
810 fpOut.write(block)
811 block = fpIn.read(10240)
812 fpIn.close()
813 fpOut.close()
814 except:
815 try:
816 os.unlink(fname)
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300817 except OSError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818 pass
819
Ned Deily4a96a372013-01-29 00:08:32 -0800820def verifyThirdPartyFile(url, checksum, fname):
821 """
822 Download file from url to filename fname if it does not already exist.
823 Abort if file contents does not match supplied md5 checksum.
824 """
825 name = os.path.basename(fname)
826 if os.path.exists(fname):
827 print("Using local copy of %s"%(name,))
828 else:
829 print("Did not find local copy of %s"%(name,))
830 print("Downloading %s"%(name,))
831 downloadURL(url, fname)
832 print("Archive for %s stored as %s"%(name, fname))
833 if os.system(
834 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
835 % (shellQuote(fname), checksum) ):
836 fatal('MD5 checksum mismatch for file %s' % fname)
837
Ned Deily5d3febf2014-12-13 00:17:46 -0800838def build_universal_openssl(basedir, archList):
839 """
840 Special case build recipe for universal build of openssl.
841
842 The upstream OpenSSL build system does not directly support
843 OS X universal builds. We need to build each architecture
844 separately then lipo them together into fat libraries.
845 """
846
847 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
848 # If we are building on a 10.4.x or earlier system,
849 # unilaterally disable assembly code building to avoid the problem.
850 no_asm = int(platform.release().split(".")[0]) < 9
851
852 def build_openssl_arch(archbase, arch):
853 "Build one architecture of openssl"
854 arch_opts = {
855 "i386": ["darwin-i386-cc"],
856 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
857 "ppc": ["darwin-ppc-cc"],
858 "ppc64": ["darwin64-ppc-cc"],
859 }
860 configure_opts = [
861 "no-krb5",
862 "no-idea",
863 "no-mdc2",
864 "no-rc5",
865 "no-zlib",
866 "enable-tlsext",
867 "no-ssl2",
868 "no-ssl3",
869 "no-ssl3-method",
870 # "enable-unit-test",
871 "shared",
872 "--install_prefix=%s"%shellQuote(archbase),
873 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
874 "--openssldir=/System/Library/OpenSSL",
875 ]
876 if no_asm:
877 configure_opts.append("no-asm")
878 runCommand(" ".join(["perl", "Configure"]
879 + arch_opts[arch] + configure_opts))
880 runCommand("make depend OSX_SDK=%s" % SDKPATH)
881 runCommand("make all OSX_SDK=%s" % SDKPATH)
882 runCommand("make install_sw OSX_SDK=%s" % SDKPATH)
883 # runCommand("make test")
884 return
885
886 srcdir = os.getcwd()
887 universalbase = os.path.join(srcdir, "..",
888 os.path.basename(srcdir) + "-universal")
889 os.mkdir(universalbase)
890 archbasefws = []
891 for arch in archList:
892 # fresh copy of the source tree
893 archsrc = os.path.join(universalbase, arch, "src")
894 shutil.copytree(srcdir, archsrc, symlinks=True)
895 # install base for this arch
896 archbase = os.path.join(universalbase, arch, "root")
897 os.mkdir(archbase)
898 # Python framework base within install_prefix:
899 # the build will install into this framework..
900 # This is to ensure that the resulting shared libs have
901 # the desired real install paths built into them.
902 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
903
904 # build one architecture
905 os.chdir(archsrc)
906 build_openssl_arch(archbase, arch)
907 os.chdir(srcdir)
908 archbasefws.append(archbasefw)
909
910 # copy arch-independent files from last build into the basedir framework
911 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
912 shutil.copytree(
913 os.path.join(archbasefw, "include", "openssl"),
914 os.path.join(basefw, "include", "openssl")
915 )
916
917 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
918 "SHLIB_VERSION_NUMBER")
919 # e.g. -> "1.0.0"
920 libcrypto = "libcrypto.dylib"
921 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
922 # e.g. -> "libcrypto.1.0.0.dylib"
923 libssl = "libssl.dylib"
924 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
925 # e.g. -> "libssl.1.0.0.dylib"
926
927 try:
928 os.mkdir(os.path.join(basefw, "lib"))
929 except OSError:
930 pass
931
932 # merge the individual arch-dependent shared libs into a fat shared lib
933 archbasefws.insert(0, basefw)
934 for (lib_unversioned, lib_versioned) in [
935 (libcrypto, libcrypto_versioned),
936 (libssl, libssl_versioned)
937 ]:
938 runCommand("lipo -create -output " +
939 " ".join(shellQuote(
940 os.path.join(fw, "lib", lib_versioned))
941 for fw in archbasefws))
942 # and create an unversioned symlink of it
943 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
944
945 # Create links in the temp include and lib dirs that will be injected
946 # into the Python build so that setup.py can find them while building
947 # and the versioned links so that the setup.py post-build import test
948 # does not fail.
949 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
950 for fn in [
951 ["include", "openssl"],
952 ["lib", libcrypto],
953 ["lib", libssl],
954 ["lib", libcrypto_versioned],
955 ["lib", libssl_versioned],
956 ]:
957 os.symlink(
958 os.path.join(relative_path, *fn),
959 os.path.join(basedir, "usr", "local", *fn)
960 )
961
962 return
963
Thomas Wouters477c8d52006-05-27 19:21:47 +0000964def buildRecipe(recipe, basedir, archList):
965 """
966 Build software using a recipe. This function does the
967 'configure;make;make install' dance for C software, with a possibility
968 to customize this process, basically a poor-mans DarwinPorts.
969 """
970 curdir = os.getcwd()
971
972 name = recipe['name']
Ned Deily5d3febf2014-12-13 00:17:46 -0800973 THIRD_PARTY_LIBS.append(name)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000974 url = recipe['url']
975 configure = recipe.get('configure', './configure')
Ned Deily5d3febf2014-12-13 00:17:46 -0800976 buildrecipe = recipe.get('buildrecipe', None)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000977 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
978 shellQuote(basedir)))
979
980 archiveName = os.path.split(url)[-1]
981 sourceArchive = os.path.join(DEPSRC, archiveName)
982
983 if not os.path.exists(DEPSRC):
984 os.mkdir(DEPSRC)
985
Ned Deily4a96a372013-01-29 00:08:32 -0800986 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
987 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 buildDir=os.path.join(WORKDIR, '_bld')
989 if not os.path.exists(buildDir):
990 os.mkdir(buildDir)
991
992 workDir = extractArchive(buildDir, sourceArchive)
993 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000994
Ned Deily4a96a372013-01-29 00:08:32 -0800995 for patch in recipe.get('patches', ()):
996 if isinstance(patch, tuple):
997 url, checksum = patch
998 fn = os.path.join(DEPSRC, os.path.basename(url))
999 verifyThirdPartyFile(url, checksum, fn)
1000 else:
1001 # patch is a file in the source directory
1002 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001003 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
1004 shellQuote(fn),))
1005
Ned Deily4a96a372013-01-29 00:08:32 -08001006 for patchscript in recipe.get('patchscripts', ()):
1007 if isinstance(patchscript, tuple):
1008 url, checksum = patchscript
1009 fn = os.path.join(DEPSRC, os.path.basename(url))
1010 verifyThirdPartyFile(url, checksum, fn)
1011 else:
1012 # patch is a file in the source directory
1013 fn = os.path.join(curdir, patchscript)
1014 if fn.endswith('.bz2'):
1015 runCommand('bunzip2 -fk %s' % shellQuote(fn))
1016 fn = fn[:-4]
1017 runCommand('sh %s' % shellQuote(fn))
1018 os.unlink(fn)
1019
Ned Deily94764b22013-10-27 19:49:29 -07001020 if 'buildDir' in recipe:
1021 os.chdir(recipe['buildDir'])
1022
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001023 if configure is not None:
1024 configure_args = [
1025 "--prefix=/usr/local",
1026 "--enable-static",
1027 "--disable-shared",
1028 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
1029 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001030
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001031 if 'configure_pre' in recipe:
1032 args = list(recipe['configure_pre'])
1033 if '--disable-static' in args:
1034 configure_args.remove('--enable-static')
1035 if '--enable-shared' in args:
1036 configure_args.remove('--disable-shared')
1037 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001038
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001039 if recipe.get('useLDFlags', 1):
1040 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -08001041 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1042 "-I%s/usr/local/include"%(
1043 recipe.get('extra_cflags', ''),
1044 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001045 ' -arch '.join(archList),
1046 shellQuote(SDKPATH)[1:-1],
1047 shellQuote(basedir)[1:-1],),
Ned Deily0134a352014-04-09 16:16:08 -07001048 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -08001049 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +00001050 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001051 shellQuote(basedir)[1:-1],
1052 ' -arch '.join(archList)),
1053 ])
1054 else:
1055 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -08001056 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1057 "-I%s/usr/local/include"%(
1058 recipe.get('extra_cflags', ''),
1059 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001060 ' -arch '.join(archList),
1061 shellQuote(SDKPATH)[1:-1],
1062 shellQuote(basedir)[1:-1],),
1063 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001064
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001065 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -08001066 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001067
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001068 configure_args.insert(0, configure)
1069 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001070
Ned Deily4a96a372013-01-29 00:08:32 -08001071 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001072 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073
Ned Deily5d3febf2014-12-13 00:17:46 -08001074 if buildrecipe is not None:
1075 # call special-case build recipe, e.g. for openssl
1076 buildrecipe(basedir, archList)
1077
1078 if install is not None:
1079 print("Running install for %s"%(name,))
1080 runCommand('{ ' + install + ' ;} 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001081
Ned Deily4a96a372013-01-29 00:08:32 -08001082 print("Done %s"%(name,))
1083 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001084
1085 os.chdir(curdir)
1086
1087def buildLibraries():
1088 """
1089 Build our dependencies into $WORKDIR/libraries/usr/local
1090 """
Ned Deily4a96a372013-01-29 00:08:32 -08001091 print("")
1092 print("Building required libraries")
1093 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001094 universal = os.path.join(WORKDIR, 'libraries')
1095 os.mkdir(universal)
1096 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1097 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1098
Ronald Oussoren1943f862009-03-30 19:39:14 +00001099 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001100 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001101
1102
1103
1104def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001105 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +00001106 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -08001107 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001108 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001109 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001110 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001111 curDir = os.getcwd()
1112 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -07001113 # The Doc build changed for 3.4 (technically, for 3.4.1) and for 2.7.9
1114 runCommand('make clean')
1115 # Assume sphinx-build is on our PATH, checked in checkEnvironment
1116 runCommand('make html')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001117 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001118 if not os.path.exists(docdir):
1119 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001120 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001121
1122
1123def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -08001124 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001125
1126 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1127 rootDir = os.path.join(WORKDIR, '_root')
1128
1129 if os.path.exists(buildDir):
1130 shutil.rmtree(buildDir)
1131 if os.path.exists(rootDir):
1132 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +00001133 os.makedirs(buildDir)
1134 os.makedirs(rootDir)
1135 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001136 curdir = os.getcwd()
1137 os.chdir(buildDir)
1138
1139 # Not sure if this is still needed, the original build script
1140 # claims that parts of the install assume python.exe exists.
1141 os.symlink('python', os.path.join(buildDir, 'python.exe'))
1142
1143 # Extract the version from the configure file, needed to calculate
1144 # several paths.
1145 version = getVersion()
1146
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001147 # Since the extra libs are not in their installed framework location
1148 # during the build, augment the library path so that the interpreter
1149 # will find them during its extension import sanity checks.
1150 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1151 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -08001152 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +00001153 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001154 "--with-universal-archs=%s "
1155 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -08001156 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +00001157 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -07001158 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001159 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
1160 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001161 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -08001162 (' ', '--without-ensurepip ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +00001163 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164 shellQuote(WORKDIR)[1:-1]))
1165
Ned Deily729148b2014-05-22 15:28:06 -07001166 print("Running make touch")
1167 runCommand("make touch")
1168
Ned Deily4a96a372013-01-29 00:08:32 -08001169 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001170 runCommand("make")
1171
Ned Deily4a96a372013-01-29 00:08:32 -08001172 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001173 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001174 shellQuote(rootDir)))
1175
Ned Deily4a96a372013-01-29 00:08:32 -08001176 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001177 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1178 shellQuote(rootDir)))
1179
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001180 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001181 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001182 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1183 runCommand("mv %s/* %s"%(
1184 shellQuote(os.path.join(
1185 WORKDIR, 'libraries', 'Library', 'Frameworks',
1186 'Python.framework', 'Versions', getVersion(),
1187 'lib')),
1188 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1189 'Python.framework', 'Versions', getVersion(),
1190 'lib'))))
1191
Ned Deily050fcd52013-10-26 03:16:44 -07001192 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1193 'Python.framework', 'Versions',
1194 version, 'lib', 'python%s'%(version,))
1195
Ned Deily4a96a372013-01-29 00:08:32 -08001196 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001197 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001198 gid = grp.getgrnam('admin').gr_gid
1199
Ned Deily4a96a372013-01-29 00:08:32 -08001200 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001201 for dirpath, dirnames, filenames in os.walk(frmDir):
1202 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001203 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001204 os.chown(os.path.join(dirpath, dn), -1, gid)
1205
Thomas Wouters477c8d52006-05-27 19:21:47 +00001206 for fn in filenames:
1207 if os.path.islink(fn):
1208 continue
1209
1210 # "chmod g+w $fn"
1211 p = os.path.join(dirpath, fn)
1212 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001213 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1214 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001215
Ned Deily4a96a372013-01-29 00:08:32 -08001216 if fn in EXPECTED_SHARED_LIBS:
1217 # check to see that this file was linked with the
1218 # expected library path and version
1219 data = captureCommand("otool -L %s" % shellQuote(p))
1220 for sl in EXPECTED_SHARED_LIBS[fn]:
1221 if ("\t%s " % sl) not in data:
1222 print("Expected shared lib %s was not linked with %s"
1223 % (sl, p))
1224 shared_lib_error = True
1225
1226 if shared_lib_error:
1227 fatal("Unexpected shared library errors.")
1228
Ned Deilye59e4c52011-01-29 18:56:28 +00001229 if PYTHON_3:
1230 LDVERSION=None
1231 VERSION=None
1232 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001233
Ned Deilye59e4c52011-01-29 18:56:28 +00001234 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001235 for ln in fp:
1236 if ln.startswith('VERSION='):
1237 VERSION=ln.split()[1]
1238 if ln.startswith('ABIFLAGS='):
1239 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001240 if ln.startswith('LDVERSION='):
1241 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001242 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001243
Ned Deilye59e4c52011-01-29 18:56:28 +00001244 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1245 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1246 config_suffix = '-' + LDVERSION
1247 else:
1248 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001249
Thomas Wouters477c8d52006-05-27 19:21:47 +00001250 # We added some directories to the search path during the configure
1251 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001252 # the end-users system. Also remove the directories from _sysconfigdata.py
1253 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001254
Ned Deilya4f6b002013-10-25 00:47:38 -07001255 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1256 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1257
Ned Deilya4f6b002013-10-25 00:47:38 -07001258 # fix Makefile
1259 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1260 fp = open(path, 'r')
1261 data = fp.read()
1262 fp.close()
1263
1264 for p in (include_path, lib_path):
1265 data = data.replace(" " + p, '')
1266 data = data.replace(p + " ", '')
1267
1268 fp = open(path, 'w')
1269 fp.write(data)
1270 fp.close()
1271
1272 # fix _sysconfigdata if it exists
1273 #
1274 # TODO: make this more robust! test_sysconfig_module of
1275 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1276 # the output from get_config_var in both sysconfig and
1277 # distutils.sysconfig is exactly the same for both CFLAGS and
1278 # LDFLAGS. The fixing up is now complicated by the pretty
1279 # printing in _sysconfigdata.py. Also, we are using the
1280 # pprint from the Python running the installer build which
1281 # may not cosmetically format the same as the pprint in the Python
1282 # being built (and which is used to originally generate
1283 # _sysconfigdata.py).
1284
1285 import pprint
1286 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1287 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001288 fp = open(path, 'r')
1289 data = fp.read()
1290 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001291 # create build_time_vars dict
1292 exec(data)
1293 vars = {}
1294 for k, v in build_time_vars.items():
1295 if type(v) == type(''):
1296 for p in (include_path, lib_path):
1297 v = v.replace(' ' + p, '')
1298 v = v.replace(p + ' ', '')
1299 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001300
Ned Deily4a96a372013-01-29 00:08:32 -08001301 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001302 # duplicated from sysconfig._generate_posix_vars()
1303 fp.write('# system configuration generated and used by'
1304 ' the sysconfig module\n')
1305 fp.write('build_time_vars = ')
1306 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001307 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001308
1309 # Add symlinks in /usr/local/bin, using relative links
1310 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1311 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1312 'Python.framework', 'Versions', version, 'bin')
1313 if os.path.exists(usr_local_bin):
1314 shutil.rmtree(usr_local_bin)
1315 os.makedirs(usr_local_bin)
1316 for fn in os.listdir(
1317 os.path.join(frmDir, 'Versions', version, 'bin')):
1318 os.symlink(os.path.join(to_framework, fn),
1319 os.path.join(usr_local_bin, fn))
1320
1321 os.chdir(curdir)
1322
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001323 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001324 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001325 # with an already installed version of python 2
1326 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1327 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001328
1329def patchFile(inPath, outPath):
1330 data = fileContents(inPath)
1331 data = data.replace('$FULL_VERSION', getFullVersion())
1332 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001333 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001334 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001335 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001336 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001337
1338 # This one is not handy as a template variable
1339 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001340 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001341 fp.write(data)
1342 fp.close()
1343
1344def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001345 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001346 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001347 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001348 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001349 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001350 fp.write(data)
1351 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001352 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001353
1354
1355
1356def packageFromRecipe(targetDir, recipe):
1357 curdir = os.getcwd()
1358 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001359 # The major version (such as 2.5) is included in the package name
1360 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001361 # common.
1362 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001363 srcdir = recipe.get('source')
1364 pkgroot = recipe.get('topdir', srcdir)
1365 postflight = recipe.get('postflight')
1366 readme = textwrap.dedent(recipe['readme'])
1367 isRequired = recipe.get('required', True)
1368
Ned Deily4a96a372013-01-29 00:08:32 -08001369 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001370
1371 # Substitute some variables
1372 textvars = dict(
1373 VER=getVersion(),
1374 FULLVER=getFullVersion(),
1375 )
1376 readme = readme % textvars
1377
1378 if pkgroot is not None:
1379 pkgroot = pkgroot % textvars
1380 else:
1381 pkgroot = '/'
1382
1383 if srcdir is not None:
1384 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1385 srcdir = srcdir % textvars
1386
1387 if postflight is not None:
1388 postflight = os.path.abspath(postflight)
1389
1390 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1391 os.makedirs(packageContents)
1392
1393 if srcdir is not None:
1394 os.chdir(srcdir)
1395 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1396 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1397 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1398
1399 fn = os.path.join(packageContents, 'PkgInfo')
1400 fp = open(fn, 'w')
1401 fp.write('pmkrpkg1')
1402 fp.close()
1403
1404 rsrcDir = os.path.join(packageContents, "Resources")
1405 os.mkdir(rsrcDir)
1406 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1407 fp.write(readme)
1408 fp.close()
1409
1410 if postflight is not None:
1411 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1412
1413 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001414 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001415 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001416 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1417 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1418 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001419 CFBundleShortVersionString=vers,
1420 IFMajorVersion=major,
1421 IFMinorVersion=minor,
1422 IFPkgFormatVersion=0.10000000149011612,
1423 IFPkgFlagAllowBackRev=False,
1424 IFPkgFlagAuthorizationAction="RootAuthorization",
1425 IFPkgFlagDefaultLocation=pkgroot,
1426 IFPkgFlagFollowLinks=True,
1427 IFPkgFlagInstallFat=True,
1428 IFPkgFlagIsRequired=isRequired,
1429 IFPkgFlagOverwritePermissions=False,
1430 IFPkgFlagRelocatable=False,
1431 IFPkgFlagRestartAction="NoRestart",
1432 IFPkgFlagRootVolumeOnly=True,
1433 IFPkgFlagUpdateInstalledLangauges=False,
1434 )
1435 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1436
1437 pl = Plist(
1438 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001439 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001440 IFPkgDescriptionVersion=vers,
1441 )
1442 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1443
1444 finally:
1445 os.chdir(curdir)
1446
1447
1448def makeMpkgPlist(path):
1449
1450 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001451 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452
1453 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001454 CFBundleGetInfoString="Python %s"%(vers,),
1455 CFBundleIdentifier='org.python.Python',
1456 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001457 CFBundleShortVersionString=vers,
1458 IFMajorVersion=major,
1459 IFMinorVersion=minor,
1460 IFPkgFlagComponentDirectory="Contents/Packages",
1461 IFPkgFlagPackageList=[
1462 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001463 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001464 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001465 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001466 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001467 ],
1468 IFPkgFormatVersion=0.10000000149011612,
1469 IFPkgFlagBackgroundScaling="proportional",
1470 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001471 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001472 )
1473
1474 writePlist(pl, path)
1475
1476
1477def buildInstaller():
1478
1479 # Zap all compiled files
1480 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1481 for fn in filenames:
1482 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1483 os.unlink(os.path.join(dirpath, fn))
1484
1485 outdir = os.path.join(WORKDIR, 'installer')
1486 if os.path.exists(outdir):
1487 shutil.rmtree(outdir)
1488 os.mkdir(outdir)
1489
Ronald Oussoren1943f862009-03-30 19:39:14 +00001490 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001491 pkgcontents = os.path.join(pkgroot, 'Packages')
1492 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001493 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001494 packageFromRecipe(pkgcontents, recipe)
1495
1496 rsrcDir = os.path.join(pkgroot, 'Resources')
1497
1498 fn = os.path.join(pkgroot, 'PkgInfo')
1499 fp = open(fn, 'w')
1500 fp.write('pmkrpkg1')
1501 fp.close()
1502
1503 os.mkdir(rsrcDir)
1504
1505 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1506 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001507 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001508 IFPkgDescriptionVersion=getVersion(),
1509 )
1510
1511 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1512 for fn in os.listdir('resources'):
1513 if fn == '.svn': continue
1514 if fn.endswith('.jpg'):
1515 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1516 else:
1517 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1518
Thomas Wouters477c8d52006-05-27 19:21:47 +00001519
1520def installSize(clear=False, _saved=[]):
1521 if clear:
1522 del _saved[:]
1523 if not _saved:
1524 data = captureCommand("du -ks %s"%(
1525 shellQuote(os.path.join(WORKDIR, '_root'))))
1526 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1527 return _saved[0]
1528
1529
1530def buildDMG():
1531 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001532 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001533 """
1534 outdir = os.path.join(WORKDIR, 'diskimage')
1535 if os.path.exists(outdir):
1536 shutil.rmtree(outdir)
1537
1538 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001539 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001540 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001541 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001542 imagepath = imagepath + '.dmg'
1543
1544 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001545 volname='Python %s'%(getFullVersion())
1546 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1547 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001548 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001549 shellQuote(imagepath + ".tmp.dmg" )))
1550
1551
1552 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1553 os.mkdir(os.path.join(WORKDIR, "mnt"))
1554 runCommand("hdiutil attach %s -mountroot %s"%(
1555 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1556
1557 # Custom icon for the DMG, shown when the DMG is mounted.
1558 shutil.copy("../Icons/Disk Image.icns",
1559 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001560 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001561 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1562
1563 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1564
1565 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1566 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1567 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1568 setIcon(imagepath, "../Icons/Disk Image.icns")
1569
1570 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001571
1572 return imagepath
1573
1574
1575def setIcon(filePath, icnsPath):
1576 """
1577 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001578 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001579
Ronald Oussoren70050672010-04-30 15:00:26 +00001580 dirPath = os.path.normpath(os.path.dirname(__file__))
1581 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001582 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1583 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1584 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001585 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1586 if not os.path.exists(appPath):
1587 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001588 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1589 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001590
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001591 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1592 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001593
1594def main():
1595 # First parse options and check if we can perform our work
1596 parseOptions()
1597 checkEnvironment()
1598
Ronald Oussoren1943f862009-03-30 19:39:14 +00001599 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001600 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001601 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001602
1603 if os.path.exists(WORKDIR):
1604 shutil.rmtree(WORKDIR)
1605 os.mkdir(WORKDIR)
1606
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001607 os.environ['LC_ALL'] = 'C'
1608
Thomas Wouters477c8d52006-05-27 19:21:47 +00001609 # Then build third-party libraries such as sleepycat DB4.
1610 buildLibraries()
1611
1612 # Now build python itself
1613 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001614
1615 # And then build the documentation
1616 # Remove the Deployment Target from the shell
1617 # environment, it's no longer needed and
1618 # an unexpected build target can cause problems
1619 # when Sphinx and its dependencies need to
1620 # be (re-)installed.
1621 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001622 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001623
1624
1625 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001626 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001627 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001628 fn = os.path.join(folder, "License.rtf")
1629 patchFile("resources/License.rtf", fn)
1630 fn = os.path.join(folder, "ReadMe.rtf")
1631 patchFile("resources/ReadMe.rtf", fn)
1632 fn = os.path.join(folder, "Update Shell Profile.command")
1633 patchScript("scripts/postflight.patch-profile", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001634 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001635 setIcon(folder, "../Icons/Python Folder.icns")
1636
1637 # Create the installer
1638 buildInstaller()
1639
1640 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001641 patchFile('resources/ReadMe.rtf',
1642 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001643
1644 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001645 patchFile('resources/License.rtf',
1646 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001647
1648 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001649 fp.write("# BUILD INFO\n")
1650 fp.write("# Date: %s\n" % time.ctime())
1651 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001652 fp.close()
1653
Thomas Wouters477c8d52006-05-27 19:21:47 +00001654 # And copy it to a DMG
1655 buildDMG()
1656
Thomas Wouters477c8d52006-05-27 19:21:47 +00001657if __name__ == "__main__":
1658 main()