blob: 038e1917c4e1e3651b1376c21ff1c1b83db32596 [file] [log] [blame]
Ned Deilye1c9794952013-01-29 00:07:46 -08001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deilyee8e4b62018-04-14 10:37:28 -04003This script is used to build "official" universal installers on macOS.
4
5NEW for 3.6.5:
6- support Intel 64-bit-only () and 32-bit-only installer builds
7- build and link with private Tcl/Tk 8.6 for 10.9+ builds
8- deprecate use of explicit SDK (--sdk-path=) since all but the oldest
9 versions of Xcode support implicit setting of an SDK via environment
10 variables (SDKROOT and friends, see the xcrun man page for more info).
11 The SDK stuff was primarily needed for building universal installers
12 for 10.4; so as of 3.6.5, building installers for 10.4 is no longer
13 supported with build-installer.
14- use generic "gcc" as compiler (CC env var) rather than "gcc-4.2"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000015
Ned Deily53c460d2011-01-30 01:43:40 +000016Please ensure that this script keeps working with Python 2.5, to avoid
Ned Deilyee8e4b62018-04-14 10:37:28 -040017bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Doc builds
18use current versions of Sphinx and require a reasonably current python3.
19Sphinx and dependencies are installed into a venv using the python3's pip
20so will fetch them from PyPI if necessary. Since python3 is now used for
21Sphinx, build-installer.py should also be converted to use python3!
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000022
Ned Deilyee8e4b62018-04-14 10:37:28 -040023For 10.9 or greater deployment targets, build-installer builds and links
24with its own copy of Tcl/Tk 8.5 and the rest of this paragraph does not
25apply. Otherwise, build-installer requires an installed third-party version
26of Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
Ned Deilye1c9794952013-01-29 00:07:46 -080027(for 10.6 or later) installed in /Library/Frameworks. When installed,
28the Python built by this script will attempt to dynamically link first to
29Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
30back to the ones in /System/Library/Framework. For the build, we recommend
Ned Deilyee8e4b62018-04-14 10:37:28 -040031installing the most recent ActiveTcl 8.5 or 8.4 version, depending
32on the deployment target. The actual version linked to depends on the
33path of /Library/Frameworks/{Tcl,Tk}.framework/Versions/Current.
Ned Deilye1c9794952013-01-29 00:07:46 -080034
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000035Usage: see USAGE variable in the script.
36"""
Ned Deilye1c9794952013-01-29 00:07:46 -080037import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
38try:
39 import urllib2 as urllib_request
40except ImportError:
41 import urllib.request as urllib_request
42
43STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
44 | stat.S_IRGRP | stat.S_IXGRP
45 | stat.S_IROTH | stat.S_IXOTH )
46
47STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
48 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
49 | stat.S_IROTH | stat.S_IXOTH )
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000050
Ronald Oussoren158ad592006-11-07 16:00:34 +000051INCLUDE_TIMESTAMP = 1
52VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000053
54from plistlib import Plist
55
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000056try:
57 from plistlib import writePlist
58except ImportError:
59 # We're run using python2.3
60 def writePlist(plist, path):
61 plist.write(path)
62
63def shellQuote(value):
64 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000065 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000066 a shell command.
67 """
68 return "'%s'"%(value.replace("'", "'\"'\"'"))
69
70def grepValue(fn, variable):
Ned Deily62a86602014-12-09 23:45:13 -080071 """
72 Return the unquoted value of a variable from a file..
73 QUOTED_VALUE='quotes' -> str('quotes')
74 UNQUOTED_VALUE=noquotes -> str('noquotes')
75 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000076 variable = variable + '='
77 for ln in open(fn, 'r'):
78 if ln.startswith(variable):
79 value = ln[len(variable):].strip()
Ned Deily62a86602014-12-09 23:45:13 -080080 return value.strip("\"'")
Ned Deilye1c9794952013-01-29 00:07:46 -080081 raise RuntimeError("Cannot find variable %s" % variable[:-1])
82
83_cache_getVersion = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000084
85def getVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080086 global _cache_getVersion
87 if _cache_getVersion is None:
88 _cache_getVersion = grepValue(
89 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
90 return _cache_getVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000091
Ned Deilye1c9794952013-01-29 00:07:46 -080092def getVersionMajorMinor():
93 return tuple([int(n) for n in getVersion().split('.', 2)])
94
95_cache_getFullVersion = None
96
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000097def getFullVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080098 global _cache_getFullVersion
99 if _cache_getFullVersion is not None:
100 return _cache_getFullVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000101 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
102 for ln in open(fn):
103 if 'PY_VERSION' in ln:
Ned Deilye1c9794952013-01-29 00:07:46 -0800104 _cache_getFullVersion = ln.split()[-1][1:-1]
105 return _cache_getFullVersion
106 raise RuntimeError("Cannot find full version??")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000107
Ned Deily62a86602014-12-09 23:45:13 -0800108FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
109FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
Ned Deilyee8e4b62018-04-14 10:37:28 -0400110FW_SSL_DIRECTORY = "--undefined--" # initialized in parseOptions
Ned Deily62a86602014-12-09 23:45:13 -0800111
Ronald Oussorenaa560962006-11-07 15:53:38 +0000112# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000113WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000114
Ronald Oussorenaa560962006-11-07 15:53:38 +0000115# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000116# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000117DEPSRC = os.path.join(WORKDIR, 'third-party')
118DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000119
Ronald Oussoren508282e2009-03-30 19:34:51 +0000120universal_opts_map = { '32-bit': ('i386', 'ppc',),
121 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000122 'intel': ('i386', 'x86_64'),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400123 'intel-32': ('i386',),
124 'intel-64': ('x86_64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000125 '3-way': ('ppc', 'i386', 'x86_64'),
126 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
127default_target_map = {
128 '64-bit': '10.5',
129 '3-way': '10.5',
130 'intel': '10.5',
Ned Deilyee8e4b62018-04-14 10:37:28 -0400131 'intel-32': '10.4',
132 'intel-64': '10.5',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000133 'all': '10.5',
134}
Ronald Oussoren508282e2009-03-30 19:34:51 +0000135
136UNIVERSALOPTS = tuple(universal_opts_map.keys())
137
138UNIVERSALARCHS = '32-bit'
139
140ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000141
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200142# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000143SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000144 os.path.dirname(
145 os.path.dirname(
146 os.path.abspath(__file__
147 ))))
148
Ronald Oussoren508282e2009-03-30 19:34:51 +0000149# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
Ned Deilyee8e4b62018-04-14 10:37:28 -0400150DEPTARGET = '10.5'
Ronald Oussoren508282e2009-03-30 19:34:51 +0000151
Ned Deily1f70b872014-06-25 13:33:57 -0700152def getDeptargetTuple():
153 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
154
155def getTargetCompilers():
156 target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -0800157 '10.4': ('gcc-4.0', 'g++-4.0'),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400158 '10.5': ('gcc', 'g++'),
159 '10.6': ('gcc', 'g++'),
Ned Deily1f70b872014-06-25 13:33:57 -0700160 }
Ned Deilyee8e4b62018-04-14 10:37:28 -0400161 return target_cc_map.get(DEPTARGET, ('gcc', 'g++') )
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000162
Ned Deily1f70b872014-06-25 13:33:57 -0700163CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000164
Ned Deilyee8e4b62018-04-14 10:37:28 -0400165PYTHON_2 = getVersionMajorMinor()[0] == 2
166PYTHON_3 = getVersionMajorMinor()[0] == 3
Ned Deily53c460d2011-01-30 01:43:40 +0000167
Ronald Oussoren158ad592006-11-07 16:00:34 +0000168USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000169 Usage: build_python [options]
170
171 Options:
172 -? or -h: Show this message
173 -b DIR
174 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
175 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
Ned Deilyee8e4b62018-04-14 10:37:28 -0400176 --sdk-path=DIR: Location of the SDK (deprecated, use SDKROOT env variable)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000177 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ned Deilyee8e4b62018-04-14 10:37:28 -0400178 --dep-target=10.n macOS deployment target (default: %(DEPTARGET)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000179 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000180""")% globals()
181
Ned Deilye1c9794952013-01-29 00:07:46 -0800182# Dict of object file names with shared library names to check after building.
183# This is to ensure that we ended up dynamically linking with the shared
184# library paths and versions we expected. For example:
185# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
186# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
187# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
188EXPECTED_SHARED_LIBS = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000189
Ned Deilyee8e4b62018-04-14 10:37:28 -0400190# Are we building and linking with our own copy of Tcl/TK?
191# For now, do so if deployment target is 10.9+.
192def internalTk():
193 return getDeptargetTuple() >= (10, 9)
194
Ned Deily62a86602014-12-09 23:45:13 -0800195# List of names of third party software built with this installer.
196# The names will be inserted into the rtf version of the License.
197THIRD_PARTY_LIBS = []
198
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000199# Instructions for building libraries that are necessary for building a
200# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000201# [The recipes are defined here for convenience but instantiated later after
202# command line options have been processed.]
203def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000204 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000205
Ned Deily1f70b872014-06-25 13:33:57 -0700206 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deilye1c9794952013-01-29 00:07:46 -0800207
Ned Deilyee8e4b62018-04-14 10:37:28 -0400208 # Since Apple removed the header files for the deprecated system
209 # OpenSSL as of the Xcode 7 release (for OS X 10.10+), we do not
210 # have much choice but to build our own copy here, too.
Ned Deily62a86602014-12-09 23:45:13 -0800211
Ned Deilyee8e4b62018-04-14 10:37:28 -0400212 result.extend([
Ned Deily62a86602014-12-09 23:45:13 -0800213 dict(
Miss Islington (bot)01a0fd42018-04-14 08:21:02 -0700214 name="OpenSSL 1.0.2o",
215 url="https://www.openssl.org/source/openssl-1.0.2o.tar.gz",
216 checksum='44279b8557c3247cbe324e2322ecd114',
Ned Deily62a86602014-12-09 23:45:13 -0800217 buildrecipe=build_universal_openssl,
218 configure=None,
219 install=None,
220 ),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400221 ])
Ned Deily62a86602014-12-09 23:45:13 -0800222
Ned Deilyee8e4b62018-04-14 10:37:28 -0400223 if internalTk():
Ned Deily0203a802013-10-25 00:40:07 -0700224 result.extend([
225 dict(
Ned Deilyee8e4b62018-04-14 10:37:28 -0400226 name="Tcl 8.6.8",
227 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tcl8.6.8-src.tar.gz",
228 checksum='81656d3367af032e0ae6157eff134f89',
Ned Deily0203a802013-10-25 00:40:07 -0700229 buildDir="unix",
230 configure_pre=[
231 '--enable-shared',
232 '--enable-threads',
233 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
234 ],
235 useLDFlags=False,
236 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
237 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400238 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
Ned Deily0203a802013-10-25 00:40:07 -0700239 },
240 ),
241 dict(
Ned Deilyee8e4b62018-04-14 10:37:28 -0400242 name="Tk 8.6.8",
243 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_6/tk8.6.8-src.tar.gz",
244 checksum='5e0faecba458ee1386078fb228d008ba',
Ned Deilya6cbff02013-10-27 19:47:23 -0700245 patches=[
Ned Deilyee8e4b62018-04-14 10:37:28 -0400246 "tk868_on_10_8_10_9.patch",
Ned Deilya6cbff02013-10-27 19:47:23 -0700247 ],
Ned Deily0203a802013-10-25 00:40:07 -0700248 buildDir="unix",
249 configure_pre=[
250 '--enable-aqua',
251 '--enable-shared',
252 '--enable-threads',
253 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
254 ],
255 useLDFlags=False,
256 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'%{
257 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400258 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.6'%(getVersion())),
259 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.6'%(getVersion())),
Ned Deily0203a802013-10-25 00:40:07 -0700260 },
261 ),
262 ])
263
Ned Deily30101822014-11-14 18:53:59 -0800264 if PYTHON_3:
Ned Deilye1c9794952013-01-29 00:07:46 -0800265 result.extend([
266 dict(
Ned Deilyee8e4b62018-04-14 10:37:28 -0400267 name="XZ 5.2.3",
268 url="http://tukaani.org/xz/xz-5.2.3.tar.gz",
269 checksum='ef68674fb47a8b8e741b34e429d86e9d',
Ned Deilye1c9794952013-01-29 00:07:46 -0800270 configure_pre=[
271 '--disable-dependency-tracking',
272 ]
273 ),
274 ])
275
276 result.extend([
277 dict(
278 name="NCurses 5.9",
279 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
280 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
281 configure_pre=[
282 "--enable-widec",
283 "--without-cxx",
284 "--without-cxx-binding",
285 "--without-ada",
286 "--without-curses-h",
287 "--enable-shared",
288 "--with-shared",
289 "--without-debug",
290 "--without-normal",
291 "--without-tests",
292 "--without-manpages",
293 "--datadir=/usr/share",
294 "--sysconfdir=/etc",
295 "--sharedstatedir=/usr/com",
296 "--with-terminfo-dirs=/usr/share/terminfo",
297 "--with-default-terminfo-dir=/usr/share/terminfo",
298 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
299 ],
300 patchscripts=[
301 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
302 "f54bf02a349f96a7c4f0d00922f3a0d4"),
303 ],
304 useLDFlags=False,
305 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
306 shellQuote(os.path.join(WORKDIR, 'libraries')),
307 shellQuote(os.path.join(WORKDIR, 'libraries')),
308 getVersion(),
309 ),
310 ),
311 dict(
Ned Deilyee8e4b62018-04-14 10:37:28 -0400312 name="SQLite 3.22.0",
313 url="https://www.sqlite.org/2018/sqlite-autoconf-3220000.tar.gz",
314 checksum='96b5648d542e8afa6ab7ffb8db8ddc3d',
Ned Deilye1c9794952013-01-29 00:07:46 -0800315 extra_cflags=('-Os '
Ned Deilyee8e4b62018-04-14 10:37:28 -0400316 '-DSQLITE_ENABLE_FTS5 '
Ned Deilye1c9794952013-01-29 00:07:46 -0800317 '-DSQLITE_ENABLE_FTS4 '
318 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
Ned Deilyee8e4b62018-04-14 10:37:28 -0400319 '-DSQLITE_ENABLE_JSON1 '
Ned Deilye1c9794952013-01-29 00:07:46 -0800320 '-DSQLITE_ENABLE_RTREE '
321 '-DSQLITE_TCL=0 '
322 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
323 configure_pre=[
324 '--enable-threadsafe',
325 '--enable-shared=no',
326 '--enable-static=yes',
327 '--disable-readline',
328 '--disable-dependency-tracking',
329 ]
330 ),
331 ])
332
Ned Deily1f70b872014-06-25 13:33:57 -0700333 if getDeptargetTuple() < (10, 5):
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000334 result.extend([
335 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000336 name="Bzip2 1.0.6",
337 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
338 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000339 configure=None,
Ned Deilyee8e4b62018-04-14 10:37:28 -0400340 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800341 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000342 shellQuote(os.path.join(WORKDIR, 'libraries')),
343 ' -arch '.join(ARCHLIST),
Ronald Oussoren508282e2009-03-30 19:34:51 +0000344 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000345 ),
346 dict(
347 name="ZLib 1.2.3",
348 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
349 checksum='debc62758716a169df9f62e6ab2bc634',
350 configure=None,
Ned Deilyee8e4b62018-04-14 10:37:28 -0400351 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s"'%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800352 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000353 shellQuote(os.path.join(WORKDIR, 'libraries')),
354 ' -arch '.join(ARCHLIST),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000355 ),
356 ),
357 dict(
358 # Note that GNU readline is GPL'd software
Ned Deily53c460d2011-01-30 01:43:40 +0000359 name="GNU Readline 6.1.2",
360 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
361 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000362 patchlevel='0',
363 patches=[
364 # The readline maintainers don't do actual micro releases, but
365 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -0800366 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
367 'c642f2e84d820884b0bf9fd176bc6c3f'),
368 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
369 '1a76781a1ea734e831588285db7ec9b1'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000370 ]
371 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000372 ])
373
Ned Deily53c460d2011-01-30 01:43:40 +0000374 if not PYTHON_3:
375 result.extend([
376 dict(
377 name="Sleepycat DB 4.7.25",
378 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
379 checksum='ec2b87e833779681a0c3a814aa71359e',
380 buildDir="build_unix",
381 configure="../dist/configure",
382 configure_pre=[
383 '--includedir=/usr/local/include/db4',
384 ]
385 ),
386 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000387
388 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000389
390
391# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000392def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000393 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000394 result = [
395 dict(
396 name="PythonFramework",
397 long_name="Python Framework",
398 source="/Library/Frameworks/Python.framework",
399 readme="""\
400 This package installs Python.framework, that is the python
Ned Deilyee8e4b62018-04-14 10:37:28 -0400401 interpreter and the standard library.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000402 """,
403 postflight="scripts/postflight.framework",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000404 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000405 ),
406 dict(
407 name="PythonApplications",
408 long_name="GUI Applications",
409 source="/Applications/Python %(VER)s",
410 readme="""\
411 This package installs IDLE (an interactive Python IDE),
412 Python Launcher and Build Applet (create application bundles
413 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000414
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000415 It also installs a number of examples and demos.
416 """,
417 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000418 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000419 ),
420 dict(
421 name="PythonUnixTools",
422 long_name="UNIX command-line tools",
423 source="/usr/local/bin",
424 readme="""\
425 This package installs the unix tools in /usr/local/bin for
426 compatibility with older releases of Python. This package
427 is not necessary to use Python.
428 """,
429 required=False,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000430 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000431 ),
432 dict(
433 name="PythonDocumentation",
434 long_name="Python Documentation",
435 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
436 source="/pydocs",
437 readme="""\
438 This package installs the python documentation at a location
Ned Deilye1c9794952013-01-29 00:07:46 -0800439 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000440 """,
441 postflight="scripts/postflight.documentation",
442 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000443 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000444 ),
445 dict(
446 name="PythonProfileChanges",
447 long_name="Shell profile updater",
448 readme="""\
449 This packages updates your shell profile to make sure that
450 the Python tools are found by your shell in preference of
451 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000452
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000453 If you don't install this package you'll have to add
454 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
455 to your PATH by hand.
456 """,
457 postflight="scripts/postflight.patch-profile",
458 topdir="/Library/Frameworks/Python.framework",
459 source="/empty-dir",
460 required=False,
Ned Deily30101822014-11-14 18:53:59 -0800461 selected='selected',
462 ),
463 dict(
464 name="PythonInstallPip",
465 long_name="Install or upgrade pip",
466 readme="""\
467 This package installs (or upgrades from an earlier version)
468 pip, a tool for installing and managing Python packages.
469 """,
470 postflight="scripts/postflight.ensurepip",
471 topdir="/Library/Frameworks/Python.framework",
472 source="/empty-dir",
473 required=False,
474 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000475 ),
476 ]
477
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000478 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000479
480def fatal(msg):
481 """
482 A fatal error, bail out.
483 """
484 sys.stderr.write('FATAL: ')
485 sys.stderr.write(msg)
486 sys.stderr.write('\n')
487 sys.exit(1)
488
489def fileContents(fn):
490 """
491 Return the contents of the named file
492 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800493 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000494
495def runCommand(commandline):
496 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200497 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000498 unless the command fails.
499 """
500 fd = os.popen(commandline, 'r')
501 data = fd.read()
502 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000503 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000504 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800505 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000506
507 if VERBOSE:
508 sys.stdout.write(data); sys.stdout.flush()
509
510def captureCommand(commandline):
511 fd = os.popen(commandline, 'r')
512 data = fd.read()
513 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000514 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000515 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800516 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000517
518 return data
519
Ronald Oussoren287128a2010-04-18 14:01:05 +0000520def getTclTkVersion(configfile, versionline):
521 """
522 search Tcl or Tk configuration file for version line
523 """
524 try:
525 f = open(configfile, "r")
Ned Deily123a58b2017-07-24 04:29:32 -0400526 except OSError:
Ronald Oussoren287128a2010-04-18 14:01:05 +0000527 fatal("Framework configuration file not found: %s" % configfile)
528
529 for l in f:
530 if l.startswith(versionline):
531 f.close()
532 return l
533
534 fatal("Version variable %s not found in framework configuration file: %s"
535 % (versionline, configfile))
536
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000537def checkEnvironment():
538 """
539 Check that we're running on a supported system.
540 """
541
Ned Deilyee8e4b62018-04-14 10:37:28 -0400542 if sys.version_info[0:2] < (2, 5):
543 fatal("This script must be run with Python 2.5 (or later)")
Ned Deily53c460d2011-01-30 01:43:40 +0000544
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000545 if platform.system() != 'Darwin':
Ned Deilyee8e4b62018-04-14 10:37:28 -0400546 fatal("This script should be run on a macOS 10.5 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000547
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000548 if int(platform.release().split('.')[0]) < 8:
Ned Deilyee8e4b62018-04-14 10:37:28 -0400549 fatal("This script should be run on a macOS 10.5 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000550
Ronald Oussoren287128a2010-04-18 14:01:05 +0000551 # Because we only support dynamic load of only one major/minor version of
Ned Deilyee8e4b62018-04-14 10:37:28 -0400552 # Tcl/Tk, if we are not using building and using our own private copy of
Ronald Oussoren287128a2010-04-18 14:01:05 +0000553 # Tcl/Tk, ensure:
Ned Deilyee8e4b62018-04-14 10:37:28 -0400554 # 1. there is a user-installed framework (usually ActiveTcl) in (or linked
555 # in) SDKROOT/Library/Frameworks. As of Python 3.6.5, we no longer
556 # enforce that the version of the user-installed framework also
557 # exists in the system-supplied Tcl/Tk frameworks. Time to support
558 # Tcl/Tk 8.6 even if Apple does not.
559 if not internalTk():
560 frameworks = {}
561 for framework in ['Tcl', 'Tk']:
562 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
563 libfw = os.path.join('/', fwpth)
564 usrfw = os.path.join(os.getenv('HOME'), fwpth)
565 frameworks[framework] = os.readlink(libfw)
566 if not os.path.exists(libfw):
567 fatal("Please install a link to a current %s %s as %s so "
568 "the user can override the system framework."
569 % (framework, frameworks[framework], libfw))
570 if os.path.exists(usrfw):
571 fatal("Please rename %s to avoid possible dynamic load issues."
572 % usrfw)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000573
Ned Deilyee8e4b62018-04-14 10:37:28 -0400574 if frameworks['Tcl'] != frameworks['Tk']:
575 fatal("The Tcl and Tk frameworks are not the same version.")
Ronald Oussoren287128a2010-04-18 14:01:05 +0000576
Ned Deilyee8e4b62018-04-14 10:37:28 -0400577 print(" -- Building with external Tcl/Tk %s frameworks"
578 % frameworks['Tk'])
Ned Deilye1c9794952013-01-29 00:07:46 -0800579
Ned Deilyee8e4b62018-04-14 10:37:28 -0400580 # add files to check after build
581 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
582 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
583 % frameworks['Tcl'],
584 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
585 % frameworks['Tk'],
586 ]
587 else:
588 print(" -- Building private copy of Tcl/Tk")
589 print("")
Ned Deilye1c9794952013-01-29 00:07:46 -0800590
Ronald Oussoren287128a2010-04-18 14:01:05 +0000591 # Remove inherited environment variables which might influence build
592 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
593 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
594 for ev in list(os.environ):
595 for prefix in environ_var_prefixes:
596 if ev.startswith(prefix) :
Ned Deilye1c9794952013-01-29 00:07:46 -0800597 print("INFO: deleting environment variable %s=%s" % (
598 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000599 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000600
Ned Deilye1c9794952013-01-29 00:07:46 -0800601 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
602 if 'SDK_TOOLS_BIN' in os.environ:
603 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
604 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
605 # add its fixed location here if it exists
606 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
607 if os.path.isdir(OLD_DEVELOPER_TOOLS):
608 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
609 os.environ['PATH'] = base_path
610 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyee8e4b62018-04-14 10:37:28 -0400611 if PYTHON_2:
612 # Ensure we have access to sphinx-build.
613 # You may have to define SDK_TOOLS_BIN and link to it there,
614 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000615
Ronald Oussoren158ad592006-11-07 16:00:34 +0000616def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000617 """
618 Parse arguments and update global settings.
619 """
Ned Deilyee8e4b62018-04-14 10:37:28 -0400620 global WORKDIR, DEPSRC, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800621 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ned Deily62a86602014-12-09 23:45:13 -0800622 global FW_VERSION_PREFIX
Ned Deilyee8e4b62018-04-14 10:37:28 -0400623 global FW_SSL_DIRECTORY
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000624
625 if args is None:
626 args = sys.argv[1:]
627
628 try:
629 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000630 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
631 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800632 except getopt.GetoptError:
633 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000634 sys.exit(1)
635
636 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800637 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000638 sys.exit(1)
639
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000640 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000641 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000642 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800643 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000644 sys.exit(0)
645
646 elif k in ('-d', '--build-dir'):
647 WORKDIR=v
648
649 elif k in ('--third-party',):
650 DEPSRC=v
651
652 elif k in ('--sdk-path',):
Ned Deilyee8e4b62018-04-14 10:37:28 -0400653 print(" WARNING: --sdk-path is no longer supported")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000654
655 elif k in ('--src-dir',):
656 SRCDIR=v
657
Ronald Oussoren508282e2009-03-30 19:34:51 +0000658 elif k in ('--dep-target', ):
659 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000660 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000661
662 elif k in ('--universal-archs', ):
663 if v in UNIVERSALOPTS:
664 UNIVERSALARCHS = v
665 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000666 if deptarget is None:
667 # Select alternate default deployment
668 # target
Ned Deilyee8e4b62018-04-14 10:37:28 -0400669 DEPTARGET = default_target_map.get(v, '10.5')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000670 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800671 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000672
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000673 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800674 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000675
676 SRCDIR=os.path.abspath(SRCDIR)
677 WORKDIR=os.path.abspath(WORKDIR)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000678 DEPSRC=os.path.abspath(DEPSRC)
679
Ned Deily1f70b872014-06-25 13:33:57 -0700680 CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000681
Ned Deily62a86602014-12-09 23:45:13 -0800682 FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
Ned Deilyee8e4b62018-04-14 10:37:28 -0400683 FW_SSL_DIRECTORY = FW_VERSION_PREFIX[:] + ["etc", "openssl"]
Ned Deily62a86602014-12-09 23:45:13 -0800684
685 print("-- Settings:")
686 print(" * Source directory: %s" % SRCDIR)
687 print(" * Build directory: %s" % WORKDIR)
Ned Deily62a86602014-12-09 23:45:13 -0800688 print(" * Third-party source: %s" % DEPSRC)
689 print(" * Deployment target: %s" % DEPTARGET)
690 print(" * Universal archs: %s" % str(ARCHLIST))
691 print(" * C compiler: %s" % CC)
692 print(" * C++ compiler: %s" % CXX)
Ned Deilye1c9794952013-01-29 00:07:46 -0800693 print("")
Ned Deily62a86602014-12-09 23:45:13 -0800694 print(" -- Building a Python %s framework at patch level %s"
695 % (getVersion(), getFullVersion()))
696 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000697
698def extractArchive(builddir, archiveName):
699 """
700 Extract a source archive into 'builddir'. Returns the path of the
701 extracted archive.
702
703 XXX: This function assumes that archives contain a toplevel directory
704 that is has the same name as the basename of the archive. This is
Ned Deily0203a802013-10-25 00:40:07 -0700705 safe enough for almost anything we use. Unfortunately, it does not
706 work for current Tcl and Tk source releases where the basename of
707 the archive ends with "-src" but the uncompressed directory does not.
708 For now, just special case Tcl and Tk tar.gz downloads.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000709 """
710 curdir = os.getcwd()
711 try:
712 os.chdir(builddir)
713 if archiveName.endswith('.tar.gz'):
714 retval = os.path.basename(archiveName[:-7])
Ned Deily0203a802013-10-25 00:40:07 -0700715 if ((retval.startswith('tcl') or retval.startswith('tk'))
716 and retval.endswith('-src')):
717 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000718 if os.path.exists(retval):
719 shutil.rmtree(retval)
720 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
721
722 elif archiveName.endswith('.tar.bz2'):
723 retval = os.path.basename(archiveName[:-8])
724 if os.path.exists(retval):
725 shutil.rmtree(retval)
726 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
727
728 elif archiveName.endswith('.tar'):
729 retval = os.path.basename(archiveName[:-4])
730 if os.path.exists(retval):
731 shutil.rmtree(retval)
732 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
733
734 elif archiveName.endswith('.zip'):
735 retval = os.path.basename(archiveName[:-4])
736 if os.path.exists(retval):
737 shutil.rmtree(retval)
738 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
739
740 data = fp.read()
741 xit = fp.close()
742 if xit is not None:
743 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800744 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000745
746 return os.path.join(builddir, retval)
747
748 finally:
749 os.chdir(curdir)
750
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000751def downloadURL(url, fname):
752 """
753 Download the contents of the url into the file.
754 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800755 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000756 fpOut = open(fname, 'wb')
757 block = fpIn.read(10240)
758 try:
759 while block:
760 fpOut.write(block)
761 block = fpIn.read(10240)
762 fpIn.close()
763 fpOut.close()
764 except:
765 try:
766 os.unlink(fname)
Ned Deily123a58b2017-07-24 04:29:32 -0400767 except OSError:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000768 pass
769
Ned Deilye1c9794952013-01-29 00:07:46 -0800770def verifyThirdPartyFile(url, checksum, fname):
771 """
772 Download file from url to filename fname if it does not already exist.
773 Abort if file contents does not match supplied md5 checksum.
774 """
775 name = os.path.basename(fname)
776 if os.path.exists(fname):
777 print("Using local copy of %s"%(name,))
778 else:
779 print("Did not find local copy of %s"%(name,))
780 print("Downloading %s"%(name,))
781 downloadURL(url, fname)
782 print("Archive for %s stored as %s"%(name, fname))
783 if os.system(
784 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
785 % (shellQuote(fname), checksum) ):
786 fatal('MD5 checksum mismatch for file %s' % fname)
787
Ned Deily62a86602014-12-09 23:45:13 -0800788def build_universal_openssl(basedir, archList):
789 """
790 Special case build recipe for universal build of openssl.
791
792 The upstream OpenSSL build system does not directly support
793 OS X universal builds. We need to build each architecture
794 separately then lipo them together into fat libraries.
795 """
796
Ned Deily0be4b1e2014-12-11 15:55:42 -0800797 # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
798 # If we are building on a 10.4.x or earlier system,
799 # unilaterally disable assembly code building to avoid the problem.
800 no_asm = int(platform.release().split(".")[0]) < 9
801
Ned Deily62a86602014-12-09 23:45:13 -0800802 def build_openssl_arch(archbase, arch):
803 "Build one architecture of openssl"
804 arch_opts = {
805 "i386": ["darwin-i386-cc"],
806 "x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
807 "ppc": ["darwin-ppc-cc"],
808 "ppc64": ["darwin64-ppc-cc"],
809 }
810 configure_opts = [
811 "no-krb5",
812 "no-idea",
813 "no-mdc2",
814 "no-rc5",
815 "no-zlib",
816 "enable-tlsext",
817 "no-ssl2",
818 "no-ssl3",
Ned Deily62a86602014-12-09 23:45:13 -0800819 # "enable-unit-test",
820 "shared",
821 "--install_prefix=%s"%shellQuote(archbase),
822 "--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400823 "--openssldir=%s"%os.path.join("/", *FW_SSL_DIRECTORY),
Ned Deily62a86602014-12-09 23:45:13 -0800824 ]
Ned Deily0be4b1e2014-12-11 15:55:42 -0800825 if no_asm:
826 configure_opts.append("no-asm")
Ned Deily62a86602014-12-09 23:45:13 -0800827 runCommand(" ".join(["perl", "Configure"]
828 + arch_opts[arch] + configure_opts))
Ned Deilyee8e4b62018-04-14 10:37:28 -0400829 runCommand("make depend")
830 runCommand("make all")
831 runCommand("make install_sw")
Ned Deily62a86602014-12-09 23:45:13 -0800832 # runCommand("make test")
833 return
834
835 srcdir = os.getcwd()
836 universalbase = os.path.join(srcdir, "..",
837 os.path.basename(srcdir) + "-universal")
838 os.mkdir(universalbase)
839 archbasefws = []
840 for arch in archList:
841 # fresh copy of the source tree
842 archsrc = os.path.join(universalbase, arch, "src")
843 shutil.copytree(srcdir, archsrc, symlinks=True)
844 # install base for this arch
845 archbase = os.path.join(universalbase, arch, "root")
846 os.mkdir(archbase)
847 # Python framework base within install_prefix:
848 # the build will install into this framework..
849 # This is to ensure that the resulting shared libs have
850 # the desired real install paths built into them.
851 archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
852
853 # build one architecture
854 os.chdir(archsrc)
855 build_openssl_arch(archbase, arch)
856 os.chdir(srcdir)
857 archbasefws.append(archbasefw)
858
859 # copy arch-independent files from last build into the basedir framework
860 basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
861 shutil.copytree(
862 os.path.join(archbasefw, "include", "openssl"),
863 os.path.join(basefw, "include", "openssl")
864 )
865
866 shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
867 "SHLIB_VERSION_NUMBER")
868 # e.g. -> "1.0.0"
869 libcrypto = "libcrypto.dylib"
870 libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
871 # e.g. -> "libcrypto.1.0.0.dylib"
872 libssl = "libssl.dylib"
873 libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
874 # e.g. -> "libssl.1.0.0.dylib"
875
876 try:
877 os.mkdir(os.path.join(basefw, "lib"))
878 except OSError:
879 pass
880
881 # merge the individual arch-dependent shared libs into a fat shared lib
882 archbasefws.insert(0, basefw)
883 for (lib_unversioned, lib_versioned) in [
884 (libcrypto, libcrypto_versioned),
885 (libssl, libssl_versioned)
886 ]:
887 runCommand("lipo -create -output " +
888 " ".join(shellQuote(
889 os.path.join(fw, "lib", lib_versioned))
890 for fw in archbasefws))
891 # and create an unversioned symlink of it
892 os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
893
894 # Create links in the temp include and lib dirs that will be injected
895 # into the Python build so that setup.py can find them while building
896 # and the versioned links so that the setup.py post-build import test
897 # does not fail.
898 relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
899 for fn in [
900 ["include", "openssl"],
901 ["lib", libcrypto],
902 ["lib", libssl],
903 ["lib", libcrypto_versioned],
904 ["lib", libssl_versioned],
905 ]:
906 os.symlink(
907 os.path.join(relative_path, *fn),
908 os.path.join(basedir, "usr", "local", *fn)
909 )
910
911 return
912
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000913def buildRecipe(recipe, basedir, archList):
914 """
915 Build software using a recipe. This function does the
916 'configure;make;make install' dance for C software, with a possibility
917 to customize this process, basically a poor-mans DarwinPorts.
918 """
919 curdir = os.getcwd()
920
921 name = recipe['name']
Ned Deily62a86602014-12-09 23:45:13 -0800922 THIRD_PARTY_LIBS.append(name)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000923 url = recipe['url']
924 configure = recipe.get('configure', './configure')
Ned Deily62a86602014-12-09 23:45:13 -0800925 buildrecipe = recipe.get('buildrecipe', None)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000926 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
927 shellQuote(basedir)))
928
929 archiveName = os.path.split(url)[-1]
930 sourceArchive = os.path.join(DEPSRC, archiveName)
931
932 if not os.path.exists(DEPSRC):
933 os.mkdir(DEPSRC)
934
Ned Deilye1c9794952013-01-29 00:07:46 -0800935 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
936 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000937 buildDir=os.path.join(WORKDIR, '_bld')
938 if not os.path.exists(buildDir):
939 os.mkdir(buildDir)
940
941 workDir = extractArchive(buildDir, sourceArchive)
942 os.chdir(workDir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000943
Ned Deilye1c9794952013-01-29 00:07:46 -0800944 for patch in recipe.get('patches', ()):
945 if isinstance(patch, tuple):
946 url, checksum = patch
947 fn = os.path.join(DEPSRC, os.path.basename(url))
948 verifyThirdPartyFile(url, checksum, fn)
949 else:
950 # patch is a file in the source directory
951 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000952 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
953 shellQuote(fn),))
954
Ned Deilye1c9794952013-01-29 00:07:46 -0800955 for patchscript in recipe.get('patchscripts', ()):
956 if isinstance(patchscript, tuple):
957 url, checksum = patchscript
958 fn = os.path.join(DEPSRC, os.path.basename(url))
959 verifyThirdPartyFile(url, checksum, fn)
960 else:
961 # patch is a file in the source directory
962 fn = os.path.join(curdir, patchscript)
963 if fn.endswith('.bz2'):
964 runCommand('bunzip2 -fk %s' % shellQuote(fn))
965 fn = fn[:-4]
966 runCommand('sh %s' % shellQuote(fn))
967 os.unlink(fn)
968
Ned Deilya6cbff02013-10-27 19:47:23 -0700969 if 'buildDir' in recipe:
970 os.chdir(recipe['buildDir'])
971
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000972 if configure is not None:
973 configure_args = [
974 "--prefix=/usr/local",
975 "--enable-static",
976 "--disable-shared",
977 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
978 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000979
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000980 if 'configure_pre' in recipe:
981 args = list(recipe['configure_pre'])
982 if '--disable-static' in args:
983 configure_args.remove('--enable-static')
984 if '--enable-shared' in args:
985 configure_args.remove('--disable-shared')
986 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000987
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000988 if recipe.get('useLDFlags', 1):
989 configure_args.extend([
Ned Deilyee8e4b62018-04-14 10:37:28 -0400990 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deilye1c9794952013-01-29 00:07:46 -0800991 "-I%s/usr/local/include"%(
992 recipe.get('extra_cflags', ''),
993 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000994 ' -arch '.join(archList),
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000995 shellQuote(basedir)[1:-1],),
Ned Deilyee8e4b62018-04-14 10:37:28 -0400996 "LDFLAGS=-mmacosx-version-min=%s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800997 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000998 shellQuote(basedir)[1:-1],
999 ' -arch '.join(archList)),
1000 ])
1001 else:
1002 configure_args.extend([
Ned Deilyee8e4b62018-04-14 10:37:28 -04001003 "CFLAGS=%s-mmacosx-version-min=%s -arch %s "
Ned Deilye1c9794952013-01-29 00:07:46 -08001004 "-I%s/usr/local/include"%(
1005 recipe.get('extra_cflags', ''),
1006 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001007 ' -arch '.join(archList),
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001008 shellQuote(basedir)[1:-1],),
1009 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001010
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001011 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -08001012 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001013
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001014 configure_args.insert(0, configure)
1015 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001016
Ned Deilye1c9794952013-01-29 00:07:46 -08001017 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001018 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001019
Ned Deily62a86602014-12-09 23:45:13 -08001020 if buildrecipe is not None:
1021 # call special-case build recipe, e.g. for openssl
1022 buildrecipe(basedir, archList)
1023
1024 if install is not None:
1025 print("Running install for %s"%(name,))
1026 runCommand('{ ' + install + ' ;} 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001027
Ned Deilye1c9794952013-01-29 00:07:46 -08001028 print("Done %s"%(name,))
1029 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001030
1031 os.chdir(curdir)
1032
1033def buildLibraries():
1034 """
1035 Build our dependencies into $WORKDIR/libraries/usr/local
1036 """
Ned Deilye1c9794952013-01-29 00:07:46 -08001037 print("")
1038 print("Building required libraries")
1039 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001040 universal = os.path.join(WORKDIR, 'libraries')
1041 os.mkdir(universal)
1042 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
1043 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
1044
Ronald Oussoren508282e2009-03-30 19:34:51 +00001045 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +00001046 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001047
1048
1049
1050def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001051 # This stores the documentation as Resources/English.lproj/Documentation
Ned Deilyee8e4b62018-04-14 10:37:28 -04001052 # inside the framework. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -08001053 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001054 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001055 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001056 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001057 curDir = os.getcwd()
1058 os.chdir(buildDir)
Ned Deily5ceffa12014-09-05 15:51:54 -07001059 runCommand('make clean')
Ned Deilyee8e4b62018-04-14 10:37:28 -04001060 if PYTHON_2:
1061 # Python 2 doc builds do not use blurb nor do they have a venv target.
1062 # Assume sphinx-build is on our PATH, checked in checkEnvironment
1063 runCommand('make html')
1064 else:
1065 # Create virtual environment for docs builds with blurb and sphinx
1066 runCommand('make venv')
1067 runCommand('make html PYTHON=venv/bin/python')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001068 os.chdir(curDir)
1069 if not os.path.exists(docdir):
1070 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001071 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001072
1073
1074def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -08001075 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001076
1077 buildDir = os.path.join(WORKDIR, '_bld', 'python')
1078 rootDir = os.path.join(WORKDIR, '_root')
1079
1080 if os.path.exists(buildDir):
1081 shutil.rmtree(buildDir)
1082 if os.path.exists(rootDir):
1083 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +00001084 os.makedirs(buildDir)
1085 os.makedirs(rootDir)
1086 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001087 curdir = os.getcwd()
1088 os.chdir(buildDir)
1089
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001090 # Extract the version from the configure file, needed to calculate
1091 # several paths.
1092 version = getVersion()
1093
Ronald Oussoren008af852009-03-30 20:02:08 +00001094 # Since the extra libs are not in their installed framework location
1095 # during the build, augment the library path so that the interpreter
1096 # will find them during its extension import sanity checks.
1097 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
1098 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -08001099 print("Running configure...")
Ned Deilyee8e4b62018-04-14 10:37:28 -04001100 runCommand("%s -C --enable-framework --enable-universalsdk=/ "
Ronald Oussoren508282e2009-03-30 19:34:51 +00001101 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +00001102 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -07001103 "%s "
Ned Deilyee8e4b62018-04-14 10:37:28 -04001104 "%s "
1105 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +00001106 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -07001107 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ned Deilyee8e4b62018-04-14 10:37:28 -04001108 shellQuote(os.path.join(SRCDIR, 'configure')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001109 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +00001110 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily30101822014-11-14 18:53:59 -08001111 (' ', '--without-ensurepip ')[PYTHON_3],
Ned Deilyee8e4b62018-04-14 10:37:28 -04001112 (' ', "--with-tcltk-includes='-I%s/libraries/usr/local/include'"%(
1113 shellQuote(WORKDIR)[1:-1],))[internalTk()],
1114 (' ', "--with-tcltk-libs='-L%s/libraries/usr/local/lib -ltcl8.6 -ltk8.6'"%(
1115 shellQuote(WORKDIR)[1:-1],))[internalTk()],
Ronald Oussoren508282e2009-03-30 19:34:51 +00001116 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001117 shellQuote(WORKDIR)[1:-1]))
1118
Ned Deily123a58b2017-07-24 04:29:32 -04001119 # Look for environment value BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS
1120 # and, if defined, append its value to the make command. This allows
1121 # us to pass in version control tags, like GITTAG, to a build from a
1122 # tarball rather than from a vcs checkout, thus eliminating the need
1123 # to have a working copy of the vcs program on the build machine.
1124 #
1125 # A typical use might be:
1126 # export BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS=" \
1127 # GITVERSION='echo 123456789a' \
1128 # GITTAG='echo v3.6.0' \
1129 # GITBRANCH='echo 3.6'"
1130
1131 make_extras = os.getenv("BUILDINSTALLER_BUILDPYTHON_MAKE_EXTRAS")
1132 if make_extras:
1133 make_cmd = "make " + make_extras
1134 else:
1135 make_cmd = "make"
1136 print("Running " + make_cmd)
1137 runCommand(make_cmd)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001138
Ned Deilye1c9794952013-01-29 00:07:46 -08001139 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +00001140 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001141 shellQuote(rootDir)))
1142
Ned Deilye1c9794952013-01-29 00:07:46 -08001143 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001144 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1145 shellQuote(rootDir)))
1146
Ronald Oussoren008af852009-03-30 20:02:08 +00001147 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -08001148 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001149 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
Ned Deilyee8e4b62018-04-14 10:37:28 -04001150 build_lib_dir = os.path.join(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001151 WORKDIR, 'libraries', 'Library', 'Frameworks',
Ned Deilyee8e4b62018-04-14 10:37:28 -04001152 'Python.framework', 'Versions', getVersion(), 'lib')
1153 fw_lib_dir = os.path.join(
1154 WORKDIR, '_root', 'Library', 'Frameworks',
1155 'Python.framework', 'Versions', getVersion(), 'lib')
1156 if internalTk():
1157 # move Tcl and Tk pkgconfig files
1158 runCommand("mv %s/pkgconfig/* %s/pkgconfig"%(
1159 shellQuote(build_lib_dir),
1160 shellQuote(fw_lib_dir) ))
1161 runCommand("rm -r %s/pkgconfig"%(
1162 shellQuote(build_lib_dir), ))
1163 runCommand("mv %s/* %s"%(
1164 shellQuote(build_lib_dir),
1165 shellQuote(fw_lib_dir) ))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001166
Ned Deilyee8e4b62018-04-14 10:37:28 -04001167 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1168 frmDirVersioned = os.path.join(frmDir, 'Versions', version)
1169 path_to_lib = os.path.join(frmDirVersioned, 'lib', 'python%s'%(version,))
1170 # create directory for OpenSSL certificates
1171 sslDir = os.path.join(frmDirVersioned, 'etc', 'openssl')
1172 os.makedirs(sslDir)
Ned Deily70f213a2013-10-26 03:16:06 -07001173
Ned Deilye1c9794952013-01-29 00:07:46 -08001174 print("Fix file modes")
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001175 gid = grp.getgrnam('admin').gr_gid
1176
Ned Deilye1c9794952013-01-29 00:07:46 -08001177 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001178 for dirpath, dirnames, filenames in os.walk(frmDir):
1179 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001180 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001181 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001182
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001183 for fn in filenames:
1184 if os.path.islink(fn):
1185 continue
1186
1187 # "chmod g+w $fn"
1188 p = os.path.join(dirpath, fn)
1189 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001190 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1191 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001192
Ned Deilye1c9794952013-01-29 00:07:46 -08001193 if fn in EXPECTED_SHARED_LIBS:
1194 # check to see that this file was linked with the
1195 # expected library path and version
1196 data = captureCommand("otool -L %s" % shellQuote(p))
1197 for sl in EXPECTED_SHARED_LIBS[fn]:
1198 if ("\t%s " % sl) not in data:
1199 print("Expected shared lib %s was not linked with %s"
1200 % (sl, p))
1201 shared_lib_error = True
1202
1203 if shared_lib_error:
1204 fatal("Unexpected shared library errors.")
1205
Ned Deily53c460d2011-01-30 01:43:40 +00001206 if PYTHON_3:
1207 LDVERSION=None
1208 VERSION=None
1209 ABIFLAGS=None
1210
1211 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1212 for ln in fp:
1213 if ln.startswith('VERSION='):
1214 VERSION=ln.split()[1]
1215 if ln.startswith('ABIFLAGS='):
1216 ABIFLAGS=ln.split()[1]
1217 if ln.startswith('LDVERSION='):
1218 LDVERSION=ln.split()[1]
1219 fp.close()
1220
1221 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1222 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1223 config_suffix = '-' + LDVERSION
Ned Deilyee8e4b62018-04-14 10:37:28 -04001224 if getVersionMajorMinor() >= (3, 6):
1225 config_suffix = config_suffix + '-darwin'
Ned Deily53c460d2011-01-30 01:43:40 +00001226 else:
1227 config_suffix = '' # Python 2.x
1228
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001229 # We added some directories to the search path during the configure
1230 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001231 # the end-users system. Also remove the directories from _sysconfigdata.py
1232 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001233
Ned Deilye6ef7022013-10-25 00:46:59 -07001234 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1235 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1236
Ned Deilye6ef7022013-10-25 00:46:59 -07001237 # fix Makefile
1238 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1239 fp = open(path, 'r')
1240 data = fp.read()
1241 fp.close()
1242
1243 for p in (include_path, lib_path):
1244 data = data.replace(" " + p, '')
1245 data = data.replace(p + " ", '')
1246
1247 fp = open(path, 'w')
1248 fp.write(data)
1249 fp.close()
1250
Ned Deilyee8e4b62018-04-14 10:37:28 -04001251 # fix _sysconfigdata
Ned Deilye6ef7022013-10-25 00:46:59 -07001252 #
1253 # TODO: make this more robust! test_sysconfig_module of
1254 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1255 # the output from get_config_var in both sysconfig and
1256 # distutils.sysconfig is exactly the same for both CFLAGS and
1257 # LDFLAGS. The fixing up is now complicated by the pretty
1258 # printing in _sysconfigdata.py. Also, we are using the
1259 # pprint from the Python running the installer build which
1260 # may not cosmetically format the same as the pprint in the Python
1261 # being built (and which is used to originally generate
1262 # _sysconfigdata.py).
1263
1264 import pprint
Ned Deilyee8e4b62018-04-14 10:37:28 -04001265 if getVersionMajorMinor() >= (3, 6):
1266 # XXX this is extra-fragile
1267 path = os.path.join(path_to_lib, '_sysconfigdata_m_darwin_darwin.py')
1268 else:
1269 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1270 fp = open(path, 'r')
1271 data = fp.read()
1272 fp.close()
1273 # create build_time_vars dict
1274 exec(data)
1275 vars = {}
1276 for k, v in build_time_vars.items():
1277 if type(v) == type(''):
1278 for p in (include_path, lib_path):
1279 v = v.replace(' ' + p, '')
1280 v = v.replace(p + ' ', '')
1281 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001282
Ned Deilyee8e4b62018-04-14 10:37:28 -04001283 fp = open(path, 'w')
1284 # duplicated from sysconfig._generate_posix_vars()
1285 fp.write('# system configuration generated and used by'
1286 ' the sysconfig module\n')
1287 fp.write('build_time_vars = ')
1288 pprint.pprint(vars, stream=fp)
1289 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001290
1291 # Add symlinks in /usr/local/bin, using relative links
1292 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1293 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1294 'Python.framework', 'Versions', version, 'bin')
1295 if os.path.exists(usr_local_bin):
1296 shutil.rmtree(usr_local_bin)
1297 os.makedirs(usr_local_bin)
1298 for fn in os.listdir(
1299 os.path.join(frmDir, 'Versions', version, 'bin')):
1300 os.symlink(os.path.join(to_framework, fn),
1301 os.path.join(usr_local_bin, fn))
1302
1303 os.chdir(curdir)
1304
Ned Deily53c460d2011-01-30 01:43:40 +00001305 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001306 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001307 # with an already installed version of python 2
1308 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1309 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001310
1311def patchFile(inPath, outPath):
1312 data = fileContents(inPath)
1313 data = data.replace('$FULL_VERSION', getFullVersion())
1314 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001315 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001316 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001317 data = data.replace('$INSTALL_SIZE', installSize())
Ned Deily62a86602014-12-09 23:45:13 -08001318 data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
Ronald Oussorenc5555542006-06-11 20:24:45 +00001319
1320 # This one is not handy as a template variable
1321 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001322 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001323 fp.write(data)
1324 fp.close()
1325
1326def patchScript(inPath, outPath):
Ned Deily30101822014-11-14 18:53:59 -08001327 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001328 data = fileContents(inPath)
Ned Deily30101822014-11-14 18:53:59 -08001329 data = data.replace('@PYMAJOR@', str(major))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001330 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001331 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001332 fp.write(data)
1333 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001334 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001335
1336
1337
1338def packageFromRecipe(targetDir, recipe):
1339 curdir = os.getcwd()
1340 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001341 # The major version (such as 2.5) is included in the package name
1342 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001343 # common.
1344 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001345 srcdir = recipe.get('source')
1346 pkgroot = recipe.get('topdir', srcdir)
1347 postflight = recipe.get('postflight')
1348 readme = textwrap.dedent(recipe['readme'])
1349 isRequired = recipe.get('required', True)
1350
Ned Deilye1c9794952013-01-29 00:07:46 -08001351 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001352
1353 # Substitute some variables
1354 textvars = dict(
1355 VER=getVersion(),
1356 FULLVER=getFullVersion(),
1357 )
1358 readme = readme % textvars
1359
1360 if pkgroot is not None:
1361 pkgroot = pkgroot % textvars
1362 else:
1363 pkgroot = '/'
1364
1365 if srcdir is not None:
1366 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1367 srcdir = srcdir % textvars
1368
1369 if postflight is not None:
1370 postflight = os.path.abspath(postflight)
1371
1372 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1373 os.makedirs(packageContents)
1374
1375 if srcdir is not None:
1376 os.chdir(srcdir)
1377 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1378 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1379 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1380
1381 fn = os.path.join(packageContents, 'PkgInfo')
1382 fp = open(fn, 'w')
1383 fp.write('pmkrpkg1')
1384 fp.close()
1385
1386 rsrcDir = os.path.join(packageContents, "Resources")
1387 os.mkdir(rsrcDir)
1388 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1389 fp.write(readme)
1390 fp.close()
1391
1392 if postflight is not None:
1393 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1394
1395 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001396 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001397 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001398 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1399 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1400 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001401 CFBundleShortVersionString=vers,
1402 IFMajorVersion=major,
1403 IFMinorVersion=minor,
1404 IFPkgFormatVersion=0.10000000149011612,
1405 IFPkgFlagAllowBackRev=False,
1406 IFPkgFlagAuthorizationAction="RootAuthorization",
1407 IFPkgFlagDefaultLocation=pkgroot,
1408 IFPkgFlagFollowLinks=True,
1409 IFPkgFlagInstallFat=True,
1410 IFPkgFlagIsRequired=isRequired,
1411 IFPkgFlagOverwritePermissions=False,
1412 IFPkgFlagRelocatable=False,
1413 IFPkgFlagRestartAction="NoRestart",
1414 IFPkgFlagRootVolumeOnly=True,
1415 IFPkgFlagUpdateInstalledLangauges=False,
1416 )
1417 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1418
1419 pl = Plist(
1420 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001421 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001422 IFPkgDescriptionVersion=vers,
1423 )
1424 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1425
1426 finally:
1427 os.chdir(curdir)
1428
1429
1430def makeMpkgPlist(path):
1431
1432 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001433 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001434
1435 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001436 CFBundleGetInfoString="Python %s"%(vers,),
1437 CFBundleIdentifier='org.python.Python',
1438 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001439 CFBundleShortVersionString=vers,
1440 IFMajorVersion=major,
1441 IFMinorVersion=minor,
1442 IFPkgFlagComponentDirectory="Contents/Packages",
1443 IFPkgFlagPackageList=[
1444 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001445 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001446 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001447 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001448 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001449 ],
1450 IFPkgFormatVersion=0.10000000149011612,
1451 IFPkgFlagBackgroundScaling="proportional",
1452 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001453 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001454 )
1455
1456 writePlist(pl, path)
1457
1458
1459def buildInstaller():
1460
1461 # Zap all compiled files
1462 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1463 for fn in filenames:
1464 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1465 os.unlink(os.path.join(dirpath, fn))
1466
1467 outdir = os.path.join(WORKDIR, 'installer')
1468 if os.path.exists(outdir):
1469 shutil.rmtree(outdir)
1470 os.mkdir(outdir)
1471
Ronald Oussoren508282e2009-03-30 19:34:51 +00001472 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001473 pkgcontents = os.path.join(pkgroot, 'Packages')
1474 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001475 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001476 packageFromRecipe(pkgcontents, recipe)
1477
1478 rsrcDir = os.path.join(pkgroot, 'Resources')
1479
1480 fn = os.path.join(pkgroot, 'PkgInfo')
1481 fp = open(fn, 'w')
1482 fp.write('pmkrpkg1')
1483 fp.close()
1484
1485 os.mkdir(rsrcDir)
1486
1487 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1488 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001489 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001490 IFPkgDescriptionVersion=getVersion(),
1491 )
1492
1493 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1494 for fn in os.listdir('resources'):
1495 if fn == '.svn': continue
1496 if fn.endswith('.jpg'):
1497 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1498 else:
1499 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1500
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001501
1502def installSize(clear=False, _saved=[]):
1503 if clear:
1504 del _saved[:]
1505 if not _saved:
1506 data = captureCommand("du -ks %s"%(
1507 shellQuote(os.path.join(WORKDIR, '_root'))))
1508 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1509 return _saved[0]
1510
1511
1512def buildDMG():
1513 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001514 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001515 """
1516 outdir = os.path.join(WORKDIR, 'diskimage')
1517 if os.path.exists(outdir):
1518 shutil.rmtree(outdir)
1519
1520 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001521 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001522 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001523 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001524 imagepath = imagepath + '.dmg'
1525
1526 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001527 volname='Python %s'%(getFullVersion())
1528 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1529 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001530 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001531 shellQuote(imagepath + ".tmp.dmg" )))
1532
Miss Islington (bot)5818f082018-05-01 22:50:12 -07001533 # Try to mitigate race condition in certain versions of macOS, e.g. 10.9,
1534 # when hdiutil fails with "Resource busy"
1535
1536 time.sleep(10)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001537
1538 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1539 os.mkdir(os.path.join(WORKDIR, "mnt"))
1540 runCommand("hdiutil attach %s -mountroot %s"%(
1541 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1542
1543 # Custom icon for the DMG, shown when the DMG is mounted.
1544 shutil.copy("../Icons/Disk Image.icns",
1545 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001546 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001547 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1548
1549 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1550
1551 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1552 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1553 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1554 setIcon(imagepath, "../Icons/Disk Image.icns")
1555
1556 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001557
1558 return imagepath
1559
1560
1561def setIcon(filePath, icnsPath):
1562 """
1563 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001564 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001565
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001566 dirPath = os.path.normpath(os.path.dirname(__file__))
1567 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001568 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1569 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1570 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001571 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1572 if not os.path.exists(appPath):
1573 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001574 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1575 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001576
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001577 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1578 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001579
1580def main():
1581 # First parse options and check if we can perform our work
1582 parseOptions()
1583 checkEnvironment()
1584
Ronald Oussoren508282e2009-03-30 19:34:51 +00001585 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001586 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001587 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001588
1589 if os.path.exists(WORKDIR):
1590 shutil.rmtree(WORKDIR)
1591 os.mkdir(WORKDIR)
1592
Ronald Oussoren287128a2010-04-18 14:01:05 +00001593 os.environ['LC_ALL'] = 'C'
1594
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001595 # Then build third-party libraries such as sleepycat DB4.
1596 buildLibraries()
1597
1598 # Now build python itself
1599 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001600
1601 # And then build the documentation
1602 # Remove the Deployment Target from the shell
1603 # environment, it's no longer needed and
1604 # an unexpected build target can cause problems
1605 # when Sphinx and its dependencies need to
1606 # be (re-)installed.
1607 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001608 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001609
1610
1611 # Prepare the applications folder
Benjamin Petersonc3104762008-10-03 11:52:06 +00001612 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001613 getVersion(),))
Ned Deily62a86602014-12-09 23:45:13 -08001614 fn = os.path.join(folder, "License.rtf")
Ned Deily91d0a552014-12-13 00:33:29 -08001615 patchFile("resources/License.rtf", fn)
Ned Deily62a86602014-12-09 23:45:13 -08001616 fn = os.path.join(folder, "ReadMe.rtf")
Ned Deily91d0a552014-12-13 00:33:29 -08001617 patchFile("resources/ReadMe.rtf", fn)
Ned Deily62a86602014-12-09 23:45:13 -08001618 fn = os.path.join(folder, "Update Shell Profile.command")
1619 patchScript("scripts/postflight.patch-profile", fn)
Ned Deilyee8e4b62018-04-14 10:37:28 -04001620 fn = os.path.join(folder, "Install Certificates.command")
1621 patchScript("resources/install_certificates.command", fn)
Ned Deilye1c9794952013-01-29 00:07:46 -08001622 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001623 setIcon(folder, "../Icons/Python Folder.icns")
1624
1625 # Create the installer
1626 buildInstaller()
1627
1628 # And copy the readme into the directory containing the installer
Ned Deily91d0a552014-12-13 00:33:29 -08001629 patchFile('resources/ReadMe.rtf',
1630 os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001631
1632 # Ditto for the license file.
Ned Deily91d0a552014-12-13 00:33:29 -08001633 patchFile('resources/License.rtf',
1634 os.path.join(WORKDIR, 'installer', 'License.rtf'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001635
1636 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001637 fp.write("# BUILD INFO\n")
1638 fp.write("# Date: %s\n" % time.ctime())
1639 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001640 fp.close()
1641
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001642 # And copy it to a DMG
1643 buildDMG()
1644
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001645if __name__ == "__main__":
1646 main()