blob: ff62096ec6cd4276a0ae18a7e50d277a061b074a [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
Ned Deily1931e642020-06-25 04:51:46 -04005NEW for 3.9.0 and backports:
6- 2.7 end-of-life issues:
7 - Python 3 installs now update the Current version link
8 in /Library/Frameworks/Python.framework/Versions
9- fully support running under Python 3 as well as 2.7
10- support building on newer macOS systems with SIP
11- fully support building on macOS 10.9+
12- support 10.6+ on best effort
13- support bypassing docs build by supplying a prebuilt
14 docs html tarball in the third-party source library,
15 in the format and filename conventional of those
16 downloadable from python.org:
17 python-3.x.y-docs-html.tar.bz2
18
Ned Deily8c9bb722018-01-30 07:42:14 -050019NEW for 3.7.0:
20- support Intel 64-bit-only () and 32-bit-only installer builds
Ned Deilyb9e7fe32018-03-29 08:47:27 -040021- build and use internal Tcl/Tk 8.6 for 10.6+ builds
Ned Deily8c9bb722018-01-30 07:42:14 -050022- deprecate use of explicit SDK (--sdk-path=) since all but the oldest
23 versions of Xcode support implicit setting of an SDK via environment
24 variables (SDKROOT and friends, see the xcrun man page for more info).
25 The SDK stuff was primarily needed for building universal installers
26 for 10.4; so as of 3.7.0, building installers for 10.4 is no longer
27 supported with build-installer.
28- use generic "gcc" as compiler (CC env var) rather than "gcc-4.2"
29
30TODO:
Ned Deily1931e642020-06-25 04:51:46 -040031- test building with SDKROOT and DEVELOPER_DIR xcrun env variables
Ned Deily4a96a372013-01-29 00:08:32 -080032
Thomas Wouters477c8d52006-05-27 19:21:47 +000033Usage: see USAGE variable in the script.
34"""
Ned Deily4a96a372013-01-29 00:08:32 -080035import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
36try:
37 import urllib2 as urllib_request
38except ImportError:
39 import urllib.request as urllib_request
40
41STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
42 | stat.S_IRGRP | stat.S_IXGRP
43 | stat.S_IROTH | stat.S_IXOTH )
44
45STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
46 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
47 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000048
Thomas Wouters89f507f2006-12-13 04:49:30 +000049INCLUDE_TIMESTAMP = 1
50VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000051
Ned Deily1931e642020-06-25 04:51:46 -040052RUNNING_ON_PYTHON2 = sys.version_info.major == 2
Thomas Wouters477c8d52006-05-27 19:21:47 +000053
Ned Deily1931e642020-06-25 04:51:46 -040054if RUNNING_ON_PYTHON2:
Thomas Wouters477c8d52006-05-27 19:21:47 +000055 from plistlib import writePlist
Ned Deily1931e642020-06-25 04:51:46 -040056else:
57 from plistlib import dump
58 def writePlist(path, plist):
59 with open(plist, 'wb') as fp:
60 dump(path, fp)
Thomas Wouters477c8d52006-05-27 19:21:47 +000061
62def shellQuote(value):
63 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000064 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000065 a shell command.
66 """
67 return "'%s'"%(value.replace("'", "'\"'\"'"))
68
69def grepValue(fn, variable):
Ned Deily5d3febf2014-12-13 00:17:46 -080070 """
71 Return the unquoted value of a variable from a file..
72 QUOTED_VALUE='quotes' -> str('quotes')
73 UNQUOTED_VALUE=noquotes -> str('noquotes')
74 """
Thomas Wouters477c8d52006-05-27 19:21:47 +000075 variable = variable + '='
76 for ln in open(fn, 'r'):
77 if ln.startswith(variable):
78 value = ln[len(variable):].strip()
Ned Deily5d3febf2014-12-13 00:17:46 -080079 return value.strip("\"'")
Ned Deily4a96a372013-01-29 00:08:32 -080080 raise RuntimeError("Cannot find variable %s" % variable[:-1])
81
82_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000083
84def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080085 global _cache_getVersion
86 if _cache_getVersion is None:
87 _cache_getVersion = grepValue(
88 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
89 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000090
Ned Deily4a96a372013-01-29 00:08:32 -080091def getVersionMajorMinor():
92 return tuple([int(n) for n in getVersion().split('.', 2)])
93
94_cache_getFullVersion = None
95
Thomas Wouters477c8d52006-05-27 19:21:47 +000096def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080097 global _cache_getFullVersion
98 if _cache_getFullVersion is not None:
99 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
101 for ln in open(fn):
102 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -0800103 _cache_getFullVersion = ln.split()[-1][1:-1]
104 return _cache_getFullVersion
105 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000106
Ned Deily5d3febf2014-12-13 00:17:46 -0800107FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
108FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
Ned Deilydde4f632016-09-12 09:39:23 -0400109FW_SSL_DIRECTORY = "--undefined--" # initialized in parseOptions
Ned Deily5d3febf2014-12-13 00:17:46 -0800110
Thomas Wouters89f507f2006-12-13 04:49:30 +0000111# The directory we'll use to create the build (will be erased and recreated)
112WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000113
Thomas Wouters89f507f2006-12-13 04:49:30 +0000114# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000115# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000116DEPSRC = os.path.join(WORKDIR, 'third-party')
117DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000118
Ronald Oussoren41761932020-11-08 10:05:27 +0100119universal_opts_map = { 'universal2': ('arm64', 'x86_64'),
120 '32-bit': ('i386', 'ppc',),
Ronald Oussoren1943f862009-03-30 19:39:14 +0000121 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000122 'intel': ('i386', 'x86_64'),
Ned Deily8c9bb722018-01-30 07:42:14 -0500123 'intel-32': ('i386',),
124 'intel-64': ('x86_64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 '3-way': ('ppc', 'i386', 'x86_64'),
126 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
127default_target_map = {
Ronald Oussoren41761932020-11-08 10:05:27 +0100128 'universal2': '10.9',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000129 '64-bit': '10.5',
130 '3-way': '10.5',
131 'intel': '10.5',
Ned Deily8c9bb722018-01-30 07:42:14 -0500132 'intel-32': '10.4',
133 'intel-64': '10.5',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000134 'all': '10.5',
135}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000136
137UNIVERSALOPTS = tuple(universal_opts_map.keys())
138
139UNIVERSALARCHS = '32-bit'
140
141ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142
Ezio Melotti42da6632011-03-15 05:18:48 +0200143# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000144SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000145 os.path.dirname(
146 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000147 os.path.abspath(__file__
148 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000149
Ronald Oussoren1943f862009-03-30 19:39:14 +0000150# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
Ned Deily8c9bb722018-01-30 07:42:14 -0500151DEPTARGET = '10.5'
Ronald Oussoren1943f862009-03-30 19:39:14 +0000152
Ned Deily04cdfa12014-06-25 13:36:14 -0700153def getDeptargetTuple():
154 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
155
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100156def getBuildTuple():
157 return tuple([int(n) for n in platform.mac_ver()[0].split('.')[0:2]])
158
Ned Deily04cdfa12014-06-25 13:36:14 -0700159def getTargetCompilers():
160 target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800161 '10.4': ('gcc-4.0', 'g++-4.0'),
Ned Deily8c9bb722018-01-30 07:42:14 -0500162 '10.5': ('gcc', 'g++'),
163 '10.6': ('gcc', 'g++'),
Ronald Oussoren90d52392021-05-03 03:29:03 +0200164 '10.7': ('gcc', 'g++'),
165 '10.8': ('gcc', 'g++'),
166 '10.9': ('gcc', 'g++'),
Ned Deily04cdfa12014-06-25 13:36:14 -0700167 }
Ronald Oussoren90d52392021-05-03 03:29:03 +0200168 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000169
Ned Deily04cdfa12014-06-25 13:36:14 -0700170CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000171
Ned Deily5d3febf2014-12-13 00:17:46 -0800172PYTHON_3 = getVersionMajorMinor() >= (3, 0)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000173
Thomas Wouters89f507f2006-12-13 04:49:30 +0000174USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000175 Usage: build_python [options]
176
177 Options:
178 -? or -h: Show this message
179 -b DIR
180 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
181 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
Ned Deily8c9bb722018-01-30 07:42:14 -0500182 --sdk-path=DIR: Location of the SDK (deprecated, use SDKROOT env variable)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000183 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ned Deily8c9bb722018-01-30 07:42:14 -0500184 --dep-target=10.n macOS deployment target (default: %(DEPTARGET)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000185 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000186""")% globals()
187
Ned Deily4a96a372013-01-29 00:08:32 -0800188# Dict of object file names with shared library names to check after building.
189# This is to ensure that we ended up dynamically linking with the shared
190# library paths and versions we expected. For example:
191# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
192# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
193# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
194EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000195
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500196# Are we building and linking with our own copy of Tcl/TK?
Ned Deilyb9e7fe32018-03-29 08:47:27 -0400197# For now, do so if deployment target is 10.6+.
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500198def internalTk():
Ned Deilyb9e7fe32018-03-29 08:47:27 -0400199 return getDeptargetTuple() >= (10, 6)
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500200
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100201# Do we use 8.6.8 when building our own copy
202# of Tcl/Tk or a modern version.
203# We use the old version when buildin on
204# old versions of macOS due to build issues.
205def useOldTk():
206 return getBuildTuple() < (10, 15)
207
Ronald Oussoren41761932020-11-08 10:05:27 +0100208
209def tweak_tcl_build(basedir, archList):
210 with open("Makefile", "r") as fp:
211 contents = fp.readlines()
212
213 # For reasons I don't understand the tcl configure script
214 # decides that some stdlib symbols aren't present, before
215 # deciding that strtod is broken.
216 new_contents = []
217 for line in contents:
218 if line.startswith("COMPAT_OBJS"):
219 # note: the space before strtod.o is intentional,
220 # the detection of a broken strtod results in
221 # "fixstrod.o" on this line.
222 for nm in ("strstr.o", "strtoul.o", " strtod.o"):
223 line = line.replace(nm, "")
224 new_contents.append(line)
225
226 with open("Makefile", "w") as fp:
227 fp.writelines(new_contents)
228
Ned Deily5d3febf2014-12-13 00:17:46 -0800229# List of names of third party software built with this installer.
230# The names will be inserted into the rtf version of the License.
231THIRD_PARTY_LIBS = []
232
Thomas Wouters477c8d52006-05-27 19:21:47 +0000233# Instructions for building libraries that are necessary for building a
234# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000235# [The recipes are defined here for convenience but instantiated later after
236# command line options have been processed.]
237def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000238 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000239
Ned Deily04cdfa12014-06-25 13:36:14 -0700240 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800241
Ned Deilydde4f632016-09-12 09:39:23 -0400242 # Since Apple removed the header files for the deprecated system
243 # OpenSSL as of the Xcode 7 release (for OS X 10.10+), we do not
244 # have much choice but to build our own copy here, too.
Ned Deily5d3febf2014-12-13 00:17:46 -0800245
Ned Deilydde4f632016-09-12 09:39:23 -0400246 result.extend([
Ned Deily5d3febf2014-12-13 00:17:46 -0800247 dict(
Christian Heimesa54fc682021-03-30 02:00:34 +0200248 name="OpenSSL 1.1.1k",
249 url="https://www.openssl.org/source/openssl-1.1.1k.tar.gz",
250 checksum='c4e7d95f782b08116afa27b30393dd27',
Ned Deily5d3febf2014-12-13 00:17:46 -0800251 buildrecipe=build_universal_openssl,
252 configure=None,
253 install=None,
254 ),
Ned Deilydde4f632016-09-12 09:39:23 -0400255 ])
Ned Deily5d3febf2014-12-13 00:17:46 -0800256
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500257 if internalTk():
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100258 if useOldTk():
259 tcl_tk_ver='8.6.8'
260 tcl_checksum='81656d3367af032e0ae6157eff134f89'
261
262 tk_checksum='5e0faecba458ee1386078fb228d008ba'
263 tk_patches = ['tk868_on_10_8_10_9.patch']
264
265 else:
Ned Deilya38e04b2021-01-04 04:43:11 -0500266 tcl_tk_ver='8.6.11'
267 tcl_checksum='8a4c004f48984a03a7747e9ba06e4da4'
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100268
Ned Deilya38e04b2021-01-04 04:43:11 -0500269 tk_checksum='c7ee71a2d05bba78dfffd76528dc17c6'
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100270 tk_patches = [ ]
271
272
Ned Deily5b3582c2013-10-25 00:41:46 -0700273 result.extend([
274 dict(
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100275 name="Tcl %s"%(tcl_tk_ver,),
276 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tcl%s-src.tar.gz"%(tcl_tk_ver,),
277 checksum=tcl_checksum,
Ned Deily5b3582c2013-10-25 00:41:46 -0700278 buildDir="unix",
279 configure_pre=[
280 '--enable-shared',
281 '--enable-threads',
282 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
283 ],
284 useLDFlags=False,
Ronald Oussoren41761932020-11-08 10:05:27 +0100285 buildrecipe=tweak_tcl_build,
Ned Deily5b3582c2013-10-25 00:41:46 -0700286 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
287 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500288 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
Ned Deily5b3582c2013-10-25 00:41:46 -0700289 },
290 ),
291 dict(
Ronald Oussoren690a5fa2020-11-22 03:05:34 +0100292 name="Tk %s"%(tcl_tk_ver,),
293 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tk%s-src.tar.gz"%(tcl_tk_ver,),
294 checksum=tk_checksum,
295 patches=tk_patches,
Ned Deily5b3582c2013-10-25 00:41:46 -0700296 buildDir="unix",
297 configure_pre=[
298 '--enable-aqua',
299 '--enable-shared',
300 '--enable-threads',
301 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
302 ],
303 useLDFlags=False,
304 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'%{
305 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500306 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
307 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.6'%(getVersion())),
Ned Deily5b3582c2013-10-25 00:41:46 -0700308 },
309 ),
310 ])
311
Ned Deilyed730102014-11-14 18:55:05 -0800312 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800313 result.extend([
314 dict(
Ned Deilye6f8a732017-12-04 22:55:20 -0500315 name="XZ 5.2.3",
316 url="http://tukaani.org/xz/xz-5.2.3.tar.gz",
317 checksum='ef68674fb47a8b8e741b34e429d86e9d',
Ned Deily4a96a372013-01-29 00:08:32 -0800318 configure_pre=[
319 '--disable-dependency-tracking',
320 ]
321 ),
322 ])
323
324 result.extend([
325 dict(
326 name="NCurses 5.9",
327 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
328 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
329 configure_pre=[
330 "--enable-widec",
331 "--without-cxx",
332 "--without-cxx-binding",
333 "--without-ada",
334 "--without-curses-h",
335 "--enable-shared",
336 "--with-shared",
337 "--without-debug",
338 "--without-normal",
339 "--without-tests",
340 "--without-manpages",
341 "--datadir=/usr/share",
342 "--sysconfdir=/etc",
343 "--sharedstatedir=/usr/com",
344 "--with-terminfo-dirs=/usr/share/terminfo",
345 "--with-default-terminfo-dir=/usr/share/terminfo",
346 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
347 ],
348 patchscripts=[
Oleg Höfling7da46b62020-05-27 12:07:15 +0200349 ("ftp://ftp.invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
Ned Deily4a96a372013-01-29 00:08:32 -0800350 "f54bf02a349f96a7c4f0d00922f3a0d4"),
351 ],
352 useLDFlags=False,
353 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
354 shellQuote(os.path.join(WORKDIR, 'libraries')),
355 shellQuote(os.path.join(WORKDIR, 'libraries')),
356 getVersion(),
357 ),
358 ),
359 dict(
Erlend Egeberg Aaslandce827812021-04-27 19:19:14 +0200360 name="SQLite 3.35.5",
361 url="https://sqlite.org/2021/sqlite-autoconf-3350500.tar.gz",
362 checksum='d1d1aba394c8e0443077dc9f1a681bb8',
Ned Deily4a96a372013-01-29 00:08:32 -0800363 extra_cflags=('-Os '
Ned Deilyb3b07672016-09-05 17:31:14 -0700364 '-DSQLITE_ENABLE_FTS5 '
Ned Deily4a96a372013-01-29 00:08:32 -0800365 '-DSQLITE_ENABLE_FTS4 '
366 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
Ned Deily9625bf52017-12-04 21:50:29 -0500367 '-DSQLITE_ENABLE_JSON1 '
Ned Deily4a96a372013-01-29 00:08:32 -0800368 '-DSQLITE_ENABLE_RTREE '
Erlend Egeberg Aasland555cbbe2021-05-03 02:37:35 +0200369 '-DSQLITE_OMIT_AUTOINIT '
Ned Deily4a96a372013-01-29 00:08:32 -0800370 '-DSQLITE_TCL=0 '
371 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
372 configure_pre=[
373 '--enable-threadsafe',
374 '--enable-shared=no',
375 '--enable-static=yes',
376 '--disable-readline',
377 '--disable-dependency-tracking',
378 ]
379 ),
380 ])
381
Ned Deily04cdfa12014-06-25 13:36:14 -0700382 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000383 result.extend([
384 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000385 name="Bzip2 1.0.6",
386 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
387 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000388 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500389 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800390 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000391 shellQuote(os.path.join(WORKDIR, 'libraries')),
392 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000393 ),
394 ),
395 dict(
396 name="ZLib 1.2.3",
397 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
398 checksum='debc62758716a169df9f62e6ab2bc634',
399 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500400 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800401 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000402 shellQuote(os.path.join(WORKDIR, 'libraries')),
403 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000404 ),
405 ),
406 dict(
407 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000408 name="GNU Readline 6.1.2",
409 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
410 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000411 patchlevel='0',
412 patches=[
413 # The readline maintainers don't do actual micro releases, but
414 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800415 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
416 'c642f2e84d820884b0bf9fd176bc6c3f'),
417 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
418 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000419 ]
420 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000421 ])
422
Ned Deily4f7ff782011-01-15 05:29:12 +0000423 if not PYTHON_3:
424 result.extend([
425 dict(
426 name="Sleepycat DB 4.7.25",
427 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
428 checksum='ec2b87e833779681a0c3a814aa71359e',
429 buildDir="build_unix",
430 configure="../dist/configure",
431 configure_pre=[
432 '--includedir=/usr/local/include/db4',
433 ]
434 ),
435 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000436
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000437 return result
438
Ronald Oussorene08059e2021-05-03 05:43:52 +0200439def compilerCanOptimize():
440 """
441 Return True iff the default Xcode version can use PGO and LTO
442 """
443 # bpo-42235: The version check is pretty conservative, can be
444 # adjusted after testing
445 mac_ver = tuple(map(int, platform.mac_ver()[0].split('.')))
446 return mac_ver >= (10, 15)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000447
Thomas Wouters477c8d52006-05-27 19:21:47 +0000448# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000449def pkg_recipes():
450 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
451 result = [
452 dict(
453 name="PythonFramework",
454 long_name="Python Framework",
455 source="/Library/Frameworks/Python.framework",
456 readme="""\
457 This package installs Python.framework, that is the python
Ned Deily8c9bb722018-01-30 07:42:14 -0500458 interpreter and the standard library.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000459 """,
460 postflight="scripts/postflight.framework",
461 selected='selected',
462 ),
463 dict(
464 name="PythonApplications",
465 long_name="GUI Applications",
466 source="/Applications/Python %(VER)s",
467 readme="""\
468 This package installs IDLE (an interactive Python IDE),
469 Python Launcher and Build Applet (create application bundles
470 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000471
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000472 It also installs a number of examples and demos.
473 """,
474 required=False,
475 selected='selected',
476 ),
477 dict(
478 name="PythonUnixTools",
479 long_name="UNIX command-line tools",
480 source="/usr/local/bin",
481 readme="""\
482 This package installs the unix tools in /usr/local/bin for
483 compatibility with older releases of Python. This package
484 is not necessary to use Python.
485 """,
486 required=False,
487 selected='selected',
488 ),
489 dict(
490 name="PythonDocumentation",
491 long_name="Python Documentation",
492 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
493 source="/pydocs",
494 readme="""\
495 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800496 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000497 """,
498 postflight="scripts/postflight.documentation",
499 required=False,
500 selected='selected',
501 ),
502 dict(
503 name="PythonProfileChanges",
504 long_name="Shell profile updater",
505 readme="""\
506 This packages updates your shell profile to make sure that
507 the Python tools are found by your shell in preference of
508 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000509
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000510 If you don't install this package you'll have to add
511 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
512 to your PATH by hand.
513 """,
514 postflight="scripts/postflight.patch-profile",
515 topdir="/Library/Frameworks/Python.framework",
516 source="/empty-dir",
517 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800518 selected='selected',
519 ),
520 dict(
521 name="PythonInstallPip",
522 long_name="Install or upgrade pip",
523 readme="""\
524 This package installs (or upgrades from an earlier version)
525 pip, a tool for installing and managing Python packages.
526 """,
527 postflight="scripts/postflight.ensurepip",
528 topdir="/Library/Frameworks/Python.framework",
529 source="/empty-dir",
530 required=False,
531 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000532 ),
533 ]
534
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000535 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000536
Thomas Wouters477c8d52006-05-27 19:21:47 +0000537def fatal(msg):
538 """
539 A fatal error, bail out.
540 """
541 sys.stderr.write('FATAL: ')
542 sys.stderr.write(msg)
543 sys.stderr.write('\n')
544 sys.exit(1)
545
546def fileContents(fn):
547 """
548 Return the contents of the named file
549 """
Ned Deily4a96a372013-01-29 00:08:32 -0800550 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000551
552def runCommand(commandline):
553 """
Ezio Melotti13925002011-03-16 11:05:33 +0200554 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555 unless the command fails.
556 """
557 fd = os.popen(commandline, 'r')
558 data = fd.read()
559 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000560 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000561 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800562 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000563
564 if VERBOSE:
565 sys.stdout.write(data); sys.stdout.flush()
566
567def captureCommand(commandline):
568 fd = os.popen(commandline, 'r')
569 data = fd.read()
570 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000571 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000572 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800573 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000574
575 return data
576
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000577def getTclTkVersion(configfile, versionline):
578 """
579 search Tcl or Tk configuration file for version line
580 """
581 try:
582 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300583 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000584 fatal("Framework configuration file not found: %s" % configfile)
585
586 for l in f:
587 if l.startswith(versionline):
588 f.close()
589 return l
590
591 fatal("Version variable %s not found in framework configuration file: %s"
592 % (versionline, configfile))
593
Thomas Wouters477c8d52006-05-27 19:21:47 +0000594def checkEnvironment():
595 """
596 Check that we're running on a supported system.
597 """
598
Ned Deily936533c2020-11-23 19:04:40 -0500599 if sys.version_info[0:2] < (2, 7):
600 fatal("This script must be run with Python 2.7 (or later)")
Ned Deilye59e4c52011-01-29 18:56:28 +0000601
Thomas Wouters477c8d52006-05-27 19:21:47 +0000602 if platform.system() != 'Darwin':
Ned Deily8c9bb722018-01-30 07:42:14 -0500603 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000604
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000605 if int(platform.release().split('.')[0]) < 8:
Ned Deily8c9bb722018-01-30 07:42:14 -0500606 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000607
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000608 # Because we only support dynamic load of only one major/minor version of
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500609 # Tcl/Tk, if we are not using building and using our own private copy of
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000610 # Tcl/Tk, ensure:
Ned Deily8c9bb722018-01-30 07:42:14 -0500611 # 1. there is a user-installed framework (usually ActiveTcl) in (or linked
612 # in) SDKROOT/Library/Frameworks. As of Python 3.7.0, we no longer
613 # enforce that the version of the user-installed framework also
614 # exists in the system-supplied Tcl/Tk frameworks. Time to support
615 # Tcl/Tk 8.6 even if Apple does not.
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500616 if not internalTk():
617 frameworks = {}
618 for framework in ['Tcl', 'Tk']:
619 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
620 libfw = os.path.join('/', fwpth)
621 usrfw = os.path.join(os.getenv('HOME'), fwpth)
622 frameworks[framework] = os.readlink(libfw)
623 if not os.path.exists(libfw):
624 fatal("Please install a link to a current %s %s as %s so "
625 "the user can override the system framework."
626 % (framework, frameworks[framework], libfw))
627 if os.path.exists(usrfw):
628 fatal("Please rename %s to avoid possible dynamic load issues."
629 % usrfw)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000630
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500631 if frameworks['Tcl'] != frameworks['Tk']:
632 fatal("The Tcl and Tk frameworks are not the same version.")
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000633
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500634 print(" -- Building with external Tcl/Tk %s frameworks"
635 % frameworks['Tk'])
Ned Deily4a96a372013-01-29 00:08:32 -0800636
Ned Deily1ca2ffd2018-01-30 17:29:53 -0500637 # add files to check after build
638 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
639 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
640 % frameworks['Tcl'],
641 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
642 % frameworks['Tk'],
643 ]
644 else:
645 print(" -- Building private copy of Tcl/Tk")
Ned Deily8c9bb722018-01-30 07:42:14 -0500646 print("")
647
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000648 # Remove inherited environment variables which might influence build
649 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
650 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
651 for ev in list(os.environ):
652 for prefix in environ_var_prefixes:
653 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800654 print("INFO: deleting environment variable %s=%s" % (
655 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000656 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000657
Ned Deily4a96a372013-01-29 00:08:32 -0800658 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
659 if 'SDK_TOOLS_BIN' in os.environ:
660 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
661 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
662 # add its fixed location here if it exists
663 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
664 if os.path.isdir(OLD_DEVELOPER_TOOLS):
665 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
666 os.environ['PATH'] = base_path
667 print("Setting default PATH: %s"%(os.environ['PATH']))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000668
Thomas Wouters89f507f2006-12-13 04:49:30 +0000669def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000670 """
671 Parse arguments and update global settings.
672 """
Ned Deily8c9bb722018-01-30 07:42:14 -0500673 global WORKDIR, DEPSRC, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800674 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily5d3febf2014-12-13 00:17:46 -0800675 global FW_VERSION_PREFIX
Ned Deilydde4f632016-09-12 09:39:23 -0400676 global FW_SSL_DIRECTORY
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677
678 if args is None:
679 args = sys.argv[1:]
680
681 try:
682 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000683 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
684 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800685 except getopt.GetoptError:
686 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000687 sys.exit(1)
688
689 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800690 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000691 sys.exit(1)
692
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000693 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000694 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000695 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800696 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000697 sys.exit(0)
698
699 elif k in ('-d', '--build-dir'):
700 WORKDIR=v
701
702 elif k in ('--third-party',):
703 DEPSRC=v
704
705 elif k in ('--sdk-path',):
Ned Deily8c9bb722018-01-30 07:42:14 -0500706 print(" WARNING: --sdk-path is no longer supported")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000707
708 elif k in ('--src-dir',):
709 SRCDIR=v
710
Ronald Oussoren1943f862009-03-30 19:39:14 +0000711 elif k in ('--dep-target', ):
712 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000713 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000714
715 elif k in ('--universal-archs', ):
716 if v in UNIVERSALOPTS:
717 UNIVERSALARCHS = v
718 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000719 if deptarget is None:
720 # Select alternate default deployment
721 # target
Ned Deily8c9bb722018-01-30 07:42:14 -0500722 DEPTARGET = default_target_map.get(v, '10.5')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000723 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800724 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000725
Thomas Wouters477c8d52006-05-27 19:21:47 +0000726 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800727 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000728
729 SRCDIR=os.path.abspath(SRCDIR)
730 WORKDIR=os.path.abspath(WORKDIR)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000731 DEPSRC=os.path.abspath(DEPSRC)
732
Ned Deily04cdfa12014-06-25 13:36:14 -0700733 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000734
Ned Deily5d3febf2014-12-13 00:17:46 -0800735 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
Ned Deilydde4f632016-09-12 09:39:23 -0400736 FW_SSL_DIRECTORY = FW_VERSION_PREFIX[:] + ["etc", "openssl"]
Ned Deily5d3febf2014-12-13 00:17:46 -0800737
738 print("-- Settings:")
739 print(" * Source directory: %s" % SRCDIR)
740 print(" * Build directory: %s" % WORKDIR)
Ned Deily5d3febf2014-12-13 00:17:46 -0800741 print(" * Third-party source: %s" % DEPSRC)
742 print(" * Deployment target: %s" % DEPTARGET)
743 print(" * Universal archs: %s" % str(ARCHLIST))
744 print(" * C compiler: %s" % CC)
745 print(" * C++ compiler: %s" % CXX)
Ned Deily4a96a372013-01-29 00:08:32 -0800746 print("")
Ned Deily5d3febf2014-12-13 00:17:46 -0800747 print(" -- Building a Python %s framework at patch level %s"
748 % (getVersion(), getFullVersion()))
749 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000750
751def extractArchive(builddir, archiveName):
752 """
753 Extract a source archive into 'builddir'. Returns the path of the
754 extracted archive.
755
756 XXX: This function assumes that archives contain a toplevel directory
757 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700758 safe enough for almost anything we use. Unfortunately, it does not
759 work for current Tcl and Tk source releases where the basename of
760 the archive ends with "-src" but the uncompressed directory does not.
761 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000762 """
763 curdir = os.getcwd()
764 try:
765 os.chdir(builddir)
766 if archiveName.endswith('.tar.gz'):
767 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700768 if ((retval.startswith('tcl') or retval.startswith('tk'))
769 and retval.endswith('-src')):
770 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000771 if os.path.exists(retval):
772 shutil.rmtree(retval)
773 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
774
775 elif archiveName.endswith('.tar.bz2'):
776 retval = os.path.basename(archiveName[:-8])
777 if os.path.exists(retval):
778 shutil.rmtree(retval)
779 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
780
781 elif archiveName.endswith('.tar'):
782 retval = os.path.basename(archiveName[:-4])
783 if os.path.exists(retval):
784 shutil.rmtree(retval)
785 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
786
787 elif archiveName.endswith('.zip'):
788 retval = os.path.basename(archiveName[:-4])
789 if os.path.exists(retval):
790 shutil.rmtree(retval)
791 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
792
793 data = fp.read()
794 xit = fp.close()
795 if xit is not None:
796 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800797 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000798
799 return os.path.join(builddir, retval)
800
801 finally:
802 os.chdir(curdir)
803
Thomas Wouters477c8d52006-05-27 19:21:47 +0000804def downloadURL(url, fname):
805 """
806 Download the contents of the url into the file.
807 """
Ned Deily4a96a372013-01-29 00:08:32 -0800808 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 fpOut = open(fname, 'wb')
810 block = fpIn.read(10240)
811 try:
812 while block:
813 fpOut.write(block)
814 block = fpIn.read(10240)
815 fpIn.close()
816 fpOut.close()
817 except:
818 try:
819 os.unlink(fname)
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300820 except OSError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000821 pass
822
Ned Deily4a96a372013-01-29 00:08:32 -0800823def verifyThirdPartyFile(url, checksum, fname):
824 """
825 Download file from url to filename fname if it does not already exist.
826 Abort if file contents does not match supplied md5 checksum.
827 """
828 name = os.path.basename(fname)
829 if os.path.exists(fname):
830 print("Using local copy of %s"%(name,))
831 else:
832 print("Did not find local copy of %s"%(name,))
833 print("Downloading %s"%(name,))
834 downloadURL(url, fname)
835 print("Archive for %s stored as %s"%(name, fname))
836 if os.system(
837 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
838 % (shellQuote(fname), checksum) ):
839 fatal('MD5 checksum mismatch for file %s' % fname)
840
Ned Deily5d3febf2014-12-13 00:17:46 -0800841def build_universal_openssl(basedir, archList):
842 """
843 Special case build recipe for universal build of openssl.
844
845 The upstream OpenSSL build system does not directly support
846 OS X universal builds. We need to build each architecture
847 separately then lipo them together into fat libraries.
848 """
849
850 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
851 # If we are building on a 10.4.x or earlier system,
852 # unilaterally disable assembly code building to avoid the problem.
853 no_asm = int(platform.release().split(".")[0]) < 9
854
855 def build_openssl_arch(archbase, arch):
856 "Build one architecture of openssl"
857 arch_opts = {
858 "i386": ["darwin-i386-cc"],
859 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
Ronald Oussoren41761932020-11-08 10:05:27 +0100860 "arm64": ["darwin64-arm64-cc"],
Ned Deily5d3febf2014-12-13 00:17:46 -0800861 "ppc": ["darwin-ppc-cc"],
862 "ppc64": ["darwin64-ppc-cc"],
863 }
Ned Deilyf3fb8392019-06-18 04:17:33 -0400864
865 # Somewhere between OpenSSL 1.1.0j and 1.1.1c, changes cause the
866 # "enable-ec_nistp_64_gcc_128" option to get compile errors when
867 # building on our 10.6 gcc-4.2 environment. There have been other
868 # reports of projects running into this when using older compilers.
869 # So, for now, do not try to use "enable-ec_nistp_64_gcc_128" when
870 # building for 10.6.
871 if getDeptargetTuple() == (10, 6):
872 arch_opts['x86_64'].remove('enable-ec_nistp_64_gcc_128')
873
Ned Deily5d3febf2014-12-13 00:17:46 -0800874 configure_opts = [
Ned Deily5d3febf2014-12-13 00:17:46 -0800875 "no-idea",
876 "no-mdc2",
877 "no-rc5",
878 "no-zlib",
Ned Deily5d3febf2014-12-13 00:17:46 -0800879 "no-ssl3",
Ned Deily5d3febf2014-12-13 00:17:46 -0800880 # "enable-unit-test",
881 "shared",
Ned Deily5d3febf2014-12-13 00:17:46 -0800882 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
Ned Deilydde4f632016-09-12 09:39:23 -0400883 "--openssldir=%s"%os.path.join("/", *FW_SSL_DIRECTORY),
Ned Deily5d3febf2014-12-13 00:17:46 -0800884 ]
885 if no_asm:
886 configure_opts.append("no-asm")
887 runCommand(" ".join(["perl", "Configure"]
888 + arch_opts[arch] + configure_opts))
Ned Deily8c9bb722018-01-30 07:42:14 -0500889 runCommand("make depend")
890 runCommand("make all")
891 runCommand("make install_sw DESTDIR=%s"%shellQuote(archbase))
Ned Deily5d3febf2014-12-13 00:17:46 -0800892 # runCommand("make test")
893 return
894
895 srcdir = os.getcwd()
896 universalbase = os.path.join(srcdir, "..",
897 os.path.basename(srcdir) + "-universal")
898 os.mkdir(universalbase)
899 archbasefws = []
900 for arch in archList:
901 # fresh copy of the source tree
902 archsrc = os.path.join(universalbase, arch, "src")
903 shutil.copytree(srcdir, archsrc, symlinks=True)
904 # install base for this arch
905 archbase = os.path.join(universalbase, arch, "root")
906 os.mkdir(archbase)
907 # Python framework base within install_prefix:
908 # the build will install into this framework..
909 # This is to ensure that the resulting shared libs have
910 # the desired real install paths built into them.
911 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
912
913 # build one architecture
914 os.chdir(archsrc)
915 build_openssl_arch(archbase, arch)
916 os.chdir(srcdir)
917 archbasefws.append(archbasefw)
918
919 # copy arch-independent files from last build into the basedir framework
920 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
921 shutil.copytree(
922 os.path.join(archbasefw, "include", "openssl"),
923 os.path.join(basefw, "include", "openssl")
924 )
925
926 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
927 "SHLIB_VERSION_NUMBER")
928 # e.g. -> "1.0.0"
929 libcrypto = "libcrypto.dylib"
930 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
931 # e.g. -> "libcrypto.1.0.0.dylib"
932 libssl = "libssl.dylib"
933 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
934 # e.g. -> "libssl.1.0.0.dylib"
935
936 try:
937 os.mkdir(os.path.join(basefw, "lib"))
938 except OSError:
939 pass
940
941 # merge the individual arch-dependent shared libs into a fat shared lib
942 archbasefws.insert(0, basefw)
943 for (lib_unversioned, lib_versioned) in [
944 (libcrypto, libcrypto_versioned),
945 (libssl, libssl_versioned)
946 ]:
947 runCommand("lipo -create -output " +
948 " ".join(shellQuote(
949 os.path.join(fw, "lib", lib_versioned))
950 for fw in archbasefws))
951 # and create an unversioned symlink of it
952 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
953
954 # Create links in the temp include and lib dirs that will be injected
955 # into the Python build so that setup.py can find them while building
956 # and the versioned links so that the setup.py post-build import test
957 # does not fail.
958 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
959 for fn in [
960 ["include", "openssl"],
961 ["lib", libcrypto],
962 ["lib", libssl],
963 ["lib", libcrypto_versioned],
964 ["lib", libssl_versioned],
965 ]:
966 os.symlink(
967 os.path.join(relative_path, *fn),
968 os.path.join(basedir, "usr", "local", *fn)
969 )
970
971 return
972
Thomas Wouters477c8d52006-05-27 19:21:47 +0000973def buildRecipe(recipe, basedir, archList):
974 """
975 Build software using a recipe. This function does the
976 'configure;make;make install' dance for C software, with a possibility
977 to customize this process, basically a poor-mans DarwinPorts.
978 """
979 curdir = os.getcwd()
980
981 name = recipe['name']
Ned Deily5d3febf2014-12-13 00:17:46 -0800982 THIRD_PARTY_LIBS.append(name)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000983 url = recipe['url']
984 configure = recipe.get('configure', './configure')
Ned Deily5d3febf2014-12-13 00:17:46 -0800985 buildrecipe = recipe.get('buildrecipe', None)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000986 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
987 shellQuote(basedir)))
988
989 archiveName = os.path.split(url)[-1]
990 sourceArchive = os.path.join(DEPSRC, archiveName)
991
992 if not os.path.exists(DEPSRC):
993 os.mkdir(DEPSRC)
994
Ned Deily4a96a372013-01-29 00:08:32 -0800995 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
996 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000997 buildDir=os.path.join(WORKDIR, '_bld')
998 if not os.path.exists(buildDir):
999 os.mkdir(buildDir)
1000
1001 workDir = extractArchive(buildDir, sourceArchive)
1002 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001003
Ned Deily4a96a372013-01-29 00:08:32 -08001004 for patch in recipe.get('patches', ()):
1005 if isinstance(patch, tuple):
1006 url, checksum = patch
1007 fn = os.path.join(DEPSRC, os.path.basename(url))
1008 verifyThirdPartyFile(url, checksum, fn)
1009 else:
1010 # patch is a file in the source directory
1011 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001012 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
1013 shellQuote(fn),))
1014
Ned Deily4a96a372013-01-29 00:08:32 -08001015 for patchscript in recipe.get('patchscripts', ()):
1016 if isinstance(patchscript, tuple):
1017 url, checksum = patchscript
1018 fn = os.path.join(DEPSRC, os.path.basename(url))
1019 verifyThirdPartyFile(url, checksum, fn)
1020 else:
1021 # patch is a file in the source directory
1022 fn = os.path.join(curdir, patchscript)
1023 if fn.endswith('.bz2'):
1024 runCommand('bunzip2 -fk %s' % shellQuote(fn))
1025 fn = fn[:-4]
1026 runCommand('sh %s' % shellQuote(fn))
1027 os.unlink(fn)
1028
Ned Deily94764b22013-10-27 19:49:29 -07001029 if 'buildDir' in recipe:
1030 os.chdir(recipe['buildDir'])
1031
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001032 if configure is not None:
1033 configure_args = [
1034 "--prefix=/usr/local",
1035 "--enable-static",
1036 "--disable-shared",
1037 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
1038 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001039
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001040 if 'configure_pre' in recipe:
1041 args = list(recipe['configure_pre'])
1042 if '--disable-static' in args:
1043 configure_args.remove('--enable-static')
1044 if '--enable-shared' in args:
1045 configure_args.remove('--disable-shared')
1046 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001047
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001048 if recipe.get('useLDFlags', 1):
1049 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -05001050 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -08001051 "-I%s/usr/local/include"%(
1052 recipe.get('extra_cflags', ''),
1053 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001054 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001055 shellQuote(basedir)[1:-1],),
Ned Deily8c9bb722018-01-30 07:42:14 -05001056 "LDFLAGS=-mmacosx-version-min=%s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -08001057 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001058 shellQuote(basedir)[1:-1],
1059 ' -arch '.join(archList)),
1060 ])
1061 else:
1062 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -05001063 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -08001064 "-I%s/usr/local/include"%(
1065 recipe.get('extra_cflags', ''),
1066 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001067 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001068 shellQuote(basedir)[1:-1],),
1069 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001070
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001071 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -08001072 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001074 configure_args.insert(0, configure)
1075 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076
Ned Deily4a96a372013-01-29 00:08:32 -08001077 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001078 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001079
Ned Deily5d3febf2014-12-13 00:17:46 -08001080 if buildrecipe is not None:
1081 # call special-case build recipe, e.g. for openssl
1082 buildrecipe(basedir, archList)
1083
1084 if install is not None:
1085 print("Running install for %s"%(name,))
1086 runCommand('{ ' + install + ' ;} 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001087
Ned Deily4a96a372013-01-29 00:08:32 -08001088 print("Done %s"%(name,))
1089 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001090
1091 os.chdir(curdir)
1092
1093def buildLibraries():
1094 """
1095 Build our dependencies into $WORKDIR/libraries/usr/local
1096 """
Ned Deily4a96a372013-01-29 00:08:32 -08001097 print("")
1098 print("Building required libraries")
1099 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100 universal = os.path.join(WORKDIR, 'libraries')
1101 os.mkdir(universal)
1102 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1103 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1104
Ronald Oussoren1943f862009-03-30 19:39:14 +00001105 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001106 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001107
1108
1109
1110def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001111 # This stores the documentation as Resources/English.lproj/Documentation
Mike53f7a7c2017-12-14 14:04:53 +03001112 # inside the framework. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -08001113 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001114 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001115 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001116 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001117 curDir = os.getcwd()
1118 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -07001119 runCommand('make clean')
Ned Deily63fc55b2020-06-08 03:52:43 -04001120
1121 # Search third-party source directory for a pre-built version of the docs.
1122 # Use the naming convention of the docs.python.org html downloads:
1123 # python-3.9.0b1-docs-html.tar.bz2
1124 doctarfiles = [ f for f in os.listdir(DEPSRC)
1125 if f.startswith('python-'+getFullVersion())
1126 if f.endswith('-docs-html.tar.bz2') ]
1127 if doctarfiles:
1128 doctarfile = doctarfiles[0]
1129 if not os.path.exists('build'):
1130 os.mkdir('build')
1131 # if build directory existed, it was emptied by make clean, above
1132 os.chdir('build')
1133 # Extract the first archive found for this version into build
1134 runCommand('tar xjf %s'%shellQuote(os.path.join(DEPSRC, doctarfile)))
1135 # see if tar extracted a directory ending in -docs-html
1136 archivefiles = [ f for f in os.listdir('.')
1137 if f.endswith('-docs-html')
1138 if os.path.isdir(f) ]
1139 if archivefiles:
1140 archivefile = archivefiles[0]
1141 # make it our 'Docs/build/html' directory
1142 print(' -- using pre-built python documentation from %s'%archivefile)
1143 os.rename(archivefile, 'html')
1144 os.chdir(buildDir)
1145
1146 htmlDir = os.path.join('build', 'html')
1147 if not os.path.exists(htmlDir):
1148 # Create virtual environment for docs builds with blurb and sphinx
1149 runCommand('make venv')
Ned Deily63fc55b2020-06-08 03:52:43 -04001150 runCommand('make html PYTHON=venv/bin/python')
1151 os.rename(htmlDir, docdir)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001152 os.chdir(curDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001153
1154
1155def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -08001156 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157
1158 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1159 rootDir = os.path.join(WORKDIR, '_root')
1160
1161 if os.path.exists(buildDir):
1162 shutil.rmtree(buildDir)
1163 if os.path.exists(rootDir):
1164 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +00001165 os.makedirs(buildDir)
1166 os.makedirs(rootDir)
1167 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001168 curdir = os.getcwd()
1169 os.chdir(buildDir)
1170
Thomas Wouters477c8d52006-05-27 19:21:47 +00001171 # Extract the version from the configure file, needed to calculate
1172 # several paths.
1173 version = getVersion()
1174
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001175 # Since the extra libs are not in their installed framework location
1176 # during the build, augment the library path so that the interpreter
1177 # will find them during its extension import sanity checks.
Ned Deily1931e642020-06-25 04:51:46 -04001178
Ned Deily4a96a372013-01-29 00:08:32 -08001179 print("Running configure...")
Ned Deily8c9bb722018-01-30 07:42:14 -05001180 runCommand("%s -C --enable-framework --enable-universalsdk=/ "
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001181 "--with-universal-archs=%s "
1182 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -08001183 "%s "
Ned Deily1ca2ffd2018-01-30 17:29:53 -05001184 "%s "
1185 "%s "
Ned Deily1931e642020-06-25 04:51:46 -04001186 "%s "
Ronald Oussorene08059e2021-05-03 05:43:52 +02001187 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +00001188 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -07001189 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ned Deily8c9bb722018-01-30 07:42:14 -05001190 shellQuote(os.path.join(SRCDIR, 'configure')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001191 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001192 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -08001193 (' ', '--without-ensurepip ')[PYTHON_3],
Ned Deily1931e642020-06-25 04:51:46 -04001194 (' ', "--with-openssl='%s/libraries/usr/local'"%(
1195 shellQuote(WORKDIR)[1:-1],))[PYTHON_3],
Ned Deily1ca2ffd2018-01-30 17:29:53 -05001196 (' ', "--with-tcltk-includes='-I%s/libraries/usr/local/include'"%(
1197 shellQuote(WORKDIR)[1:-1],))[internalTk()],
1198 (' ', "--with-tcltk-libs='-L%s/libraries/usr/local/lib -ltcl8.6 -ltk8.6'"%(
1199 shellQuote(WORKDIR)[1:-1],))[internalTk()],
Ronald Oussorene08059e2021-05-03 05:43:52 +02001200 (' ', "--enable-optimizations --with-lto")[compilerCanOptimize()],
Ronald Oussoren1943f862009-03-30 19:39:14 +00001201 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +00001202 shellQuote(WORKDIR)[1:-1]))
1203
Ned Deily1931e642020-06-25 04:51:46 -04001204 # As of macOS 10.11 with SYSTEM INTEGRITY PROTECTION, DYLD_*
1205 # environment variables are no longer automatically inherited
1206 # by child processes from their parents. We used to just set
1207 # DYLD_LIBRARY_PATH, pointing to the third-party libs,
1208 # in build-installer.py's process environment and it was
1209 # passed through the make utility into the environment of
1210 # setup.py. Instead, we now append DYLD_LIBRARY_PATH to
1211 # the existing RUNSHARED configuration value when we call
1212 # make for extension module builds.
1213
1214 runshared_for_make = "".join([
1215 " RUNSHARED=",
1216 "'",
1217 grepValue("Makefile", "RUNSHARED"),
1218 ' DYLD_LIBRARY_PATH=',
1219 os.path.join(WORKDIR, 'libraries', 'usr', 'local', 'lib'),
1220 "'" ])
1221
Ned Deilyb364d9f2017-07-24 04:58:43 -04001222 # Look for environment value BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS
1223 # and, if defined, append its value to the make command. This allows
1224 # us to pass in version control tags, like GITTAG, to a build from a
1225 # tarball rather than from a vcs checkout, thus eliminating the need
1226 # to have a working copy of the vcs program on the build machine.
1227 #
1228 # A typical use might be:
1229 # export BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS=" \
1230 # GITVERSION='echo 123456789a' \
1231 # GITTAG='echo v3.6.0' \
1232 # GITBRANCH='echo 3.6'"
1233
1234 make_extras = os.getenv("BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS")
1235 if make_extras:
Ned Deily1931e642020-06-25 04:51:46 -04001236 make_cmd = "make " + make_extras + runshared_for_make
Ned Deilyb364d9f2017-07-24 04:58:43 -04001237 else:
Ned Deily1931e642020-06-25 04:51:46 -04001238 make_cmd = "make" + runshared_for_make
Ned Deilyb364d9f2017-07-24 04:58:43 -04001239 print("Running " + make_cmd)
1240 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001241
Ned Deily1931e642020-06-25 04:51:46 -04001242 make_cmd = "make install DESTDIR=%s %s"%(
1243 shellQuote(rootDir),
1244 runshared_for_make)
1245 print("Running " + make_cmd)
1246 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001247
Ned Deily1931e642020-06-25 04:51:46 -04001248 make_cmd = "make frameworkinstallextras DESTDIR=%s %s"%(
1249 shellQuote(rootDir),
1250 runshared_for_make)
1251 print("Running " + make_cmd)
1252 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001253
Ned Deily4a96a372013-01-29 00:08:32 -08001254 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001255 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
Ned Deily1ca2ffd2018-01-30 17:29:53 -05001256 build_lib_dir = os.path.join(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001257 WORKDIR, 'libraries', 'Library', 'Frameworks',
Ned Deily1ca2ffd2018-01-30 17:29:53 -05001258 'Python.framework', 'Versions', getVersion(), 'lib')
1259 fw_lib_dir = os.path.join(
1260 WORKDIR, '_root', 'Library', 'Frameworks',
1261 'Python.framework', 'Versions', getVersion(), 'lib')
1262 if internalTk():
1263 # move Tcl and Tk pkgconfig files
1264 runCommand("mv %s/pkgconfig/* %s/pkgconfig"%(
1265 shellQuote(build_lib_dir),
1266 shellQuote(fw_lib_dir) ))
1267 runCommand("rm -r %s/pkgconfig"%(
1268 shellQuote(build_lib_dir), ))
1269 runCommand("mv %s/* %s"%(
1270 shellQuote(build_lib_dir),
1271 shellQuote(fw_lib_dir) ))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001272
Ned Deilydde4f632016-09-12 09:39:23 -04001273 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1274 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1275 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1276 # create directory for OpenSSL certificates
1277 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1278 os.makedirs(sslDir)
Ned Deily050fcd52013-10-26 03:16:44 -07001279
Ned Deily4a96a372013-01-29 00:08:32 -08001280 print("Fix file modes")
Thomas Wouters89f507f2006-12-13 04:49:30 +00001281 gid = grp.getgrnam('admin').gr_gid
1282
Ned Deily4a96a372013-01-29 00:08:32 -08001283 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001284 for dirpath, dirnames, filenames in os.walk(frmDir):
1285 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001286 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001287 os.chown(os.path.join(dirpath, dn), -1, gid)
1288
Thomas Wouters477c8d52006-05-27 19:21:47 +00001289 for fn in filenames:
1290 if os.path.islink(fn):
1291 continue
1292
1293 # "chmod g+w $fn"
1294 p = os.path.join(dirpath, fn)
1295 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001296 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1297 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001298
Ned Deily4a96a372013-01-29 00:08:32 -08001299 if fn in EXPECTED_SHARED_LIBS:
1300 # check to see that this file was linked with the
1301 # expected library path and version
1302 data = captureCommand("otool -L %s" % shellQuote(p))
1303 for sl in EXPECTED_SHARED_LIBS[fn]:
1304 if ("\t%s " % sl) not in data:
1305 print("Expected shared lib %s was not linked with %s"
1306 % (sl, p))
1307 shared_lib_error = True
1308
1309 if shared_lib_error:
1310 fatal("Unexpected shared library errors.")
1311
Ned Deilye59e4c52011-01-29 18:56:28 +00001312 if PYTHON_3:
1313 LDVERSION=None
1314 VERSION=None
1315 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001316
Ned Deilye59e4c52011-01-29 18:56:28 +00001317 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001318 for ln in fp:
1319 if ln.startswith('VERSION='):
1320 VERSION=ln.split()[1]
1321 if ln.startswith('ABIFLAGS='):
Ned Deily9bdd6d12019-04-29 15:11:53 -04001322 ABIFLAGS=ln.split()
1323 ABIFLAGS=ABIFLAGS[1] if len(ABIFLAGS) > 1 else ''
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001324 if ln.startswith('LDVERSION='):
1325 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001326 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001327
Ned Deilye59e4c52011-01-29 18:56:28 +00001328 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1329 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1330 config_suffix = '-' + LDVERSION
Ned Deily652bad42016-08-15 14:37:14 -04001331 if getVersionMajorMinor() >= (3, 6):
1332 config_suffix = config_suffix + '-darwin'
Ned Deilye59e4c52011-01-29 18:56:28 +00001333 else:
1334 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001335
Thomas Wouters477c8d52006-05-27 19:21:47 +00001336 # We added some directories to the search path during the configure
1337 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001338 # the end-users system. Also remove the directories from _sysconfigdata.py
1339 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001340
Ned Deilya4f6b002013-10-25 00:47:38 -07001341 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1342 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1343
Ned Deilya4f6b002013-10-25 00:47:38 -07001344 # fix Makefile
1345 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1346 fp = open(path, 'r')
1347 data = fp.read()
1348 fp.close()
1349
1350 for p in (include_path, lib_path):
1351 data = data.replace(" " + p, '')
1352 data = data.replace(p + " ", '')
1353
1354 fp = open(path, 'w')
1355 fp.write(data)
1356 fp.close()
1357
Ned Deily652bad42016-08-15 14:37:14 -04001358 # fix _sysconfigdata
Ned Deilya4f6b002013-10-25 00:47:38 -07001359 #
1360 # TODO: make this more robust! test_sysconfig_module of
1361 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1362 # the output from get_config_var in both sysconfig and
1363 # distutils.sysconfig is exactly the same for both CFLAGS and
1364 # LDFLAGS. The fixing up is now complicated by the pretty
1365 # printing in _sysconfigdata.py. Also, we are using the
1366 # pprint from the Python running the installer build which
1367 # may not cosmetically format the same as the pprint in the Python
1368 # being built (and which is used to originally generate
1369 # _sysconfigdata.py).
1370
1371 import pprint
Ned Deily652bad42016-08-15 14:37:14 -04001372 if getVersionMajorMinor() >= (3, 6):
Zachary Warec4b53af2016-09-09 17:59:49 -07001373 # XXX this is extra-fragile
Ned Deily9bdd6d12019-04-29 15:11:53 -04001374 path = os.path.join(path_to_lib,
1375 '_sysconfigdata_%s_darwin_darwin.py' % (ABIFLAGS,))
Ned Deily652bad42016-08-15 14:37:14 -04001376 else:
1377 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1378 fp = open(path, 'r')
1379 data = fp.read()
1380 fp.close()
1381 # create build_time_vars dict
Ned Deily1931e642020-06-25 04:51:46 -04001382 if RUNNING_ON_PYTHON2:
1383 exec(data)
1384 else:
1385 g_dict = {}
1386 l_dict = {}
1387 exec(data, g_dict, l_dict)
1388 build_time_vars = l_dict['build_time_vars']
Ned Deily652bad42016-08-15 14:37:14 -04001389 vars = {}
1390 for k, v in build_time_vars.items():
1391 if type(v) == type(''):
1392 for p in (include_path, lib_path):
1393 v = v.replace(' ' + p, '')
1394 v = v.replace(p + ' ', '')
1395 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001396
Ned Deily652bad42016-08-15 14:37:14 -04001397 fp = open(path, 'w')
1398 # duplicated from sysconfig._generate_posix_vars()
1399 fp.write('# system configuration generated and used by'
1400 ' the sysconfig module\n')
1401 fp.write('build_time_vars = ')
1402 pprint.pprint(vars, stream=fp)
1403 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001404
1405 # Add symlinks in /usr/local/bin, using relative links
1406 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1407 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1408 'Python.framework', 'Versions', version, 'bin')
1409 if os.path.exists(usr_local_bin):
1410 shutil.rmtree(usr_local_bin)
1411 os.makedirs(usr_local_bin)
1412 for fn in os.listdir(
1413 os.path.join(frmDir, 'Versions', version, 'bin')):
1414 os.symlink(os.path.join(to_framework, fn),
1415 os.path.join(usr_local_bin, fn))
1416
1417 os.chdir(curdir)
1418
Thomas Wouters477c8d52006-05-27 19:21:47 +00001419def patchFile(inPath, outPath):
1420 data = fileContents(inPath)
1421 data = data.replace('$FULL_VERSION', getFullVersion())
1422 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001423 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001424 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001425 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001426 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001427
1428 # This one is not handy as a template variable
1429 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001430 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001431 fp.write(data)
1432 fp.close()
1433
1434def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001435 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001436 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001437 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001438 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001439 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001440 fp.write(data)
1441 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001442 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001443
1444
1445
1446def packageFromRecipe(targetDir, recipe):
1447 curdir = os.getcwd()
1448 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001449 # The major version (such as 2.5) is included in the package name
1450 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001451 # common.
1452 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001453 srcdir = recipe.get('source')
1454 pkgroot = recipe.get('topdir', srcdir)
1455 postflight = recipe.get('postflight')
1456 readme = textwrap.dedent(recipe['readme'])
1457 isRequired = recipe.get('required', True)
1458
Ned Deily4a96a372013-01-29 00:08:32 -08001459 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001460
1461 # Substitute some variables
1462 textvars = dict(
1463 VER=getVersion(),
1464 FULLVER=getFullVersion(),
1465 )
1466 readme = readme % textvars
1467
1468 if pkgroot is not None:
1469 pkgroot = pkgroot % textvars
1470 else:
1471 pkgroot = '/'
1472
1473 if srcdir is not None:
1474 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1475 srcdir = srcdir % textvars
1476
1477 if postflight is not None:
1478 postflight = os.path.abspath(postflight)
1479
1480 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1481 os.makedirs(packageContents)
1482
1483 if srcdir is not None:
1484 os.chdir(srcdir)
1485 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1486 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1487 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1488
1489 fn = os.path.join(packageContents, 'PkgInfo')
1490 fp = open(fn, 'w')
1491 fp.write('pmkrpkg1')
1492 fp.close()
1493
1494 rsrcDir = os.path.join(packageContents, "Resources")
1495 os.mkdir(rsrcDir)
1496 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1497 fp.write(readme)
1498 fp.close()
1499
1500 if postflight is not None:
1501 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1502
1503 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001504 major, minor = getVersionMajorMinor()
Ned Deily1931e642020-06-25 04:51:46 -04001505 pl = dict(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001506 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1507 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1508 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001509 CFBundleShortVersionString=vers,
1510 IFMajorVersion=major,
1511 IFMinorVersion=minor,
1512 IFPkgFormatVersion=0.10000000149011612,
1513 IFPkgFlagAllowBackRev=False,
1514 IFPkgFlagAuthorizationAction="RootAuthorization",
1515 IFPkgFlagDefaultLocation=pkgroot,
1516 IFPkgFlagFollowLinks=True,
1517 IFPkgFlagInstallFat=True,
1518 IFPkgFlagIsRequired=isRequired,
1519 IFPkgFlagOverwritePermissions=False,
1520 IFPkgFlagRelocatable=False,
1521 IFPkgFlagRestartAction="NoRestart",
1522 IFPkgFlagRootVolumeOnly=True,
1523 IFPkgFlagUpdateInstalledLangauges=False,
1524 )
1525 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1526
Ned Deily1931e642020-06-25 04:51:46 -04001527 pl = dict(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001528 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001529 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001530 IFPkgDescriptionVersion=vers,
1531 )
1532 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1533
1534 finally:
1535 os.chdir(curdir)
1536
1537
1538def makeMpkgPlist(path):
1539
1540 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001541 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001542
Ned Deily1931e642020-06-25 04:51:46 -04001543 pl = dict(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001544 CFBundleGetInfoString="Python %s"%(vers,),
1545 CFBundleIdentifier='org.python.Python',
1546 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001547 CFBundleShortVersionString=vers,
1548 IFMajorVersion=major,
1549 IFMinorVersion=minor,
1550 IFPkgFlagComponentDirectory="Contents/Packages",
1551 IFPkgFlagPackageList=[
1552 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001553 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001554 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001555 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001556 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001557 ],
1558 IFPkgFormatVersion=0.10000000149011612,
1559 IFPkgFlagBackgroundScaling="proportional",
1560 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001561 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001562 )
1563
1564 writePlist(pl, path)
1565
1566
1567def buildInstaller():
1568
1569 # Zap all compiled files
1570 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1571 for fn in filenames:
1572 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1573 os.unlink(os.path.join(dirpath, fn))
1574
1575 outdir = os.path.join(WORKDIR, 'installer')
1576 if os.path.exists(outdir):
1577 shutil.rmtree(outdir)
1578 os.mkdir(outdir)
1579
Ronald Oussoren1943f862009-03-30 19:39:14 +00001580 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001581 pkgcontents = os.path.join(pkgroot, 'Packages')
1582 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001583 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001584 packageFromRecipe(pkgcontents, recipe)
1585
1586 rsrcDir = os.path.join(pkgroot, 'Resources')
1587
1588 fn = os.path.join(pkgroot, 'PkgInfo')
1589 fp = open(fn, 'w')
1590 fp.write('pmkrpkg1')
1591 fp.close()
1592
1593 os.mkdir(rsrcDir)
1594
1595 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
Ned Deily1931e642020-06-25 04:51:46 -04001596 pl = dict(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001597 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001598 IFPkgDescriptionVersion=getVersion(),
1599 )
1600
1601 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1602 for fn in os.listdir('resources'):
1603 if fn == '.svn': continue
1604 if fn.endswith('.jpg'):
1605 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1606 else:
1607 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1608
Thomas Wouters477c8d52006-05-27 19:21:47 +00001609
1610def installSize(clear=False, _saved=[]):
1611 if clear:
1612 del _saved[:]
1613 if not _saved:
1614 data = captureCommand("du -ks %s"%(
1615 shellQuote(os.path.join(WORKDIR, '_root'))))
1616 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1617 return _saved[0]
1618
1619
1620def buildDMG():
1621 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001622 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001623 """
1624 outdir = os.path.join(WORKDIR, 'diskimage')
1625 if os.path.exists(outdir):
1626 shutil.rmtree(outdir)
1627
Erlend Egeberg Aaslandc94ee132021-01-04 05:48:19 +01001628 # We used to use the deployment target as the last characters of the
Ned Deily936533c2020-11-23 19:04:40 -05001629 # installer file name. With the introduction of weaklinked installer
1630 # variants, we may have two variants with the same file name, i.e.
1631 # both ending in '10.9'. To avoid this, we now use the major/minor
Ned Deily8a374632021-04-27 13:23:39 -04001632 # version numbers of the macOS version we are building on.
1633 # Also, as of macOS 11, operating system version numbering has
1634 # changed from three components to two, i.e.
1635 # 10.14.1, 10.14.2, ...
1636 # 10.15.1, 10.15.2, ...
1637 # 11.1, 11.2, ...
1638 # 12.1, 12.2, ...
1639 # (A further twist is that, when running on macOS 11, binaries built
1640 # on older systems may be shown an operating system version of 10.16
1641 # instead of 11. We should not run into that situation here.)
1642 # Also we should use "macos" instead of "macosx" going forward.
1643 #
1644 # To maintain compability for legacy variants, the file name for
1645 # builds on macOS 10.15 and earlier remains:
1646 # python-3.x.y-macosx10.z.{dmg->pkg}
1647 # e.g. python-3.9.4-macosx10.9.{dmg->pkg}
1648 # and for builds on macOS 11+:
1649 # python-3.x.y-macosz.{dmg->pkg}
1650 # e.g. python-3.9.4-macos11.{dmg->pkg}
1651
1652 build_tuple = getBuildTuple()
1653 if build_tuple[0] < 11:
1654 os_name = 'macosx'
1655 build_system_version = '%s.%s' % build_tuple
1656 else:
1657 os_name = 'macos'
1658 build_system_version = str(build_tuple[0])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001659 imagepath = os.path.join(outdir,
Ned Deily8a374632021-04-27 13:23:39 -04001660 'python-%s-%s%s'%(getFullVersion(),os_name,build_system_version))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001661 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001662 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001663 imagepath = imagepath + '.dmg'
1664
1665 os.mkdir(outdir)
Ned Deily0133f9f2018-12-27 16:38:41 -05001666
1667 # Try to mitigate race condition in certain versions of macOS, e.g. 10.9,
1668 # when hdiutil create fails with "Resource busy". For now, just retry
1669 # the create a few times and hope that it eventually works.
1670
Ronald Oussoren1943f862009-03-30 19:39:14 +00001671 volname='Python %s'%(getFullVersion())
Ned Deily0133f9f2018-12-27 16:38:41 -05001672 cmd = ("hdiutil create -format UDRW -volname %s -srcfolder %s -size 100m %s"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001673 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001674 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001675 shellQuote(imagepath + ".tmp.dmg" )))
Ned Deily0133f9f2018-12-27 16:38:41 -05001676 for i in range(5):
1677 fd = os.popen(cmd, 'r')
1678 data = fd.read()
1679 xit = fd.close()
1680 if not xit:
1681 break
1682 sys.stdout.write(data)
1683 print(" -- retrying hdiutil create")
1684 time.sleep(5)
1685 else:
cclaussd3371692019-06-03 05:19:44 +02001686 raise RuntimeError("command failed: %s"%(cmd,))
Ronald Oussoren1943f862009-03-30 19:39:14 +00001687
1688 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1689 os.mkdir(os.path.join(WORKDIR, "mnt"))
1690 runCommand("hdiutil attach %s -mountroot %s"%(
1691 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1692
1693 # Custom icon for the DMG, shown when the DMG is mounted.
1694 shutil.copy("../Icons/Disk Image.icns",
1695 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001696 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001697 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1698
1699 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1700
1701 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1702 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1703 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1704 setIcon(imagepath, "../Icons/Disk Image.icns")
1705
1706 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001707
1708 return imagepath
1709
1710
1711def setIcon(filePath, icnsPath):
1712 """
1713 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001714 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001715
Ronald Oussoren70050672010-04-30 15:00:26 +00001716 dirPath = os.path.normpath(os.path.dirname(__file__))
1717 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001718 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1719 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1720 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001721 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1722 if not os.path.exists(appPath):
1723 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001724 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1725 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001726
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001727 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1728 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001729
1730def main():
1731 # First parse options and check if we can perform our work
1732 parseOptions()
1733 checkEnvironment()
1734
Ronald Oussoren1943f862009-03-30 19:39:14 +00001735 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001736 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001737 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001738
1739 if os.path.exists(WORKDIR):
1740 shutil.rmtree(WORKDIR)
1741 os.mkdir(WORKDIR)
1742
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001743 os.environ['LC_ALL'] = 'C'
1744
Thomas Wouters477c8d52006-05-27 19:21:47 +00001745 # Then build third-party libraries such as sleepycat DB4.
1746 buildLibraries()
1747
1748 # Now build python itself
1749 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001750
1751 # And then build the documentation
1752 # Remove the Deployment Target from the shell
1753 # environment, it's no longer needed and
1754 # an unexpected build target can cause problems
1755 # when Sphinx and its dependencies need to
1756 # be (re-)installed.
1757 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001758 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001759
1760
1761 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001762 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001763 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001764 fn = os.path.join(folder, "License.rtf")
1765 patchFile("resources/License.rtf", fn)
1766 fn = os.path.join(folder, "ReadMe.rtf")
1767 patchFile("resources/ReadMe.rtf", fn)
1768 fn = os.path.join(folder, "Update Shell Profile.command")
1769 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilydde4f632016-09-12 09:39:23 -04001770 fn = os.path.join(folder, "Install Certificates.command")
1771 patchScript("resources/install_certificates.command", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001772 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001773 setIcon(folder, "../Icons/Python Folder.icns")
1774
1775 # Create the installer
1776 buildInstaller()
1777
1778 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001779 patchFile('resources/ReadMe.rtf',
1780 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001781
1782 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001783 patchFile('resources/License.rtf',
1784 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001785
1786 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001787 fp.write("# BUILD INFO\n")
1788 fp.write("# Date: %s\n" % time.ctime())
1789 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001790 fp.close()
1791
Thomas Wouters477c8d52006-05-27 19:21:47 +00001792 # And copy it to a DMG
1793 buildDMG()
1794
Thomas Wouters477c8d52006-05-27 19:21:47 +00001795if __name__ == "__main__":
1796 main()