blob: 8e2e104a0b7e119299a48cb82494efcde42ee028 [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 }
Ned Deily8c9bb722018-01-30 07:42:14 -0500164 return target_cc_map.get(DEPTARGET, ('gcc', 'gcc++') )
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 Deily5d3febf2014-12-13 00:17:46 -0800192# List of names of third party software built with this installer.
193# The names will be inserted into the rtf version of the License.
194THIRD_PARTY_LIBS = []
195
Thomas Wouters477c8d52006-05-27 19:21:47 +0000196# Instructions for building libraries that are necessary for building a
197# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000198# [The recipes are defined here for convenience but instantiated later after
199# command line options have been processed.]
200def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000201 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000202
Ned Deily04cdfa12014-06-25 13:36:14 -0700203 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800204
Ned Deilydde4f632016-09-12 09:39:23 -0400205 # Since Apple removed the header files for the deprecated system
206 # OpenSSL as of the Xcode 7 release (for OS X 10.10+), we do not
207 # have much choice but to build our own copy here, too.
Ned Deily5d3febf2014-12-13 00:17:46 -0800208
Ned Deilydde4f632016-09-12 09:39:23 -0400209 result.extend([
Ned Deily5d3febf2014-12-13 00:17:46 -0800210 dict(
Ned Deily8c9bb722018-01-30 07:42:14 -0500211 name="OpenSSL 1.1.0g",
212 url="https://www.openssl.org/source/openssl-1.1.0g.tar.gz",
213 checksum='ba5f1b8b835b88cadbce9b35ed9531a6',
Ned Deily5d3febf2014-12-13 00:17:46 -0800214 buildrecipe=build_universal_openssl,
215 configure=None,
216 install=None,
217 ),
Ned Deilydde4f632016-09-12 09:39:23 -0400218 ])
Ned Deily5d3febf2014-12-13 00:17:46 -0800219
Ned Deilyaa6a2122013-11-23 03:30:11 -0800220# Disable for now
Ned Deilyed730102014-11-14 18:55:05 -0800221 if False: # if getDeptargetTuple() > (10, 5):
Ned Deily5b3582c2013-10-25 00:41:46 -0700222 result.extend([
223 dict(
224 name="Tcl 8.5.15",
225 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
226 checksum='f3df162f92c69b254079c4d0af7a690f',
227 buildDir="unix",
228 configure_pre=[
229 '--enable-shared',
230 '--enable-threads',
231 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
232 ],
233 useLDFlags=False,
234 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
235 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
236 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
237 },
238 ),
239 dict(
240 name="Tk 8.5.15",
241 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
242 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700243 patches=[
244 "issue19373_tk_8_5_15_source.patch",
245 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700246 buildDir="unix",
247 configure_pre=[
248 '--enable-aqua',
249 '--enable-shared',
250 '--enable-threads',
251 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
252 ],
253 useLDFlags=False,
254 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'%{
255 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
256 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
257 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
258 },
259 ),
260 ])
261
Ned Deilyed730102014-11-14 18:55:05 -0800262 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800263 result.extend([
264 dict(
Ned Deilye6f8a732017-12-04 22:55:20 -0500265 name="XZ 5.2.3",
266 url="http://tukaani.org/xz/xz-5.2.3.tar.gz",
267 checksum='ef68674fb47a8b8e741b34e429d86e9d',
Ned Deily4a96a372013-01-29 00:08:32 -0800268 configure_pre=[
269 '--disable-dependency-tracking',
270 ]
271 ),
272 ])
273
274 result.extend([
275 dict(
276 name="NCurses 5.9",
277 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
278 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
279 configure_pre=[
280 "--enable-widec",
281 "--without-cxx",
282 "--without-cxx-binding",
283 "--without-ada",
284 "--without-curses-h",
285 "--enable-shared",
286 "--with-shared",
287 "--without-debug",
288 "--without-normal",
289 "--without-tests",
290 "--without-manpages",
291 "--datadir=/usr/share",
292 "--sysconfdir=/etc",
293 "--sharedstatedir=/usr/com",
294 "--with-terminfo-dirs=/usr/share/terminfo",
295 "--with-default-terminfo-dir=/usr/share/terminfo",
296 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
297 ],
298 patchscripts=[
299 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
300 "f54bf02a349f96a7c4f0d00922f3a0d4"),
301 ],
302 useLDFlags=False,
303 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
304 shellQuote(os.path.join(WORKDIR, 'libraries')),
305 shellQuote(os.path.join(WORKDIR, 'libraries')),
306 getVersion(),
307 ),
308 ),
309 dict(
Ned Deily8c9bb722018-01-30 07:42:14 -0500310 name="SQLite 3.22.0",
311 url="https://www.sqlite.org/2018/sqlite-autoconf-3220000.tar.gz",
312 checksum='96b5648d542e8afa6ab7ffb8db8ddc3d',
Ned Deily4a96a372013-01-29 00:08:32 -0800313 extra_cflags=('-Os '
Ned Deilyb3b07672016-09-05 17:31:14 -0700314 '-DSQLITE_ENABLE_FTS5 '
Ned Deily4a96a372013-01-29 00:08:32 -0800315 '-DSQLITE_ENABLE_FTS4 '
316 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
Ned Deily9625bf52017-12-04 21:50:29 -0500317 '-DSQLITE_ENABLE_JSON1 '
Ned Deily4a96a372013-01-29 00:08:32 -0800318 '-DSQLITE_ENABLE_RTREE '
319 '-DSQLITE_TCL=0 '
320 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
321 configure_pre=[
322 '--enable-threadsafe',
323 '--enable-shared=no',
324 '--enable-static=yes',
325 '--disable-readline',
326 '--disable-dependency-tracking',
327 ]
328 ),
329 ])
330
Ned Deily04cdfa12014-06-25 13:36:14 -0700331 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000332 result.extend([
333 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000334 name="Bzip2 1.0.6",
335 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
336 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000337 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500338 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800339 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000340 shellQuote(os.path.join(WORKDIR, 'libraries')),
341 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000342 ),
343 ),
344 dict(
345 name="ZLib 1.2.3",
346 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
347 checksum='debc62758716a169df9f62e6ab2bc634',
348 configure=None,
Ned Deily8c9bb722018-01-30 07:42:14 -0500349 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deily4a96a372013-01-29 00:08:32 -0800350 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000351 shellQuote(os.path.join(WORKDIR, 'libraries')),
352 ' -arch '.join(ARCHLIST),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000353 ),
354 ),
355 dict(
356 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000357 name="GNU Readline 6.1.2",
358 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
359 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000360 patchlevel='0',
361 patches=[
362 # The readline maintainers don't do actual micro releases, but
363 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800364 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
365 'c642f2e84d820884b0bf9fd176bc6c3f'),
366 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
367 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000368 ]
369 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000370 ])
371
Ned Deily4f7ff782011-01-15 05:29:12 +0000372 if not PYTHON_3:
373 result.extend([
374 dict(
375 name="Sleepycat DB 4.7.25",
376 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
377 checksum='ec2b87e833779681a0c3a814aa71359e',
378 buildDir="build_unix",
379 configure="../dist/configure",
380 configure_pre=[
381 '--includedir=/usr/local/include/db4',
382 ]
383 ),
384 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000385
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000386 return result
387
Thomas Wouters477c8d52006-05-27 19:21:47 +0000388
Thomas Wouters477c8d52006-05-27 19:21:47 +0000389# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000390def pkg_recipes():
391 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
392 result = [
393 dict(
394 name="PythonFramework",
395 long_name="Python Framework",
396 source="/Library/Frameworks/Python.framework",
397 readme="""\
398 This package installs Python.framework, that is the python
Ned Deily8c9bb722018-01-30 07:42:14 -0500399 interpreter and the standard library.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000400 """,
401 postflight="scripts/postflight.framework",
402 selected='selected',
403 ),
404 dict(
405 name="PythonApplications",
406 long_name="GUI Applications",
407 source="/Applications/Python %(VER)s",
408 readme="""\
409 This package installs IDLE (an interactive Python IDE),
410 Python Launcher and Build Applet (create application bundles
411 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000412
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000413 It also installs a number of examples and demos.
414 """,
415 required=False,
416 selected='selected',
417 ),
418 dict(
419 name="PythonUnixTools",
420 long_name="UNIX command-line tools",
421 source="/usr/local/bin",
422 readme="""\
423 This package installs the unix tools in /usr/local/bin for
424 compatibility with older releases of Python. This package
425 is not necessary to use Python.
426 """,
427 required=False,
428 selected='selected',
429 ),
430 dict(
431 name="PythonDocumentation",
432 long_name="Python Documentation",
433 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
434 source="/pydocs",
435 readme="""\
436 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800437 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000438 """,
439 postflight="scripts/postflight.documentation",
440 required=False,
441 selected='selected',
442 ),
443 dict(
444 name="PythonProfileChanges",
445 long_name="Shell profile updater",
446 readme="""\
447 This packages updates your shell profile to make sure that
448 the Python tools are found by your shell in preference of
449 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000450
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000451 If you don't install this package you'll have to add
452 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
453 to your PATH by hand.
454 """,
455 postflight="scripts/postflight.patch-profile",
456 topdir="/Library/Frameworks/Python.framework",
457 source="/empty-dir",
458 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800459 selected='selected',
460 ),
461 dict(
462 name="PythonInstallPip",
463 long_name="Install or upgrade pip",
464 readme="""\
465 This package installs (or upgrades from an earlier version)
466 pip, a tool for installing and managing Python packages.
467 """,
468 postflight="scripts/postflight.ensurepip",
469 topdir="/Library/Frameworks/Python.framework",
470 source="/empty-dir",
471 required=False,
472 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000473 ),
474 ]
475
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000476 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478def fatal(msg):
479 """
480 A fatal error, bail out.
481 """
482 sys.stderr.write('FATAL: ')
483 sys.stderr.write(msg)
484 sys.stderr.write('\n')
485 sys.exit(1)
486
487def fileContents(fn):
488 """
489 Return the contents of the named file
490 """
Ned Deily4a96a372013-01-29 00:08:32 -0800491 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000492
493def runCommand(commandline):
494 """
Ezio Melotti13925002011-03-16 11:05:33 +0200495 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000496 unless the command fails.
497 """
498 fd = os.popen(commandline, 'r')
499 data = fd.read()
500 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000501 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000502 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800503 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000504
505 if VERBOSE:
506 sys.stdout.write(data); sys.stdout.flush()
507
508def captureCommand(commandline):
509 fd = os.popen(commandline, 'r')
510 data = fd.read()
511 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000512 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800514 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000515
516 return data
517
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000518def getTclTkVersion(configfile, versionline):
519 """
520 search Tcl or Tk configuration file for version line
521 """
522 try:
523 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300524 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000525 fatal("Framework configuration file not found: %s" % configfile)
526
527 for l in f:
528 if l.startswith(versionline):
529 f.close()
530 return l
531
532 fatal("Version variable %s not found in framework configuration file: %s"
533 % (versionline, configfile))
534
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535def checkEnvironment():
536 """
537 Check that we're running on a supported system.
538 """
539
Ned Deily8c9bb722018-01-30 07:42:14 -0500540 if sys.version_info[0:2] < (2, 5):
541 fatal("This script must be run with Python 2.5 (or later)")
Ned Deilye59e4c52011-01-29 18:56:28 +0000542
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543 if platform.system() != 'Darwin':
Ned Deily8c9bb722018-01-30 07:42:14 -0500544 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000545
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000546 if int(platform.release().split('.')[0]) < 8:
Ned Deily8c9bb722018-01-30 07:42:14 -0500547 fatal("This script should be run on a macOS 10.5 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000549 # Because we only support dynamic load of only one major/minor version of
550 # Tcl/Tk, ensure:
Ned Deily8c9bb722018-01-30 07:42:14 -0500551 # 1. there is a user-installed framework (usually ActiveTcl) in (or linked
552 # in) SDKROOT/Library/Frameworks. As of Python 3.7.0, we no longer
553 # enforce that the version of the user-installed framework also
554 # exists in the system-supplied Tcl/Tk frameworks. Time to support
555 # Tcl/Tk 8.6 even if Apple does not.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000556
Ned Deily4a96a372013-01-29 00:08:32 -0800557 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000558 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800559 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily8c9bb722018-01-30 07:42:14 -0500560 libfw = os.path.join('/', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000561 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily8c9bb722018-01-30 07:42:14 -0500562 frameworks[framework] = os.readlink(libfw)
Ned Deily4a96a372013-01-29 00:08:32 -0800563 if not os.path.exists(libfw):
564 fatal("Please install a link to a current %s %s as %s so "
565 "the user can override the system framework."
566 % (framework, frameworks[framework], libfw))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000567 if os.path.exists(usrfw):
568 fatal("Please rename %s to avoid possible dynamic load issues."
569 % usrfw)
570
Ned Deily4a96a372013-01-29 00:08:32 -0800571 if frameworks['Tcl'] != frameworks['Tk']:
572 fatal("The Tcl and Tk frameworks are not the same version.")
573
Ned Deily8c9bb722018-01-30 07:42:14 -0500574 print(" -- Building with Tcl/Tk %s frameworks"
575 % frameworks['Tk'])
576 print("")
577
Ned Deily4a96a372013-01-29 00:08:32 -0800578 # add files to check after build
579 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
580 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
581 % frameworks['Tcl'],
582 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
583 % frameworks['Tk'],
584 ]
585
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000586 # Remove inherited environment variables which might influence build
587 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
588 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
589 for ev in list(os.environ):
590 for prefix in environ_var_prefixes:
591 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800592 print("INFO: deleting environment variable %s=%s" % (
593 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000594 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000595
Ned Deily4a96a372013-01-29 00:08:32 -0800596 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
597 if 'SDK_TOOLS_BIN' in os.environ:
598 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
599 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
600 # add its fixed location here if it exists
601 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
602 if os.path.isdir(OLD_DEVELOPER_TOOLS):
603 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
604 os.environ['PATH'] = base_path
605 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyb364d9f2017-07-24 04:58:43 -0400606 # Ensure we have access to sphinx-build.
607 # You may have to create a link in /usr/bin for it.
Ned Deily1ff32a92014-09-05 15:57:05 -0700608 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000609
Thomas Wouters89f507f2006-12-13 04:49:30 +0000610def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000611 """
612 Parse arguments and update global settings.
613 """
Ned Deily8c9bb722018-01-30 07:42:14 -0500614 global WORKDIR, DEPSRC, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800615 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily5d3febf2014-12-13 00:17:46 -0800616 global FW_VERSION_PREFIX
Ned Deilydde4f632016-09-12 09:39:23 -0400617 global FW_SSL_DIRECTORY
Thomas Wouters477c8d52006-05-27 19:21:47 +0000618
619 if args is None:
620 args = sys.argv[1:]
621
622 try:
623 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000624 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
625 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800626 except getopt.GetoptError:
627 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000628 sys.exit(1)
629
630 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800631 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632 sys.exit(1)
633
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000634 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000635 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000636 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800637 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000638 sys.exit(0)
639
640 elif k in ('-d', '--build-dir'):
641 WORKDIR=v
642
643 elif k in ('--third-party',):
644 DEPSRC=v
645
646 elif k in ('--sdk-path',):
Ned Deily8c9bb722018-01-30 07:42:14 -0500647 print(" WARNING: --sdk-path is no longer supported")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000648
649 elif k in ('--src-dir',):
650 SRCDIR=v
651
Ronald Oussoren1943f862009-03-30 19:39:14 +0000652 elif k in ('--dep-target', ):
653 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000654 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000655
656 elif k in ('--universal-archs', ):
657 if v in UNIVERSALOPTS:
658 UNIVERSALARCHS = v
659 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000660 if deptarget is None:
661 # Select alternate default deployment
662 # target
Ned Deily8c9bb722018-01-30 07:42:14 -0500663 DEPTARGET = default_target_map.get(v, '10.5')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000664 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800665 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000666
Thomas Wouters477c8d52006-05-27 19:21:47 +0000667 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800668 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000669
670 SRCDIR=os.path.abspath(SRCDIR)
671 WORKDIR=os.path.abspath(WORKDIR)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000672 DEPSRC=os.path.abspath(DEPSRC)
673
Ned Deily04cdfa12014-06-25 13:36:14 -0700674 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000675
Ned Deily5d3febf2014-12-13 00:17:46 -0800676 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
Ned Deilydde4f632016-09-12 09:39:23 -0400677 FW_SSL_DIRECTORY = FW_VERSION_PREFIX[:] + ["etc", "openssl"]
Ned Deily5d3febf2014-12-13 00:17:46 -0800678
679 print("-- Settings:")
680 print(" * Source directory: %s" % SRCDIR)
681 print(" * Build directory: %s" % WORKDIR)
Ned Deily5d3febf2014-12-13 00:17:46 -0800682 print(" * Third-party source: %s" % DEPSRC)
683 print(" * Deployment target: %s" % DEPTARGET)
684 print(" * Universal archs: %s" % str(ARCHLIST))
685 print(" * C compiler: %s" % CC)
686 print(" * C++ compiler: %s" % CXX)
Ned Deily4a96a372013-01-29 00:08:32 -0800687 print("")
Ned Deily5d3febf2014-12-13 00:17:46 -0800688 print(" -- Building a Python %s framework at patch level %s"
689 % (getVersion(), getFullVersion()))
690 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000691
692def extractArchive(builddir, archiveName):
693 """
694 Extract a source archive into 'builddir'. Returns the path of the
695 extracted archive.
696
697 XXX: This function assumes that archives contain a toplevel directory
698 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700699 safe enough for almost anything we use. Unfortunately, it does not
700 work for current Tcl and Tk source releases where the basename of
701 the archive ends with "-src" but the uncompressed directory does not.
702 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000703 """
704 curdir = os.getcwd()
705 try:
706 os.chdir(builddir)
707 if archiveName.endswith('.tar.gz'):
708 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700709 if ((retval.startswith('tcl') or retval.startswith('tk'))
710 and retval.endswith('-src')):
711 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000712 if os.path.exists(retval):
713 shutil.rmtree(retval)
714 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
715
716 elif archiveName.endswith('.tar.bz2'):
717 retval = os.path.basename(archiveName[:-8])
718 if os.path.exists(retval):
719 shutil.rmtree(retval)
720 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
721
722 elif archiveName.endswith('.tar'):
723 retval = os.path.basename(archiveName[:-4])
724 if os.path.exists(retval):
725 shutil.rmtree(retval)
726 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
727
728 elif archiveName.endswith('.zip'):
729 retval = os.path.basename(archiveName[:-4])
730 if os.path.exists(retval):
731 shutil.rmtree(retval)
732 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
733
734 data = fp.read()
735 xit = fp.close()
736 if xit is not None:
737 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800738 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739
740 return os.path.join(builddir, retval)
741
742 finally:
743 os.chdir(curdir)
744
Thomas Wouters477c8d52006-05-27 19:21:47 +0000745def downloadURL(url, fname):
746 """
747 Download the contents of the url into the file.
748 """
Ned Deily4a96a372013-01-29 00:08:32 -0800749 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000750 fpOut = open(fname, 'wb')
751 block = fpIn.read(10240)
752 try:
753 while block:
754 fpOut.write(block)
755 block = fpIn.read(10240)
756 fpIn.close()
757 fpOut.close()
758 except:
759 try:
760 os.unlink(fname)
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300761 except OSError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000762 pass
763
Ned Deily4a96a372013-01-29 00:08:32 -0800764def verifyThirdPartyFile(url, checksum, fname):
765 """
766 Download file from url to filename fname if it does not already exist.
767 Abort if file contents does not match supplied md5 checksum.
768 """
769 name = os.path.basename(fname)
770 if os.path.exists(fname):
771 print("Using local copy of %s"%(name,))
772 else:
773 print("Did not find local copy of %s"%(name,))
774 print("Downloading %s"%(name,))
775 downloadURL(url, fname)
776 print("Archive for %s stored as %s"%(name, fname))
777 if os.system(
778 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
779 % (shellQuote(fname), checksum) ):
780 fatal('MD5 checksum mismatch for file %s' % fname)
781
Ned Deily5d3febf2014-12-13 00:17:46 -0800782def build_universal_openssl(basedir, archList):
783 """
784 Special case build recipe for universal build of openssl.
785
786 The upstream OpenSSL build system does not directly support
787 OS X universal builds. We need to build each architecture
788 separately then lipo them together into fat libraries.
789 """
790
791 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
792 # If we are building on a 10.4.x or earlier system,
793 # unilaterally disable assembly code building to avoid the problem.
794 no_asm = int(platform.release().split(".")[0]) < 9
795
796 def build_openssl_arch(archbase, arch):
797 "Build one architecture of openssl"
798 arch_opts = {
799 "i386": ["darwin-i386-cc"],
800 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
801 "ppc": ["darwin-ppc-cc"],
802 "ppc64": ["darwin64-ppc-cc"],
803 }
804 configure_opts = [
Ned Deily5d3febf2014-12-13 00:17:46 -0800805 "no-idea",
806 "no-mdc2",
807 "no-rc5",
808 "no-zlib",
Ned Deily5d3febf2014-12-13 00:17:46 -0800809 "no-ssl3",
Ned Deily5d3febf2014-12-13 00:17:46 -0800810 # "enable-unit-test",
811 "shared",
Ned Deily5d3febf2014-12-13 00:17:46 -0800812 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
Ned Deilydde4f632016-09-12 09:39:23 -0400813 "--openssldir=%s"%os.path.join("/", *FW_SSL_DIRECTORY),
Ned Deily5d3febf2014-12-13 00:17:46 -0800814 ]
815 if no_asm:
816 configure_opts.append("no-asm")
817 runCommand(" ".join(["perl", "Configure"]
818 + arch_opts[arch] + configure_opts))
Ned Deily8c9bb722018-01-30 07:42:14 -0500819 runCommand("make depend")
820 runCommand("make all")
821 runCommand("make install_sw DESTDIR=%s"%shellQuote(archbase))
Ned Deily5d3febf2014-12-13 00:17:46 -0800822 # runCommand("make test")
823 return
824
825 srcdir = os.getcwd()
826 universalbase = os.path.join(srcdir, "..",
827 os.path.basename(srcdir) + "-universal")
828 os.mkdir(universalbase)
829 archbasefws = []
830 for arch in archList:
831 # fresh copy of the source tree
832 archsrc = os.path.join(universalbase, arch, "src")
833 shutil.copytree(srcdir, archsrc, symlinks=True)
834 # install base for this arch
835 archbase = os.path.join(universalbase, arch, "root")
836 os.mkdir(archbase)
837 # Python framework base within install_prefix:
838 # the build will install into this framework..
839 # This is to ensure that the resulting shared libs have
840 # the desired real install paths built into them.
841 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
842
843 # build one architecture
844 os.chdir(archsrc)
845 build_openssl_arch(archbase, arch)
846 os.chdir(srcdir)
847 archbasefws.append(archbasefw)
848
849 # copy arch-independent files from last build into the basedir framework
850 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
851 shutil.copytree(
852 os.path.join(archbasefw, "include", "openssl"),
853 os.path.join(basefw, "include", "openssl")
854 )
855
856 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
857 "SHLIB_VERSION_NUMBER")
858 # e.g. -> "1.0.0"
859 libcrypto = "libcrypto.dylib"
860 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
861 # e.g. -> "libcrypto.1.0.0.dylib"
862 libssl = "libssl.dylib"
863 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
864 # e.g. -> "libssl.1.0.0.dylib"
865
866 try:
867 os.mkdir(os.path.join(basefw, "lib"))
868 except OSError:
869 pass
870
871 # merge the individual arch-dependent shared libs into a fat shared lib
872 archbasefws.insert(0, basefw)
873 for (lib_unversioned, lib_versioned) in [
874 (libcrypto, libcrypto_versioned),
875 (libssl, libssl_versioned)
876 ]:
877 runCommand("lipo -create -output " +
878 " ".join(shellQuote(
879 os.path.join(fw, "lib", lib_versioned))
880 for fw in archbasefws))
881 # and create an unversioned symlink of it
882 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
883
884 # Create links in the temp include and lib dirs that will be injected
885 # into the Python build so that setup.py can find them while building
886 # and the versioned links so that the setup.py post-build import test
887 # does not fail.
888 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
889 for fn in [
890 ["include", "openssl"],
891 ["lib", libcrypto],
892 ["lib", libssl],
893 ["lib", libcrypto_versioned],
894 ["lib", libssl_versioned],
895 ]:
896 os.symlink(
897 os.path.join(relative_path, *fn),
898 os.path.join(basedir, "usr", "local", *fn)
899 )
900
901 return
902
Thomas Wouters477c8d52006-05-27 19:21:47 +0000903def buildRecipe(recipe, basedir, archList):
904 """
905 Build software using a recipe. This function does the
906 'configure;make;make install' dance for C software, with a possibility
907 to customize this process, basically a poor-mans DarwinPorts.
908 """
909 curdir = os.getcwd()
910
911 name = recipe['name']
Ned Deily5d3febf2014-12-13 00:17:46 -0800912 THIRD_PARTY_LIBS.append(name)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000913 url = recipe['url']
914 configure = recipe.get('configure', './configure')
Ned Deily5d3febf2014-12-13 00:17:46 -0800915 buildrecipe = recipe.get('buildrecipe', None)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000916 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
917 shellQuote(basedir)))
918
919 archiveName = os.path.split(url)[-1]
920 sourceArchive = os.path.join(DEPSRC, archiveName)
921
922 if not os.path.exists(DEPSRC):
923 os.mkdir(DEPSRC)
924
Ned Deily4a96a372013-01-29 00:08:32 -0800925 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
926 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000927 buildDir=os.path.join(WORKDIR, '_bld')
928 if not os.path.exists(buildDir):
929 os.mkdir(buildDir)
930
931 workDir = extractArchive(buildDir, sourceArchive)
932 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000933
Ned Deily4a96a372013-01-29 00:08:32 -0800934 for patch in recipe.get('patches', ()):
935 if isinstance(patch, tuple):
936 url, checksum = patch
937 fn = os.path.join(DEPSRC, os.path.basename(url))
938 verifyThirdPartyFile(url, checksum, fn)
939 else:
940 # patch is a file in the source directory
941 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000942 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
943 shellQuote(fn),))
944
Ned Deily4a96a372013-01-29 00:08:32 -0800945 for patchscript in recipe.get('patchscripts', ()):
946 if isinstance(patchscript, tuple):
947 url, checksum = patchscript
948 fn = os.path.join(DEPSRC, os.path.basename(url))
949 verifyThirdPartyFile(url, checksum, fn)
950 else:
951 # patch is a file in the source directory
952 fn = os.path.join(curdir, patchscript)
953 if fn.endswith('.bz2'):
954 runCommand('bunzip2 -fk %s' % shellQuote(fn))
955 fn = fn[:-4]
956 runCommand('sh %s' % shellQuote(fn))
957 os.unlink(fn)
958
Ned Deily94764b22013-10-27 19:49:29 -0700959 if 'buildDir' in recipe:
960 os.chdir(recipe['buildDir'])
961
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000962 if configure is not None:
963 configure_args = [
964 "--prefix=/usr/local",
965 "--enable-static",
966 "--disable-shared",
967 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
968 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000969
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000970 if 'configure_pre' in recipe:
971 args = list(recipe['configure_pre'])
972 if '--disable-static' in args:
973 configure_args.remove('--enable-static')
974 if '--enable-shared' in args:
975 configure_args.remove('--disable-shared')
976 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000977
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000978 if recipe.get('useLDFlags', 1):
979 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -0500980 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -0800981 "-I%s/usr/local/include"%(
982 recipe.get('extra_cflags', ''),
983 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000984 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000985 shellQuote(basedir)[1:-1],),
Ned Deily8c9bb722018-01-30 07:42:14 -0500986 "LDFLAGS=-mmacosx-version-min=%s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800987 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000988 shellQuote(basedir)[1:-1],
989 ' -arch '.join(archList)),
990 ])
991 else:
992 configure_args.extend([
Ned Deily8c9bb722018-01-30 07:42:14 -0500993 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deily4a96a372013-01-29 00:08:32 -0800994 "-I%s/usr/local/include"%(
995 recipe.get('extra_cflags', ''),
996 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000997 ' -arch '.join(archList),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000998 shellQuote(basedir)[1:-1],),
999 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001000
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001001 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -08001002 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001003
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001004 configure_args.insert(0, configure)
1005 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001006
Ned Deily4a96a372013-01-29 00:08:32 -08001007 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001008 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001009
Ned Deily5d3febf2014-12-13 00:17:46 -08001010 if buildrecipe is not None:
1011 # call special-case build recipe, e.g. for openssl
1012 buildrecipe(basedir, archList)
1013
1014 if install is not None:
1015 print("Running install for %s"%(name,))
1016 runCommand('{ ' + install + ' ;} 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001017
Ned Deily4a96a372013-01-29 00:08:32 -08001018 print("Done %s"%(name,))
1019 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001020
1021 os.chdir(curdir)
1022
1023def buildLibraries():
1024 """
1025 Build our dependencies into $WORKDIR/libraries/usr/local
1026 """
Ned Deily4a96a372013-01-29 00:08:32 -08001027 print("")
1028 print("Building required libraries")
1029 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001030 universal = os.path.join(WORKDIR, 'libraries')
1031 os.mkdir(universal)
1032 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1033 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1034
Ronald Oussoren1943f862009-03-30 19:39:14 +00001035 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001036 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001037
1038
1039
1040def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001041 # This stores the documentation as Resources/English.lproj/Documentation
Mike53f7a7c2017-12-14 14:04:53 +03001042 # inside the framework. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -08001043 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001044 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001045 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001046 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001047 curDir = os.getcwd()
1048 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -07001049 runCommand('make clean')
Ned Deily4c7532e2017-07-23 16:39:54 -04001050 # Create virtual environment for docs builds with blurb and sphinx
1051 runCommand('make venv')
1052 runCommand('make html PYTHON=venv/bin/python')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001053 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001054 if not os.path.exists(docdir):
1055 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001056 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001057
1058
1059def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -08001060 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001061
1062 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1063 rootDir = os.path.join(WORKDIR, '_root')
1064
1065 if os.path.exists(buildDir):
1066 shutil.rmtree(buildDir)
1067 if os.path.exists(rootDir):
1068 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +00001069 os.makedirs(buildDir)
1070 os.makedirs(rootDir)
1071 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001072 curdir = os.getcwd()
1073 os.chdir(buildDir)
1074
Thomas Wouters477c8d52006-05-27 19:21:47 +00001075 # Extract the version from the configure file, needed to calculate
1076 # several paths.
1077 version = getVersion()
1078
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001079 # Since the extra libs are not in their installed framework location
1080 # during the build, augment the library path so that the interpreter
1081 # will find them during its extension import sanity checks.
1082 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1083 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -08001084 print("Running configure...")
Ned Deily8c9bb722018-01-30 07:42:14 -05001085 runCommand("%s -C --enable-framework --enable-universalsdk=/ "
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001086 "--with-universal-archs=%s "
1087 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -08001088 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +00001089 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -07001090 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ned Deily8c9bb722018-01-30 07:42:14 -05001091 shellQuote(os.path.join(SRCDIR, 'configure')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001092 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001093 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -08001094 (' ', '--without-ensurepip ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +00001095 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +00001096 shellQuote(WORKDIR)[1:-1]))
1097
Ned Deilyb364d9f2017-07-24 04:58:43 -04001098 # Look for environment value BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS
1099 # and, if defined, append its value to the make command. This allows
1100 # us to pass in version control tags, like GITTAG, to a build from a
1101 # tarball rather than from a vcs checkout, thus eliminating the need
1102 # to have a working copy of the vcs program on the build machine.
1103 #
1104 # A typical use might be:
1105 # export BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS=" \
1106 # GITVERSION='echo 123456789a' \
1107 # GITTAG='echo v3.6.0' \
1108 # GITBRANCH='echo 3.6'"
1109
1110 make_extras = os.getenv("BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS")
1111 if make_extras:
1112 make_cmd = "make " + make_extras
1113 else:
1114 make_cmd = "make"
1115 print("Running " + make_cmd)
1116 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001117
Ned Deily4a96a372013-01-29 00:08:32 -08001118 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001119 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001120 shellQuote(rootDir)))
1121
Ned Deily4a96a372013-01-29 00:08:32 -08001122 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001123 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1124 shellQuote(rootDir)))
1125
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001126 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001127 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001128 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1129 runCommand("mv %s/* %s"%(
1130 shellQuote(os.path.join(
1131 WORKDIR, 'libraries', 'Library', 'Frameworks',
1132 'Python.framework', 'Versions', getVersion(),
1133 'lib')),
1134 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1135 'Python.framework', 'Versions', getVersion(),
1136 'lib'))))
1137
Ned Deilydde4f632016-09-12 09:39:23 -04001138 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1139 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1140 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1141 # create directory for OpenSSL certificates
1142 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1143 os.makedirs(sslDir)
Ned Deily050fcd52013-10-26 03:16:44 -07001144
Ned Deily4a96a372013-01-29 00:08:32 -08001145 print("Fix file modes")
Thomas Wouters89f507f2006-12-13 04:49:30 +00001146 gid = grp.getgrnam('admin').gr_gid
1147
Ned Deily4a96a372013-01-29 00:08:32 -08001148 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001149 for dirpath, dirnames, filenames in os.walk(frmDir):
1150 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001151 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001152 os.chown(os.path.join(dirpath, dn), -1, gid)
1153
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154 for fn in filenames:
1155 if os.path.islink(fn):
1156 continue
1157
1158 # "chmod g+w $fn"
1159 p = os.path.join(dirpath, fn)
1160 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001161 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1162 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001163
Ned Deily4a96a372013-01-29 00:08:32 -08001164 if fn in EXPECTED_SHARED_LIBS:
1165 # check to see that this file was linked with the
1166 # expected library path and version
1167 data = captureCommand("otool -L %s" % shellQuote(p))
1168 for sl in EXPECTED_SHARED_LIBS[fn]:
1169 if ("\t%s " % sl) not in data:
1170 print("Expected shared lib %s was not linked with %s"
1171 % (sl, p))
1172 shared_lib_error = True
1173
1174 if shared_lib_error:
1175 fatal("Unexpected shared library errors.")
1176
Ned Deilye59e4c52011-01-29 18:56:28 +00001177 if PYTHON_3:
1178 LDVERSION=None
1179 VERSION=None
1180 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001181
Ned Deilye59e4c52011-01-29 18:56:28 +00001182 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001183 for ln in fp:
1184 if ln.startswith('VERSION='):
1185 VERSION=ln.split()[1]
1186 if ln.startswith('ABIFLAGS='):
1187 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001188 if ln.startswith('LDVERSION='):
1189 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001190 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001191
Ned Deilye59e4c52011-01-29 18:56:28 +00001192 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1193 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1194 config_suffix = '-' + LDVERSION
Ned Deily652bad42016-08-15 14:37:14 -04001195 if getVersionMajorMinor() >= (3, 6):
1196 config_suffix = config_suffix + '-darwin'
Ned Deilye59e4c52011-01-29 18:56:28 +00001197 else:
1198 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001199
Thomas Wouters477c8d52006-05-27 19:21:47 +00001200 # We added some directories to the search path during the configure
1201 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001202 # the end-users system. Also remove the directories from _sysconfigdata.py
1203 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001204
Ned Deilya4f6b002013-10-25 00:47:38 -07001205 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1206 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1207
Ned Deilya4f6b002013-10-25 00:47:38 -07001208 # fix Makefile
1209 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1210 fp = open(path, 'r')
1211 data = fp.read()
1212 fp.close()
1213
1214 for p in (include_path, lib_path):
1215 data = data.replace(" " + p, '')
1216 data = data.replace(p + " ", '')
1217
1218 fp = open(path, 'w')
1219 fp.write(data)
1220 fp.close()
1221
Ned Deily652bad42016-08-15 14:37:14 -04001222 # fix _sysconfigdata
Ned Deilya4f6b002013-10-25 00:47:38 -07001223 #
1224 # TODO: make this more robust! test_sysconfig_module of
1225 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1226 # the output from get_config_var in both sysconfig and
1227 # distutils.sysconfig is exactly the same for both CFLAGS and
1228 # LDFLAGS. The fixing up is now complicated by the pretty
1229 # printing in _sysconfigdata.py. Also, we are using the
1230 # pprint from the Python running the installer build which
1231 # may not cosmetically format the same as the pprint in the Python
1232 # being built (and which is used to originally generate
1233 # _sysconfigdata.py).
1234
1235 import pprint
Ned Deily652bad42016-08-15 14:37:14 -04001236 if getVersionMajorMinor() >= (3, 6):
Zachary Warec4b53af2016-09-09 17:59:49 -07001237 # XXX this is extra-fragile
1238 path = os.path.join(path_to_lib, '_sysconfigdata_m_darwin_darwin.py')
Ned Deily652bad42016-08-15 14:37:14 -04001239 else:
1240 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1241 fp = open(path, 'r')
1242 data = fp.read()
1243 fp.close()
1244 # create build_time_vars dict
1245 exec(data)
1246 vars = {}
1247 for k, v in build_time_vars.items():
1248 if type(v) == type(''):
1249 for p in (include_path, lib_path):
1250 v = v.replace(' ' + p, '')
1251 v = v.replace(p + ' ', '')
1252 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001253
Ned Deily652bad42016-08-15 14:37:14 -04001254 fp = open(path, 'w')
1255 # duplicated from sysconfig._generate_posix_vars()
1256 fp.write('# system configuration generated and used by'
1257 ' the sysconfig module\n')
1258 fp.write('build_time_vars = ')
1259 pprint.pprint(vars, stream=fp)
1260 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001261
1262 # Add symlinks in /usr/local/bin, using relative links
1263 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1264 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1265 'Python.framework', 'Versions', version, 'bin')
1266 if os.path.exists(usr_local_bin):
1267 shutil.rmtree(usr_local_bin)
1268 os.makedirs(usr_local_bin)
1269 for fn in os.listdir(
1270 os.path.join(frmDir, 'Versions', version, 'bin')):
1271 os.symlink(os.path.join(to_framework, fn),
1272 os.path.join(usr_local_bin, fn))
1273
1274 os.chdir(curdir)
1275
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001276 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001277 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001278 # with an already installed version of python 2
1279 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1280 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001281
1282def patchFile(inPath, outPath):
1283 data = fileContents(inPath)
1284 data = data.replace('$FULL_VERSION', getFullVersion())
1285 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001286 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001287 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001288 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001289 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001290
1291 # This one is not handy as a template variable
1292 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001293 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001294 fp.write(data)
1295 fp.close()
1296
1297def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001298 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001299 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001300 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001301 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001302 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001303 fp.write(data)
1304 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001305 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001306
1307
1308
1309def packageFromRecipe(targetDir, recipe):
1310 curdir = os.getcwd()
1311 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001312 # The major version (such as 2.5) is included in the package name
1313 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001314 # common.
1315 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001316 srcdir = recipe.get('source')
1317 pkgroot = recipe.get('topdir', srcdir)
1318 postflight = recipe.get('postflight')
1319 readme = textwrap.dedent(recipe['readme'])
1320 isRequired = recipe.get('required', True)
1321
Ned Deily4a96a372013-01-29 00:08:32 -08001322 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001323
1324 # Substitute some variables
1325 textvars = dict(
1326 VER=getVersion(),
1327 FULLVER=getFullVersion(),
1328 )
1329 readme = readme % textvars
1330
1331 if pkgroot is not None:
1332 pkgroot = pkgroot % textvars
1333 else:
1334 pkgroot = '/'
1335
1336 if srcdir is not None:
1337 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1338 srcdir = srcdir % textvars
1339
1340 if postflight is not None:
1341 postflight = os.path.abspath(postflight)
1342
1343 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1344 os.makedirs(packageContents)
1345
1346 if srcdir is not None:
1347 os.chdir(srcdir)
1348 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1349 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1350 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1351
1352 fn = os.path.join(packageContents, 'PkgInfo')
1353 fp = open(fn, 'w')
1354 fp.write('pmkrpkg1')
1355 fp.close()
1356
1357 rsrcDir = os.path.join(packageContents, "Resources")
1358 os.mkdir(rsrcDir)
1359 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1360 fp.write(readme)
1361 fp.close()
1362
1363 if postflight is not None:
1364 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1365
1366 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001367 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001368 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001369 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1370 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1371 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001372 CFBundleShortVersionString=vers,
1373 IFMajorVersion=major,
1374 IFMinorVersion=minor,
1375 IFPkgFormatVersion=0.10000000149011612,
1376 IFPkgFlagAllowBackRev=False,
1377 IFPkgFlagAuthorizationAction="RootAuthorization",
1378 IFPkgFlagDefaultLocation=pkgroot,
1379 IFPkgFlagFollowLinks=True,
1380 IFPkgFlagInstallFat=True,
1381 IFPkgFlagIsRequired=isRequired,
1382 IFPkgFlagOverwritePermissions=False,
1383 IFPkgFlagRelocatable=False,
1384 IFPkgFlagRestartAction="NoRestart",
1385 IFPkgFlagRootVolumeOnly=True,
1386 IFPkgFlagUpdateInstalledLangauges=False,
1387 )
1388 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1389
1390 pl = Plist(
1391 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001392 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001393 IFPkgDescriptionVersion=vers,
1394 )
1395 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1396
1397 finally:
1398 os.chdir(curdir)
1399
1400
1401def makeMpkgPlist(path):
1402
1403 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001404 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001405
1406 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001407 CFBundleGetInfoString="Python %s"%(vers,),
1408 CFBundleIdentifier='org.python.Python',
1409 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001410 CFBundleShortVersionString=vers,
1411 IFMajorVersion=major,
1412 IFMinorVersion=minor,
1413 IFPkgFlagComponentDirectory="Contents/Packages",
1414 IFPkgFlagPackageList=[
1415 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001416 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001417 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001418 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001419 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001420 ],
1421 IFPkgFormatVersion=0.10000000149011612,
1422 IFPkgFlagBackgroundScaling="proportional",
1423 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001424 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001425 )
1426
1427 writePlist(pl, path)
1428
1429
1430def buildInstaller():
1431
1432 # Zap all compiled files
1433 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1434 for fn in filenames:
1435 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1436 os.unlink(os.path.join(dirpath, fn))
1437
1438 outdir = os.path.join(WORKDIR, 'installer')
1439 if os.path.exists(outdir):
1440 shutil.rmtree(outdir)
1441 os.mkdir(outdir)
1442
Ronald Oussoren1943f862009-03-30 19:39:14 +00001443 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001444 pkgcontents = os.path.join(pkgroot, 'Packages')
1445 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001446 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001447 packageFromRecipe(pkgcontents, recipe)
1448
1449 rsrcDir = os.path.join(pkgroot, 'Resources')
1450
1451 fn = os.path.join(pkgroot, 'PkgInfo')
1452 fp = open(fn, 'w')
1453 fp.write('pmkrpkg1')
1454 fp.close()
1455
1456 os.mkdir(rsrcDir)
1457
1458 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1459 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001460 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001461 IFPkgDescriptionVersion=getVersion(),
1462 )
1463
1464 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1465 for fn in os.listdir('resources'):
1466 if fn == '.svn': continue
1467 if fn.endswith('.jpg'):
1468 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1469 else:
1470 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1471
Thomas Wouters477c8d52006-05-27 19:21:47 +00001472
1473def installSize(clear=False, _saved=[]):
1474 if clear:
1475 del _saved[:]
1476 if not _saved:
1477 data = captureCommand("du -ks %s"%(
1478 shellQuote(os.path.join(WORKDIR, '_root'))))
1479 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1480 return _saved[0]
1481
1482
1483def buildDMG():
1484 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001485 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001486 """
1487 outdir = os.path.join(WORKDIR, 'diskimage')
1488 if os.path.exists(outdir):
1489 shutil.rmtree(outdir)
1490
1491 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001492 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001493 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001494 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001495 imagepath = imagepath + '.dmg'
1496
1497 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001498 volname='Python %s'%(getFullVersion())
1499 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1500 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001501 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001502 shellQuote(imagepath + ".tmp.dmg" )))
1503
1504
1505 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1506 os.mkdir(os.path.join(WORKDIR, "mnt"))
1507 runCommand("hdiutil attach %s -mountroot %s"%(
1508 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1509
1510 # Custom icon for the DMG, shown when the DMG is mounted.
1511 shutil.copy("../Icons/Disk Image.icns",
1512 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001513 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001514 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1515
1516 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1517
1518 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1519 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1520 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1521 setIcon(imagepath, "../Icons/Disk Image.icns")
1522
1523 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001524
1525 return imagepath
1526
1527
1528def setIcon(filePath, icnsPath):
1529 """
1530 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001531 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001532
Ronald Oussoren70050672010-04-30 15:00:26 +00001533 dirPath = os.path.normpath(os.path.dirname(__file__))
1534 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001535 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1536 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1537 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001538 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1539 if not os.path.exists(appPath):
1540 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001541 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1542 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001543
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001544 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1545 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001546
1547def main():
1548 # First parse options and check if we can perform our work
1549 parseOptions()
1550 checkEnvironment()
1551
Ronald Oussoren1943f862009-03-30 19:39:14 +00001552 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001553 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001554 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001555
1556 if os.path.exists(WORKDIR):
1557 shutil.rmtree(WORKDIR)
1558 os.mkdir(WORKDIR)
1559
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001560 os.environ['LC_ALL'] = 'C'
1561
Thomas Wouters477c8d52006-05-27 19:21:47 +00001562 # Then build third-party libraries such as sleepycat DB4.
1563 buildLibraries()
1564
1565 # Now build python itself
1566 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001567
1568 # And then build the documentation
1569 # Remove the Deployment Target from the shell
1570 # environment, it's no longer needed and
1571 # an unexpected build target can cause problems
1572 # when Sphinx and its dependencies need to
1573 # be (re-)installed.
1574 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001575 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001576
1577
1578 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001579 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001580 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001581 fn = os.path.join(folder, "License.rtf")
1582 patchFile("resources/License.rtf", fn)
1583 fn = os.path.join(folder, "ReadMe.rtf")
1584 patchFile("resources/ReadMe.rtf", fn)
1585 fn = os.path.join(folder, "Update Shell Profile.command")
1586 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilydde4f632016-09-12 09:39:23 -04001587 fn = os.path.join(folder, "Install Certificates.command")
1588 patchScript("resources/install_certificates.command", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001589 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001590 setIcon(folder, "../Icons/Python Folder.icns")
1591
1592 # Create the installer
1593 buildInstaller()
1594
1595 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001596 patchFile('resources/ReadMe.rtf',
1597 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001598
1599 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001600 patchFile('resources/License.rtf',
1601 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001602
1603 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001604 fp.write("# BUILD INFO\n")
1605 fp.write("# Date: %s\n" % time.ctime())
1606 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001607 fp.close()
1608
Thomas Wouters477c8d52006-05-27 19:21:47 +00001609 # And copy it to a DMG
1610 buildDMG()
1611
Thomas Wouters477c8d52006-05-27 19:21:47 +00001612if __name__ == "__main__":
1613 main()