blob: 5c9b5901795869822a615de03ecc4e672c1540e0 [file] [log] [blame]
Ned Deilye1c9794952013-01-29 00:07:46 -08001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deily53c460d2011-01-30 01:43:40 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deilye1c9794952013-01-29 00:07:46 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deily53c460d2011-01-30 01:43:40 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
Ned Deily53c460d2011-01-30 01:43:40 +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 Deilyebd63dc2014-04-09 16:12:11 -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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000014
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -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 Deilye1c9794952013-01-29 00:07:46 -080028
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000029Usage: see USAGE variable in the script.
30"""
Ned Deilye1c9794952013-01-29 00:07:46 -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 )
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000044
Ronald Oussoren158ad592006-11-07 16:00:34 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000047
48from plistlib import Plist
49
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000059 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
Ned Deily62a86602014-12-09 23:45:13 -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 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000070 variable = variable + '='
71 for ln in open(fn, 'r'):
72 if ln.startswith(variable):
73 value = ln[len(variable):].strip()
Ned Deily62a86602014-12-09 23:45:13 -080074 return value.strip("\"'")
Ned Deilye1c9794952013-01-29 00:07:46 -080075 raise RuntimeError("Cannot find variable %s" % variable[:-1])
76
77_cache_getVersion = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000078
79def getVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000085
Ned Deilye1c9794952013-01-29 00:07:46 -080086def getVersionMajorMinor():
87 return tuple([int(n) for n in getVersion().split('.', 2)])
88
89_cache_getFullVersion = None
90
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000091def getFullVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080092 global _cache_getFullVersion
93 if _cache_getFullVersion is not None:
94 return _cache_getFullVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000095 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
96 for ln in open(fn):
97 if 'PY_VERSION' in ln:
Ned Deilye1c9794952013-01-29 00:07:46 -080098 _cache_getFullVersion = ln.split()[-1][1:-1]
99 return _cache_getFullVersion
100 raise RuntimeError("Cannot find full version??")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000101
Ned Deily62a86602014-12-09 23:45:13 -0800102FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
103FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
104
Ronald Oussorenaa560962006-11-07 15:53:38 +0000105# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000106WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000107
Ronald Oussorenaa560962006-11-07 15:53:38 +0000108# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000109# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000110DEPSRC = os.path.join(WORKDIR, 'third-party')
111DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000112
113# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +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 Melotti6d0f0f22013-08-26 01:31:30 +0300118### issue is resolved.
Ronald Oussoren209d4c32009-09-29 19:34:13 +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
128SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000129
Ronald Oussoren508282e2009-03-30 19:34:51 +0000130universal_opts_map = { '32-bit': ('i386', 'ppc',),
131 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Oussoren508282e2009-03-30 19:34:51 +0000141
142UNIVERSALOPTS = tuple(universal_opts_map.keys())
143
144UNIVERSALARCHS = '32-bit'
145
146ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000147
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200148# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000149SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000150 os.path.dirname(
151 os.path.dirname(
152 os.path.abspath(__file__
153 ))))
154
Ronald Oussoren508282e2009-03-30 19:34:51 +0000155# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
156DEPTARGET = '10.3'
157
Ned Deily1f70b872014-06-25 13:33:57 -0700158def getDeptargetTuple():
159 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
160
161def getTargetCompilers():
162 target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily1f70b872014-06-25 13:33:57 -0700167 }
168 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000169
Ned Deily1f70b872014-06-25 13:33:57 -0700170CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000171
Ned Deily62a86602014-12-09 23:45:13 -0800172PYTHON_3 = getVersionMajorMinor() >= (3, 0)
Ned Deily53c460d2011-01-30 01:43:40 +0000173
Ronald Oussoren158ad592006-11-07 16:00:34 +0000174USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000184 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
185 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000186""")% globals()
187
Ned Deilye1c9794952013-01-29 00:07:46 -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 = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000195
Ned Deily62a86602014-12-09 23:45:13 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000200# Instructions for building libraries that are necessary for building a
201# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000202# [The recipes are defined here for convenience but instantiated later after
203# command line options have been processed.]
204def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000205 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000206
Ned Deily1f70b872014-06-25 13:33:57 -0700207 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deilye1c9794952013-01-29 00:07:46 -0800208
Ned Deily62a86602014-12-09 23:45:13 -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(
240 name="OpenSSL 1.0.1j",
241 url="https://www.openssl.org/source/openssl-1.0.1j.tar.gz",
242 checksum='f7175c9cd3c39bb1907ac8bba9df8ed3',
243 patches=[
244 "openssl_sdk_makedepend.patch",
245 ],
246 buildrecipe=build_universal_openssl,
247 configure=None,
248 install=None,
249 ),
250 ])
251
Ned Deilyebd63dc2014-04-09 16:12:11 -0700252# Disable for now
Ned Deily30101822014-11-14 18:53:59 -0800253 if False: # if getDeptargetTuple() > (10, 5):
Ned Deily0203a802013-10-25 00:40:07 -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 Deilya6cbff02013-10-27 19:47:23 -0700275 patches=[
276 "issue19373_tk_8_5_15_source.patch",
277 ],
Ned Deily0203a802013-10-25 00:40:07 -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 Deily30101822014-11-14 18:53:59 -0800294 if PYTHON_3:
Ned Deilye1c9794952013-01-29 00:07:46 -0800295 result.extend([
296 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700297 name="XZ 5.0.5",
298 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
299 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -0700342 name="SQLite 3.8.3.1",
343 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
344 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily1f70b872014-06-25 13:33:57 -0700361 if getDeptargetTuple() < (10, 5):
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000362 result.extend([
363 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000364 name="Bzip2 1.0.6",
365 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
366 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000367 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800368 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
369 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000370 shellQuote(os.path.join(WORKDIR, 'libraries')),
371 ' -arch '.join(ARCHLIST),
372 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000373 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000374 ),
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 Deilye1c9794952013-01-29 00:07:46 -0800380 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
381 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deily53c460d2011-01-30 01:43:40 +0000389 name="GNU Readline 6.1.2",
390 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
391 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000392 patchlevel='0',
393 patches=[
394 # The readline maintainers don't do actual micro releases, but
395 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -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'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000400 ]
401 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000402 ])
403
Ned Deily53c460d2011-01-30 01:43:40 +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 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000417
418 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000419
420
421# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000422def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000423 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000424 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",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000435 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000436 ),
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).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000445
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000446 It also installs a number of examples and demos.
447 """,
448 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000449 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000450 ),
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,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000461 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000462 ),
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 Deilye1c9794952013-01-29 00:07:46 -0800470 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000471 """,
472 postflight="scripts/postflight.documentation",
473 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000474 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000475 ),
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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000483
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deily30101822014-11-14 18:53:59 -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',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000506 ),
507 ]
508
Ned Deily1f70b872014-06-25 13:33:57 -0700509 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Ronald Oussorenc66ced32009-09-20 20:16:11 +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,
Ned Deily53c460d2011-01-30 01:43:40 +0000523 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000524 )
525 )
Ned Deilyebd63dc2014-04-09 16:12:11 -0700526
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000527 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000528
529def 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 Deilye1c9794952013-01-29 00:07:46 -0800542 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000543
544def runCommand(commandline):
545 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200546 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000547 unless the command fails.
548 """
549 fd = os.popen(commandline, 'r')
550 data = fd.read()
551 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000552 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000553 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800554 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Peterson5b63acd2008-03-29 15:24:25 +0000563 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000564 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800565 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000566
567 return data
568
Ronald Oussoren287128a2010-04-18 14:01:05 +0000569def getTclTkVersion(configfile, versionline):
570 """
571 search Tcl or Tk configuration file for version line
572 """
573 try:
574 f = open(configfile, "r")
575 except:
576 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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000586def checkEnvironment():
587 """
588 Check that we're running on a supported system.
589 """
590
Ned Deily53c460d2011-01-30 01:43:40 +0000591 if sys.version_info[0:2] < (2, 4):
592 fatal("This script must be run with Python 2.4 or later")
593
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000594 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000595 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000596
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000597 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000598 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -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 Oussoren287128a2010-04-18 14:01:05 +0000612
Ned Deilye1c9794952013-01-29 00:07:46 -0800613 frameworks = {}
Ronald Oussoren287128a2010-04-18 14:01:05 +0000614 for framework in ['Tcl', 'Tk']:
Ned Deilye1c9794952013-01-29 00:07:46 -0800615 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily53c460d2011-01-30 01:43:40 +0000616 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800617 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000618 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -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))
Ned Deily53c460d2011-01-30 01:43:40 +0000624 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -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 Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -0800647 print("INFO: deleting environment variable %s=%s" % (
648 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000649 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000650
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -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 Deily5ceffa12014-09-05 15:51:54 -0700664 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000665
Ronald Oussoren158ad592006-11-07 16:00:34 +0000666def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000667 """
668 Parse arguments and update global settings.
669 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000670 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800671 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily62a86602014-12-09 23:45:13 -0800672 global FW_VERSION_PREFIX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000673
674 if args is None:
675 args = sys.argv[1:]
676
677 try:
678 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000679 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
680 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800681 except getopt.GetoptError:
682 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000683 sys.exit(1)
684
685 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800686 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000687 sys.exit(1)
688
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000689 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000690 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000691 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800692 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000707 elif k in ('--dep-target', ):
708 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000709 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000710
711 elif k in ('--universal-archs', ):
712 if v in UNIVERSALOPTS:
713 UNIVERSALARCHS = v
714 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000715 if deptarget is None:
716 # Select alternate default deployment
717 # target
718 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000719 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800720 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000721
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000722 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800723 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily1f70b872014-06-25 13:33:57 -0700730 CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000731
Ned Deily62a86602014-12-09 23:45:13 -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 Deilye1c9794952013-01-29 00:07:46 -0800743 print("")
Ned Deily62a86602014-12-09 23:45:13 -0800744 print(" -- Building a Python %s framework at patch level %s"
745 % (getVersion(), getFullVersion()))
746 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily0203a802013-10-25 00:40:07 -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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily0203a802013-10-25 00:40:07 -0700765 if ((retval.startswith('tcl') or retval.startswith('tk'))
766 and retval.endswith('-src')):
767 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -0800794 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000795
796 return os.path.join(builddir, retval)
797
798 finally:
799 os.chdir(curdir)
800
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000801def downloadURL(url, fname):
802 """
803 Download the contents of the url into the file.
804 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800805 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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)
817 except:
818 pass
819
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily62a86602014-12-09 23:45:13 -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 def build_openssl_arch(archbase, arch):
848 "Build one architecture of openssl"
849 arch_opts = {
850 "i386": ["darwin-i386-cc"],
851 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
852 "ppc": ["darwin-ppc-cc"],
853 "ppc64": ["darwin64-ppc-cc"],
854 }
855 configure_opts = [
856 "no-krb5",
857 "no-idea",
858 "no-mdc2",
859 "no-rc5",
860 "no-zlib",
861 "enable-tlsext",
862 "no-ssl2",
863 "no-ssl3",
864 "no-ssl3-method",
865 # "enable-unit-test",
866 "shared",
867 "--install_prefix=%s"%shellQuote(archbase),
868 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
869 "--openssldir=/System/Library/OpenSSL",
870 ]
871 runCommand(" ".join(["perl", "Configure"]
872 + arch_opts[arch] + configure_opts))
873 runCommand("make depend OSX_SDK=%s" % SDKPATH)
874 runCommand("make all OSX_SDK=%s" % SDKPATH)
875 runCommand("make install_sw OSX_SDK=%s" % SDKPATH)
876 # runCommand("make test")
877 return
878
879 srcdir = os.getcwd()
880 universalbase = os.path.join(srcdir, "..",
881 os.path.basename(srcdir) + "-universal")
882 os.mkdir(universalbase)
883 archbasefws = []
884 for arch in archList:
885 # fresh copy of the source tree
886 archsrc = os.path.join(universalbase, arch, "src")
887 shutil.copytree(srcdir, archsrc, symlinks=True)
888 # install base for this arch
889 archbase = os.path.join(universalbase, arch, "root")
890 os.mkdir(archbase)
891 # Python framework base within install_prefix:
892 # the build will install into this framework..
893 # This is to ensure that the resulting shared libs have
894 # the desired real install paths built into them.
895 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
896
897 # build one architecture
898 os.chdir(archsrc)
899 build_openssl_arch(archbase, arch)
900 os.chdir(srcdir)
901 archbasefws.append(archbasefw)
902
903 # copy arch-independent files from last build into the basedir framework
904 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
905 shutil.copytree(
906 os.path.join(archbasefw, "include", "openssl"),
907 os.path.join(basefw, "include", "openssl")
908 )
909
910 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
911 "SHLIB_VERSION_NUMBER")
912 # e.g. -> "1.0.0"
913 libcrypto = "libcrypto.dylib"
914 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
915 # e.g. -> "libcrypto.1.0.0.dylib"
916 libssl = "libssl.dylib"
917 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
918 # e.g. -> "libssl.1.0.0.dylib"
919
920 try:
921 os.mkdir(os.path.join(basefw, "lib"))
922 except OSError:
923 pass
924
925 # merge the individual arch-dependent shared libs into a fat shared lib
926 archbasefws.insert(0, basefw)
927 for (lib_unversioned, lib_versioned) in [
928 (libcrypto, libcrypto_versioned),
929 (libssl, libssl_versioned)
930 ]:
931 runCommand("lipo -create -output " +
932 " ".join(shellQuote(
933 os.path.join(fw, "lib", lib_versioned))
934 for fw in archbasefws))
935 # and create an unversioned symlink of it
936 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
937
938 # Create links in the temp include and lib dirs that will be injected
939 # into the Python build so that setup.py can find them while building
940 # and the versioned links so that the setup.py post-build import test
941 # does not fail.
942 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
943 for fn in [
944 ["include", "openssl"],
945 ["lib", libcrypto],
946 ["lib", libssl],
947 ["lib", libcrypto_versioned],
948 ["lib", libssl_versioned],
949 ]:
950 os.symlink(
951 os.path.join(relative_path, *fn),
952 os.path.join(basedir, "usr", "local", *fn)
953 )
954
955 return
956
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000957def buildRecipe(recipe, basedir, archList):
958 """
959 Build software using a recipe. This function does the
960 'configure;make;make install' dance for C software, with a possibility
961 to customize this process, basically a poor-mans DarwinPorts.
962 """
963 curdir = os.getcwd()
964
965 name = recipe['name']
Ned Deily62a86602014-12-09 23:45:13 -0800966 THIRD_PARTY_LIBS.append(name)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000967 url = recipe['url']
968 configure = recipe.get('configure', './configure')
Ned Deily62a86602014-12-09 23:45:13 -0800969 buildrecipe = recipe.get('buildrecipe', None)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000970 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
971 shellQuote(basedir)))
972
973 archiveName = os.path.split(url)[-1]
974 sourceArchive = os.path.join(DEPSRC, archiveName)
975
976 if not os.path.exists(DEPSRC):
977 os.mkdir(DEPSRC)
978
Ned Deilye1c9794952013-01-29 00:07:46 -0800979 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
980 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000981 buildDir=os.path.join(WORKDIR, '_bld')
982 if not os.path.exists(buildDir):
983 os.mkdir(buildDir)
984
985 workDir = extractArchive(buildDir, sourceArchive)
986 os.chdir(workDir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000987
Ned Deilye1c9794952013-01-29 00:07:46 -0800988 for patch in recipe.get('patches', ()):
989 if isinstance(patch, tuple):
990 url, checksum = patch
991 fn = os.path.join(DEPSRC, os.path.basename(url))
992 verifyThirdPartyFile(url, checksum, fn)
993 else:
994 # patch is a file in the source directory
995 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000996 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
997 shellQuote(fn),))
998
Ned Deilye1c9794952013-01-29 00:07:46 -0800999 for patchscript in recipe.get('patchscripts', ()):
1000 if isinstance(patchscript, tuple):
1001 url, checksum = patchscript
1002 fn = os.path.join(DEPSRC, os.path.basename(url))
1003 verifyThirdPartyFile(url, checksum, fn)
1004 else:
1005 # patch is a file in the source directory
1006 fn = os.path.join(curdir, patchscript)
1007 if fn.endswith('.bz2'):
1008 runCommand('bunzip2 -fk %s' % shellQuote(fn))
1009 fn = fn[:-4]
1010 runCommand('sh %s' % shellQuote(fn))
1011 os.unlink(fn)
1012
Ned Deilya6cbff02013-10-27 19:47:23 -07001013 if 'buildDir' in recipe:
1014 os.chdir(recipe['buildDir'])
1015
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001016 if configure is not None:
1017 configure_args = [
1018 "--prefix=/usr/local",
1019 "--enable-static",
1020 "--disable-shared",
1021 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
1022 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001023
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001024 if 'configure_pre' in recipe:
1025 args = list(recipe['configure_pre'])
1026 if '--disable-static' in args:
1027 configure_args.remove('--enable-static')
1028 if '--enable-shared' in args:
1029 configure_args.remove('--disable-shared')
1030 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001031
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001032 if recipe.get('useLDFlags', 1):
1033 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -08001034 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1035 "-I%s/usr/local/include"%(
1036 recipe.get('extra_cflags', ''),
1037 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001038 ' -arch '.join(archList),
1039 shellQuote(SDKPATH)[1:-1],
1040 shellQuote(basedir)[1:-1],),
Ned Deilyc177b1c2014-04-09 16:13:46 -07001041 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -08001042 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001043 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001044 shellQuote(basedir)[1:-1],
1045 ' -arch '.join(archList)),
1046 ])
1047 else:
1048 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -08001049 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1050 "-I%s/usr/local/include"%(
1051 recipe.get('extra_cflags', ''),
1052 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001053 ' -arch '.join(archList),
1054 shellQuote(SDKPATH)[1:-1],
1055 shellQuote(basedir)[1:-1],),
1056 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001057
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001058 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -08001059 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001060
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001061 configure_args.insert(0, configure)
1062 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001063
Ned Deilye1c9794952013-01-29 00:07:46 -08001064 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001065 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001066
Ned Deily62a86602014-12-09 23:45:13 -08001067 if buildrecipe is not None:
1068 # call special-case build recipe, e.g. for openssl
1069 buildrecipe(basedir, archList)
1070
1071 if install is not None:
1072 print("Running install for %s"%(name,))
1073 runCommand('{ ' + install + ' ;} 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001074
Ned Deilye1c9794952013-01-29 00:07:46 -08001075 print("Done %s"%(name,))
1076 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001077
1078 os.chdir(curdir)
1079
1080def buildLibraries():
1081 """
1082 Build our dependencies into $WORKDIR/libraries/usr/local
1083 """
Ned Deilye1c9794952013-01-29 00:07:46 -08001084 print("")
1085 print("Building required libraries")
1086 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001087 universal = os.path.join(WORKDIR, 'libraries')
1088 os.mkdir(universal)
1089 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1090 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1091
Ronald Oussoren508282e2009-03-30 19:34:51 +00001092 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001093 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001094
1095
1096
1097def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001098 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001099 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -08001100 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001101 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001102 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001103 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001104 curDir = os.getcwd()
1105 os.chdir(buildDir)
Ned Deily5ceffa12014-09-05 15:51:54 -07001106 # The Doc build changed for 3.4 (technically, for 3.4.1) and for 2.7.9
1107 runCommand('make clean')
1108 # Assume sphinx-build is on our PATH, checked in checkEnvironment
1109 runCommand('make html')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001110 os.chdir(curDir)
1111 if not os.path.exists(docdir):
1112 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001113 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001114
1115
1116def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -08001117 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001118
1119 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1120 rootDir = os.path.join(WORKDIR, '_root')
1121
1122 if os.path.exists(buildDir):
1123 shutil.rmtree(buildDir)
1124 if os.path.exists(rootDir):
1125 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +00001126 os.makedirs(buildDir)
1127 os.makedirs(rootDir)
1128 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001129 curdir = os.getcwd()
1130 os.chdir(buildDir)
1131
1132 # Not sure if this is still needed, the original build script
1133 # claims that parts of the install assume python.exe exists.
1134 os.symlink('python', os.path.join(buildDir, 'python.exe'))
1135
1136 # Extract the version from the configure file, needed to calculate
1137 # several paths.
1138 version = getVersion()
1139
Ronald Oussoren008af852009-03-30 20:02:08 +00001140 # Since the extra libs are not in their installed framework location
1141 # during the build, augment the library path so that the interpreter
1142 # will find them during its extension import sanity checks.
1143 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1144 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -08001145 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +00001146 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
1147 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +00001148 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -07001149 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +00001150 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -07001151 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001152 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
1153 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +00001154 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily30101822014-11-14 18:53:59 -08001155 (' ', '--without-ensurepip ')[PYTHON_3],
Ronald Oussoren508282e2009-03-30 19:34:51 +00001156 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001157 shellQuote(WORKDIR)[1:-1]))
1158
Ned Deilyfbb60d52014-05-22 15:27:01 -07001159 print("Running make touch")
1160 runCommand("make touch")
1161
Ned Deilye1c9794952013-01-29 00:07:46 -08001162 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001163 runCommand("make")
1164
Ned Deilye1c9794952013-01-29 00:07:46 -08001165 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +00001166 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001167 shellQuote(rootDir)))
1168
Ned Deilye1c9794952013-01-29 00:07:46 -08001169 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001170 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1171 shellQuote(rootDir)))
1172
Ronald Oussoren008af852009-03-30 20:02:08 +00001173 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -08001174 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001175 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1176 runCommand("mv %s/* %s"%(
1177 shellQuote(os.path.join(
1178 WORKDIR, 'libraries', 'Library', 'Frameworks',
1179 'Python.framework', 'Versions', getVersion(),
1180 'lib')),
1181 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1182 'Python.framework', 'Versions', getVersion(),
1183 'lib'))))
1184
Ned Deily70f213a2013-10-26 03:16:06 -07001185 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1186 'Python.framework', 'Versions',
1187 version, 'lib', 'python%s'%(version,))
1188
Ned Deilye1c9794952013-01-29 00:07:46 -08001189 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001190 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001191 gid = grp.getgrnam('admin').gr_gid
1192
Ned Deilye1c9794952013-01-29 00:07:46 -08001193 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001194 for dirpath, dirnames, filenames in os.walk(frmDir):
1195 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001196 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001197 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001198
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001199 for fn in filenames:
1200 if os.path.islink(fn):
1201 continue
1202
1203 # "chmod g+w $fn"
1204 p = os.path.join(dirpath, fn)
1205 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001206 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1207 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001208
Ned Deilye1c9794952013-01-29 00:07:46 -08001209 if fn in EXPECTED_SHARED_LIBS:
1210 # check to see that this file was linked with the
1211 # expected library path and version
1212 data = captureCommand("otool -L %s" % shellQuote(p))
1213 for sl in EXPECTED_SHARED_LIBS[fn]:
1214 if ("\t%s " % sl) not in data:
1215 print("Expected shared lib %s was not linked with %s"
1216 % (sl, p))
1217 shared_lib_error = True
1218
1219 if shared_lib_error:
1220 fatal("Unexpected shared library errors.")
1221
Ned Deily53c460d2011-01-30 01:43:40 +00001222 if PYTHON_3:
1223 LDVERSION=None
1224 VERSION=None
1225 ABIFLAGS=None
1226
1227 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1228 for ln in fp:
1229 if ln.startswith('VERSION='):
1230 VERSION=ln.split()[1]
1231 if ln.startswith('ABIFLAGS='):
1232 ABIFLAGS=ln.split()[1]
1233 if ln.startswith('LDVERSION='):
1234 LDVERSION=ln.split()[1]
1235 fp.close()
1236
1237 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1238 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1239 config_suffix = '-' + LDVERSION
1240 else:
1241 config_suffix = '' # Python 2.x
1242
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001243 # We added some directories to the search path during the configure
1244 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001245 # the end-users system. Also remove the directories from _sysconfigdata.py
1246 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001247
Ned Deilye6ef7022013-10-25 00:46:59 -07001248 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1249 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1250
Ned Deilye6ef7022013-10-25 00:46:59 -07001251 # fix Makefile
1252 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1253 fp = open(path, 'r')
1254 data = fp.read()
1255 fp.close()
1256
1257 for p in (include_path, lib_path):
1258 data = data.replace(" " + p, '')
1259 data = data.replace(p + " ", '')
1260
1261 fp = open(path, 'w')
1262 fp.write(data)
1263 fp.close()
1264
1265 # fix _sysconfigdata if it exists
1266 #
1267 # TODO: make this more robust! test_sysconfig_module of
1268 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1269 # the output from get_config_var in both sysconfig and
1270 # distutils.sysconfig is exactly the same for both CFLAGS and
1271 # LDFLAGS. The fixing up is now complicated by the pretty
1272 # printing in _sysconfigdata.py. Also, we are using the
1273 # pprint from the Python running the installer build which
1274 # may not cosmetically format the same as the pprint in the Python
1275 # being built (and which is used to originally generate
1276 # _sysconfigdata.py).
1277
1278 import pprint
1279 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1280 if os.path.exists(path):
Ned Deilye1c9794952013-01-29 00:07:46 -08001281 fp = open(path, 'r')
1282 data = fp.read()
1283 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -07001284 # create build_time_vars dict
1285 exec(data)
1286 vars = {}
1287 for k, v in build_time_vars.items():
1288 if type(v) == type(''):
1289 for p in (include_path, lib_path):
1290 v = v.replace(' ' + p, '')
1291 v = v.replace(p + ' ', '')
1292 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001293
Ned Deilye1c9794952013-01-29 00:07:46 -08001294 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -07001295 # duplicated from sysconfig._generate_posix_vars()
1296 fp.write('# system configuration generated and used by'
1297 ' the sysconfig module\n')
1298 fp.write('build_time_vars = ')
1299 pprint.pprint(vars, stream=fp)
Ned Deilye1c9794952013-01-29 00:07:46 -08001300 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001301
1302 # Add symlinks in /usr/local/bin, using relative links
1303 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1304 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1305 'Python.framework', 'Versions', version, 'bin')
1306 if os.path.exists(usr_local_bin):
1307 shutil.rmtree(usr_local_bin)
1308 os.makedirs(usr_local_bin)
1309 for fn in os.listdir(
1310 os.path.join(frmDir, 'Versions', version, 'bin')):
1311 os.symlink(os.path.join(to_framework, fn),
1312 os.path.join(usr_local_bin, fn))
1313
1314 os.chdir(curdir)
1315
Ned Deily53c460d2011-01-30 01:43:40 +00001316 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001317 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001318 # with an already installed version of python 2
1319 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1320 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001321
1322def patchFile(inPath, outPath):
1323 data = fileContents(inPath)
1324 data = data.replace('$FULL_VERSION', getFullVersion())
1325 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001326 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001327 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001328 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily62a86602014-12-09 23:45:13 -08001329 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Ronald Oussorenc5555542006-06-11 20:24:45 +00001330
1331 # This one is not handy as a template variable
1332 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001333 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001334 fp.write(data)
1335 fp.close()
1336
1337def patchScript(inPath, outPath):
Ned Deily30101822014-11-14 18:53:59 -08001338 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001339 data = fileContents(inPath)
Ned Deily30101822014-11-14 18:53:59 -08001340 data = data.replace('@PYMAJOR@', str(major))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001341 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001342 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001343 fp.write(data)
1344 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001345 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001346
1347
1348
1349def packageFromRecipe(targetDir, recipe):
1350 curdir = os.getcwd()
1351 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001352 # The major version (such as 2.5) is included in the package name
1353 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001354 # common.
1355 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001356 srcdir = recipe.get('source')
1357 pkgroot = recipe.get('topdir', srcdir)
1358 postflight = recipe.get('postflight')
1359 readme = textwrap.dedent(recipe['readme'])
1360 isRequired = recipe.get('required', True)
1361
Ned Deilye1c9794952013-01-29 00:07:46 -08001362 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001363
1364 # Substitute some variables
1365 textvars = dict(
1366 VER=getVersion(),
1367 FULLVER=getFullVersion(),
1368 )
1369 readme = readme % textvars
1370
1371 if pkgroot is not None:
1372 pkgroot = pkgroot % textvars
1373 else:
1374 pkgroot = '/'
1375
1376 if srcdir is not None:
1377 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1378 srcdir = srcdir % textvars
1379
1380 if postflight is not None:
1381 postflight = os.path.abspath(postflight)
1382
1383 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1384 os.makedirs(packageContents)
1385
1386 if srcdir is not None:
1387 os.chdir(srcdir)
1388 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1389 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1390 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1391
1392 fn = os.path.join(packageContents, 'PkgInfo')
1393 fp = open(fn, 'w')
1394 fp.write('pmkrpkg1')
1395 fp.close()
1396
1397 rsrcDir = os.path.join(packageContents, "Resources")
1398 os.mkdir(rsrcDir)
1399 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1400 fp.write(readme)
1401 fp.close()
1402
1403 if postflight is not None:
1404 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1405
1406 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001407 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001408 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001409 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1410 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1411 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001412 CFBundleShortVersionString=vers,
1413 IFMajorVersion=major,
1414 IFMinorVersion=minor,
1415 IFPkgFormatVersion=0.10000000149011612,
1416 IFPkgFlagAllowBackRev=False,
1417 IFPkgFlagAuthorizationAction="RootAuthorization",
1418 IFPkgFlagDefaultLocation=pkgroot,
1419 IFPkgFlagFollowLinks=True,
1420 IFPkgFlagInstallFat=True,
1421 IFPkgFlagIsRequired=isRequired,
1422 IFPkgFlagOverwritePermissions=False,
1423 IFPkgFlagRelocatable=False,
1424 IFPkgFlagRestartAction="NoRestart",
1425 IFPkgFlagRootVolumeOnly=True,
1426 IFPkgFlagUpdateInstalledLangauges=False,
1427 )
1428 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1429
1430 pl = Plist(
1431 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001432 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001433 IFPkgDescriptionVersion=vers,
1434 )
1435 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1436
1437 finally:
1438 os.chdir(curdir)
1439
1440
1441def makeMpkgPlist(path):
1442
1443 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001444 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001445
1446 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001447 CFBundleGetInfoString="Python %s"%(vers,),
1448 CFBundleIdentifier='org.python.Python',
1449 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001450 CFBundleShortVersionString=vers,
1451 IFMajorVersion=major,
1452 IFMinorVersion=minor,
1453 IFPkgFlagComponentDirectory="Contents/Packages",
1454 IFPkgFlagPackageList=[
1455 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001456 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001457 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001458 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001459 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001460 ],
1461 IFPkgFormatVersion=0.10000000149011612,
1462 IFPkgFlagBackgroundScaling="proportional",
1463 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001464 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001465 )
1466
1467 writePlist(pl, path)
1468
1469
1470def buildInstaller():
1471
1472 # Zap all compiled files
1473 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1474 for fn in filenames:
1475 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1476 os.unlink(os.path.join(dirpath, fn))
1477
1478 outdir = os.path.join(WORKDIR, 'installer')
1479 if os.path.exists(outdir):
1480 shutil.rmtree(outdir)
1481 os.mkdir(outdir)
1482
Ronald Oussoren508282e2009-03-30 19:34:51 +00001483 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001484 pkgcontents = os.path.join(pkgroot, 'Packages')
1485 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001486 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001487 packageFromRecipe(pkgcontents, recipe)
1488
1489 rsrcDir = os.path.join(pkgroot, 'Resources')
1490
1491 fn = os.path.join(pkgroot, 'PkgInfo')
1492 fp = open(fn, 'w')
1493 fp.write('pmkrpkg1')
1494 fp.close()
1495
1496 os.mkdir(rsrcDir)
1497
1498 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1499 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001500 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001501 IFPkgDescriptionVersion=getVersion(),
1502 )
1503
1504 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1505 for fn in os.listdir('resources'):
1506 if fn == '.svn': continue
1507 if fn.endswith('.jpg'):
1508 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1509 else:
1510 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1511
Ronald Oussorenc5555542006-06-11 20:24:45 +00001512 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001513
1514
1515def installSize(clear=False, _saved=[]):
1516 if clear:
1517 del _saved[:]
1518 if not _saved:
1519 data = captureCommand("du -ks %s"%(
1520 shellQuote(os.path.join(WORKDIR, '_root'))))
1521 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1522 return _saved[0]
1523
1524
1525def buildDMG():
1526 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001527 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001528 """
1529 outdir = os.path.join(WORKDIR, 'diskimage')
1530 if os.path.exists(outdir):
1531 shutil.rmtree(outdir)
1532
1533 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001534 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001535 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001536 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001537 imagepath = imagepath + '.dmg'
1538
1539 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001540 volname='Python %s'%(getFullVersion())
1541 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1542 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001543 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001544 shellQuote(imagepath + ".tmp.dmg" )))
1545
1546
1547 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1548 os.mkdir(os.path.join(WORKDIR, "mnt"))
1549 runCommand("hdiutil attach %s -mountroot %s"%(
1550 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1551
1552 # Custom icon for the DMG, shown when the DMG is mounted.
1553 shutil.copy("../Icons/Disk Image.icns",
1554 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001555 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001556 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1557
1558 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1559
1560 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1561 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1562 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1563 setIcon(imagepath, "../Icons/Disk Image.icns")
1564
1565 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001566
1567 return imagepath
1568
1569
1570def setIcon(filePath, icnsPath):
1571 """
1572 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001573 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001574
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001575 dirPath = os.path.normpath(os.path.dirname(__file__))
1576 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001577 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1578 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1579 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001580 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1581 if not os.path.exists(appPath):
1582 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001583 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1584 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001585
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001586 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1587 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001588
1589def main():
1590 # First parse options and check if we can perform our work
1591 parseOptions()
1592 checkEnvironment()
1593
Ronald Oussoren508282e2009-03-30 19:34:51 +00001594 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001595 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001596 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001597
1598 if os.path.exists(WORKDIR):
1599 shutil.rmtree(WORKDIR)
1600 os.mkdir(WORKDIR)
1601
Ronald Oussoren287128a2010-04-18 14:01:05 +00001602 os.environ['LC_ALL'] = 'C'
1603
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001604 # Then build third-party libraries such as sleepycat DB4.
1605 buildLibraries()
1606
1607 # Now build python itself
1608 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001609
1610 # And then build the documentation
1611 # Remove the Deployment Target from the shell
1612 # environment, it's no longer needed and
1613 # an unexpected build target can cause problems
1614 # when Sphinx and its dependencies need to
1615 # be (re-)installed.
1616 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001617 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001618
1619
1620 # Prepare the applications folder
Benjamin Petersonc3104762008-10-03 11:52:06 +00001621 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001622 getVersion(),))
Ned Deily62a86602014-12-09 23:45:13 -08001623 fn = os.path.join(folder, "License.rtf")
1624 patchFile("resources/license.rtf", fn)
1625 fn = os.path.join(folder, "ReadMe.rtf")
1626 patchFile("resources/readme.rtf", fn)
1627 fn = os.path.join(folder, "Update Shell Profile.command")
1628 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilye1c9794952013-01-29 00:07:46 -08001629 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001630 setIcon(folder, "../Icons/Python Folder.icns")
1631
1632 # Create the installer
1633 buildInstaller()
1634
1635 # And copy the readme into the directory containing the installer
1636 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1637
1638 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001639 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001640
1641 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001642 fp.write("# BUILD INFO\n")
1643 fp.write("# Date: %s\n" % time.ctime())
1644 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001645 fp.close()
1646
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001647 # And copy it to a DMG
1648 buildDMG()
1649
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001650if __name__ == "__main__":
1651 main()