blob: bacdbfb0feba1b7fe3805e7160f255adf27301a9 [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deily8c9bb722018-01-30 07:42:14 -05003This script is used to build "official" universal installers on macOS.
4
5NEW for 3.7.0:
6- support Intel 64-bit-only () and 32-bit-only installer builds
7- use external Tcl/Tk 8.6 for 10.9+ builds
8- deprecate use of explicit SDK (--sdk-path=) since all but the oldest
9 versions of Xcode support implicit setting of an SDK via environment
10 variables (SDKROOT and friends, see the xcrun man page for more info).
11 The SDK stuff was primarily needed for building universal installers
12 for 10.4; so as of 3.7.0, building installers for 10.4 is no longer
13 supported with build-installer.
14- use generic "gcc" as compiler (CC env var) rather than "gcc-4.2"
15
16TODO:
17- support SDKROOT and DEVELOPER_DIR xcrun env variables
18- test with 10.5 and 10.4 and determine support status
Thomas Wouters477c8d52006-05-27 19:21:47 +000019
Ned Deilye59e4c52011-01-29 18:56:28 +000020Please ensure that this script keeps working with Python 2.5, to avoid
Ned Deily8c9bb722018-01-30 07:42:14 -050021bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Doc builds
22use current versions of Sphinx and require a reasonably current python3.
23Sphinx and dependencies are installed into a venv using the python3's pip
24so will fetch them from PyPI if necessary. Since python3 is now used for
25Sphinx, build-installer.py should also be converted to use python3!
Thomas Wouters477c8d52006-05-27 19:21:47 +000026
Ned Deily8c9bb722018-01-30 07:42:14 -050027build-installer currently requires an installed third-party version of
28Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets), Tcl/TK 8.5
29(for 10.6 or later), or Tcl/TK 8.6 (for 10.9 or later)
30installed in /Library/Frameworks. When installed,
Ned Deily4a96a372013-01-29 00:08:32 -080031the Python built by this script will attempt to dynamically link first to
32Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
33back to the ones in /System/Library/Framework. For the build, we recommend
Ned Deily8c9bb722018-01-30 07:42:14 -050034installing the most recent ActiveTcl 8.6. 8.5, or 8.4 version, depending
35on the deployment target. The actual version linked to depends on the
36path of /Library/Frameworks/{Tcl,Tk}.framework/Versions/Current.
Ned Deily4a96a372013-01-29 00:08:32 -080037
Thomas Wouters477c8d52006-05-27 19:21:47 +000038Usage: see USAGE variable in the script.
39"""
Ned Deily4a96a372013-01-29 00:08:32 -080040import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
41try:
42 import urllib2 as urllib_request
43except ImportError:
44 import urllib.request as urllib_request
45
46STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
47 | stat.S_IRGRP | stat.S_IXGRP
48 | stat.S_IROTH | stat.S_IXOTH )
49
50STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
51 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
52 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000053
Thomas Wouters89f507f2006-12-13 04:49:30 +000054INCLUDE_TIMESTAMP = 1
55VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000056
57from plistlib import Plist
58
Thomas Wouters477c8d52006-05-27 19:21:47 +000059try:
60 from plistlib import writePlist
61except ImportError:
62 # We're run using python2.3
63 def writePlist(plist, path):
64 plist.write(path)
65
66def shellQuote(value):
67 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000068 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000069 a shell command.
70 """
71 return "'%s'"%(value.replace("'", "'\"'\"'"))
72
73def grepValue(fn, variable):
Ned Deily5d3febf2014-12-13 00:17:46 -080074 """
75 Return the unquoted value of a variable from a file..
76 QUOTED_VALUE='quotes' -> str('quotes')
77 UNQUOTED_VALUE=noquotes -> str('noquotes')
78 """
Thomas Wouters477c8d52006-05-27 19:21:47 +000079 variable = variable + '='
80 for ln in open(fn, 'r'):
81 if ln.startswith(variable):
82 value = ln[len(variable):].strip()
Ned Deily5d3febf2014-12-13 00:17:46 -080083 return value.strip("\"'")
Ned Deily4a96a372013-01-29 00:08:32 -080084 raise RuntimeError("Cannot find variable %s" % variable[:-1])
85
86_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000087
88def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080089 global _cache_getVersion
90 if _cache_getVersion is None:
91 _cache_getVersion = grepValue(
92 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
93 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000094
Ned Deily4a96a372013-01-29 00:08:32 -080095def getVersionMajorMinor():
96 return tuple([int(n) for n in getVersion().split('.', 2)])
97
98_cache_getFullVersion = None
99
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -0800101 global _cache_getFullVersion
102 if _cache_getFullVersion is not None:
103 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +0000104 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
105 for ln in open(fn):
106 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -0800107 _cache_getFullVersion = ln.split()[-1][1:-1]
108 return _cache_getFullVersion
109 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000110
Ned Deily5d3febf2014-12-13 00:17:46 -0800111FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
112FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
Ned Deilydde4f632016-09-12 09:39:23 -0400113FW_SSL_DIRECTORY = "--undefined--" # initialized in parseOptions
Ned Deily5d3febf2014-12-13 00:17:46 -0800114
Thomas Wouters89f507f2006-12-13 04:49:30 +0000115# The directory we'll use to create the build (will be erased and recreated)
116WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000117
Thomas Wouters89f507f2006-12-13 04:49:30 +0000118# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000119# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000120DEPSRC = os.path.join(WORKDIR, 'third-party')
121DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000122
Ronald Oussoren1943f862009-03-30 19:39:14 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 'intel': ('i386', 'x86_64'),
Ned Deily8c9bb722018-01-30 07:42:14 -0500126 'intel-32': ('i386',),
127 'intel-64': ('x86_64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000128 '3-way': ('ppc', 'i386', 'x86_64'),
129 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
130default_target_map = {
131 '64-bit': '10.5',
132 '3-way': '10.5',
133 'intel': '10.5',
Ned Deily8c9bb722018-01-30 07:42:14 -0500134 'intel-32': '10.4',
135 'intel-64': '10.5',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000136 'all': '10.5',
137}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000138
139UNIVERSALOPTS = tuple(universal_opts_map.keys())
140
141UNIVERSALARCHS = '32-bit'
142
143ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144
Ezio Melotti42da6632011-03-15 05:18:48 +0200145# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000146SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000147 os.path.dirname(
148 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000149 os.path.abspath(__file__
150 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000151
Ronald Oussoren1943f862009-03-30 19:39:14 +0000152# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
Ned Deily8c9bb722018-01-30 07:42:14 -0500153DEPTARGET = '10.5'
Ronald Oussoren1943f862009-03-30 19:39:14 +0000154
Ned Deily04cdfa12014-06-25 13:36:14 -0700155def getDeptargetTuple():
156 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
157
158def getTargetCompilers():
159 target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800160 '10.4': ('gcc-4.0', 'g++-4.0'),
Ned Deily8c9bb722018-01-30 07:42:14 -0500161 '10.5': ('gcc', 'g++'),
162 '10.6': ('gcc', 'g++'),
Ned Deily04cdfa12014-06-25 13:36:14 -0700163 }
Miss Islington (bot)d24c5a02018-02-24 11:59:25 -0800164 return target_cc_map.get(DEPTARGET, ('gcc', 'g++') )
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000165
Ned Deily04cdfa12014-06-25 13:36:14 -0700166CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000167
Ned Deily5d3febf2014-12-13 00:17:46 -0800168PYTHON_3 = getVersionMajorMinor() >= (3, 0)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000169
Thomas Wouters89f507f2006-12-13 04:49:30 +0000170USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000171 Usage: build_python [options]
172
173 Options:
174 -? or -h: Show this message
175 -b DIR
176 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
177 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
Ned Deily8c9bb722018-01-30 07:42:14 -0500178 --sdk-path=DIR: Location of the SDK (deprecated, use SDKROOT env variable)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000179 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ned Deily8c9bb722018-01-30 07:42:14 -0500180 --dep-target=10.n macOS deployment target (default: %(DEPTARGET)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000181 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000182""")% globals()
183
Ned Deily4a96a372013-01-29 00:08:32 -0800184# Dict of object file names with shared library names to check after building.
185# This is to ensure that we ended up dynamically linking with the shared
186# library paths and versions we expected. For example:
187# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
188# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
189# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
190EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000191
Ned Deily7fdefac2018-01-30 17:29:53 -0500192# Are we building and linking with our own copy of Tcl/TK?
193# For now, do so if deployment target is 10.9+.
194def internalTk():
195 return getDeptargetTuple() >= (10, 9)
196
Ned Deily5d3febf2014-12-13 00:17:46 -0800197# List of names of third party software built with this installer.
198# The names will be inserted into the rtf version of the License.
199THIRD_PARTY_LIBS = []
200
Thomas Wouters477c8d52006-05-27 19:21:47 +0000201# Instructions for building libraries that are necessary for building a
202# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000203# [The recipes are defined here for convenience but instantiated later after
204# command line options have been processed.]
205def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000206 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000207
Ned Deily04cdfa12014-06-25 13:36:14 -0700208 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800209
Ned Deilydde4f632016-09-12 09:39:23 -0400210 # Since Apple removed the header files for the deprecated system
211 # OpenSSL as of the Xcode 7 release (for OS X 10.10+), we do not
212 # have much choice but to build our own copy here, too.
Ned Deily5d3febf2014-12-13 00:17:46 -0800213
Ned Deilydde4f632016-09-12 09:39:23 -0400214 result.extend([
Ned Deily5d3febf2014-12-13 00:17:46 -0800215 dict(
Ned Deily8c9bb722018-01-30 07:42:14 -0500216 name="OpenSSL 1.1.0g",
217 url="https://www.openssl.org/source/openssl-1.1.0g.tar.gz",
218 checksum='ba5f1b8b835b88cadbce9b35ed9531a6',
Ned Deily5d3febf2014-12-13 00:17:46 -0800219 buildrecipe=build_universal_openssl,
220 configure=None,
221 install=None,
222 ),
Ned Deilydde4f632016-09-12 09:39:23 -0400223 ])
Ned Deily5d3febf2014-12-13 00:17:46 -0800224
Ned Deily7fdefac2018-01-30 17:29:53 -0500225 if internalTk():
Ned Deily5b3582c2013-10-25 00:41:46 -0700226 result.extend([
227 dict(
Ned Deily7fdefac2018-01-30 17:29:53 -0500228 name="Tcl 8.6.7",
229 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tcl8.6.7-src.tar.gz",
230 checksum='5673aaf45b5de5d8dd80bb3daaeb8838',
Ned Deily5b3582c2013-10-25 00:41:46 -0700231 buildDir="unix",
232 configure_pre=[
233 '--enable-shared',
234 '--enable-threads',
235 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
236 ],
237 useLDFlags=False,
238 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
239 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deily7fdefac2018-01-30 17:29:53 -0500240 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
Ned Deily5b3582c2013-10-25 00:41:46 -0700241 },
242 ),
243 dict(
Ned Deily7fdefac2018-01-30 17:29:53 -0500244 name="Tk 8.6.7",
245 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tk8.6.7-src.tar.gz",
246 checksum='46ea9c0165c515d87393700f4891ab6f',
Ned Deily5b3582c2013-10-25 00:41:46 -0700247 buildDir="unix",
248 configure_pre=[
249 '--enable-aqua',
250 '--enable-shared',
251 '--enable-threads',
252 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
253 ],
254 useLDFlags=False,
255 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'%{
256 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deily7fdefac2018-01-30 17:29:53 -0500257 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
258 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.6'%(getVersion())),
Ned Deily5b3582c2013-10-25 00:41:46 -0700259 },
260 ),
261 ])
262
Ned Deilyed730102014-11-14 18:55:05 -0800263 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800264 result.extend([
265 dict(
Ned Deilye6f8a732017-12-04 22:55:20 -0500266 name="XZ 5.2.3",
267 url="http://tukaani.org/xz/xz-5.2.3.tar.gz",
268 checksum='ef68674fb47a8b8e741b34e429d86e9d',
Ned Deily4a96a372013-01-29 00:08:32 -0800269 configure_pre=[
270 '--disable-dependency-tracking',
271 ]
272 ),
273 ])
274
275 result.extend([
276 dict(
277 name="NCurses 5.9",
278 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
279 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
280 configure_pre=[
281 "--enable-widec",
282 "--without-cxx",
283 "--without-cxx-binding",
284 "--without-ada",
285 "--without-curses-h",
286 "--enable-shared",
287 "--with-shared",
288 "--without-debug",
289 "--without-normal",
290 "--without-tests",
291 "--without-manpages",
292 "--datadir=/usr/share",
293 "--sysconfdir=/etc",
294 "--sharedstatedir=/usr/com",
295 "--with-terminfo-dirs=/usr/share/terminfo",
296 "--with-default-terminfo-dir=/usr/share/terminfo",
297 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
298 ],
299 patchscripts=[
300 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
301 "f54bf02a349f96a7c4f0d00922f3a0d4"),
302 ],
303 useLDFlags=False,
304 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
305 shellQuote(os.path.join(WORKDIR, 'libraries')),
306 shellQuote(os.path.join(WORKDIR, 'libraries')),
307 getVersion(),
308 ),
309 ),
310 dict(
Ned Deily8c9bb722018-01-30 07:42:14 -0500311 name="SQLite 3.22.0",
312 url="https://www.sqlite.org/2018/sqlite-autoconf-3220000.tar.gz",
313 checksum='96b5648d542e8afa6ab7ffb8db8ddc3d',
Ned Deily4a96a372013-01-29 00:08:32 -0800314 extra_cflags=('-Os '
Ned Deilyb3b07672016-09-05 17:31:14 -0700315 '-DSQLITE_ENABLE_FTS5 '
Ned Deily4a96a372013-01-29 00:08:32 -0800316 '-DSQLITE_ENABLE_FTS4 '
317 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
Ned Deily9625bf52017-12-04 21:50:29 -0500318 '-DSQLITE_ENABLE_JSON1 '
Ned Deily4a96a372013-01-29 00:08:32 -0800319 '-DSQLITE_ENABLE_RTREE '
320 '-DSQLITE_TCL=0 '
321 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
322 configure_pre=[
323 '--enable-threadsafe',
324 '--enable-shared=no',
325 '--enable-static=yes',
326 '--disable-readline',
327 '--disable-dependency-tracking',
328 ]
329 ),
330 ])
331
Ned Deily04cdfa12014-06-25 13:36:14 -0700332 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000333 result.extend([
334 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000335 name="Bzip2 1.0.6",
336 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
337 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000338 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500339 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800340 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000341 shellQuote(os.path.join(WORKDIR, 'libraries')),
342 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000343 ),
344 ),
345 dict(
346 name="ZLib 1.2.3",
347 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
348 checksum='debc62758716a169df9f62e6ab2bc634',
349 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500350 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800351 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000352 shellQuote(os.path.join(WORKDIR, 'libraries')),
353 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000354 ),
355 ),
356 dict(
357 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000358 name="GNU Readline 6.1.2",
359 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
360 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000361 patchlevel='0',
362 patches=[
363 # The readline maintainers don't do actual micro releases, but
364 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800365 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
366 'c642f2e84d820884b0bf9fd176bc6c3f'),
367 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
368 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000369 ]
370 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000371 ])
372
Ned Deily4f7ff782011-01-15 05:29:12 +0000373 if not PYTHON_3:
374 result.extend([
375 dict(
376 name="Sleepycat DB 4.7.25",
377 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
378 checksum='ec2b87e833779681a0c3a814aa71359e',
379 buildDir="build_unix",
380 configure="../dist/configure",
381 configure_pre=[
382 '--includedir=/usr/local/include/db4',
383 ]
384 ),
385 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000386
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000387 return result
388
Thomas Wouters477c8d52006-05-27 19:21:47 +0000389
Thomas Wouters477c8d52006-05-27 19:21:47 +0000390# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000391def pkg_recipes():
392 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
393 result = [
394 dict(
395 name="PythonFramework",
396 long_name="Python Framework",
397 source="/Library/Frameworks/Python.framework",
398 readme="""\
399 This package installs Python.framework, that is the python
Ned Deily8c9bb722018-01-30 07:42:14 -0500400 interpreter and the standard library.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000401 """,
402 postflight="scripts/postflight.framework",
403 selected='selected',
404 ),
405 dict(
406 name="PythonApplications",
407 long_name="GUI Applications",
408 source="/Applications/Python %(VER)s",
409 readme="""\
410 This package installs IDLE (an interactive Python IDE),
411 Python Launcher and Build Applet (create application bundles
412 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000413
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000414 It also installs a number of examples and demos.
415 """,
416 required=False,
417 selected='selected',
418 ),
419 dict(
420 name="PythonUnixTools",
421 long_name="UNIX command-line tools",
422 source="/usr/local/bin",
423 readme="""\
424 This package installs the unix tools in /usr/local/bin for
425 compatibility with older releases of Python. This package
426 is not necessary to use Python.
427 """,
428 required=False,
429 selected='selected',
430 ),
431 dict(
432 name="PythonDocumentation",
433 long_name="Python Documentation",
434 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
435 source="/pydocs",
436 readme="""\
437 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800438 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000439 """,
440 postflight="scripts/postflight.documentation",
441 required=False,
442 selected='selected',
443 ),
444 dict(
445 name="PythonProfileChanges",
446 long_name="Shell profile updater",
447 readme="""\
448 This packages updates your shell profile to make sure that
449 the Python tools are found by your shell in preference of
450 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000451
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000452 If you don't install this package you'll have to add
453 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
454 to your PATH by hand.
455 """,
456 postflight="scripts/postflight.patch-profile",
457 topdir="/Library/Frameworks/Python.framework",
458 source="/empty-dir",
459 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800460 selected='selected',
461 ),
462 dict(
463 name="PythonInstallPip",
464 long_name="Install or upgrade pip",
465 readme="""\
466 This package installs (or upgrades from an earlier version)
467 pip, a tool for installing and managing Python packages.
468 """,
469 postflight="scripts/postflight.ensurepip",
470 topdir="/Library/Frameworks/Python.framework",
471 source="/empty-dir",
472 required=False,
473 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000474 ),
475 ]
476
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000477 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478
Thomas Wouters477c8d52006-05-27 19:21:47 +0000479def fatal(msg):
480 """
481 A fatal error, bail out.
482 """
483 sys.stderr.write('FATAL: ')
484 sys.stderr.write(msg)
485 sys.stderr.write('\n')
486 sys.exit(1)
487
488def fileContents(fn):
489 """
490 Return the contents of the named file
491 """
Ned Deily4a96a372013-01-29 00:08:32 -0800492 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000493
494def runCommand(commandline):
495 """
Ezio Melotti13925002011-03-16 11:05:33 +0200496 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000497 unless the command fails.
498 """
499 fd = os.popen(commandline, 'r')
500 data = fd.read()
501 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000502 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000503 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800504 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000505
506 if VERBOSE:
507 sys.stdout.write(data); sys.stdout.flush()
508
509def captureCommand(commandline):
510 fd = os.popen(commandline, 'r')
511 data = fd.read()
512 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000513 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000514 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800515 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000516
517 return data
518
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000519def getTclTkVersion(configfile, versionline):
520 """
521 search Tcl or Tk configuration file for version line
522 """
523 try:
524 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300525 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000526 fatal("Framework configuration file not found: %s" % configfile)
527
528 for l in f:
529 if l.startswith(versionline):
530 f.close()
531 return l
532
533 fatal("Version variable %s not found in framework configuration file: %s"
534 % (versionline, configfile))
535
Thomas Wouters477c8d52006-05-27 19:21:47 +0000536def checkEnvironment():
537 """
538 Check that we're running on a supported system.
539 """
540
Ned Deily8c9bb722018-01-30 07:42:14 -0500541 if sys.version_info[0:2] < (2, 5):
542 fatal("This script must be run with Python 2.5 (or later)")
Ned Deilye59e4c52011-01-29 18:56:28 +0000543
Thomas Wouters477c8d52006-05-27 19:21:47 +0000544 if platform.system() != 'Darwin':
Ned Deily8c9bb722018-01-30 07:42:14 -0500545 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000546
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000547 if int(platform.release().split('.')[0]) < 8:
Ned Deily8c9bb722018-01-30 07:42:14 -0500548 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000549
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000550 # Because we only support dynamic load of only one major/minor version of
Ned Deily7fdefac2018-01-30 17:29:53 -0500551 # Tcl/Tk, if we are not using building and using our own private copy of
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000552 # Tcl/Tk, ensure:
Ned Deily8c9bb722018-01-30 07:42:14 -0500553 # 1. there is a user-installed framework (usually ActiveTcl) in (or linked
554 # in) SDKROOT/Library/Frameworks. As of Python 3.7.0, we no longer
555 # enforce that the version of the user-installed framework also
556 # exists in the system-supplied Tcl/Tk frameworks. Time to support
557 # Tcl/Tk 8.6 even if Apple does not.
Ned Deily7fdefac2018-01-30 17:29:53 -0500558 if not internalTk():
559 frameworks = {}
560 for framework in ['Tcl', 'Tk']:
561 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
562 libfw = os.path.join('/', fwpth)
563 usrfw = os.path.join(os.getenv('HOME'), fwpth)
564 frameworks[framework] = os.readlink(libfw)
565 if not os.path.exists(libfw):
566 fatal("Please install a link to a current %s %s as %s so "
567 "the user can override the system framework."
568 % (framework, frameworks[framework], libfw))
569 if os.path.exists(usrfw):
570 fatal("Please rename %s to avoid possible dynamic load issues."
571 % usrfw)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000572
Ned Deily7fdefac2018-01-30 17:29:53 -0500573 if frameworks['Tcl'] != frameworks['Tk']:
574 fatal("The Tcl and Tk frameworks are not the same version.")
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000575
Ned Deily7fdefac2018-01-30 17:29:53 -0500576 print(" -- Building with external Tcl/Tk %s frameworks"
577 % frameworks['Tk'])
Ned Deily4a96a372013-01-29 00:08:32 -0800578
Ned Deily7fdefac2018-01-30 17:29:53 -0500579 # add files to check after build
580 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
581 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
582 % frameworks['Tcl'],
583 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
584 % frameworks['Tk'],
585 ]
586 else:
587 print(" -- Building private copy of Tcl/Tk")
Ned Deily8c9bb722018-01-30 07:42:14 -0500588 print("")
589
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000590 # Remove inherited environment variables which might influence build
591 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
592 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
593 for ev in list(os.environ):
594 for prefix in environ_var_prefixes:
595 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800596 print("INFO: deleting environment variable %s=%s" % (
597 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000598 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000599
Ned Deily4a96a372013-01-29 00:08:32 -0800600 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
601 if 'SDK_TOOLS_BIN' in os.environ:
602 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
603 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
604 # add its fixed location here if it exists
605 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
606 if os.path.isdir(OLD_DEVELOPER_TOOLS):
607 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
608 os.environ['PATH'] = base_path
609 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyb364d9f2017-07-24 04:58:43 -0400610 # Ensure we have access to sphinx-build.
611 # You may have to create a link in /usr/bin for it.
Ned Deily1ff32a92014-09-05 15:57:05 -0700612 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000613
Thomas Wouters89f507f2006-12-13 04:49:30 +0000614def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000615 """
616 Parse arguments and update global settings.
617 """
Ned Deily8c9bb722018-01-30 07:42:14 -0500618 global WORKDIR, DEPSRC, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800619 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily5d3febf2014-12-13 00:17:46 -0800620 global FW_VERSION_PREFIX
Ned Deilydde4f632016-09-12 09:39:23 -0400621 global FW_SSL_DIRECTORY
Thomas Wouters477c8d52006-05-27 19:21:47 +0000622
623 if args is None:
624 args = sys.argv[1:]
625
626 try:
627 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000628 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
629 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800630 except getopt.GetoptError:
631 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632 sys.exit(1)
633
634 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800635 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000636 sys.exit(1)
637
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000638 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000639 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000640 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800641 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000642 sys.exit(0)
643
644 elif k in ('-d', '--build-dir'):
645 WORKDIR=v
646
647 elif k in ('--third-party',):
648 DEPSRC=v
649
650 elif k in ('--sdk-path',):
Ned Deily8c9bb722018-01-30 07:42:14 -0500651 print(" WARNING: --sdk-path is no longer supported")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000652
653 elif k in ('--src-dir',):
654 SRCDIR=v
655
Ronald Oussoren1943f862009-03-30 19:39:14 +0000656 elif k in ('--dep-target', ):
657 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000658 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000659
660 elif k in ('--universal-archs', ):
661 if v in UNIVERSALOPTS:
662 UNIVERSALARCHS = v
663 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000664 if deptarget is None:
665 # Select alternate default deployment
666 # target
Ned Deily8c9bb722018-01-30 07:42:14 -0500667 DEPTARGET = default_target_map.get(v, '10.5')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000668 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800669 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000670
Thomas Wouters477c8d52006-05-27 19:21:47 +0000671 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800672 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000673
674 SRCDIR=os.path.abspath(SRCDIR)
675 WORKDIR=os.path.abspath(WORKDIR)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000676 DEPSRC=os.path.abspath(DEPSRC)
677
Ned Deily04cdfa12014-06-25 13:36:14 -0700678 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000679
Ned Deily5d3febf2014-12-13 00:17:46 -0800680 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
Ned Deilydde4f632016-09-12 09:39:23 -0400681 FW_SSL_DIRECTORY = FW_VERSION_PREFIX[:] + ["etc", "openssl"]
Ned Deily5d3febf2014-12-13 00:17:46 -0800682
683 print("-- Settings:")
684 print(" * Source directory: %s" % SRCDIR)
685 print(" * Build directory: %s" % WORKDIR)
Ned Deily5d3febf2014-12-13 00:17:46 -0800686 print(" * Third-party source: %s" % DEPSRC)
687 print(" * Deployment target: %s" % DEPTARGET)
688 print(" * Universal archs: %s" % str(ARCHLIST))
689 print(" * C compiler: %s" % CC)
690 print(" * C++ compiler: %s" % CXX)
Ned Deily4a96a372013-01-29 00:08:32 -0800691 print("")
Ned Deily5d3febf2014-12-13 00:17:46 -0800692 print(" -- Building a Python %s framework at patch level %s"
693 % (getVersion(), getFullVersion()))
694 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000695
696def extractArchive(builddir, archiveName):
697 """
698 Extract a source archive into 'builddir'. Returns the path of the
699 extracted archive.
700
701 XXX: This function assumes that archives contain a toplevel directory
702 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700703 safe enough for almost anything we use. Unfortunately, it does not
704 work for current Tcl and Tk source releases where the basename of
705 the archive ends with "-src" but the uncompressed directory does not.
706 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000707 """
708 curdir = os.getcwd()
709 try:
710 os.chdir(builddir)
711 if archiveName.endswith('.tar.gz'):
712 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700713 if ((retval.startswith('tcl') or retval.startswith('tk'))
714 and retval.endswith('-src')):
715 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000716 if os.path.exists(retval):
717 shutil.rmtree(retval)
718 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
719
720 elif archiveName.endswith('.tar.bz2'):
721 retval = os.path.basename(archiveName[:-8])
722 if os.path.exists(retval):
723 shutil.rmtree(retval)
724 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
725
726 elif archiveName.endswith('.tar'):
727 retval = os.path.basename(archiveName[:-4])
728 if os.path.exists(retval):
729 shutil.rmtree(retval)
730 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
731
732 elif archiveName.endswith('.zip'):
733 retval = os.path.basename(archiveName[:-4])
734 if os.path.exists(retval):
735 shutil.rmtree(retval)
736 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
737
738 data = fp.read()
739 xit = fp.close()
740 if xit is not None:
741 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800742 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743
744 return os.path.join(builddir, retval)
745
746 finally:
747 os.chdir(curdir)
748
Thomas Wouters477c8d52006-05-27 19:21:47 +0000749def downloadURL(url, fname):
750 """
751 Download the contents of the url into the file.
752 """
Ned Deily4a96a372013-01-29 00:08:32 -0800753 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000754 fpOut = open(fname, 'wb')
755 block = fpIn.read(10240)
756 try:
757 while block:
758 fpOut.write(block)
759 block = fpIn.read(10240)
760 fpIn.close()
761 fpOut.close()
762 except:
763 try:
764 os.unlink(fname)
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300765 except OSError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000766 pass
767
Ned Deily4a96a372013-01-29 00:08:32 -0800768def verifyThirdPartyFile(url, checksum, fname):
769 """
770 Download file from url to filename fname if it does not already exist.
771 Abort if file contents does not match supplied md5 checksum.
772 """
773 name = os.path.basename(fname)
774 if os.path.exists(fname):
775 print("Using local copy of %s"%(name,))
776 else:
777 print("Did not find local copy of %s"%(name,))
778 print("Downloading %s"%(name,))
779 downloadURL(url, fname)
780 print("Archive for %s stored as %s"%(name, fname))
781 if os.system(
782 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
783 % (shellQuote(fname), checksum) ):
784 fatal('MD5 checksum mismatch for file %s' % fname)
785
Ned Deily5d3febf2014-12-13 00:17:46 -0800786def build_universal_openssl(basedir, archList):
787 """
788 Special case build recipe for universal build of openssl.
789
790 The upstream OpenSSL build system does not directly support
791 OS X universal builds. We need to build each architecture
792 separately then lipo them together into fat libraries.
793 """
794
795 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
796 # If we are building on a 10.4.x or earlier system,
797 # unilaterally disable assembly code building to avoid the problem.
798 no_asm = int(platform.release().split(".")[0]) < 9
799
800 def build_openssl_arch(archbase, arch):
801 "Build one architecture of openssl"
802 arch_opts = {
803 "i386": ["darwin-i386-cc"],
804 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
805 "ppc": ["darwin-ppc-cc"],
806 "ppc64": ["darwin64-ppc-cc"],
807 }
808 configure_opts = [
Ned Deily5d3febf2014-12-13 00:17:46 -0800809 "no-idea",
810 "no-mdc2",
811 "no-rc5",
812 "no-zlib",
Ned Deily5d3febf2014-12-13 00:17:46 -0800813 "no-ssl3",
Ned Deily5d3febf2014-12-13 00:17:46 -0800814 # "enable-unit-test",
815 "shared",
Ned Deily5d3febf2014-12-13 00:17:46 -0800816 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
Ned Deilydde4f632016-09-12 09:39:23 -0400817 "--openssldir=%s"%os.path.join("/", *FW_SSL_DIRECTORY),
Ned Deily5d3febf2014-12-13 00:17:46 -0800818 ]
819 if no_asm:
820 configure_opts.append("no-asm")
821 runCommand(" ".join(["perl", "Configure"]
822 + arch_opts[arch] + configure_opts))
Ned Deily8c9bb722018-01-30 07:42:14 -0500823 runCommand("make depend")
824 runCommand("make all")
825 runCommand("make install_sw DESTDIR=%s"%shellQuote(archbase))
Ned Deily5d3febf2014-12-13 00:17:46 -0800826 # runCommand("make test")
827 return
828
829 srcdir = os.getcwd()
830 universalbase = os.path.join(srcdir, "..",
831 os.path.basename(srcdir) + "-universal")
832 os.mkdir(universalbase)
833 archbasefws = []
834 for arch in archList:
835 # fresh copy of the source tree
836 archsrc = os.path.join(universalbase, arch, "src")
837 shutil.copytree(srcdir, archsrc, symlinks=True)
838 # install base for this arch
839 archbase = os.path.join(universalbase, arch, "root")
840 os.mkdir(archbase)
841 # Python framework base within install_prefix:
842 # the build will install into this framework..
843 # This is to ensure that the resulting shared libs have
844 # the desired real install paths built into them.
845 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
846
847 # build one architecture
848 os.chdir(archsrc)
849 build_openssl_arch(archbase, arch)
850 os.chdir(srcdir)
851 archbasefws.append(archbasefw)
852
853 # copy arch-independent files from last build into the basedir framework
854 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
855 shutil.copytree(
856 os.path.join(archbasefw, "include", "openssl"),
857 os.path.join(basefw, "include", "openssl")
858 )
859
860 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
861 "SHLIB_VERSION_NUMBER")
862 # e.g. -> "1.0.0"
863 libcrypto = "libcrypto.dylib"
864 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
865 # e.g. -> "libcrypto.1.0.0.dylib"
866 libssl = "libssl.dylib"
867 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
868 # e.g. -> "libssl.1.0.0.dylib"
869
870 try:
871 os.mkdir(os.path.join(basefw, "lib"))
872 except OSError:
873 pass
874
875 # merge the individual arch-dependent shared libs into a fat shared lib
876 archbasefws.insert(0, basefw)
877 for (lib_unversioned, lib_versioned) in [
878 (libcrypto, libcrypto_versioned),
879 (libssl, libssl_versioned)
880 ]:
881 runCommand("lipo -create -output " +
882 " ".join(shellQuote(
883 os.path.join(fw, "lib", lib_versioned))
884 for fw in archbasefws))
885 # and create an unversioned symlink of it
886 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
887
888 # Create links in the temp include and lib dirs that will be injected
889 # into the Python build so that setup.py can find them while building
890 # and the versioned links so that the setup.py post-build import test
891 # does not fail.
892 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
893 for fn in [
894 ["include", "openssl"],
895 ["lib", libcrypto],
896 ["lib", libssl],
897 ["lib", libcrypto_versioned],
898 ["lib", libssl_versioned],
899 ]:
900 os.symlink(
901 os.path.join(relative_path, *fn),
902 os.path.join(basedir, "usr", "local", *fn)
903 )
904
905 return
906
Thomas Wouters477c8d52006-05-27 19:21:47 +0000907def buildRecipe(recipe, basedir, archList):
908 """
909 Build software using a recipe. This function does the
910 'configure;make;make install' dance for C software, with a possibility
911 to customize this process, basically a poor-mans DarwinPorts.
912 """
913 curdir = os.getcwd()
914
915 name = recipe['name']
Ned Deily5d3febf2014-12-13 00:17:46 -0800916 THIRD_PARTY_LIBS.append(name)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000917 url = recipe['url']
918 configure = recipe.get('configure', './configure')
Ned Deily5d3febf2014-12-13 00:17:46 -0800919 buildrecipe = recipe.get('buildrecipe', None)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
921 shellQuote(basedir)))
922
923 archiveName = os.path.split(url)[-1]
924 sourceArchive = os.path.join(DEPSRC, archiveName)
925
926 if not os.path.exists(DEPSRC):
927 os.mkdir(DEPSRC)
928
Ned Deily4a96a372013-01-29 00:08:32 -0800929 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
930 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000931 buildDir=os.path.join(WORKDIR, '_bld')
932 if not os.path.exists(buildDir):
933 os.mkdir(buildDir)
934
935 workDir = extractArchive(buildDir, sourceArchive)
936 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000937
Ned Deily4a96a372013-01-29 00:08:32 -0800938 for patch in recipe.get('patches', ()):
939 if isinstance(patch, tuple):
940 url, checksum = patch
941 fn = os.path.join(DEPSRC, os.path.basename(url))
942 verifyThirdPartyFile(url, checksum, fn)
943 else:
944 # patch is a file in the source directory
945 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000946 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
947 shellQuote(fn),))
948
Ned Deily4a96a372013-01-29 00:08:32 -0800949 for patchscript in recipe.get('patchscripts', ()):
950 if isinstance(patchscript, tuple):
951 url, checksum = patchscript
952 fn = os.path.join(DEPSRC, os.path.basename(url))
953 verifyThirdPartyFile(url, checksum, fn)
954 else:
955 # patch is a file in the source directory
956 fn = os.path.join(curdir, patchscript)
957 if fn.endswith('.bz2'):
958 runCommand('bunzip2 -fk %s' % shellQuote(fn))
959 fn = fn[:-4]
960 runCommand('sh %s' % shellQuote(fn))
961 os.unlink(fn)
962
Ned Deily94764b22013-10-27 19:49:29 -0700963 if 'buildDir' in recipe:
964 os.chdir(recipe['buildDir'])
965
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000966 if configure is not None:
967 configure_args = [
968 "--prefix=/usr/local",
969 "--enable-static",
970 "--disable-shared",
971 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
972 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000973
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000974 if 'configure_pre' in recipe:
975 args = list(recipe['configure_pre'])
976 if '--disable-static' in args:
977 configure_args.remove('--enable-static')
978 if '--enable-shared' in args:
979 configure_args.remove('--disable-shared')
980 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000981
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000982 if recipe.get('useLDFlags', 1):
983 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -0500984 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -0800985 "-I%s/usr/local/include"%(
986 recipe.get('extra_cflags', ''),
987 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000988 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000989 shellQuote(basedir)[1:-1],),
Ned Deily8c9bb722018-01-30 07:42:14 -0500990 "LDFLAGS=-mmacosx-version-min=%s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800991 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000992 shellQuote(basedir)[1:-1],
993 ' -arch '.join(archList)),
994 ])
995 else:
996 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -0500997 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -0800998 "-I%s/usr/local/include"%(
999 recipe.get('extra_cflags', ''),
1000 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001001 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001002 shellQuote(basedir)[1:-1],),
1003 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001004
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001005 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -08001006 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001007
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001008 configure_args.insert(0, configure)
1009 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001010
Ned Deily4a96a372013-01-29 00:08:32 -08001011 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001012 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001013
Ned Deily5d3febf2014-12-13 00:17:46 -08001014 if buildrecipe is not None:
1015 # call special-case build recipe, e.g. for openssl
1016 buildrecipe(basedir, archList)
1017
1018 if install is not None:
1019 print("Running install for %s"%(name,))
1020 runCommand('{ ' + install + ' ;} 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001021
Ned Deily4a96a372013-01-29 00:08:32 -08001022 print("Done %s"%(name,))
1023 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001024
1025 os.chdir(curdir)
1026
1027def buildLibraries():
1028 """
1029 Build our dependencies into $WORKDIR/libraries/usr/local
1030 """
Ned Deily4a96a372013-01-29 00:08:32 -08001031 print("")
1032 print("Building required libraries")
1033 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001034 universal = os.path.join(WORKDIR, 'libraries')
1035 os.mkdir(universal)
1036 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1037 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1038
Ronald Oussoren1943f862009-03-30 19:39:14 +00001039 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001040 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001041
1042
1043
1044def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001045 # This stores the documentation as Resources/English.lproj/Documentation
Mike53f7a7c2017-12-14 14:04:53 +03001046 # inside the framework. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -08001047 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001048 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001049 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001050 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001051 curDir = os.getcwd()
1052 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -07001053 runCommand('make clean')
Ned Deily4c7532e2017-07-23 16:39:54 -04001054 # Create virtual environment for docs builds with blurb and sphinx
1055 runCommand('make venv')
1056 runCommand('make html PYTHON=venv/bin/python')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001057 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001058 if not os.path.exists(docdir):
1059 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001060 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001061
1062
1063def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -08001064 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001065
1066 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1067 rootDir = os.path.join(WORKDIR, '_root')
1068
1069 if os.path.exists(buildDir):
1070 shutil.rmtree(buildDir)
1071 if os.path.exists(rootDir):
1072 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +00001073 os.makedirs(buildDir)
1074 os.makedirs(rootDir)
1075 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076 curdir = os.getcwd()
1077 os.chdir(buildDir)
1078
Thomas Wouters477c8d52006-05-27 19:21:47 +00001079 # Extract the version from the configure file, needed to calculate
1080 # several paths.
1081 version = getVersion()
1082
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001083 # Since the extra libs are not in their installed framework location
1084 # during the build, augment the library path so that the interpreter
1085 # will find them during its extension import sanity checks.
1086 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1087 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -08001088 print("Running configure...")
Ned Deily8c9bb722018-01-30 07:42:14 -05001089 runCommand("%s -C --enable-framework --enable-universalsdk=/ "
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001090 "--with-universal-archs=%s "
1091 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -08001092 "%s "
Ned Deily7fdefac2018-01-30 17:29:53 -05001093 "%s "
1094 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +00001095 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -07001096 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ned Deily8c9bb722018-01-30 07:42:14 -05001097 shellQuote(os.path.join(SRCDIR, 'configure')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001098 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001099 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -08001100 (' ', '--without-ensurepip ')[PYTHON_3],
Ned Deily7fdefac2018-01-30 17:29:53 -05001101 (' ', "--with-tcltk-includes='-I%s/libraries/usr/local/include'"%(
1102 shellQuote(WORKDIR)[1:-1],))[internalTk()],
1103 (' ', "--with-tcltk-libs='-L%s/libraries/usr/local/lib -ltcl8.6 -ltk8.6'"%(
1104 shellQuote(WORKDIR)[1:-1],))[internalTk()],
Ronald Oussoren1943f862009-03-30 19:39:14 +00001105 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +00001106 shellQuote(WORKDIR)[1:-1]))
1107
Ned Deilyb364d9f2017-07-24 04:58:43 -04001108 # Look for environment value BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS
1109 # and, if defined, append its value to the make command. This allows
1110 # us to pass in version control tags, like GITTAG, to a build from a
1111 # tarball rather than from a vcs checkout, thus eliminating the need
1112 # to have a working copy of the vcs program on the build machine.
1113 #
1114 # A typical use might be:
1115 # export BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS=" \
1116 # GITVERSION='echo 123456789a' \
1117 # GITTAG='echo v3.6.0' \
1118 # GITBRANCH='echo 3.6'"
1119
1120 make_extras = os.getenv("BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS")
1121 if make_extras:
1122 make_cmd = "make " + make_extras
1123 else:
1124 make_cmd = "make"
1125 print("Running " + make_cmd)
1126 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001127
Ned Deily4a96a372013-01-29 00:08:32 -08001128 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001129 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001130 shellQuote(rootDir)))
1131
Ned Deily4a96a372013-01-29 00:08:32 -08001132 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001133 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1134 shellQuote(rootDir)))
1135
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001136 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001137 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001138 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
Ned Deily7fdefac2018-01-30 17:29:53 -05001139 build_lib_dir = os.path.join(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001140 WORKDIR, 'libraries', 'Library', 'Frameworks',
Ned Deily7fdefac2018-01-30 17:29:53 -05001141 'Python.framework', 'Versions', getVersion(), 'lib')
1142 fw_lib_dir = os.path.join(
1143 WORKDIR, '_root', 'Library', 'Frameworks',
1144 'Python.framework', 'Versions', getVersion(), 'lib')
1145 if internalTk():
1146 # move Tcl and Tk pkgconfig files
1147 runCommand("mv %s/pkgconfig/* %s/pkgconfig"%(
1148 shellQuote(build_lib_dir),
1149 shellQuote(fw_lib_dir) ))
1150 runCommand("rm -r %s/pkgconfig"%(
1151 shellQuote(build_lib_dir), ))
1152 runCommand("mv %s/* %s"%(
1153 shellQuote(build_lib_dir),
1154 shellQuote(fw_lib_dir) ))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001155
Ned Deilydde4f632016-09-12 09:39:23 -04001156 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1157 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1158 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1159 # create directory for OpenSSL certificates
1160 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1161 os.makedirs(sslDir)
Ned Deily050fcd52013-10-26 03:16:44 -07001162
Ned Deily4a96a372013-01-29 00:08:32 -08001163 print("Fix file modes")
Thomas Wouters89f507f2006-12-13 04:49:30 +00001164 gid = grp.getgrnam('admin').gr_gid
1165
Ned Deily4a96a372013-01-29 00:08:32 -08001166 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001167 for dirpath, dirnames, filenames in os.walk(frmDir):
1168 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001169 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001170 os.chown(os.path.join(dirpath, dn), -1, gid)
1171
Thomas Wouters477c8d52006-05-27 19:21:47 +00001172 for fn in filenames:
1173 if os.path.islink(fn):
1174 continue
1175
1176 # "chmod g+w $fn"
1177 p = os.path.join(dirpath, fn)
1178 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001179 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1180 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001181
Ned Deily4a96a372013-01-29 00:08:32 -08001182 if fn in EXPECTED_SHARED_LIBS:
1183 # check to see that this file was linked with the
1184 # expected library path and version
1185 data = captureCommand("otool -L %s" % shellQuote(p))
1186 for sl in EXPECTED_SHARED_LIBS[fn]:
1187 if ("\t%s " % sl) not in data:
1188 print("Expected shared lib %s was not linked with %s"
1189 % (sl, p))
1190 shared_lib_error = True
1191
1192 if shared_lib_error:
1193 fatal("Unexpected shared library errors.")
1194
Ned Deilye59e4c52011-01-29 18:56:28 +00001195 if PYTHON_3:
1196 LDVERSION=None
1197 VERSION=None
1198 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001199
Ned Deilye59e4c52011-01-29 18:56:28 +00001200 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001201 for ln in fp:
1202 if ln.startswith('VERSION='):
1203 VERSION=ln.split()[1]
1204 if ln.startswith('ABIFLAGS='):
1205 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001206 if ln.startswith('LDVERSION='):
1207 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001208 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001209
Ned Deilye59e4c52011-01-29 18:56:28 +00001210 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1211 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1212 config_suffix = '-' + LDVERSION
Ned Deily652bad42016-08-15 14:37:14 -04001213 if getVersionMajorMinor() >= (3, 6):
1214 config_suffix = config_suffix + '-darwin'
Ned Deilye59e4c52011-01-29 18:56:28 +00001215 else:
1216 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001217
Thomas Wouters477c8d52006-05-27 19:21:47 +00001218 # We added some directories to the search path during the configure
1219 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001220 # the end-users system. Also remove the directories from _sysconfigdata.py
1221 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001222
Ned Deilya4f6b002013-10-25 00:47:38 -07001223 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1224 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1225
Ned Deilya4f6b002013-10-25 00:47:38 -07001226 # fix Makefile
1227 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1228 fp = open(path, 'r')
1229 data = fp.read()
1230 fp.close()
1231
1232 for p in (include_path, lib_path):
1233 data = data.replace(" " + p, '')
1234 data = data.replace(p + " ", '')
1235
1236 fp = open(path, 'w')
1237 fp.write(data)
1238 fp.close()
1239
Ned Deily652bad42016-08-15 14:37:14 -04001240 # fix _sysconfigdata
Ned Deilya4f6b002013-10-25 00:47:38 -07001241 #
1242 # TODO: make this more robust! test_sysconfig_module of
1243 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1244 # the output from get_config_var in both sysconfig and
1245 # distutils.sysconfig is exactly the same for both CFLAGS and
1246 # LDFLAGS. The fixing up is now complicated by the pretty
1247 # printing in _sysconfigdata.py. Also, we are using the
1248 # pprint from the Python running the installer build which
1249 # may not cosmetically format the same as the pprint in the Python
1250 # being built (and which is used to originally generate
1251 # _sysconfigdata.py).
1252
1253 import pprint
Ned Deily652bad42016-08-15 14:37:14 -04001254 if getVersionMajorMinor() >= (3, 6):
Zachary Warec4b53af2016-09-09 17:59:49 -07001255 # XXX this is extra-fragile
1256 path = os.path.join(path_to_lib, '_sysconfigdata_m_darwin_darwin.py')
Ned Deily652bad42016-08-15 14:37:14 -04001257 else:
1258 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1259 fp = open(path, 'r')
1260 data = fp.read()
1261 fp.close()
1262 # create build_time_vars dict
1263 exec(data)
1264 vars = {}
1265 for k, v in build_time_vars.items():
1266 if type(v) == type(''):
1267 for p in (include_path, lib_path):
1268 v = v.replace(' ' + p, '')
1269 v = v.replace(p + ' ', '')
1270 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001271
Ned Deily652bad42016-08-15 14:37:14 -04001272 fp = open(path, 'w')
1273 # duplicated from sysconfig._generate_posix_vars()
1274 fp.write('# system configuration generated and used by'
1275 ' the sysconfig module\n')
1276 fp.write('build_time_vars = ')
1277 pprint.pprint(vars, stream=fp)
1278 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001279
1280 # Add symlinks in /usr/local/bin, using relative links
1281 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1282 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1283 'Python.framework', 'Versions', version, 'bin')
1284 if os.path.exists(usr_local_bin):
1285 shutil.rmtree(usr_local_bin)
1286 os.makedirs(usr_local_bin)
1287 for fn in os.listdir(
1288 os.path.join(frmDir, 'Versions', version, 'bin')):
1289 os.symlink(os.path.join(to_framework, fn),
1290 os.path.join(usr_local_bin, fn))
1291
1292 os.chdir(curdir)
1293
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001294 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001295 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001296 # with an already installed version of python 2
1297 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1298 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001299
1300def patchFile(inPath, outPath):
1301 data = fileContents(inPath)
1302 data = data.replace('$FULL_VERSION', getFullVersion())
1303 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001304 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001305 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001306 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001307 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001308
1309 # This one is not handy as a template variable
1310 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001311 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001312 fp.write(data)
1313 fp.close()
1314
1315def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001316 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001317 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001318 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001319 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001320 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001321 fp.write(data)
1322 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001323 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001324
1325
1326
1327def packageFromRecipe(targetDir, recipe):
1328 curdir = os.getcwd()
1329 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001330 # The major version (such as 2.5) is included in the package name
1331 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001332 # common.
1333 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001334 srcdir = recipe.get('source')
1335 pkgroot = recipe.get('topdir', srcdir)
1336 postflight = recipe.get('postflight')
1337 readme = textwrap.dedent(recipe['readme'])
1338 isRequired = recipe.get('required', True)
1339
Ned Deily4a96a372013-01-29 00:08:32 -08001340 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001341
1342 # Substitute some variables
1343 textvars = dict(
1344 VER=getVersion(),
1345 FULLVER=getFullVersion(),
1346 )
1347 readme = readme % textvars
1348
1349 if pkgroot is not None:
1350 pkgroot = pkgroot % textvars
1351 else:
1352 pkgroot = '/'
1353
1354 if srcdir is not None:
1355 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1356 srcdir = srcdir % textvars
1357
1358 if postflight is not None:
1359 postflight = os.path.abspath(postflight)
1360
1361 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1362 os.makedirs(packageContents)
1363
1364 if srcdir is not None:
1365 os.chdir(srcdir)
1366 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1367 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1368 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1369
1370 fn = os.path.join(packageContents, 'PkgInfo')
1371 fp = open(fn, 'w')
1372 fp.write('pmkrpkg1')
1373 fp.close()
1374
1375 rsrcDir = os.path.join(packageContents, "Resources")
1376 os.mkdir(rsrcDir)
1377 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1378 fp.write(readme)
1379 fp.close()
1380
1381 if postflight is not None:
1382 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1383
1384 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001385 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001386 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001387 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1388 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1389 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001390 CFBundleShortVersionString=vers,
1391 IFMajorVersion=major,
1392 IFMinorVersion=minor,
1393 IFPkgFormatVersion=0.10000000149011612,
1394 IFPkgFlagAllowBackRev=False,
1395 IFPkgFlagAuthorizationAction="RootAuthorization",
1396 IFPkgFlagDefaultLocation=pkgroot,
1397 IFPkgFlagFollowLinks=True,
1398 IFPkgFlagInstallFat=True,
1399 IFPkgFlagIsRequired=isRequired,
1400 IFPkgFlagOverwritePermissions=False,
1401 IFPkgFlagRelocatable=False,
1402 IFPkgFlagRestartAction="NoRestart",
1403 IFPkgFlagRootVolumeOnly=True,
1404 IFPkgFlagUpdateInstalledLangauges=False,
1405 )
1406 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1407
1408 pl = Plist(
1409 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001410 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411 IFPkgDescriptionVersion=vers,
1412 )
1413 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1414
1415 finally:
1416 os.chdir(curdir)
1417
1418
1419def makeMpkgPlist(path):
1420
1421 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001422 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001423
1424 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001425 CFBundleGetInfoString="Python %s"%(vers,),
1426 CFBundleIdentifier='org.python.Python',
1427 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001428 CFBundleShortVersionString=vers,
1429 IFMajorVersion=major,
1430 IFMinorVersion=minor,
1431 IFPkgFlagComponentDirectory="Contents/Packages",
1432 IFPkgFlagPackageList=[
1433 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001434 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001435 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001436 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001437 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001438 ],
1439 IFPkgFormatVersion=0.10000000149011612,
1440 IFPkgFlagBackgroundScaling="proportional",
1441 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001442 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001443 )
1444
1445 writePlist(pl, path)
1446
1447
1448def buildInstaller():
1449
1450 # Zap all compiled files
1451 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1452 for fn in filenames:
1453 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1454 os.unlink(os.path.join(dirpath, fn))
1455
1456 outdir = os.path.join(WORKDIR, 'installer')
1457 if os.path.exists(outdir):
1458 shutil.rmtree(outdir)
1459 os.mkdir(outdir)
1460
Ronald Oussoren1943f862009-03-30 19:39:14 +00001461 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001462 pkgcontents = os.path.join(pkgroot, 'Packages')
1463 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001464 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001465 packageFromRecipe(pkgcontents, recipe)
1466
1467 rsrcDir = os.path.join(pkgroot, 'Resources')
1468
1469 fn = os.path.join(pkgroot, 'PkgInfo')
1470 fp = open(fn, 'w')
1471 fp.write('pmkrpkg1')
1472 fp.close()
1473
1474 os.mkdir(rsrcDir)
1475
1476 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1477 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001478 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001479 IFPkgDescriptionVersion=getVersion(),
1480 )
1481
1482 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1483 for fn in os.listdir('resources'):
1484 if fn == '.svn': continue
1485 if fn.endswith('.jpg'):
1486 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1487 else:
1488 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1489
Thomas Wouters477c8d52006-05-27 19:21:47 +00001490
1491def installSize(clear=False, _saved=[]):
1492 if clear:
1493 del _saved[:]
1494 if not _saved:
1495 data = captureCommand("du -ks %s"%(
1496 shellQuote(os.path.join(WORKDIR, '_root'))))
1497 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1498 return _saved[0]
1499
1500
1501def buildDMG():
1502 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001503 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001504 """
1505 outdir = os.path.join(WORKDIR, 'diskimage')
1506 if os.path.exists(outdir):
1507 shutil.rmtree(outdir)
1508
1509 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001510 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001511 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001512 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001513 imagepath = imagepath + '.dmg'
1514
1515 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001516 volname='Python %s'%(getFullVersion())
1517 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1518 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001519 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001520 shellQuote(imagepath + ".tmp.dmg" )))
1521
1522
1523 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1524 os.mkdir(os.path.join(WORKDIR, "mnt"))
1525 runCommand("hdiutil attach %s -mountroot %s"%(
1526 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1527
1528 # Custom icon for the DMG, shown when the DMG is mounted.
1529 shutil.copy("../Icons/Disk Image.icns",
1530 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001531 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001532 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1533
1534 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1535
1536 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1537 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1538 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1539 setIcon(imagepath, "../Icons/Disk Image.icns")
1540
1541 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001542
1543 return imagepath
1544
1545
1546def setIcon(filePath, icnsPath):
1547 """
1548 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001549 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001550
Ronald Oussoren70050672010-04-30 15:00:26 +00001551 dirPath = os.path.normpath(os.path.dirname(__file__))
1552 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001553 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1554 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1555 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001556 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1557 if not os.path.exists(appPath):
1558 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001559 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1560 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001561
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001562 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1563 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001564
1565def main():
1566 # First parse options and check if we can perform our work
1567 parseOptions()
1568 checkEnvironment()
1569
Ronald Oussoren1943f862009-03-30 19:39:14 +00001570 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001571 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001572 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001573
1574 if os.path.exists(WORKDIR):
1575 shutil.rmtree(WORKDIR)
1576 os.mkdir(WORKDIR)
1577
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001578 os.environ['LC_ALL'] = 'C'
1579
Thomas Wouters477c8d52006-05-27 19:21:47 +00001580 # Then build third-party libraries such as sleepycat DB4.
1581 buildLibraries()
1582
1583 # Now build python itself
1584 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001585
1586 # And then build the documentation
1587 # Remove the Deployment Target from the shell
1588 # environment, it's no longer needed and
1589 # an unexpected build target can cause problems
1590 # when Sphinx and its dependencies need to
1591 # be (re-)installed.
1592 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001593 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001594
1595
1596 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001597 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001598 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001599 fn = os.path.join(folder, "License.rtf")
1600 patchFile("resources/License.rtf", fn)
1601 fn = os.path.join(folder, "ReadMe.rtf")
1602 patchFile("resources/ReadMe.rtf", fn)
1603 fn = os.path.join(folder, "Update Shell Profile.command")
1604 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilydde4f632016-09-12 09:39:23 -04001605 fn = os.path.join(folder, "Install Certificates.command")
1606 patchScript("resources/install_certificates.command", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001607 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001608 setIcon(folder, "../Icons/Python Folder.icns")
1609
1610 # Create the installer
1611 buildInstaller()
1612
1613 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001614 patchFile('resources/ReadMe.rtf',
1615 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001616
1617 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001618 patchFile('resources/License.rtf',
1619 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001620
1621 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001622 fp.write("# BUILD INFO\n")
1623 fp.write("# Date: %s\n" % time.ctime())
1624 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001625 fp.close()
1626
Thomas Wouters477c8d52006-05-27 19:21:47 +00001627 # And copy it to a DMG
1628 buildDMG()
1629
Thomas Wouters477c8d52006-05-27 19:21:47 +00001630if __name__ == "__main__":
1631 main()