blob: f59ae34df2549a5bb6d57419c769e52dfbeea23b [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 Deily5ddddb12017-03-04 02:55:09 -050016requires an installed version of git and a 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 Deily5ddddb12017-03-04 02:55:09 -050026(2.5 is needed for Python parser updates), git, 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 Deily343d4832017-03-04 03:04:48 -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(
Ned Deily473ce1d2016-10-31 19:32:48 -0400318 name="SQLite 3.14.2",
319 url="https://www.sqlite.org/2016/sqlite-autoconf-3140200.tar.gz",
320 checksum='90c53cacb811db27f990b8292bd96159',
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 '
325 '-DSQLITE_ENABLE_RTREE '
326 '-DSQLITE_TCL=0 '
327 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
328 configure_pre=[
329 '--enable-threadsafe',
330 '--enable-shared=no',
331 '--enable-static=yes',
332 '--disable-readline',
333 '--disable-dependency-tracking',
334 ]
335 ),
336 ])
337
Ned Deily04cdfa12014-06-25 13:36:14 -0700338 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000339 result.extend([
340 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000341 name="Bzip2 1.0.6",
342 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
343 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000344 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800345 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
346 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000347 shellQuote(os.path.join(WORKDIR, 'libraries')),
348 ' -arch '.join(ARCHLIST),
349 SDKPATH,
350 ),
351 ),
352 dict(
353 name="ZLib 1.2.3",
354 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
355 checksum='debc62758716a169df9f62e6ab2bc634',
356 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800357 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
358 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000359 shellQuote(os.path.join(WORKDIR, 'libraries')),
360 ' -arch '.join(ARCHLIST),
361 SDKPATH,
362 ),
363 ),
364 dict(
365 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000366 name="GNU Readline 6.1.2",
367 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
368 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000369 patchlevel='0',
370 patches=[
371 # The readline maintainers don't do actual micro releases, but
372 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800373 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
374 'c642f2e84d820884b0bf9fd176bc6c3f'),
375 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
376 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000377 ]
378 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000379 ])
380
Ned Deily4f7ff782011-01-15 05:29:12 +0000381 if not PYTHON_3:
382 result.extend([
383 dict(
384 name="Sleepycat DB 4.7.25",
385 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
386 checksum='ec2b87e833779681a0c3a814aa71359e',
387 buildDir="build_unix",
388 configure="../dist/configure",
389 configure_pre=[
390 '--includedir=/usr/local/include/db4',
391 ]
392 ),
393 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000394
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000395 return result
396
Thomas Wouters477c8d52006-05-27 19:21:47 +0000397
Thomas Wouters477c8d52006-05-27 19:21:47 +0000398# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000399def pkg_recipes():
400 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
401 result = [
402 dict(
403 name="PythonFramework",
404 long_name="Python Framework",
405 source="/Library/Frameworks/Python.framework",
406 readme="""\
407 This package installs Python.framework, that is the python
408 interpreter and the standard library. This also includes Python
409 wrappers for lots of Mac OS X API's.
410 """,
411 postflight="scripts/postflight.framework",
412 selected='selected',
413 ),
414 dict(
415 name="PythonApplications",
416 long_name="GUI Applications",
417 source="/Applications/Python %(VER)s",
418 readme="""\
419 This package installs IDLE (an interactive Python IDE),
420 Python Launcher and Build Applet (create application bundles
421 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000422
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000423 It also installs a number of examples and demos.
424 """,
425 required=False,
426 selected='selected',
427 ),
428 dict(
429 name="PythonUnixTools",
430 long_name="UNIX command-line tools",
431 source="/usr/local/bin",
432 readme="""\
433 This package installs the unix tools in /usr/local/bin for
434 compatibility with older releases of Python. This package
435 is not necessary to use Python.
436 """,
437 required=False,
438 selected='selected',
439 ),
440 dict(
441 name="PythonDocumentation",
442 long_name="Python Documentation",
443 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
444 source="/pydocs",
445 readme="""\
446 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800447 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000448 """,
449 postflight="scripts/postflight.documentation",
450 required=False,
451 selected='selected',
452 ),
453 dict(
454 name="PythonProfileChanges",
455 long_name="Shell profile updater",
456 readme="""\
457 This packages updates your shell profile to make sure that
458 the Python tools are found by your shell in preference of
459 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000460
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000461 If you don't install this package you'll have to add
462 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
463 to your PATH by hand.
464 """,
465 postflight="scripts/postflight.patch-profile",
466 topdir="/Library/Frameworks/Python.framework",
467 source="/empty-dir",
468 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800469 selected='selected',
470 ),
471 dict(
472 name="PythonInstallPip",
473 long_name="Install or upgrade pip",
474 readme="""\
475 This package installs (or upgrades from an earlier version)
476 pip, a tool for installing and managing Python packages.
477 """,
478 postflight="scripts/postflight.ensurepip",
479 topdir="/Library/Frameworks/Python.framework",
480 source="/empty-dir",
481 required=False,
482 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000483 ),
484 ]
485
Ned Deily04cdfa12014-06-25 13:36:14 -0700486 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000487 result.append(
488 dict(
489 name="PythonSystemFixes",
490 long_name="Fix system Python",
491 readme="""\
492 This package updates the system python installation on
493 Mac OS X 10.3 to ensure that you can build new python extensions
494 using that copy of python after installing this version.
495 """,
496 postflight="../Tools/fixapplepython23.py",
497 topdir="/Library/Frameworks/Python.framework",
498 source="/empty-dir",
499 required=False,
500 selected=unselected_for_python3,
501 )
502 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800503
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000504 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000505
Thomas Wouters477c8d52006-05-27 19:21:47 +0000506def fatal(msg):
507 """
508 A fatal error, bail out.
509 """
510 sys.stderr.write('FATAL: ')
511 sys.stderr.write(msg)
512 sys.stderr.write('\n')
513 sys.exit(1)
514
515def fileContents(fn):
516 """
517 Return the contents of the named file
518 """
Ned Deily4a96a372013-01-29 00:08:32 -0800519 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000520
521def runCommand(commandline):
522 """
Ezio Melotti13925002011-03-16 11:05:33 +0200523 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000524 unless the command fails.
525 """
526 fd = os.popen(commandline, 'r')
527 data = fd.read()
528 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000529 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000530 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800531 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000532
533 if VERBOSE:
534 sys.stdout.write(data); sys.stdout.flush()
535
536def captureCommand(commandline):
537 fd = os.popen(commandline, 'r')
538 data = fd.read()
539 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000540 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000541 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800542 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543
544 return data
545
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000546def getTclTkVersion(configfile, versionline):
547 """
548 search Tcl or Tk configuration file for version line
549 """
550 try:
551 f = open(configfile, "r")
Serhiy Storchakaba9ac5b2015-05-20 10:33:40 +0300552 except OSError:
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000553 fatal("Framework configuration file not found: %s" % configfile)
554
555 for l in f:
556 if l.startswith(versionline):
557 f.close()
558 return l
559
560 fatal("Version variable %s not found in framework configuration file: %s"
561 % (versionline, configfile))
562
Thomas Wouters477c8d52006-05-27 19:21:47 +0000563def checkEnvironment():
564 """
565 Check that we're running on a supported system.
566 """
567
Ned Deilye59e4c52011-01-29 18:56:28 +0000568 if sys.version_info[0:2] < (2, 4):
569 fatal("This script must be run with Python 2.4 or later")
570
Thomas Wouters477c8d52006-05-27 19:21:47 +0000571 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000572 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000573
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000574 if int(platform.release().split('.')[0]) < 8:
575 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000576
577 if not os.path.exists(SDKPATH):
578 fatal("Please install the latest version of Xcode and the %s SDK"%(
579 os.path.basename(SDKPATH[:-4])))
580
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000581 # Because we only support dynamic load of only one major/minor version of
582 # Tcl/Tk, ensure:
583 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800584 # higher than the Apple-supplied system version in
585 # SDKROOT/System/Library/Frameworks
586 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
587 # in) SDKROOT/Library/Frameworks with the same version as the system
588 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000589
Ned Deily4a96a372013-01-29 00:08:32 -0800590 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000591 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800592 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000593 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800594 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000595 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800596 frameworks[framework] = os.readlink(sysfw)
597 if not os.path.exists(libfw):
598 fatal("Please install a link to a current %s %s as %s so "
599 "the user can override the system framework."
600 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000601 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000602 fatal("Version of %s must match %s" % (libfw, sysfw) )
603 if os.path.exists(usrfw):
604 fatal("Please rename %s to avoid possible dynamic load issues."
605 % usrfw)
606
Ned Deily4a96a372013-01-29 00:08:32 -0800607 if frameworks['Tcl'] != frameworks['Tk']:
608 fatal("The Tcl and Tk frameworks are not the same version.")
609
610 # add files to check after build
611 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
612 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
613 % frameworks['Tcl'],
614 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
615 % frameworks['Tk'],
616 ]
617
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000618 # Remove inherited environment variables which might influence build
619 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
620 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
621 for ev in list(os.environ):
622 for prefix in environ_var_prefixes:
623 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800624 print("INFO: deleting environment variable %s=%s" % (
625 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000626 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000627
Ned Deily4a96a372013-01-29 00:08:32 -0800628 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
629 if 'SDK_TOOLS_BIN' in os.environ:
630 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
631 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
632 # add its fixed location here if it exists
633 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
634 if os.path.isdir(OLD_DEVELOPER_TOOLS):
635 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
636 os.environ['PATH'] = base_path
637 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deily5ddddb12017-03-04 02:55:09 -0500638 # Ensure ws have access to git and to sphinx-build.
Ned Deily7e60f512014-04-07 12:10:21 -0700639 # You may have to create links in /usr/bin for them.
Ned Deily5ddddb12017-03-04 02:55:09 -0500640 runCommand('git --version')
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 # The Doc build changed for 3.4 (technically, for 3.4.1) and for 2.7.9
1093 runCommand('make clean')
1094 # Assume sphinx-build is on our PATH, checked in checkEnvironment
1095 runCommand('make html')
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 Deily5ddddb12017-03-04 02:55:09 -05001145 # bpo-29550: avoid using make touch until it is fixed for git
1146 # print("Running make touch")
1147 # runCommand("make touch")
Ned Deily729148b2014-05-22 15:28:06 -07001148
Ned Deily4a96a372013-01-29 00:08:32 -08001149 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001150 runCommand("make")
1151
Ned Deily4a96a372013-01-29 00:08:32 -08001152 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +00001153 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154 shellQuote(rootDir)))
1155
Ned Deily4a96a372013-01-29 00:08:32 -08001156 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1158 shellQuote(rootDir)))
1159
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001160 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001161 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001162 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1163 runCommand("mv %s/* %s"%(
1164 shellQuote(os.path.join(
1165 WORKDIR, 'libraries', 'Library', 'Frameworks',
1166 'Python.framework', 'Versions', getVersion(),
1167 'lib')),
1168 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1169 'Python.framework', 'Versions', getVersion(),
1170 'lib'))))
1171
Ned Deilydde4f632016-09-12 09:39:23 -04001172 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1173 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1174 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1175 # create directory for OpenSSL certificates
1176 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1177 os.makedirs(sslDir)
Ned Deily050fcd52013-10-26 03:16:44 -07001178
Ned Deily4a96a372013-01-29 00:08:32 -08001179 print("Fix file modes")
Thomas Wouters89f507f2006-12-13 04:49:30 +00001180 gid = grp.getgrnam('admin').gr_gid
1181
Ned Deily4a96a372013-01-29 00:08:32 -08001182 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001183 for dirpath, dirnames, filenames in os.walk(frmDir):
1184 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001185 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001186 os.chown(os.path.join(dirpath, dn), -1, gid)
1187
Thomas Wouters477c8d52006-05-27 19:21:47 +00001188 for fn in filenames:
1189 if os.path.islink(fn):
1190 continue
1191
1192 # "chmod g+w $fn"
1193 p = os.path.join(dirpath, fn)
1194 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001195 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1196 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001197
Ned Deily4a96a372013-01-29 00:08:32 -08001198 if fn in EXPECTED_SHARED_LIBS:
1199 # check to see that this file was linked with the
1200 # expected library path and version
1201 data = captureCommand("otool -L %s" % shellQuote(p))
1202 for sl in EXPECTED_SHARED_LIBS[fn]:
1203 if ("\t%s " % sl) not in data:
1204 print("Expected shared lib %s was not linked with %s"
1205 % (sl, p))
1206 shared_lib_error = True
1207
1208 if shared_lib_error:
1209 fatal("Unexpected shared library errors.")
1210
Ned Deilye59e4c52011-01-29 18:56:28 +00001211 if PYTHON_3:
1212 LDVERSION=None
1213 VERSION=None
1214 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001215
Ned Deilye59e4c52011-01-29 18:56:28 +00001216 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001217 for ln in fp:
1218 if ln.startswith('VERSION='):
1219 VERSION=ln.split()[1]
1220 if ln.startswith('ABIFLAGS='):
1221 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001222 if ln.startswith('LDVERSION='):
1223 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001224 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001225
Ned Deilye59e4c52011-01-29 18:56:28 +00001226 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1227 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1228 config_suffix = '-' + LDVERSION
Ned Deily652bad42016-08-15 14:37:14 -04001229 if getVersionMajorMinor() >= (3, 6):
1230 config_suffix = config_suffix + '-darwin'
Ned Deilye59e4c52011-01-29 18:56:28 +00001231 else:
1232 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001233
Thomas Wouters477c8d52006-05-27 19:21:47 +00001234 # We added some directories to the search path during the configure
1235 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001236 # the end-users system. Also remove the directories from _sysconfigdata.py
1237 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001238
Ned Deilya4f6b002013-10-25 00:47:38 -07001239 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1240 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1241
Ned Deilya4f6b002013-10-25 00:47:38 -07001242 # fix Makefile
1243 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1244 fp = open(path, 'r')
1245 data = fp.read()
1246 fp.close()
1247
1248 for p in (include_path, lib_path):
1249 data = data.replace(" " + p, '')
1250 data = data.replace(p + " ", '')
1251
1252 fp = open(path, 'w')
1253 fp.write(data)
1254 fp.close()
1255
Ned Deily652bad42016-08-15 14:37:14 -04001256 # fix _sysconfigdata
Ned Deilya4f6b002013-10-25 00:47:38 -07001257 #
1258 # TODO: make this more robust! test_sysconfig_module of
1259 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1260 # the output from get_config_var in both sysconfig and
1261 # distutils.sysconfig is exactly the same for both CFLAGS and
1262 # LDFLAGS. The fixing up is now complicated by the pretty
1263 # printing in _sysconfigdata.py. Also, we are using the
1264 # pprint from the Python running the installer build which
1265 # may not cosmetically format the same as the pprint in the Python
1266 # being built (and which is used to originally generate
1267 # _sysconfigdata.py).
1268
1269 import pprint
Ned Deily652bad42016-08-15 14:37:14 -04001270 if getVersionMajorMinor() >= (3, 6):
Zachary Warec4b53af2016-09-09 17:59:49 -07001271 # XXX this is extra-fragile
1272 path = os.path.join(path_to_lib, '_sysconfigdata_m_darwin_darwin.py')
Ned Deily652bad42016-08-15 14:37:14 -04001273 else:
1274 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1275 fp = open(path, 'r')
1276 data = fp.read()
1277 fp.close()
1278 # create build_time_vars dict
1279 exec(data)
1280 vars = {}
1281 for k, v in build_time_vars.items():
1282 if type(v) == type(''):
1283 for p in (include_path, lib_path):
1284 v = v.replace(' ' + p, '')
1285 v = v.replace(p + ' ', '')
1286 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001287
Ned Deily652bad42016-08-15 14:37:14 -04001288 fp = open(path, 'w')
1289 # duplicated from sysconfig._generate_posix_vars()
1290 fp.write('# system configuration generated and used by'
1291 ' the sysconfig module\n')
1292 fp.write('build_time_vars = ')
1293 pprint.pprint(vars, stream=fp)
1294 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001295
1296 # Add symlinks in /usr/local/bin, using relative links
1297 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1298 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1299 'Python.framework', 'Versions', version, 'bin')
1300 if os.path.exists(usr_local_bin):
1301 shutil.rmtree(usr_local_bin)
1302 os.makedirs(usr_local_bin)
1303 for fn in os.listdir(
1304 os.path.join(frmDir, 'Versions', version, 'bin')):
1305 os.symlink(os.path.join(to_framework, fn),
1306 os.path.join(usr_local_bin, fn))
1307
1308 os.chdir(curdir)
1309
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001310 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001311 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001312 # with an already installed version of python 2
1313 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1314 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001315
1316def patchFile(inPath, outPath):
1317 data = fileContents(inPath)
1318 data = data.replace('$FULL_VERSION', getFullVersion())
1319 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001320 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001321 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001322 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily5d3febf2014-12-13 00:17:46 -08001323 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001324
1325 # This one is not handy as a template variable
1326 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001327 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001328 fp.write(data)
1329 fp.close()
1330
1331def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001332 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001333 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001334 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001335 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001336 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001337 fp.write(data)
1338 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001339 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001340
1341
1342
1343def packageFromRecipe(targetDir, recipe):
1344 curdir = os.getcwd()
1345 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001346 # The major version (such as 2.5) is included in the package name
1347 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001348 # common.
1349 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001350 srcdir = recipe.get('source')
1351 pkgroot = recipe.get('topdir', srcdir)
1352 postflight = recipe.get('postflight')
1353 readme = textwrap.dedent(recipe['readme'])
1354 isRequired = recipe.get('required', True)
1355
Ned Deily4a96a372013-01-29 00:08:32 -08001356 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001357
1358 # Substitute some variables
1359 textvars = dict(
1360 VER=getVersion(),
1361 FULLVER=getFullVersion(),
1362 )
1363 readme = readme % textvars
1364
1365 if pkgroot is not None:
1366 pkgroot = pkgroot % textvars
1367 else:
1368 pkgroot = '/'
1369
1370 if srcdir is not None:
1371 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1372 srcdir = srcdir % textvars
1373
1374 if postflight is not None:
1375 postflight = os.path.abspath(postflight)
1376
1377 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1378 os.makedirs(packageContents)
1379
1380 if srcdir is not None:
1381 os.chdir(srcdir)
1382 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1383 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1384 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1385
1386 fn = os.path.join(packageContents, 'PkgInfo')
1387 fp = open(fn, 'w')
1388 fp.write('pmkrpkg1')
1389 fp.close()
1390
1391 rsrcDir = os.path.join(packageContents, "Resources")
1392 os.mkdir(rsrcDir)
1393 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1394 fp.write(readme)
1395 fp.close()
1396
1397 if postflight is not None:
1398 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1399
1400 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001401 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001402 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001403 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1404 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1405 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001406 CFBundleShortVersionString=vers,
1407 IFMajorVersion=major,
1408 IFMinorVersion=minor,
1409 IFPkgFormatVersion=0.10000000149011612,
1410 IFPkgFlagAllowBackRev=False,
1411 IFPkgFlagAuthorizationAction="RootAuthorization",
1412 IFPkgFlagDefaultLocation=pkgroot,
1413 IFPkgFlagFollowLinks=True,
1414 IFPkgFlagInstallFat=True,
1415 IFPkgFlagIsRequired=isRequired,
1416 IFPkgFlagOverwritePermissions=False,
1417 IFPkgFlagRelocatable=False,
1418 IFPkgFlagRestartAction="NoRestart",
1419 IFPkgFlagRootVolumeOnly=True,
1420 IFPkgFlagUpdateInstalledLangauges=False,
1421 )
1422 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1423
1424 pl = Plist(
1425 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001426 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001427 IFPkgDescriptionVersion=vers,
1428 )
1429 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1430
1431 finally:
1432 os.chdir(curdir)
1433
1434
1435def makeMpkgPlist(path):
1436
1437 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001438 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001439
1440 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001441 CFBundleGetInfoString="Python %s"%(vers,),
1442 CFBundleIdentifier='org.python.Python',
1443 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001444 CFBundleShortVersionString=vers,
1445 IFMajorVersion=major,
1446 IFMinorVersion=minor,
1447 IFPkgFlagComponentDirectory="Contents/Packages",
1448 IFPkgFlagPackageList=[
1449 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001450 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001451 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001453 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001454 ],
1455 IFPkgFormatVersion=0.10000000149011612,
1456 IFPkgFlagBackgroundScaling="proportional",
1457 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001458 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001459 )
1460
1461 writePlist(pl, path)
1462
1463
1464def buildInstaller():
1465
1466 # Zap all compiled files
1467 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1468 for fn in filenames:
1469 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1470 os.unlink(os.path.join(dirpath, fn))
1471
1472 outdir = os.path.join(WORKDIR, 'installer')
1473 if os.path.exists(outdir):
1474 shutil.rmtree(outdir)
1475 os.mkdir(outdir)
1476
Ronald Oussoren1943f862009-03-30 19:39:14 +00001477 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001478 pkgcontents = os.path.join(pkgroot, 'Packages')
1479 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001480 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001481 packageFromRecipe(pkgcontents, recipe)
1482
1483 rsrcDir = os.path.join(pkgroot, 'Resources')
1484
1485 fn = os.path.join(pkgroot, 'PkgInfo')
1486 fp = open(fn, 'w')
1487 fp.write('pmkrpkg1')
1488 fp.close()
1489
1490 os.mkdir(rsrcDir)
1491
1492 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1493 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001494 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001495 IFPkgDescriptionVersion=getVersion(),
1496 )
1497
1498 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1499 for fn in os.listdir('resources'):
1500 if fn == '.svn': continue
1501 if fn.endswith('.jpg'):
1502 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1503 else:
1504 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1505
Thomas Wouters477c8d52006-05-27 19:21:47 +00001506
1507def installSize(clear=False, _saved=[]):
1508 if clear:
1509 del _saved[:]
1510 if not _saved:
1511 data = captureCommand("du -ks %s"%(
1512 shellQuote(os.path.join(WORKDIR, '_root'))))
1513 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1514 return _saved[0]
1515
1516
1517def buildDMG():
1518 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001519 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001520 """
1521 outdir = os.path.join(WORKDIR, 'diskimage')
1522 if os.path.exists(outdir):
1523 shutil.rmtree(outdir)
1524
1525 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001526 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001527 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001528 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001529 imagepath = imagepath + '.dmg'
1530
1531 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001532 volname='Python %s'%(getFullVersion())
1533 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1534 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001535 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001536 shellQuote(imagepath + ".tmp.dmg" )))
1537
1538
1539 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1540 os.mkdir(os.path.join(WORKDIR, "mnt"))
1541 runCommand("hdiutil attach %s -mountroot %s"%(
1542 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1543
1544 # Custom icon for the DMG, shown when the DMG is mounted.
1545 shutil.copy("../Icons/Disk Image.icns",
1546 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001547 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001548 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1549
1550 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1551
1552 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1553 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1554 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1555 setIcon(imagepath, "../Icons/Disk Image.icns")
1556
1557 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001558
1559 return imagepath
1560
1561
1562def setIcon(filePath, icnsPath):
1563 """
1564 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001565 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001566
Ronald Oussoren70050672010-04-30 15:00:26 +00001567 dirPath = os.path.normpath(os.path.dirname(__file__))
1568 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001569 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1570 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1571 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001572 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1573 if not os.path.exists(appPath):
1574 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001575 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1576 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001577
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001578 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1579 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001580
1581def main():
1582 # First parse options and check if we can perform our work
1583 parseOptions()
1584 checkEnvironment()
1585
Ronald Oussoren1943f862009-03-30 19:39:14 +00001586 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001587 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001588 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001589
1590 if os.path.exists(WORKDIR):
1591 shutil.rmtree(WORKDIR)
1592 os.mkdir(WORKDIR)
1593
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001594 os.environ['LC_ALL'] = 'C'
1595
Thomas Wouters477c8d52006-05-27 19:21:47 +00001596 # Then build third-party libraries such as sleepycat DB4.
1597 buildLibraries()
1598
1599 # Now build python itself
1600 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001601
1602 # And then build the documentation
1603 # Remove the Deployment Target from the shell
1604 # environment, it's no longer needed and
1605 # an unexpected build target can cause problems
1606 # when Sphinx and its dependencies need to
1607 # be (re-)installed.
1608 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001609 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001610
1611
1612 # Prepare the applications folder
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001613 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001614 getVersion(),))
Ned Deily5d3febf2014-12-13 00:17:46 -08001615 fn = os.path.join(folder, "License.rtf")
1616 patchFile("resources/License.rtf", fn)
1617 fn = os.path.join(folder, "ReadMe.rtf")
1618 patchFile("resources/ReadMe.rtf", fn)
1619 fn = os.path.join(folder, "Update Shell Profile.command")
1620 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilydde4f632016-09-12 09:39:23 -04001621 fn = os.path.join(folder, "Install Certificates.command")
1622 patchScript("resources/install_certificates.command", fn)
Ned Deily4a96a372013-01-29 00:08:32 -08001623 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001624 setIcon(folder, "../Icons/Python Folder.icns")
1625
1626 # Create the installer
1627 buildInstaller()
1628
1629 # And copy the readme into the directory containing the installer
Ned Deily5d3febf2014-12-13 00:17:46 -08001630 patchFile('resources/ReadMe.rtf',
1631 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001632
1633 # Ditto for the license file.
Ned Deily5d3febf2014-12-13 00:17:46 -08001634 patchFile('resources/License.rtf',
1635 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001636
1637 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001638 fp.write("# BUILD INFO\n")
1639 fp.write("# Date: %s\n" % time.ctime())
1640 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001641 fp.close()
1642
Thomas Wouters477c8d52006-05-27 19:21:47 +00001643 # And copy it to a DMG
1644 buildDMG()
1645
Thomas Wouters477c8d52006-05-27 19:21:47 +00001646if __name__ == "__main__":
1647 main()