blob: 674f025db3e160da1b3a6a4f8dc5734fd911d742 [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deily4a96a372013-01-29 00:08:32 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
Ned Deily9978a932014-04-09 16:15:20 -070011Python 2.4. However, as of Python 3.4.1, Doc builds require an external
12sphinx-build and the current versions of Sphinx now require at least
13Python 2.6.
Thomas Wouters477c8d52006-05-27 19:21:47 +000014
Ned Deily4a96a372013-01-29 00:08:32 -080015In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
Ned Deilyb364d9f2017-07-24 04:58:43 -040016requires an installed third-party version of
Ned Deily4a96a372013-01-29 00:08:32 -080017Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
18(for 10.6 or later) installed in /Library/Frameworks. When installed,
19the Python built by this script will attempt to dynamically link first to
20Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
21back to the ones in /System/Library/Framework. For the build, we recommend
22installing the most recent ActiveTcl 8.4 or 8.5 version.
23
2432-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
25and the installation of additional components, such as a newer Python
Ned Deilyb364d9f2017-07-24 04:58:43 -040026(2.5 is needed for Python parser updates) and for the documentation
Ned Deily9978a932014-04-09 16:15:20 -070027build either svn (pre-3.4.1) or sphinx-build (3.4.1 and later).
Ned Deily4a96a372013-01-29 00:08:32 -080028
Thomas Wouters477c8d52006-05-27 19:21:47 +000029Usage: see USAGE variable in the script.
30"""
Ned Deily4a96a372013-01-29 00:08:32 -080031import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
32try:
33 import urllib2 as urllib_request
34except ImportError:
35 import urllib.request as urllib_request
36
37STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38 | stat.S_IRGRP | stat.S_IXGRP
39 | stat.S_IROTH | stat.S_IXOTH )
40
41STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
42 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
43 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
Thomas Wouters89f507f2006-12-13 04:49:30 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000047
48from plistlib import Plist
49
Thomas Wouters477c8d52006-05-27 19:21:47 +000050try:
51 from plistlib import writePlist
52except ImportError:
53 # We're run using python2.3
54 def writePlist(plist, path):
55 plist.write(path)
56
57def shellQuote(value):
58 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000059 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
Ned Deily5d3febf2014-12-13 00:17:46 -080065 """
66 Return the unquoted value of a variable from a file..
67 QUOTED_VALUE='quotes' -> str('quotes')
68 UNQUOTED_VALUE=noquotes -> str('noquotes')
69 """
Thomas Wouters477c8d52006-05-27 19:21:47 +000070 variable = variable + '='
71 for ln in open(fn, 'r'):
72 if ln.startswith(variable):
73 value = ln[len(variable):].strip()
Ned Deily5d3febf2014-12-13 00:17:46 -080074 return value.strip("\"'")
Ned Deily4a96a372013-01-29 00:08:32 -080075 raise RuntimeError("Cannot find variable %s" % variable[:-1])
76
77_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
79def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080080 global _cache_getVersion
81 if _cache_getVersion is None:
82 _cache_getVersion = grepValue(
83 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
84 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000085
Ned Deily4a96a372013-01-29 00:08:32 -080086def getVersionMajorMinor():
87 return tuple([int(n) for n in getVersion().split('.', 2)])
88
89_cache_getFullVersion = None
90
Thomas Wouters477c8d52006-05-27 19:21:47 +000091def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080092 global _cache_getFullVersion
93 if _cache_getFullVersion is not None:
94 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000095 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
96 for ln in open(fn):
97 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080098 _cache_getFullVersion = ln.split()[-1][1:-1]
99 return _cache_getFullVersion
100 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000101
Ned Deily5d3febf2014-12-13 00:17:46 -0800102FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
103FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
Ned Deilydde4f632016-09-12 09:39:23 -0400104FW_SSL_DIRECTORY = "--undefined--" # initialized in parseOptions
Ned Deily5d3febf2014-12-13 00:17:46 -0800105
Thomas Wouters89f507f2006-12-13 04:49:30 +0000106# The directory we'll use to create the build (will be erased and recreated)
107WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000108
Thomas Wouters89f507f2006-12-13 04:49:30 +0000109# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000110# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000111DEPSRC = os.path.join(WORKDIR, 'third-party')
112DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000113
114# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000115
116### There are some issues with the SDK selection below here,
117### The resulting binary doesn't work on all platforms that
118### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300119### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000120###
121##if int(os.uname()[2].split('.')[0]) == 8:
122## # Explicitly use the 10.4u (universal) SDK when
123## # building on 10.4, the system headers are not
124## # useable for a universal build
125## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
126##else:
127## SDKPATH = "/"
128
Thomas Wouters89f507f2006-12-13 04:49:30 +0000129SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000130
Ronald Oussoren1943f862009-03-30 19:39:14 +0000131universal_opts_map = { '32-bit': ('i386', 'ppc',),
132 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000133 'intel': ('i386', 'x86_64'),
134 '3-way': ('ppc', 'i386', 'x86_64'),
135 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
136default_target_map = {
137 '64-bit': '10.5',
138 '3-way': '10.5',
139 'intel': '10.5',
140 'all': '10.5',
141}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000142
143UNIVERSALOPTS = tuple(universal_opts_map.keys())
144
145UNIVERSALARCHS = '32-bit'
146
147ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148
Ezio Melotti42da6632011-03-15 05:18:48 +0200149# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000150SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000151 os.path.dirname(
152 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000153 os.path.abspath(__file__
154 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000155
Ronald Oussoren1943f862009-03-30 19:39:14 +0000156# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
157DEPTARGET = '10.3'
158
Ned Deily04cdfa12014-06-25 13:36:14 -0700159def getDeptargetTuple():
160 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
161
162def getTargetCompilers():
163 target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800164 '10.3': ('gcc-4.0', 'g++-4.0'),
165 '10.4': ('gcc-4.0', 'g++-4.0'),
166 '10.5': ('gcc-4.2', 'g++-4.2'),
167 '10.6': ('gcc-4.2', 'g++-4.2'),
Ned Deily04cdfa12014-06-25 13:36:14 -0700168 }
169 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000170
Ned Deily04cdfa12014-06-25 13:36:14 -0700171CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000172
Ned Deily5d3febf2014-12-13 00:17:46 -0800173PYTHON_3 = getVersionMajorMinor() >= (3, 0)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000174
Thomas Wouters89f507f2006-12-13 04:49:30 +0000175USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000176 Usage: build_python [options]
177
178 Options:
179 -? or -h: Show this message
180 -b DIR
181 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
182 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
183 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
184 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000185 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
186 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000187""")% globals()
188
Ned Deily4a96a372013-01-29 00:08:32 -0800189# Dict of object file names with shared library names to check after building.
190# This is to ensure that we ended up dynamically linking with the shared
191# library paths and versions we expected. For example:
192# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
193# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
194# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
195EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000196
Ned Deily5d3febf2014-12-13 00:17:46 -0800197# List of names of third party software built with this installer.
198# The names will be inserted into the rtf version of the License.
199THIRD_PARTY_LIBS = []
200
Thomas Wouters477c8d52006-05-27 19:21:47 +0000201# Instructions for building libraries that are necessary for building a
202# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000203# [The recipes are defined here for convenience but instantiated later after
204# command line options have been processed.]
205def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000206 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000207
Ned Deily04cdfa12014-06-25 13:36:14 -0700208 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800209
Ned Deilydde4f632016-09-12 09:39:23 -0400210 # Since Apple removed the header files for the deprecated system
211 # OpenSSL as of the Xcode 7 release (for OS X 10.10+), we do not
212 # have much choice but to build our own copy here, too.
Ned Deily5d3febf2014-12-13 00:17:46 -0800213
Ned Deilydde4f632016-09-12 09:39:23 -0400214 result.extend([
Ned Deily5d3febf2014-12-13 00:17:46 -0800215 dict(
Ned Deilycfcd7672017-03-04 02:33:25 -0500216 name="OpenSSL 1.0.2k",
217 url="https://www.openssl.org/source/openssl-1.0.2k.tar.gz",
218 checksum='f965fc0bf01bf882b31314b61391ae65',
Ned Deily5d3febf2014-12-13 00:17:46 -0800219 patches=[
220 "openssl_sdk_makedepend.patch",
221 ],
222 buildrecipe=build_universal_openssl,
223 configure=None,
224 install=None,
225 ),
Ned Deilydde4f632016-09-12 09:39:23 -0400226 ])
Ned Deily5d3febf2014-12-13 00:17:46 -0800227
Ned Deilyaa6a2122013-11-23 03:30:11 -0800228# Disable for now
Ned Deilyed730102014-11-14 18:55:05 -0800229 if False: # if getDeptargetTuple() > (10, 5):
Ned Deily5b3582c2013-10-25 00:41:46 -0700230 result.extend([
231 dict(
232 name="Tcl 8.5.15",
233 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
234 checksum='f3df162f92c69b254079c4d0af7a690f',
235 buildDir="unix",
236 configure_pre=[
237 '--enable-shared',
238 '--enable-threads',
239 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
240 ],
241 useLDFlags=False,
242 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
243 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
244 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
245 },
246 ),
247 dict(
248 name="Tk 8.5.15",
249 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
250 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700251 patches=[
252 "issue19373_tk_8_5_15_source.patch",
253 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700254 buildDir="unix",
255 configure_pre=[
256 '--enable-aqua',
257 '--enable-shared',
258 '--enable-threads',
259 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
260 ],
261 useLDFlags=False,
262 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'%{
263 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
264 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
265 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
266 },
267 ),
268 ])
269
Ned Deilyed730102014-11-14 18:55:05 -0800270 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800271 result.extend([
272 dict(
Ned Deilyb3b07672016-09-05 17:31:14 -0700273 name="XZ 5.2.2",
274 url="http://tukaani.org/xz/xz-5.2.2.tar.gz",
275 checksum='7cf6a8544a7dae8e8106fdf7addfa28c',
Ned Deily4a96a372013-01-29 00:08:32 -0800276 configure_pre=[
277 '--disable-dependency-tracking',
278 ]
279 ),
280 ])
281
282 result.extend([
283 dict(
284 name="NCurses 5.9",
285 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
286 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
287 configure_pre=[
288 "--enable-widec",
289 "--without-cxx",
290 "--without-cxx-binding",
291 "--without-ada",
292 "--without-curses-h",
293 "--enable-shared",
294 "--with-shared",
295 "--without-debug",
296 "--without-normal",
297 "--without-tests",
298 "--without-manpages",
299 "--datadir=/usr/share",
300 "--sysconfdir=/etc",
301 "--sharedstatedir=/usr/com",
302 "--with-terminfo-dirs=/usr/share/terminfo",
303 "--with-default-terminfo-dir=/usr/share/terminfo",
304 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
305 ],
306 patchscripts=[
307 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
308 "f54bf02a349f96a7c4f0d00922f3a0d4"),
309 ],
310 useLDFlags=False,
311 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
312 shellQuote(os.path.join(WORKDIR, 'libraries')),
313 shellQuote(os.path.join(WORKDIR, 'libraries')),
314 getVersion(),
315 ),
316 ),
317 dict(
Mariattaca7562a2017-12-04 18:04:49 -0800318 name="SQLite 3.21.0",
319 url="https://www.sqlite.org/2017/sqlite-autoconf-3210000.tar.gz",
320 checksum='7913de4c3126ba3c24689cb7a199ea31',
Ned Deily4a96a372013-01-29 00:08:32 -0800321 extra_cflags=('-Os '
Ned Deilyb3b07672016-09-05 17:31:14 -0700322 '-DSQLITE_ENABLE_FTS5 '
Ned Deily4a96a372013-01-29 00:08:32 -0800323 '-DSQLITE_ENABLE_FTS4 '
324 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
Ned Deily9625bf52017-12-04 21:50:29 -0500325 '-DSQLITE_ENABLE_JSON1 '
Ned Deily4a96a372013-01-29 00:08:32 -0800326 '-DSQLITE_ENABLE_RTREE '
327 '-DSQLITE_TCL=0 '
328 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
329 configure_pre=[
330 '--enable-threadsafe',
331 '--enable-shared=no',
332 '--enable-static=yes',
333 '--disable-readline',
334 '--disable-dependency-tracking',
335 ]
336 ),
337 ])
338
Ned Deily04cdfa12014-06-25 13:36:14 -0700339 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000340 result.extend([
341 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000342 name="Bzip2 1.0.6",
343 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
344 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000345 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800346 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
347 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000348 shellQuote(os.path.join(WORKDIR, 'libraries')),
349 ' -arch '.join(ARCHLIST),
350 SDKPATH,
351 ),
352 ),
353 dict(
354 name="ZLib 1.2.3",
355 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
356 checksum='debc62758716a169df9f62e6ab2bc634',
357 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800358 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
359 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000360 shellQuote(os.path.join(WORKDIR, 'libraries')),
361 ' -arch '.join(ARCHLIST),
362 SDKPATH,
363 ),
364 ),
365 dict(
366 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000367 name="GNU Readline 6.1.2",
368 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
369 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000370 patchlevel='0',
371 patches=[
372 # The readline maintainers don't do actual micro releases, but
373 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800374 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
375 'c642f2e84d820884b0bf9fd176bc6c3f'),
376 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
377 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000378 ]
379 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000380 ])
381
Ned Deily4f7ff782011-01-15 05:29:12 +0000382 if not PYTHON_3:
383 result.extend([
384 dict(
385 name="Sleepycat DB 4.7.25",
386 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
387 checksum='ec2b87e833779681a0c3a814aa71359e',
388 buildDir="build_unix",
389 configure="../dist/configure",
390 configure_pre=[
391 '--includedir=/usr/local/include/db4',
392 ]
393 ),
394 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000395
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000396 return result
397
Thomas Wouters477c8d52006-05-27 19:21:47 +0000398
Thomas Wouters477c8d52006-05-27 19:21:47 +0000399# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000400def pkg_recipes():
401 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
402 result = [
403 dict(
404 name="PythonFramework",
405 long_name="Python Framework",
406 source="/Library/Frameworks/Python.framework",
407 readme="""\
408 This package installs Python.framework, that is the python
409 interpreter and the standard library. This also includes Python
410 wrappers for lots of Mac OS X API's.
411 """,
412 postflight="scripts/postflight.framework",
413 selected='selected',
414 ),
415 dict(
416 name="PythonApplications",
417 long_name="GUI Applications",
418 source="/Applications/Python %(VER)s",
419 readme="""\
420 This package installs IDLE (an interactive Python IDE),
421 Python Launcher and Build Applet (create application bundles
422 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000423
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000424 It also installs a number of examples and demos.
425 """,
426 required=False,
427 selected='selected',
428 ),
429 dict(
430 name="PythonUnixTools",
431 long_name="UNIX command-line tools",
432 source="/usr/local/bin",
433 readme="""\
434 This package installs the unix tools in /usr/local/bin for
435 compatibility with older releases of Python. This package
436 is not necessary to use Python.
437 """,
438 required=False,
439 selected='selected',
440 ),
441 dict(
442 name="PythonDocumentation",
443 long_name="Python Documentation",
444 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
445 source="/pydocs",
446 readme="""\
447 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800448 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000449 """,
450 postflight="scripts/postflight.documentation",
451 required=False,
452 selected='selected',
453 ),
454 dict(
455 name="PythonProfileChanges",
456 long_name="Shell profile updater",
457 readme="""\
458 This packages updates your shell profile to make sure that
459 the Python tools are found by your shell in preference of
460 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000461
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000462 If you don't install this package you'll have to add
463 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
464 to your PATH by hand.
465 """,
466 postflight="scripts/postflight.patch-profile",
467 topdir="/Library/Frameworks/Python.framework",
468 source="/empty-dir",
469 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800470 selected='selected',
471 ),
472 dict(
473 name="PythonInstallPip",
474 long_name="Install or upgrade pip",
475 readme="""\
476 This package installs (or upgrades from an earlier version)
477 pip, a tool for installing and managing Python packages.
478 """,
479 postflight="scripts/postflight.ensurepip",
480 topdir="/Library/Frameworks/Python.framework",
481 source="/empty-dir",
482 required=False,
483 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000484 ),
485 ]
486
Ned Deily04cdfa12014-06-25 13:36:14 -0700487 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000488 result.append(
489 dict(
490 name="PythonSystemFixes",
491 long_name="Fix system Python",
492 readme="""\
493 This package updates the system python installation on
494 Mac OS X 10.3 to ensure that you can build new python extensions
495 using that copy of python after installing this version.
496 """,
497 postflight="../Tools/fixapplepython23.py",
498 topdir="/Library/Frameworks/Python.framework",
499 source="/empty-dir",
500 required=False,
501 selected=unselected_for_python3,
502 )
503 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800504
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000505 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000506
Thomas Wouters477c8d52006-05-27 19:21:47 +0000507def fatal(msg):
508 """
509 A fatal error, bail out.
510 """
511 sys.stderr.write('FATAL: ')
512 sys.stderr.write(msg)
513 sys.stderr.write('\n')
514 sys.exit(1)
515
516def fileContents(fn):
517 """
518 Return the contents of the named file
519 """
Ned Deily4a96a372013-01-29 00:08:32 -0800520 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000521
522def runCommand(commandline):
523 """
Ezio Melotti13925002011-03-16 11:05:33 +0200524 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000525 unless the command fails.
526 """
527 fd = os.popen(commandline, 'r')
528 data = fd.read()
529 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000530 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000531 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800532 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000533
534 if VERBOSE:
535 sys.stdout.write(data); sys.stdout.flush()
536
537def captureCommand(commandline):
538 fd = os.popen(commandline, 'r')
539 data = fd.read()
540 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000541 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000542 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800543 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000544
545 return data
546
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000547def getTclTkVersion(configfile, versionline):
548 """
549 search Tcl or Tk configuration file for version line
550 """
551 try:
552 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300553 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000554 fatal("Framework configuration file not found: %s" % configfile)
555
556 for l in f:
557 if l.startswith(versionline):
558 f.close()
559 return l
560
561 fatal("Version variable %s not found in framework configuration file: %s"
562 % (versionline, configfile))
563
Thomas Wouters477c8d52006-05-27 19:21:47 +0000564def checkEnvironment():
565 """
566 Check that we're running on a supported system.
567 """
568
Ned Deilye59e4c52011-01-29 18:56:28 +0000569 if sys.version_info[0:2] < (2, 4):
570 fatal("This script must be run with Python 2.4 or later")
571
Thomas Wouters477c8d52006-05-27 19:21:47 +0000572 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000573 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000574
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000575 if int(platform.release().split('.')[0]) < 8:
576 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000577
578 if not os.path.exists(SDKPATH):
579 fatal("Please install the latest version of Xcode and the %s SDK"%(
580 os.path.basename(SDKPATH[:-4])))
581
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000582 # Because we only support dynamic load of only one major/minor version of
583 # Tcl/Tk, ensure:
584 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800585 # higher than the Apple-supplied system version in
586 # SDKROOT/System/Library/Frameworks
587 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
588 # in) SDKROOT/Library/Frameworks with the same version as the system
589 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000590
Ned Deily4a96a372013-01-29 00:08:32 -0800591 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000592 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800593 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000594 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800595 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000596 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800597 frameworks[framework] = os.readlink(sysfw)
598 if not os.path.exists(libfw):
599 fatal("Please install a link to a current %s %s as %s so "
600 "the user can override the system framework."
601 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000602 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000603 fatal("Version of %s must match %s" % (libfw, sysfw) )
604 if os.path.exists(usrfw):
605 fatal("Please rename %s to avoid possible dynamic load issues."
606 % usrfw)
607
Ned Deily4a96a372013-01-29 00:08:32 -0800608 if frameworks['Tcl'] != frameworks['Tk']:
609 fatal("The Tcl and Tk frameworks are not the same version.")
610
611 # add files to check after build
612 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
613 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
614 % frameworks['Tcl'],
615 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
616 % frameworks['Tk'],
617 ]
618
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000619 # Remove inherited environment variables which might influence build
620 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
621 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
622 for ev in list(os.environ):
623 for prefix in environ_var_prefixes:
624 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800625 print("INFO: deleting environment variable %s=%s" % (
626 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000627 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000628
Ned Deily4a96a372013-01-29 00:08:32 -0800629 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
630 if 'SDK_TOOLS_BIN' in os.environ:
631 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
632 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
633 # add its fixed location here if it exists
634 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
635 if os.path.isdir(OLD_DEVELOPER_TOOLS):
636 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
637 os.environ['PATH'] = base_path
638 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyb364d9f2017-07-24 04:58:43 -0400639 # Ensure we have access to sphinx-build.
640 # You may have to create a link in /usr/bin for it.
Ned Deily1ff32a92014-09-05 15:57:05 -0700641 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000642
Thomas Wouters89f507f2006-12-13 04:49:30 +0000643def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000644 """
645 Parse arguments and update global settings.
646 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000647 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800648 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily5d3febf2014-12-13 00:17:46 -0800649 global FW_VERSION_PREFIX
Ned Deilydde4f632016-09-12 09:39:23 -0400650 global FW_SSL_DIRECTORY
Thomas Wouters477c8d52006-05-27 19:21:47 +0000651
652 if args is None:
653 args = sys.argv[1:]
654
655 try:
656 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000657 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
658 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800659 except getopt.GetoptError:
660 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000661 sys.exit(1)
662
663 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800664 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665 sys.exit(1)
666
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000667 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000668 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000669 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800670 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000671 sys.exit(0)
672
673 elif k in ('-d', '--build-dir'):
674 WORKDIR=v
675
676 elif k in ('--third-party',):
677 DEPSRC=v
678
679 elif k in ('--sdk-path',):
680 SDKPATH=v
681
682 elif k in ('--src-dir',):
683 SRCDIR=v
684
Ronald Oussoren1943f862009-03-30 19:39:14 +0000685 elif k in ('--dep-target', ):
686 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000687 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000688
689 elif k in ('--universal-archs', ):
690 if v in UNIVERSALOPTS:
691 UNIVERSALARCHS = v
692 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000693 if deptarget is None:
694 # Select alternate default deployment
695 # target
696 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000697 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800698 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000699
Thomas Wouters477c8d52006-05-27 19:21:47 +0000700 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800701 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000702
703 SRCDIR=os.path.abspath(SRCDIR)
704 WORKDIR=os.path.abspath(WORKDIR)
705 SDKPATH=os.path.abspath(SDKPATH)
706 DEPSRC=os.path.abspath(DEPSRC)
707
Ned Deily04cdfa12014-06-25 13:36:14 -0700708 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000709
Ned Deily5d3febf2014-12-13 00:17:46 -0800710 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
Ned Deilydde4f632016-09-12 09:39:23 -0400711 FW_SSL_DIRECTORY = FW_VERSION_PREFIX[:] + ["etc", "openssl"]
Ned Deily5d3febf2014-12-13 00:17:46 -0800712
713 print("-- Settings:")
714 print(" * Source directory: %s" % SRCDIR)
715 print(" * Build directory: %s" % WORKDIR)
716 print(" * SDK location: %s" % SDKPATH)
717 print(" * Third-party source: %s" % DEPSRC)
718 print(" * Deployment target: %s" % DEPTARGET)
719 print(" * Universal archs: %s" % str(ARCHLIST))
720 print(" * C compiler: %s" % CC)
721 print(" * C++ compiler: %s" % CXX)
Ned Deily4a96a372013-01-29 00:08:32 -0800722 print("")
Ned Deily5d3febf2014-12-13 00:17:46 -0800723 print(" -- Building a Python %s framework at patch level %s"
724 % (getVersion(), getFullVersion()))
725 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000726
727def extractArchive(builddir, archiveName):
728 """
729 Extract a source archive into 'builddir'. Returns the path of the
730 extracted archive.
731
732 XXX: This function assumes that archives contain a toplevel directory
733 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700734 safe enough for almost anything we use. Unfortunately, it does not
735 work for current Tcl and Tk source releases where the basename of
736 the archive ends with "-src" but the uncompressed directory does not.
737 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000738 """
739 curdir = os.getcwd()
740 try:
741 os.chdir(builddir)
742 if archiveName.endswith('.tar.gz'):
743 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700744 if ((retval.startswith('tcl') or retval.startswith('tk'))
745 and retval.endswith('-src')):
746 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000747 if os.path.exists(retval):
748 shutil.rmtree(retval)
749 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
750
751 elif archiveName.endswith('.tar.bz2'):
752 retval = os.path.basename(archiveName[:-8])
753 if os.path.exists(retval):
754 shutil.rmtree(retval)
755 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
756
757 elif archiveName.endswith('.tar'):
758 retval = os.path.basename(archiveName[:-4])
759 if os.path.exists(retval):
760 shutil.rmtree(retval)
761 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
762
763 elif archiveName.endswith('.zip'):
764 retval = os.path.basename(archiveName[:-4])
765 if os.path.exists(retval):
766 shutil.rmtree(retval)
767 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
768
769 data = fp.read()
770 xit = fp.close()
771 if xit is not None:
772 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800773 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000774
775 return os.path.join(builddir, retval)
776
777 finally:
778 os.chdir(curdir)
779
Thomas Wouters477c8d52006-05-27 19:21:47 +0000780def downloadURL(url, fname):
781 """
782 Download the contents of the url into the file.
783 """
Ned Deily4a96a372013-01-29 00:08:32 -0800784 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000785 fpOut = open(fname, 'wb')
786 block = fpIn.read(10240)
787 try:
788 while block:
789 fpOut.write(block)
790 block = fpIn.read(10240)
791 fpIn.close()
792 fpOut.close()
793 except:
794 try:
795 os.unlink(fname)
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300796 except OSError:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000797 pass
798
Ned Deily4a96a372013-01-29 00:08:32 -0800799def verifyThirdPartyFile(url, checksum, fname):
800 """
801 Download file from url to filename fname if it does not already exist.
802 Abort if file contents does not match supplied md5 checksum.
803 """
804 name = os.path.basename(fname)
805 if os.path.exists(fname):
806 print("Using local copy of %s"%(name,))
807 else:
808 print("Did not find local copy of %s"%(name,))
809 print("Downloading %s"%(name,))
810 downloadURL(url, fname)
811 print("Archive for %s stored as %s"%(name, fname))
812 if os.system(
813 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
814 % (shellQuote(fname), checksum) ):
815 fatal('MD5 checksum mismatch for file %s' % fname)
816
Ned Deily5d3febf2014-12-13 00:17:46 -0800817def build_universal_openssl(basedir, archList):
818 """
819 Special case build recipe for universal build of openssl.
820
821 The upstream OpenSSL build system does not directly support
822 OS X universal builds. We need to build each architecture
823 separately then lipo them together into fat libraries.
824 """
825
826 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
827 # If we are building on a 10.4.x or earlier system,
828 # unilaterally disable assembly code building to avoid the problem.
829 no_asm = int(platform.release().split(".")[0]) < 9
830
831 def build_openssl_arch(archbase, arch):
832 "Build one architecture of openssl"
833 arch_opts = {
834 "i386": ["darwin-i386-cc"],
835 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
836 "ppc": ["darwin-ppc-cc"],
837 "ppc64": ["darwin64-ppc-cc"],
838 }
839 configure_opts = [
840 "no-krb5",
841 "no-idea",
842 "no-mdc2",
843 "no-rc5",
844 "no-zlib",
845 "enable-tlsext",
846 "no-ssl2",
847 "no-ssl3",
848 "no-ssl3-method",
849 # "enable-unit-test",
850 "shared",
851 "--install_prefix=%s"%shellQuote(archbase),
852 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
Ned Deilydde4f632016-09-12 09:39:23 -0400853 "--openssldir=%s"%os.path.join("/", *FW_SSL_DIRECTORY),
Ned Deily5d3febf2014-12-13 00:17:46 -0800854 ]
855 if no_asm:
856 configure_opts.append("no-asm")
857 runCommand(" ".join(["perl", "Configure"]
858 + arch_opts[arch] + configure_opts))
859 runCommand("make depend OSX_SDK=%s" % SDKPATH)
860 runCommand("make all OSX_SDK=%s" % SDKPATH)
861 runCommand("make install_sw OSX_SDK=%s" % SDKPATH)
862 # runCommand("make test")
863 return
864
865 srcdir = os.getcwd()
866 universalbase = os.path.join(srcdir, "..",
867 os.path.basename(srcdir) + "-universal")
868 os.mkdir(universalbase)
869 archbasefws = []
870 for arch in archList:
871 # fresh copy of the source tree
872 archsrc = os.path.join(universalbase, arch, "src")
873 shutil.copytree(srcdir, archsrc, symlinks=True)
874 # install base for this arch
875 archbase = os.path.join(universalbase, arch, "root")
876 os.mkdir(archbase)
877 # Python framework base within install_prefix:
878 # the build will install into this framework..
879 # This is to ensure that the resulting shared libs have
880 # the desired real install paths built into them.
881 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
882
883 # build one architecture
884 os.chdir(archsrc)
885 build_openssl_arch(archbase, arch)
886 os.chdir(srcdir)
887 archbasefws.append(archbasefw)
888
889 # copy arch-independent files from last build into the basedir framework
890 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
891 shutil.copytree(
892 os.path.join(archbasefw, "include", "openssl"),
893 os.path.join(basefw, "include", "openssl")
894 )
895
896 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
897 "SHLIB_VERSION_NUMBER")
898 # e.g. -> "1.0.0"
899 libcrypto = "libcrypto.dylib"
900 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
901 # e.g. -> "libcrypto.1.0.0.dylib"
902 libssl = "libssl.dylib"
903 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
904 # e.g. -> "libssl.1.0.0.dylib"
905
906 try:
907 os.mkdir(os.path.join(basefw, "lib"))
908 except OSError:
909 pass
910
911 # merge the individual arch-dependent shared libs into a fat shared lib
912 archbasefws.insert(0, basefw)
913 for (lib_unversioned, lib_versioned) in [
914 (libcrypto, libcrypto_versioned),
915 (libssl, libssl_versioned)
916 ]:
917 runCommand("lipo -create -output " +
918 " ".join(shellQuote(
919 os.path.join(fw, "lib", lib_versioned))
920 for fw in archbasefws))
921 # and create an unversioned symlink of it
922 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
923
924 # Create links in the temp include and lib dirs that will be injected
925 # into the Python build so that setup.py can find them while building
926 # and the versioned links so that the setup.py post-build import test
927 # does not fail.
928 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
929 for fn in [
930 ["include", "openssl"],
931 ["lib", libcrypto],
932 ["lib", libssl],
933 ["lib", libcrypto_versioned],
934 ["lib", libssl_versioned],
935 ]:
936 os.symlink(
937 os.path.join(relative_path, *fn),
938 os.path.join(basedir, "usr", "local", *fn)
939 )
940
941 return
942
Thomas Wouters477c8d52006-05-27 19:21:47 +0000943def buildRecipe(recipe, basedir, archList):
944 """
945 Build software using a recipe. This function does the
946 'configure;make;make install' dance for C software, with a possibility
947 to customize this process, basically a poor-mans DarwinPorts.
948 """
949 curdir = os.getcwd()
950
951 name = recipe['name']
Ned Deily5d3febf2014-12-13 00:17:46 -0800952 THIRD_PARTY_LIBS.append(name)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000953 url = recipe['url']
954 configure = recipe.get('configure', './configure')
Ned Deily5d3febf2014-12-13 00:17:46 -0800955 buildrecipe = recipe.get('buildrecipe', None)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000956 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
957 shellQuote(basedir)))
958
959 archiveName = os.path.split(url)[-1]
960 sourceArchive = os.path.join(DEPSRC, archiveName)
961
962 if not os.path.exists(DEPSRC):
963 os.mkdir(DEPSRC)
964
Ned Deily4a96a372013-01-29 00:08:32 -0800965 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
966 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000967 buildDir=os.path.join(WORKDIR, '_bld')
968 if not os.path.exists(buildDir):
969 os.mkdir(buildDir)
970
971 workDir = extractArchive(buildDir, sourceArchive)
972 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000973
Ned Deily4a96a372013-01-29 00:08:32 -0800974 for patch in recipe.get('patches', ()):
975 if isinstance(patch, tuple):
976 url, checksum = patch
977 fn = os.path.join(DEPSRC, os.path.basename(url))
978 verifyThirdPartyFile(url, checksum, fn)
979 else:
980 # patch is a file in the source directory
981 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000982 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
983 shellQuote(fn),))
984
Ned Deily4a96a372013-01-29 00:08:32 -0800985 for patchscript in recipe.get('patchscripts', ()):
986 if isinstance(patchscript, tuple):
987 url, checksum = patchscript
988 fn = os.path.join(DEPSRC, os.path.basename(url))
989 verifyThirdPartyFile(url, checksum, fn)
990 else:
991 # patch is a file in the source directory
992 fn = os.path.join(curdir, patchscript)
993 if fn.endswith('.bz2'):
994 runCommand('bunzip2 -fk %s' % shellQuote(fn))
995 fn = fn[:-4]
996 runCommand('sh %s' % shellQuote(fn))
997 os.unlink(fn)
998
Ned Deily94764b22013-10-27 19:49:29 -0700999 if 'buildDir' in recipe:
1000 os.chdir(recipe['buildDir'])
1001
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001002 if configure is not None:
1003 configure_args = [
1004 "--prefix=/usr/local",
1005 "--enable-static",
1006 "--disable-shared",
1007 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
1008 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001009
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001010 if 'configure_pre' in recipe:
1011 args = list(recipe['configure_pre'])
1012 if '--disable-static' in args:
1013 configure_args.remove('--enable-static')
1014 if '--enable-shared' in args:
1015 configure_args.remove('--disable-shared')
1016 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001017
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001018 if recipe.get('useLDFlags', 1):
1019 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -08001020 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1021 "-I%s/usr/local/include"%(
1022 recipe.get('extra_cflags', ''),
1023 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001024 ' -arch '.join(archList),
1025 shellQuote(SDKPATH)[1:-1],
1026 shellQuote(basedir)[1:-1],),
Ned Deily0134a352014-04-09 16:16:08 -07001027 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -08001028 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +00001029 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001030 shellQuote(basedir)[1:-1],
1031 ' -arch '.join(archList)),
1032 ])
1033 else:
1034 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -08001035 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
1036 "-I%s/usr/local/include"%(
1037 recipe.get('extra_cflags', ''),
1038 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001039 ' -arch '.join(archList),
1040 shellQuote(SDKPATH)[1:-1],
1041 shellQuote(basedir)[1:-1],),
1042 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001043
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001044 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -08001045 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001046
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001047 configure_args.insert(0, configure)
1048 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001049
Ned Deily4a96a372013-01-29 00:08:32 -08001050 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001051 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001052
Ned Deily5d3febf2014-12-13 00:17:46 -08001053 if buildrecipe is not None:
1054 # call special-case build recipe, e.g. for openssl
1055 buildrecipe(basedir, archList)
1056
1057 if install is not None:
1058 print("Running install for %s"%(name,))
1059 runCommand('{ ' + install + ' ;} 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001060
Ned Deily4a96a372013-01-29 00:08:32 -08001061 print("Done %s"%(name,))
1062 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001063
1064 os.chdir(curdir)
1065
1066def buildLibraries():
1067 """
1068 Build our dependencies into $WORKDIR/libraries/usr/local
1069 """
Ned Deily4a96a372013-01-29 00:08:32 -08001070 print("")
1071 print("Building required libraries")
1072 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073 universal = os.path.join(WORKDIR, 'libraries')
1074 os.mkdir(universal)
1075 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1076 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1077
Ronald Oussoren1943f862009-03-30 19:39:14 +00001078 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001079 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001080
1081
1082
1083def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001084 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +00001085 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -08001086 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001087 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001088 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001089 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001090 curDir = os.getcwd()
1091 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -07001092 runCommand('make clean')
Ned Deily4c7532e2017-07-23 16:39:54 -04001093 # Create virtual environment for docs builds with blurb and sphinx
1094 runCommand('make venv')
1095 runCommand('make html PYTHON=venv/bin/python')
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001096 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001097 if not os.path.exists(docdir):
1098 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001099 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100
1101
1102def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -08001103 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001104
1105 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1106 rootDir = os.path.join(WORKDIR, '_root')
1107
1108 if os.path.exists(buildDir):
1109 shutil.rmtree(buildDir)
1110 if os.path.exists(rootDir):
1111 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +00001112 os.makedirs(buildDir)
1113 os.makedirs(rootDir)
1114 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001115 curdir = os.getcwd()
1116 os.chdir(buildDir)
1117
1118 # Not sure if this is still needed, the original build script
1119 # claims that parts of the install assume python.exe exists.
1120 os.symlink('python', os.path.join(buildDir, 'python.exe'))
1121
1122 # Extract the version from the configure file, needed to calculate
1123 # several paths.
1124 version = getVersion()
1125
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001126 # Since the extra libs are not in their installed framework location
1127 # during the build, augment the library path so that the interpreter
1128 # will find them during its extension import sanity checks.
1129 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1130 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -08001131 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +00001132 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001133 "--with-universal-archs=%s "
1134 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -08001135 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +00001136 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -07001137 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001138 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
1139 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001140 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -08001141 (' ', '--without-ensurepip ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +00001142 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +00001143 shellQuote(WORKDIR)[1:-1]))
1144
Ned Deilyb364d9f2017-07-24 04:58:43 -04001145 # Look for environment value BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS
1146 # and, if defined, append its value to the make command. This allows
1147 # us to pass in version control tags, like GITTAG, to a build from a
1148 # tarball rather than from a vcs checkout, thus eliminating the need
1149 # to have a working copy of the vcs program on the build machine.
1150 #
1151 # A typical use might be:
1152 # export BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS=" \
1153 # GITVERSION='echo 123456789a' \
1154 # GITTAG='echo v3.6.0' \
1155 # GITBRANCH='echo 3.6'"
1156
1157 make_extras = os.getenv("BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS")
1158 if make_extras:
1159 make_cmd = "make " + make_extras
1160 else:
1161 make_cmd = "make"
1162 print("Running " + make_cmd)
1163 runCommand(make_cmd)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164
Ned Deily4a96a372013-01-29 00:08:32 -08001165 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001166 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001167 shellQuote(rootDir)))
1168
Ned Deily4a96a372013-01-29 00:08:32 -08001169 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001170 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1171 shellQuote(rootDir)))
1172
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001173 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001174 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001175 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1176 runCommand("mv %s/* %s"%(
1177 shellQuote(os.path.join(
1178 WORKDIR, 'libraries', 'Library', 'Frameworks',
1179 'Python.framework', 'Versions', getVersion(),
1180 'lib')),
1181 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1182 'Python.framework', 'Versions', getVersion(),
1183 'lib'))))
1184
Ned Deilydde4f632016-09-12 09:39:23 -04001185 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1186 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1187 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1188 # create directory for OpenSSL certificates
1189 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1190 os.makedirs(sslDir)
Ned Deily050fcd52013-10-26 03:16:44 -07001191
Ned Deily4a96a372013-01-29 00:08:32 -08001192 print("Fix file modes")
Thomas Wouters89f507f2006-12-13 04:49:30 +00001193 gid = grp.getgrnam('admin').gr_gid
1194
Ned Deily4a96a372013-01-29 00:08:32 -08001195 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001196 for dirpath, dirnames, filenames in os.walk(frmDir):
1197 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001198 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001199 os.chown(os.path.join(dirpath, dn), -1, gid)
1200
Thomas Wouters477c8d52006-05-27 19:21:47 +00001201 for fn in filenames:
1202 if os.path.islink(fn):
1203 continue
1204
1205 # "chmod g+w $fn"
1206 p = os.path.join(dirpath, fn)
1207 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001208 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1209 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001210
Ned Deily4a96a372013-01-29 00:08:32 -08001211 if fn in EXPECTED_SHARED_LIBS:
1212 # check to see that this file was linked with the
1213 # expected library path and version
1214 data = captureCommand("otool -L %s" % shellQuote(p))
1215 for sl in EXPECTED_SHARED_LIBS[fn]:
1216 if ("\t%s " % sl) not in data:
1217 print("Expected shared lib %s was not linked with %s"
1218 % (sl, p))
1219 shared_lib_error = True
1220
1221 if shared_lib_error:
1222 fatal("Unexpected shared library errors.")
1223
Ned Deilye59e4c52011-01-29 18:56:28 +00001224 if PYTHON_3:
1225 LDVERSION=None
1226 VERSION=None
1227 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001228
Ned Deilye59e4c52011-01-29 18:56:28 +00001229 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001230 for ln in fp:
1231 if ln.startswith('VERSION='):
1232 VERSION=ln.split()[1]
1233 if ln.startswith('ABIFLAGS='):
1234 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001235 if ln.startswith('LDVERSION='):
1236 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001237 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001238
Ned Deilye59e4c52011-01-29 18:56:28 +00001239 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1240 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1241 config_suffix = '-' + LDVERSION
Ned Deily652bad42016-08-15 14:37:14 -04001242 if getVersionMajorMinor() >= (3, 6):
1243 config_suffix = config_suffix + '-darwin'
Ned Deilye59e4c52011-01-29 18:56:28 +00001244 else:
1245 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001246
Thomas Wouters477c8d52006-05-27 19:21:47 +00001247 # We added some directories to the search path during the configure
1248 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001249 # the end-users system. Also remove the directories from _sysconfigdata.py
1250 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001251
Ned Deilya4f6b002013-10-25 00:47:38 -07001252 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1253 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1254
Ned Deilya4f6b002013-10-25 00:47:38 -07001255 # fix Makefile
1256 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1257 fp = open(path, 'r')
1258 data = fp.read()
1259 fp.close()
1260
1261 for p in (include_path, lib_path):
1262 data = data.replace(" " + p, '')
1263 data = data.replace(p + " ", '')
1264
1265 fp = open(path, 'w')
1266 fp.write(data)
1267 fp.close()
1268
Ned Deily652bad42016-08-15 14:37:14 -04001269 # fix _sysconfigdata
Ned Deilya4f6b002013-10-25 00:47:38 -07001270 #
1271 # TODO: make this more robust! test_sysconfig_module of
1272 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1273 # the output from get_config_var in both sysconfig and
1274 # distutils.sysconfig is exactly the same for both CFLAGS and
1275 # LDFLAGS. The fixing up is now complicated by the pretty
1276 # printing in _sysconfigdata.py. Also, we are using the
1277 # pprint from the Python running the installer build which
1278 # may not cosmetically format the same as the pprint in the Python
1279 # being built (and which is used to originally generate
1280 # _sysconfigdata.py).
1281
1282 import pprint
Ned Deily652bad42016-08-15 14:37:14 -04001283 if getVersionMajorMinor() >= (3, 6):
Zachary Warec4b53af2016-09-09 17:59:49 -07001284 # XXX this is extra-fragile
1285 path = os.path.join(path_to_lib, '_sysconfigdata_m_darwin_darwin.py')
Ned Deily652bad42016-08-15 14:37:14 -04001286 else:
1287 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1288 fp = open(path, 'r')
1289 data = fp.read()
1290 fp.close()
1291 # create build_time_vars dict
1292 exec(data)
1293 vars = {}
1294 for k, v in build_time_vars.items():
1295 if type(v) == type(''):
1296 for p in (include_path, lib_path):
1297 v = v.replace(' ' + p, '')
1298 v = v.replace(p + ' ', '')
1299 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001300
Ned Deily652bad42016-08-15 14:37:14 -04001301 fp = open(path, 'w')
1302 # duplicated from sysconfig._generate_posix_vars()
1303 fp.write('# system configuration generated and used by'
1304 ' the sysconfig module\n')
1305 fp.write('build_time_vars = ')
1306 pprint.pprint(vars, stream=fp)
1307 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001308
1309 # Add symlinks in /usr/local/bin, using relative links
1310 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1311 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1312 'Python.framework', 'Versions', version, 'bin')
1313 if os.path.exists(usr_local_bin):
1314 shutil.rmtree(usr_local_bin)
1315 os.makedirs(usr_local_bin)
1316 for fn in os.listdir(
1317 os.path.join(frmDir, 'Versions', version, 'bin')):
1318 os.symlink(os.path.join(to_framework, fn),
1319 os.path.join(usr_local_bin, fn))
1320
1321 os.chdir(curdir)
1322
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001323 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001324 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001325 # with an already installed version of python 2
1326 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1327 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001328
1329def patchFile(inPath, outPath):
1330 data = fileContents(inPath)
1331 data = data.replace('$FULL_VERSION', getFullVersion())
1332 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001333 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001334 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001335 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001336 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001337
1338 # This one is not handy as a template variable
1339 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001340 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001341 fp.write(data)
1342 fp.close()
1343
1344def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001345 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001346 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001347 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001348 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001349 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001350 fp.write(data)
1351 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001352 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001353
1354
1355
1356def packageFromRecipe(targetDir, recipe):
1357 curdir = os.getcwd()
1358 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001359 # The major version (such as 2.5) is included in the package name
1360 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001361 # common.
1362 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001363 srcdir = recipe.get('source')
1364 pkgroot = recipe.get('topdir', srcdir)
1365 postflight = recipe.get('postflight')
1366 readme = textwrap.dedent(recipe['readme'])
1367 isRequired = recipe.get('required', True)
1368
Ned Deily4a96a372013-01-29 00:08:32 -08001369 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001370
1371 # Substitute some variables
1372 textvars = dict(
1373 VER=getVersion(),
1374 FULLVER=getFullVersion(),
1375 )
1376 readme = readme % textvars
1377
1378 if pkgroot is not None:
1379 pkgroot = pkgroot % textvars
1380 else:
1381 pkgroot = '/'
1382
1383 if srcdir is not None:
1384 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1385 srcdir = srcdir % textvars
1386
1387 if postflight is not None:
1388 postflight = os.path.abspath(postflight)
1389
1390 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1391 os.makedirs(packageContents)
1392
1393 if srcdir is not None:
1394 os.chdir(srcdir)
1395 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1396 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1397 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1398
1399 fn = os.path.join(packageContents, 'PkgInfo')
1400 fp = open(fn, 'w')
1401 fp.write('pmkrpkg1')
1402 fp.close()
1403
1404 rsrcDir = os.path.join(packageContents, "Resources")
1405 os.mkdir(rsrcDir)
1406 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1407 fp.write(readme)
1408 fp.close()
1409
1410 if postflight is not None:
1411 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1412
1413 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001414 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001415 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001416 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1417 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1418 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001419 CFBundleShortVersionString=vers,
1420 IFMajorVersion=major,
1421 IFMinorVersion=minor,
1422 IFPkgFormatVersion=0.10000000149011612,
1423 IFPkgFlagAllowBackRev=False,
1424 IFPkgFlagAuthorizationAction="RootAuthorization",
1425 IFPkgFlagDefaultLocation=pkgroot,
1426 IFPkgFlagFollowLinks=True,
1427 IFPkgFlagInstallFat=True,
1428 IFPkgFlagIsRequired=isRequired,
1429 IFPkgFlagOverwritePermissions=False,
1430 IFPkgFlagRelocatable=False,
1431 IFPkgFlagRestartAction="NoRestart",
1432 IFPkgFlagRootVolumeOnly=True,
1433 IFPkgFlagUpdateInstalledLangauges=False,
1434 )
1435 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1436
1437 pl = Plist(
1438 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001439 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001440 IFPkgDescriptionVersion=vers,
1441 )
1442 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1443
1444 finally:
1445 os.chdir(curdir)
1446
1447
1448def makeMpkgPlist(path):
1449
1450 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001451 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452
1453 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001454 CFBundleGetInfoString="Python %s"%(vers,),
1455 CFBundleIdentifier='org.python.Python',
1456 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001457 CFBundleShortVersionString=vers,
1458 IFMajorVersion=major,
1459 IFMinorVersion=minor,
1460 IFPkgFlagComponentDirectory="Contents/Packages",
1461 IFPkgFlagPackageList=[
1462 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001463 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001464 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001465 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001466 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001467 ],
1468 IFPkgFormatVersion=0.10000000149011612,
1469 IFPkgFlagBackgroundScaling="proportional",
1470 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001471 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001472 )
1473
1474 writePlist(pl, path)
1475
1476
1477def buildInstaller():
1478
1479 # Zap all compiled files
1480 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1481 for fn in filenames:
1482 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1483 os.unlink(os.path.join(dirpath, fn))
1484
1485 outdir = os.path.join(WORKDIR, 'installer')
1486 if os.path.exists(outdir):
1487 shutil.rmtree(outdir)
1488 os.mkdir(outdir)
1489
Ronald Oussoren1943f862009-03-30 19:39:14 +00001490 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001491 pkgcontents = os.path.join(pkgroot, 'Packages')
1492 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001493 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001494 packageFromRecipe(pkgcontents, recipe)
1495
1496 rsrcDir = os.path.join(pkgroot, 'Resources')
1497
1498 fn = os.path.join(pkgroot, 'PkgInfo')
1499 fp = open(fn, 'w')
1500 fp.write('pmkrpkg1')
1501 fp.close()
1502
1503 os.mkdir(rsrcDir)
1504
1505 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1506 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001507 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001508 IFPkgDescriptionVersion=getVersion(),
1509 )
1510
1511 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1512 for fn in os.listdir('resources'):
1513 if fn == '.svn': continue
1514 if fn.endswith('.jpg'):
1515 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1516 else:
1517 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1518
Thomas Wouters477c8d52006-05-27 19:21:47 +00001519
1520def installSize(clear=False, _saved=[]):
1521 if clear:
1522 del _saved[:]
1523 if not _saved:
1524 data = captureCommand("du -ks %s"%(
1525 shellQuote(os.path.join(WORKDIR, '_root'))))
1526 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1527 return _saved[0]
1528
1529
1530def buildDMG():
1531 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001532 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001533 """
1534 outdir = os.path.join(WORKDIR, 'diskimage')
1535 if os.path.exists(outdir):
1536 shutil.rmtree(outdir)
1537
1538 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001539 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001540 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001541 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001542 imagepath = imagepath + '.dmg'
1543
1544 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001545 volname='Python %s'%(getFullVersion())
1546 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1547 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001548 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001549 shellQuote(imagepath + ".tmp.dmg" )))
1550
1551
1552 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1553 os.mkdir(os.path.join(WORKDIR, "mnt"))
1554 runCommand("hdiutil attach %s -mountroot %s"%(
1555 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1556
1557 # Custom icon for the DMG, shown when the DMG is mounted.
1558 shutil.copy("../Icons/Disk Image.icns",
1559 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001560 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001561 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1562
1563 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1564
1565 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1566 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1567 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1568 setIcon(imagepath, "../Icons/Disk Image.icns")
1569
1570 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001571
1572 return imagepath
1573
1574
1575def setIcon(filePath, icnsPath):
1576 """
1577 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001578 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001579
Ronald Oussoren70050672010-04-30 15:00:26 +00001580 dirPath = os.path.normpath(os.path.dirname(__file__))
1581 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001582 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1583 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1584 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001585 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1586 if not os.path.exists(appPath):
1587 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001588 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1589 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001590
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001591 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1592 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001593
1594def main():
1595 # First parse options and check if we can perform our work
1596 parseOptions()
1597 checkEnvironment()
1598
Ronald Oussoren1943f862009-03-30 19:39:14 +00001599 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001600 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001601 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001602
1603 if os.path.exists(WORKDIR):
1604 shutil.rmtree(WORKDIR)
1605 os.mkdir(WORKDIR)
1606
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001607 os.environ['LC_ALL'] = 'C'
1608
Thomas Wouters477c8d52006-05-27 19:21:47 +00001609 # Then build third-party libraries such as sleepycat DB4.
1610 buildLibraries()
1611
1612 # Now build python itself
1613 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001614
1615 # And then build the documentation
1616 # Remove the Deployment Target from the shell
1617 # environment, it's no longer needed and
1618 # an unexpected build target can cause problems
1619 # when Sphinx and its dependencies need to
1620 # be (re-)installed.
1621 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001622 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001623
1624
1625 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001626 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001627 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001628 fn = os.path.join(folder, "License.rtf")
1629 patchFile("resources/License.rtf", fn)
1630 fn = os.path.join(folder, "ReadMe.rtf")
1631 patchFile("resources/ReadMe.rtf", fn)
1632 fn = os.path.join(folder, "Update Shell Profile.command")
1633 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilydde4f632016-09-12 09:39:23 -04001634 fn = os.path.join(folder, "Install Certificates.command")
1635 patchScript("resources/install_certificates.command", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001636 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001637 setIcon(folder, "../Icons/Python Folder.icns")
1638
1639 # Create the installer
1640 buildInstaller()
1641
1642 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001643 patchFile('resources/ReadMe.rtf',
1644 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001645
1646 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001647 patchFile('resources/License.rtf',
1648 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001649
1650 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001651 fp.write("# BUILD INFO\n")
1652 fp.write("# Date: %s\n" % time.ctime())
1653 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001654 fp.close()
1655
Thomas Wouters477c8d52006-05-27 19:21:47 +00001656 # And copy it to a DMG
1657 buildDMG()
1658
Thomas Wouters477c8d52006-05-27 19:21:47 +00001659if __name__ == "__main__":
1660 main()