blob: 74c274646a9b0e072183ef40b32aca8fc35b7d95 [file] [log] [blame]
Ned Deilye1c9794952013-01-29 00:07:46 -08001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deily53c460d2011-01-30 01:43:40 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deilye1c9794952013-01-29 00:07:46 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deily53c460d2011-01-30 01:43:40 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
Ned Deily53c460d2011-01-30 01:43:40 +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 Deilyebd63dc2014-04-09 16:12:11 -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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000014
Ned Deilye1c9794952013-01-29 00:07:46 -080015In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
16requires an installed version of hg and a third-party version of
17Tcl/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 Deilyebd63dc2014-04-09 16:12:11 -070026(2.5 is needed for Python parser updates), hg, and for the documentation
27build either svn (pre-3.4.1) or sphinx-build (3.4.1 and later).
Ned Deilye1c9794952013-01-29 00:07:46 -080028
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000029Usage: see USAGE variable in the script.
30"""
Ned Deilye1c9794952013-01-29 00:07:46 -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 )
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000044
Ronald Oussoren158ad592006-11-07 16:00:34 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000047
48from plistlib import Plist
49
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000059 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
65 variable = variable + '='
66 for ln in open(fn, 'r'):
67 if ln.startswith(variable):
68 value = ln[len(variable):].strip()
69 return value[1:-1]
Ned Deilye1c9794952013-01-29 00:07:46 -080070 raise RuntimeError("Cannot find variable %s" % variable[:-1])
71
72_cache_getVersion = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000073
74def getVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080075 global _cache_getVersion
76 if _cache_getVersion is None:
77 _cache_getVersion = grepValue(
78 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
79 return _cache_getVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000080
Ned Deily53c460d2011-01-30 01:43:40 +000081def getVersionTuple():
82 return tuple([int(n) for n in getVersion().split('.')])
83
Ned Deilye1c9794952013-01-29 00:07:46 -080084def getVersionMajorMinor():
85 return tuple([int(n) for n in getVersion().split('.', 2)])
86
87_cache_getFullVersion = None
88
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000089def getFullVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080090 global _cache_getFullVersion
91 if _cache_getFullVersion is not None:
92 return _cache_getFullVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000093 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
94 for ln in open(fn):
95 if 'PY_VERSION' in ln:
Ned Deilye1c9794952013-01-29 00:07:46 -080096 _cache_getFullVersion = ln.split()[-1][1:-1]
97 return _cache_getFullVersion
98 raise RuntimeError("Cannot find full version??")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000099
Ronald Oussorenaa560962006-11-07 15:53:38 +0000100# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000101WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000102
Ronald Oussorenaa560962006-11-07 15:53:38 +0000103# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000104# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000105DEPSRC = os.path.join(WORKDIR, 'third-party')
106DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000107
108# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000109
110### There are some issues with the SDK selection below here,
111### The resulting binary doesn't work on all platforms that
112### it should. Always default to the 10.4u SDK until that
Ezio Melotti6d0f0f22013-08-26 01:31:30 +0300113### issue is resolved.
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000114###
115##if int(os.uname()[2].split('.')[0]) == 8:
116## # Explicitly use the 10.4u (universal) SDK when
117## # building on 10.4, the system headers are not
118## # useable for a universal build
119## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
120##else:
121## SDKPATH = "/"
122
123SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000124
Ronald Oussoren508282e2009-03-30 19:34:51 +0000125universal_opts_map = { '32-bit': ('i386', 'ppc',),
126 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000127 'intel': ('i386', 'x86_64'),
128 '3-way': ('ppc', 'i386', 'x86_64'),
129 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
130default_target_map = {
131 '64-bit': '10.5',
132 '3-way': '10.5',
133 'intel': '10.5',
134 'all': '10.5',
135}
Ronald Oussoren508282e2009-03-30 19:34:51 +0000136
137UNIVERSALOPTS = tuple(universal_opts_map.keys())
138
139UNIVERSALARCHS = '32-bit'
140
141ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000142
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200143# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000144SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000145 os.path.dirname(
146 os.path.dirname(
147 os.path.abspath(__file__
148 ))))
149
Ronald Oussoren508282e2009-03-30 19:34:51 +0000150# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
151DEPTARGET = '10.3'
152
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000153target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -0800154 '10.3': ('gcc-4.0', 'g++-4.0'),
155 '10.4': ('gcc-4.0', 'g++-4.0'),
156 '10.5': ('gcc-4.2', 'g++-4.2'),
157 '10.6': ('gcc-4.2', 'g++-4.2'),
158 '10.7': ('clang', 'clang++'),
159 '10.8': ('clang', 'clang++'),
Ned Deily11f880a2013-10-18 20:40:23 -0700160 '10.9': ('clang', 'clang++'),
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000161}
162
Ned Deilye1c9794952013-01-29 00:07:46 -0800163CC, CXX = target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000164
Ned Deily53c460d2011-01-30 01:43:40 +0000165PYTHON_3 = getVersionTuple() >= (3, 0)
166
Ronald Oussoren158ad592006-11-07 16:00:34 +0000167USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000168 Usage: build_python [options]
169
170 Options:
171 -? or -h: Show this message
172 -b DIR
173 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
174 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
175 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
176 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000177 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
178 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000179""")% globals()
180
Ned Deilye1c9794952013-01-29 00:07:46 -0800181# Dict of object file names with shared library names to check after building.
182# This is to ensure that we ended up dynamically linking with the shared
183# library paths and versions we expected. For example:
184# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
185# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
186# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
187EXPECTED_SHARED_LIBS = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000188
189# Instructions for building libraries that are necessary for building a
190# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000191# [The recipes are defined here for convenience but instantiated later after
192# command line options have been processed.]
193def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000194 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000195
Ned Deilye1c9794952013-01-29 00:07:46 -0800196 LT_10_5 = bool(DEPTARGET < '10.5')
197
Ned Deilyebd63dc2014-04-09 16:12:11 -0700198# Disable for now
199 if False: # if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 5)):
Ned Deily0203a802013-10-25 00:40:07 -0700200 result.extend([
201 dict(
202 name="Tcl 8.5.15",
203 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
204 checksum='f3df162f92c69b254079c4d0af7a690f',
205 buildDir="unix",
206 configure_pre=[
207 '--enable-shared',
208 '--enable-threads',
209 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
210 ],
211 useLDFlags=False,
212 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
213 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
214 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
215 },
216 ),
217 dict(
218 name="Tk 8.5.15",
219 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
220 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deilya6cbff02013-10-27 19:47:23 -0700221 patches=[
222 "issue19373_tk_8_5_15_source.patch",
223 ],
Ned Deily0203a802013-10-25 00:40:07 -0700224 buildDir="unix",
225 configure_pre=[
226 '--enable-aqua',
227 '--enable-shared',
228 '--enable-threads',
229 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
230 ],
231 useLDFlags=False,
232 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'%{
233 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
234 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
235 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
236 },
237 ),
238 ])
239
Ned Deilye1c9794952013-01-29 00:07:46 -0800240 if getVersionTuple() >= (3, 3):
241 result.extend([
242 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700243 name="XZ 5.0.5",
244 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
245 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deilye1c9794952013-01-29 00:07:46 -0800246 configure_pre=[
247 '--disable-dependency-tracking',
248 ]
249 ),
250 ])
251
252 result.extend([
253 dict(
254 name="NCurses 5.9",
255 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
256 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
257 configure_pre=[
258 "--enable-widec",
259 "--without-cxx",
260 "--without-cxx-binding",
261 "--without-ada",
262 "--without-curses-h",
263 "--enable-shared",
264 "--with-shared",
265 "--without-debug",
266 "--without-normal",
267 "--without-tests",
268 "--without-manpages",
269 "--datadir=/usr/share",
270 "--sysconfdir=/etc",
271 "--sharedstatedir=/usr/com",
272 "--with-terminfo-dirs=/usr/share/terminfo",
273 "--with-default-terminfo-dir=/usr/share/terminfo",
274 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
275 ],
276 patchscripts=[
277 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
278 "f54bf02a349f96a7c4f0d00922f3a0d4"),
279 ],
280 useLDFlags=False,
281 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
282 shellQuote(os.path.join(WORKDIR, 'libraries')),
283 shellQuote(os.path.join(WORKDIR, 'libraries')),
284 getVersion(),
285 ),
286 ),
287 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700288 name="SQLite 3.8.3.1",
289 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
290 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deilye1c9794952013-01-29 00:07:46 -0800291 extra_cflags=('-Os '
292 '-DSQLITE_ENABLE_FTS4 '
293 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
294 '-DSQLITE_ENABLE_RTREE '
295 '-DSQLITE_TCL=0 '
296 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
297 configure_pre=[
298 '--enable-threadsafe',
299 '--enable-shared=no',
300 '--enable-static=yes',
301 '--disable-readline',
302 '--disable-dependency-tracking',
303 ]
304 ),
305 ])
306
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000307 if DEPTARGET < '10.5':
308 result.extend([
309 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000310 name="Bzip2 1.0.6",
311 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
312 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000313 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800314 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
315 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000316 shellQuote(os.path.join(WORKDIR, 'libraries')),
317 ' -arch '.join(ARCHLIST),
318 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000319 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000320 ),
321 dict(
322 name="ZLib 1.2.3",
323 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
324 checksum='debc62758716a169df9f62e6ab2bc634',
325 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800326 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
327 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000328 shellQuote(os.path.join(WORKDIR, 'libraries')),
329 ' -arch '.join(ARCHLIST),
330 SDKPATH,
331 ),
332 ),
333 dict(
334 # Note that GNU readline is GPL'd software
Ned Deily53c460d2011-01-30 01:43:40 +0000335 name="GNU Readline 6.1.2",
336 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
337 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000338 patchlevel='0',
339 patches=[
340 # The readline maintainers don't do actual micro releases, but
341 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -0800342 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
343 'c642f2e84d820884b0bf9fd176bc6c3f'),
344 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
345 '1a76781a1ea734e831588285db7ec9b1'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000346 ]
347 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000348 ])
349
Ned Deily53c460d2011-01-30 01:43:40 +0000350 if not PYTHON_3:
351 result.extend([
352 dict(
353 name="Sleepycat DB 4.7.25",
354 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
355 checksum='ec2b87e833779681a0c3a814aa71359e',
356 buildDir="build_unix",
357 configure="../dist/configure",
358 configure_pre=[
359 '--includedir=/usr/local/include/db4',
360 ]
361 ),
362 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000363
364 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000365
366
367# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000368def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000369 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deilyebd63dc2014-04-09 16:12:11 -0700370 # unselected if 3.0 through 3.3, selected otherwise (2.x or >= 3.4)
371 unselected_for_lt_python34 = ('selected', 'unselected')[(3, 0) <= getVersionTuple() < (3, 4)]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000372 result = [
373 dict(
374 name="PythonFramework",
375 long_name="Python Framework",
376 source="/Library/Frameworks/Python.framework",
377 readme="""\
378 This package installs Python.framework, that is the python
379 interpreter and the standard library. This also includes Python
380 wrappers for lots of Mac OS X API's.
381 """,
382 postflight="scripts/postflight.framework",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000383 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000384 ),
385 dict(
386 name="PythonApplications",
387 long_name="GUI Applications",
388 source="/Applications/Python %(VER)s",
389 readme="""\
390 This package installs IDLE (an interactive Python IDE),
391 Python Launcher and Build Applet (create application bundles
392 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000393
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000394 It also installs a number of examples and demos.
395 """,
396 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000397 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000398 ),
399 dict(
400 name="PythonUnixTools",
401 long_name="UNIX command-line tools",
402 source="/usr/local/bin",
403 readme="""\
404 This package installs the unix tools in /usr/local/bin for
405 compatibility with older releases of Python. This package
406 is not necessary to use Python.
407 """,
408 required=False,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000409 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000410 ),
411 dict(
412 name="PythonDocumentation",
413 long_name="Python Documentation",
414 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
415 source="/pydocs",
416 readme="""\
417 This package installs the python documentation at a location
Ned Deilye1c9794952013-01-29 00:07:46 -0800418 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000419 """,
420 postflight="scripts/postflight.documentation",
421 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000422 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000423 ),
424 dict(
425 name="PythonProfileChanges",
426 long_name="Shell profile updater",
427 readme="""\
428 This packages updates your shell profile to make sure that
429 the Python tools are found by your shell in preference of
430 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000431
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000432 If you don't install this package you'll have to add
433 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
434 to your PATH by hand.
435 """,
436 postflight="scripts/postflight.patch-profile",
437 topdir="/Library/Frameworks/Python.framework",
438 source="/empty-dir",
439 required=False,
Ned Deilyebd63dc2014-04-09 16:12:11 -0700440 selected=unselected_for_lt_python34,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000441 ),
442 ]
443
Ned Deilyebd63dc2014-04-09 16:12:11 -0700444 if getVersionTuple() >= (3, 4):
445 result.append(
446 dict(
447 name="PythonInstallPip",
448 long_name="Install or upgrade pip",
449 readme="""\
450 This package installs (or upgrades from an earlier version)
451 pip, a tool for installing and managing Python packages.
452 """,
453 postflight="scripts/postflight.ensurepip",
454 topdir="/Library/Frameworks/Python.framework",
455 source="/empty-dir",
456 required=False,
457 selected='selected',
458 )
459 )
460
Ned Deilye1c9794952013-01-29 00:07:46 -0800461 if DEPTARGET < '10.4' and not PYTHON_3:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000462 result.append(
463 dict(
464 name="PythonSystemFixes",
465 long_name="Fix system Python",
466 readme="""\
467 This package updates the system python installation on
468 Mac OS X 10.3 to ensure that you can build new python extensions
469 using that copy of python after installing this version.
470 """,
471 postflight="../Tools/fixapplepython23.py",
472 topdir="/Library/Frameworks/Python.framework",
473 source="/empty-dir",
474 required=False,
Ned Deily53c460d2011-01-30 01:43:40 +0000475 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000476 )
477 )
Ned Deilyebd63dc2014-04-09 16:12:11 -0700478
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000479 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000480
481def fatal(msg):
482 """
483 A fatal error, bail out.
484 """
485 sys.stderr.write('FATAL: ')
486 sys.stderr.write(msg)
487 sys.stderr.write('\n')
488 sys.exit(1)
489
490def fileContents(fn):
491 """
492 Return the contents of the named file
493 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800494 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000495
496def runCommand(commandline):
497 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200498 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000499 unless the command fails.
500 """
501 fd = os.popen(commandline, 'r')
502 data = fd.read()
503 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000504 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000505 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800506 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000507
508 if VERBOSE:
509 sys.stdout.write(data); sys.stdout.flush()
510
511def captureCommand(commandline):
512 fd = os.popen(commandline, 'r')
513 data = fd.read()
514 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000515 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000516 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800517 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000518
519 return data
520
Ronald Oussoren287128a2010-04-18 14:01:05 +0000521def getTclTkVersion(configfile, versionline):
522 """
523 search Tcl or Tk configuration file for version line
524 """
525 try:
526 f = open(configfile, "r")
527 except:
528 fatal("Framework configuration file not found: %s" % configfile)
529
530 for l in f:
531 if l.startswith(versionline):
532 f.close()
533 return l
534
535 fatal("Version variable %s not found in framework configuration file: %s"
536 % (versionline, configfile))
537
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000538def checkEnvironment():
539 """
540 Check that we're running on a supported system.
541 """
542
Ned Deily53c460d2011-01-30 01:43:40 +0000543 if sys.version_info[0:2] < (2, 4):
544 fatal("This script must be run with Python 2.4 or later")
545
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000546 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000547 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000548
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000549 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000550 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000551
552 if not os.path.exists(SDKPATH):
553 fatal("Please install the latest version of Xcode and the %s SDK"%(
554 os.path.basename(SDKPATH[:-4])))
555
Ronald Oussoren287128a2010-04-18 14:01:05 +0000556 # Because we only support dynamic load of only one major/minor version of
557 # Tcl/Tk, ensure:
558 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deilye1c9794952013-01-29 00:07:46 -0800559 # higher than the Apple-supplied system version in
560 # SDKROOT/System/Library/Frameworks
561 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
562 # in) SDKROOT/Library/Frameworks with the same version as the system
563 # version. This allows users to choose to install a newer patch level.
Ronald Oussoren287128a2010-04-18 14:01:05 +0000564
Ned Deilye1c9794952013-01-29 00:07:46 -0800565 frameworks = {}
Ronald Oussoren287128a2010-04-18 14:01:05 +0000566 for framework in ['Tcl', 'Tk']:
Ned Deilye1c9794952013-01-29 00:07:46 -0800567 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily53c460d2011-01-30 01:43:40 +0000568 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800569 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000570 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800571 frameworks[framework] = os.readlink(sysfw)
572 if not os.path.exists(libfw):
573 fatal("Please install a link to a current %s %s as %s so "
574 "the user can override the system framework."
575 % (framework, frameworks[framework], libfw))
Ned Deily53c460d2011-01-30 01:43:40 +0000576 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +0000577 fatal("Version of %s must match %s" % (libfw, sysfw) )
578 if os.path.exists(usrfw):
579 fatal("Please rename %s to avoid possible dynamic load issues."
580 % usrfw)
581
Ned Deilye1c9794952013-01-29 00:07:46 -0800582 if frameworks['Tcl'] != frameworks['Tk']:
583 fatal("The Tcl and Tk frameworks are not the same version.")
584
585 # add files to check after build
586 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
587 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
588 % frameworks['Tcl'],
589 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
590 % frameworks['Tk'],
591 ]
592
Ronald Oussoren287128a2010-04-18 14:01:05 +0000593 # Remove inherited environment variables which might influence build
594 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
595 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
596 for ev in list(os.environ):
597 for prefix in environ_var_prefixes:
598 if ev.startswith(prefix) :
Ned Deilye1c9794952013-01-29 00:07:46 -0800599 print("INFO: deleting environment variable %s=%s" % (
600 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000601 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000602
Ned Deilye1c9794952013-01-29 00:07:46 -0800603 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
604 if 'SDK_TOOLS_BIN' in os.environ:
605 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
606 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
607 # add its fixed location here if it exists
608 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
609 if os.path.isdir(OLD_DEVELOPER_TOOLS):
610 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
611 os.environ['PATH'] = base_path
612 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyebd63dc2014-04-09 16:12:11 -0700613 # Ensure ws have access to hg and to sphinx-build.
614 # You may have to create links in /usr/bin for them.
615 runCommand('hg --version')
616 if getVersionTuple() >= (3, 4):
617 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000618
Ronald Oussoren158ad592006-11-07 16:00:34 +0000619def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000620 """
621 Parse arguments and update global settings.
622 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000623 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800624 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000625
626 if args is None:
627 args = sys.argv[1:]
628
629 try:
630 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000631 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
632 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800633 except getopt.GetoptError:
634 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000635 sys.exit(1)
636
637 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800638 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000639 sys.exit(1)
640
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000641 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000642 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000643 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800644 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000645 sys.exit(0)
646
647 elif k in ('-d', '--build-dir'):
648 WORKDIR=v
649
650 elif k in ('--third-party',):
651 DEPSRC=v
652
653 elif k in ('--sdk-path',):
654 SDKPATH=v
655
656 elif k in ('--src-dir',):
657 SRCDIR=v
658
Ronald Oussoren508282e2009-03-30 19:34:51 +0000659 elif k in ('--dep-target', ):
660 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000661 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000662
663 elif k in ('--universal-archs', ):
664 if v in UNIVERSALOPTS:
665 UNIVERSALARCHS = v
666 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000667 if deptarget is None:
668 # Select alternate default deployment
669 # target
670 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000671 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800672 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000673
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000674 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800675 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000676
677 SRCDIR=os.path.abspath(SRCDIR)
678 WORKDIR=os.path.abspath(WORKDIR)
679 SDKPATH=os.path.abspath(SDKPATH)
680 DEPSRC=os.path.abspath(DEPSRC)
681
Ned Deilye1c9794952013-01-29 00:07:46 -0800682 CC, CXX=target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000683
Ned Deilye1c9794952013-01-29 00:07:46 -0800684 print("Settings:")
685 print(" * Source directory:", SRCDIR)
686 print(" * Build directory: ", WORKDIR)
687 print(" * SDK location: ", SDKPATH)
688 print(" * Third-party source:", DEPSRC)
689 print(" * Deployment target:", DEPTARGET)
690 print(" * Universal architectures:", ARCHLIST)
691 print(" * C compiler:", CC)
692 print(" * C++ compiler:", CXX)
693 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000694
695
696
697
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)
767 except:
768 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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000788def buildRecipe(recipe, basedir, archList):
789 """
790 Build software using a recipe. This function does the
791 'configure;make;make install' dance for C software, with a possibility
792 to customize this process, basically a poor-mans DarwinPorts.
793 """
794 curdir = os.getcwd()
795
796 name = recipe['name']
797 url = recipe['url']
798 configure = recipe.get('configure', './configure')
799 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
800 shellQuote(basedir)))
801
802 archiveName = os.path.split(url)[-1]
803 sourceArchive = os.path.join(DEPSRC, archiveName)
804
805 if not os.path.exists(DEPSRC):
806 os.mkdir(DEPSRC)
807
Ned Deilye1c9794952013-01-29 00:07:46 -0800808 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
809 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000810 buildDir=os.path.join(WORKDIR, '_bld')
811 if not os.path.exists(buildDir):
812 os.mkdir(buildDir)
813
814 workDir = extractArchive(buildDir, sourceArchive)
815 os.chdir(workDir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000816
Ned Deilye1c9794952013-01-29 00:07:46 -0800817 for patch in recipe.get('patches', ()):
818 if isinstance(patch, tuple):
819 url, checksum = patch
820 fn = os.path.join(DEPSRC, os.path.basename(url))
821 verifyThirdPartyFile(url, checksum, fn)
822 else:
823 # patch is a file in the source directory
824 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000825 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
826 shellQuote(fn),))
827
Ned Deilye1c9794952013-01-29 00:07:46 -0800828 for patchscript in recipe.get('patchscripts', ()):
829 if isinstance(patchscript, tuple):
830 url, checksum = patchscript
831 fn = os.path.join(DEPSRC, os.path.basename(url))
832 verifyThirdPartyFile(url, checksum, fn)
833 else:
834 # patch is a file in the source directory
835 fn = os.path.join(curdir, patchscript)
836 if fn.endswith('.bz2'):
837 runCommand('bunzip2 -fk %s' % shellQuote(fn))
838 fn = fn[:-4]
839 runCommand('sh %s' % shellQuote(fn))
840 os.unlink(fn)
841
Ned Deilya6cbff02013-10-27 19:47:23 -0700842 if 'buildDir' in recipe:
843 os.chdir(recipe['buildDir'])
844
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000845 if configure is not None:
846 configure_args = [
847 "--prefix=/usr/local",
848 "--enable-static",
849 "--disable-shared",
850 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
851 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000852
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000853 if 'configure_pre' in recipe:
854 args = list(recipe['configure_pre'])
855 if '--disable-static' in args:
856 configure_args.remove('--enable-static')
857 if '--enable-shared' in args:
858 configure_args.remove('--disable-shared')
859 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000860
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000861 if recipe.get('useLDFlags', 1):
862 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800863 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
864 "-I%s/usr/local/include"%(
865 recipe.get('extra_cflags', ''),
866 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000867 ' -arch '.join(archList),
868 shellQuote(SDKPATH)[1:-1],
869 shellQuote(basedir)[1:-1],),
Ned Deilyc177b1c2014-04-09 16:13:46 -0700870 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800871 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000872 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000873 shellQuote(basedir)[1:-1],
874 ' -arch '.join(archList)),
875 ])
876 else:
877 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800878 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
879 "-I%s/usr/local/include"%(
880 recipe.get('extra_cflags', ''),
881 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000882 ' -arch '.join(archList),
883 shellQuote(SDKPATH)[1:-1],
884 shellQuote(basedir)[1:-1],),
885 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000886
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000887 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -0800888 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000889
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000890 configure_args.insert(0, configure)
891 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000892
Ned Deilye1c9794952013-01-29 00:07:46 -0800893 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000894 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000895
Ned Deilye1c9794952013-01-29 00:07:46 -0800896 print("Running install for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000897 runCommand('{ ' + install + ' ;} 2>&1')
898
Ned Deilye1c9794952013-01-29 00:07:46 -0800899 print("Done %s"%(name,))
900 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000901
902 os.chdir(curdir)
903
904def buildLibraries():
905 """
906 Build our dependencies into $WORKDIR/libraries/usr/local
907 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800908 print("")
909 print("Building required libraries")
910 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000911 universal = os.path.join(WORKDIR, 'libraries')
912 os.mkdir(universal)
913 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
914 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
915
Ronald Oussoren508282e2009-03-30 19:34:51 +0000916 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000917 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000918
919
920
921def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000922 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000923 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -0800924 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000925 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000926 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000927 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000928 curDir = os.getcwd()
929 os.chdir(buildDir)
Ned Deilyebd63dc2014-04-09 16:12:11 -0700930 # The Doc build changed for 3.4 (technically, for 3.4.1)
931 if getVersionTuple() < (3, 4):
932 # This step does an svn checkout of sphinx and its dependencies
933 runCommand('make update')
934 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
935 else:
936 runCommand('make clean')
937 # Assume sphinx-build is on our PATH, checked in checkEnvironment
938 runCommand('make html')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000939 os.chdir(curDir)
940 if not os.path.exists(docdir):
941 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000942 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000943
944
945def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -0800946 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000947
948 buildDir = os.path.join(WORKDIR, '_bld', 'python')
949 rootDir = os.path.join(WORKDIR, '_root')
950
951 if os.path.exists(buildDir):
952 shutil.rmtree(buildDir)
953 if os.path.exists(rootDir):
954 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +0000955 os.makedirs(buildDir)
956 os.makedirs(rootDir)
957 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000958 curdir = os.getcwd()
959 os.chdir(buildDir)
960
961 # Not sure if this is still needed, the original build script
962 # claims that parts of the install assume python.exe exists.
963 os.symlink('python', os.path.join(buildDir, 'python.exe'))
964
965 # Extract the version from the configure file, needed to calculate
966 # several paths.
967 version = getVersion()
968
Ronald Oussoren008af852009-03-30 20:02:08 +0000969 # Since the extra libs are not in their installed framework location
970 # during the build, augment the library path so that the interpreter
971 # will find them during its extension import sanity checks.
972 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
973 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -0800974 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +0000975 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
976 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000977 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -0700978 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +0000979 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -0700980 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000981 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
982 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +0000983 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyebd63dc2014-04-09 16:12:11 -0700984 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000985 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000986 shellQuote(WORKDIR)[1:-1]))
987
Ned Deilyfbb60d52014-05-22 15:27:01 -0700988 print("Running make touch")
989 runCommand("make touch")
990
Ned Deilye1c9794952013-01-29 00:07:46 -0800991 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000992 runCommand("make")
993
Ned Deilye1c9794952013-01-29 00:07:46 -0800994 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +0000995 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000996 shellQuote(rootDir)))
997
Ned Deilye1c9794952013-01-29 00:07:46 -0800998 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000999 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1000 shellQuote(rootDir)))
1001
Ronald Oussoren008af852009-03-30 20:02:08 +00001002 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -08001003 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001004 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1005 runCommand("mv %s/* %s"%(
1006 shellQuote(os.path.join(
1007 WORKDIR, 'libraries', 'Library', 'Frameworks',
1008 'Python.framework', 'Versions', getVersion(),
1009 'lib')),
1010 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1011 'Python.framework', 'Versions', getVersion(),
1012 'lib'))))
1013
Ned Deily70f213a2013-10-26 03:16:06 -07001014 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1015 'Python.framework', 'Versions',
1016 version, 'lib', 'python%s'%(version,))
1017
Ned Deilye1c9794952013-01-29 00:07:46 -08001018 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001019 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001020 gid = grp.getgrnam('admin').gr_gid
1021
Ned Deilye1c9794952013-01-29 00:07:46 -08001022 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001023 for dirpath, dirnames, filenames in os.walk(frmDir):
1024 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001025 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001026 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001027
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001028 for fn in filenames:
1029 if os.path.islink(fn):
1030 continue
1031
1032 # "chmod g+w $fn"
1033 p = os.path.join(dirpath, fn)
1034 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001035 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1036 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001037
Ned Deilye1c9794952013-01-29 00:07:46 -08001038 if fn in EXPECTED_SHARED_LIBS:
1039 # check to see that this file was linked with the
1040 # expected library path and version
1041 data = captureCommand("otool -L %s" % shellQuote(p))
1042 for sl in EXPECTED_SHARED_LIBS[fn]:
1043 if ("\t%s " % sl) not in data:
1044 print("Expected shared lib %s was not linked with %s"
1045 % (sl, p))
1046 shared_lib_error = True
1047
1048 if shared_lib_error:
1049 fatal("Unexpected shared library errors.")
1050
Ned Deily53c460d2011-01-30 01:43:40 +00001051 if PYTHON_3:
1052 LDVERSION=None
1053 VERSION=None
1054 ABIFLAGS=None
1055
1056 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1057 for ln in fp:
1058 if ln.startswith('VERSION='):
1059 VERSION=ln.split()[1]
1060 if ln.startswith('ABIFLAGS='):
1061 ABIFLAGS=ln.split()[1]
1062 if ln.startswith('LDVERSION='):
1063 LDVERSION=ln.split()[1]
1064 fp.close()
1065
1066 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1067 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1068 config_suffix = '-' + LDVERSION
1069 else:
1070 config_suffix = '' # Python 2.x
1071
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001072 # We added some directories to the search path during the configure
1073 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001074 # the end-users system. Also remove the directories from _sysconfigdata.py
1075 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001076
Ned Deilye6ef7022013-10-25 00:46:59 -07001077 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1078 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1079
Ned Deilye6ef7022013-10-25 00:46:59 -07001080 # fix Makefile
1081 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1082 fp = open(path, 'r')
1083 data = fp.read()
1084 fp.close()
1085
1086 for p in (include_path, lib_path):
1087 data = data.replace(" " + p, '')
1088 data = data.replace(p + " ", '')
1089
1090 fp = open(path, 'w')
1091 fp.write(data)
1092 fp.close()
1093
1094 # fix _sysconfigdata if it exists
1095 #
1096 # TODO: make this more robust! test_sysconfig_module of
1097 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1098 # the output from get_config_var in both sysconfig and
1099 # distutils.sysconfig is exactly the same for both CFLAGS and
1100 # LDFLAGS. The fixing up is now complicated by the pretty
1101 # printing in _sysconfigdata.py. Also, we are using the
1102 # pprint from the Python running the installer build which
1103 # may not cosmetically format the same as the pprint in the Python
1104 # being built (and which is used to originally generate
1105 # _sysconfigdata.py).
1106
1107 import pprint
1108 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1109 if os.path.exists(path):
Ned Deilye1c9794952013-01-29 00:07:46 -08001110 fp = open(path, 'r')
1111 data = fp.read()
1112 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -07001113 # create build_time_vars dict
1114 exec(data)
1115 vars = {}
1116 for k, v in build_time_vars.items():
1117 if type(v) == type(''):
1118 for p in (include_path, lib_path):
1119 v = v.replace(' ' + p, '')
1120 v = v.replace(p + ' ', '')
1121 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001122
Ned Deilye1c9794952013-01-29 00:07:46 -08001123 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -07001124 # duplicated from sysconfig._generate_posix_vars()
1125 fp.write('# system configuration generated and used by'
1126 ' the sysconfig module\n')
1127 fp.write('build_time_vars = ')
1128 pprint.pprint(vars, stream=fp)
Ned Deilye1c9794952013-01-29 00:07:46 -08001129 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001130
1131 # Add symlinks in /usr/local/bin, using relative links
1132 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1133 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1134 'Python.framework', 'Versions', version, 'bin')
1135 if os.path.exists(usr_local_bin):
1136 shutil.rmtree(usr_local_bin)
1137 os.makedirs(usr_local_bin)
1138 for fn in os.listdir(
1139 os.path.join(frmDir, 'Versions', version, 'bin')):
1140 os.symlink(os.path.join(to_framework, fn),
1141 os.path.join(usr_local_bin, fn))
1142
1143 os.chdir(curdir)
1144
Ned Deily53c460d2011-01-30 01:43:40 +00001145 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001146 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001147 # with an already installed version of python 2
1148 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1149 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001150
1151def patchFile(inPath, outPath):
1152 data = fileContents(inPath)
1153 data = data.replace('$FULL_VERSION', getFullVersion())
1154 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001155 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001156 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001157 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +00001158
1159 # This one is not handy as a template variable
1160 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001161 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001162 fp.write(data)
1163 fp.close()
1164
1165def patchScript(inPath, outPath):
1166 data = fileContents(inPath)
1167 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001168 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001169 fp.write(data)
1170 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001171 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001172
1173
1174
1175def packageFromRecipe(targetDir, recipe):
1176 curdir = os.getcwd()
1177 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001178 # The major version (such as 2.5) is included in the package name
1179 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001180 # common.
1181 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001182 srcdir = recipe.get('source')
1183 pkgroot = recipe.get('topdir', srcdir)
1184 postflight = recipe.get('postflight')
1185 readme = textwrap.dedent(recipe['readme'])
1186 isRequired = recipe.get('required', True)
1187
Ned Deilye1c9794952013-01-29 00:07:46 -08001188 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001189
1190 # Substitute some variables
1191 textvars = dict(
1192 VER=getVersion(),
1193 FULLVER=getFullVersion(),
1194 )
1195 readme = readme % textvars
1196
1197 if pkgroot is not None:
1198 pkgroot = pkgroot % textvars
1199 else:
1200 pkgroot = '/'
1201
1202 if srcdir is not None:
1203 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1204 srcdir = srcdir % textvars
1205
1206 if postflight is not None:
1207 postflight = os.path.abspath(postflight)
1208
1209 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1210 os.makedirs(packageContents)
1211
1212 if srcdir is not None:
1213 os.chdir(srcdir)
1214 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1215 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1216 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1217
1218 fn = os.path.join(packageContents, 'PkgInfo')
1219 fp = open(fn, 'w')
1220 fp.write('pmkrpkg1')
1221 fp.close()
1222
1223 rsrcDir = os.path.join(packageContents, "Resources")
1224 os.mkdir(rsrcDir)
1225 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1226 fp.write(readme)
1227 fp.close()
1228
1229 if postflight is not None:
1230 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1231
1232 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001233 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001234 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001235 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1236 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1237 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001238 CFBundleShortVersionString=vers,
1239 IFMajorVersion=major,
1240 IFMinorVersion=minor,
1241 IFPkgFormatVersion=0.10000000149011612,
1242 IFPkgFlagAllowBackRev=False,
1243 IFPkgFlagAuthorizationAction="RootAuthorization",
1244 IFPkgFlagDefaultLocation=pkgroot,
1245 IFPkgFlagFollowLinks=True,
1246 IFPkgFlagInstallFat=True,
1247 IFPkgFlagIsRequired=isRequired,
1248 IFPkgFlagOverwritePermissions=False,
1249 IFPkgFlagRelocatable=False,
1250 IFPkgFlagRestartAction="NoRestart",
1251 IFPkgFlagRootVolumeOnly=True,
1252 IFPkgFlagUpdateInstalledLangauges=False,
1253 )
1254 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1255
1256 pl = Plist(
1257 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001258 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001259 IFPkgDescriptionVersion=vers,
1260 )
1261 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1262
1263 finally:
1264 os.chdir(curdir)
1265
1266
1267def makeMpkgPlist(path):
1268
1269 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001270 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001271
1272 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001273 CFBundleGetInfoString="Python %s"%(vers,),
1274 CFBundleIdentifier='org.python.Python',
1275 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001276 CFBundleShortVersionString=vers,
1277 IFMajorVersion=major,
1278 IFMinorVersion=minor,
1279 IFPkgFlagComponentDirectory="Contents/Packages",
1280 IFPkgFlagPackageList=[
1281 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001282 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001283 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001284 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001285 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001286 ],
1287 IFPkgFormatVersion=0.10000000149011612,
1288 IFPkgFlagBackgroundScaling="proportional",
1289 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001290 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001291 )
1292
1293 writePlist(pl, path)
1294
1295
1296def buildInstaller():
1297
1298 # Zap all compiled files
1299 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1300 for fn in filenames:
1301 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1302 os.unlink(os.path.join(dirpath, fn))
1303
1304 outdir = os.path.join(WORKDIR, 'installer')
1305 if os.path.exists(outdir):
1306 shutil.rmtree(outdir)
1307 os.mkdir(outdir)
1308
Ronald Oussoren508282e2009-03-30 19:34:51 +00001309 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001310 pkgcontents = os.path.join(pkgroot, 'Packages')
1311 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001312 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001313 packageFromRecipe(pkgcontents, recipe)
1314
1315 rsrcDir = os.path.join(pkgroot, 'Resources')
1316
1317 fn = os.path.join(pkgroot, 'PkgInfo')
1318 fp = open(fn, 'w')
1319 fp.write('pmkrpkg1')
1320 fp.close()
1321
1322 os.mkdir(rsrcDir)
1323
1324 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1325 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001326 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001327 IFPkgDescriptionVersion=getVersion(),
1328 )
1329
1330 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1331 for fn in os.listdir('resources'):
1332 if fn == '.svn': continue
1333 if fn.endswith('.jpg'):
1334 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1335 else:
1336 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1337
Ronald Oussorenc5555542006-06-11 20:24:45 +00001338 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001339
1340
1341def installSize(clear=False, _saved=[]):
1342 if clear:
1343 del _saved[:]
1344 if not _saved:
1345 data = captureCommand("du -ks %s"%(
1346 shellQuote(os.path.join(WORKDIR, '_root'))))
1347 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1348 return _saved[0]
1349
1350
1351def buildDMG():
1352 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001353 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001354 """
1355 outdir = os.path.join(WORKDIR, 'diskimage')
1356 if os.path.exists(outdir):
1357 shutil.rmtree(outdir)
1358
1359 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001360 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001361 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001362 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001363 imagepath = imagepath + '.dmg'
1364
1365 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001366 volname='Python %s'%(getFullVersion())
1367 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1368 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001369 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001370 shellQuote(imagepath + ".tmp.dmg" )))
1371
1372
1373 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1374 os.mkdir(os.path.join(WORKDIR, "mnt"))
1375 runCommand("hdiutil attach %s -mountroot %s"%(
1376 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1377
1378 # Custom icon for the DMG, shown when the DMG is mounted.
1379 shutil.copy("../Icons/Disk Image.icns",
1380 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001381 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001382 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1383
1384 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1385
1386 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1387 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1388 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1389 setIcon(imagepath, "../Icons/Disk Image.icns")
1390
1391 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001392
1393 return imagepath
1394
1395
1396def setIcon(filePath, icnsPath):
1397 """
1398 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001399 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001400
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001401 dirPath = os.path.normpath(os.path.dirname(__file__))
1402 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001403 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1404 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1405 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001406 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1407 if not os.path.exists(appPath):
1408 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001409 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1410 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001411
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001412 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1413 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001414
1415def main():
1416 # First parse options and check if we can perform our work
1417 parseOptions()
1418 checkEnvironment()
1419
Ronald Oussoren508282e2009-03-30 19:34:51 +00001420 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001421 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001422 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001423
1424 if os.path.exists(WORKDIR):
1425 shutil.rmtree(WORKDIR)
1426 os.mkdir(WORKDIR)
1427
Ronald Oussoren287128a2010-04-18 14:01:05 +00001428 os.environ['LC_ALL'] = 'C'
1429
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001430 # Then build third-party libraries such as sleepycat DB4.
1431 buildLibraries()
1432
1433 # Now build python itself
1434 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001435
1436 # And then build the documentation
1437 # Remove the Deployment Target from the shell
1438 # environment, it's no longer needed and
1439 # an unexpected build target can cause problems
1440 # when Sphinx and its dependencies need to
1441 # be (re-)installed.
1442 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001443 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001444
1445
1446 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001447 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001448 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001449 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001450
Benjamin Petersonc3104762008-10-03 11:52:06 +00001451 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001452 getVersion(),))
Ned Deilye1c9794952013-01-29 00:07:46 -08001453 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001454 setIcon(folder, "../Icons/Python Folder.icns")
1455
1456 # Create the installer
1457 buildInstaller()
1458
1459 # And copy the readme into the directory containing the installer
1460 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1461
1462 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001463 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001464
1465 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001466 fp.write("# BUILD INFO\n")
1467 fp.write("# Date: %s\n" % time.ctime())
1468 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001469 fp.close()
1470
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001471 # And copy it to a DMG
1472 buildDMG()
1473
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001474if __name__ == "__main__":
1475 main()