blob: d4962ff8411239445d9e98dd1c058e463829b643 [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deily4a96a372013-01-29 00:08:32 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
Ned Deily9978a932014-04-09 16:15:20 -070011Python 2.4. However, as of Python 3.4.1, Doc builds require an external
12sphinx-build and the current versions of Sphinx now require at least
13Python 2.6.
Thomas Wouters477c8d52006-05-27 19:21:47 +000014
Ned Deily4a96a372013-01-29 00:08:32 -080015In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
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 Deily9978a932014-04-09 16:15:20 -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 Deily4a96a372013-01-29 00:08:32 -080028
Thomas Wouters477c8d52006-05-27 19:21:47 +000029Usage: see USAGE variable in the script.
30"""
Ned Deily4a96a372013-01-29 00:08:32 -080031import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
32try:
33 import urllib2 as urllib_request
34except ImportError:
35 import urllib.request as urllib_request
36
37STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38 | stat.S_IRGRP | stat.S_IXGRP
39 | stat.S_IROTH | stat.S_IXOTH )
40
41STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
42 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
43 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
Thomas Wouters89f507f2006-12-13 04:49:30 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000047
48from plistlib import Plist
49
Thomas Wouters477c8d52006-05-27 19:21:47 +000050try:
51 from plistlib import writePlist
52except ImportError:
53 # We're run using python2.3
54 def writePlist(plist, path):
55 plist.write(path)
56
57def shellQuote(value):
58 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000059 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
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 Deily4a96a372013-01-29 00:08:32 -080070 raise RuntimeError("Cannot find variable %s" % variable[:-1])
71
72_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000073
74def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -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
Thomas Wouters477c8d52006-05-27 19:21:47 +000080
Benjamin Petersond9b7d482010-03-19 21:42:45 +000081def getVersionTuple():
82 return tuple([int(n) for n in getVersion().split('.')])
83
Ned Deily4a96a372013-01-29 00:08:32 -080084def getVersionMajorMinor():
85 return tuple([int(n) for n in getVersion().split('.', 2)])
86
87_cache_getFullVersion = None
88
Thomas Wouters477c8d52006-05-27 19:21:47 +000089def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080090 global _cache_getFullVersion
91 if _cache_getFullVersion is not None:
92 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000093 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
94 for ln in open(fn):
95 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080096 _cache_getFullVersion = ln.split()[-1][1:-1]
97 return _cache_getFullVersion
98 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000099
Thomas Wouters89f507f2006-12-13 04:49:30 +0000100# The directory we'll use to create the build (will be erased and recreated)
101WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000104# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000105DEPSRC = os.path.join(WORKDIR, 'third-party')
106DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000107
108# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +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 Melotti7c4a7e62013-08-26 01:32:56 +0300113### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +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
Thomas Wouters89f507f2006-12-13 04:49:30 +0000123SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000124
Ronald Oussoren1943f862009-03-30 19:39:14 +0000125universal_opts_map = { '32-bit': ('i386', 'ppc',),
126 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +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 Oussoren1943f862009-03-30 19:39:14 +0000136
137UNIVERSALOPTS = tuple(universal_opts_map.keys())
138
139UNIVERSALARCHS = '32-bit'
140
141ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142
Ezio Melotti42da6632011-03-15 05:18:48 +0200143# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000144SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000145 os.path.dirname(
146 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000147 os.path.abspath(__file__
148 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000149
Ronald Oussoren1943f862009-03-30 19:39:14 +0000150# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
151DEPTARGET = '10.3'
152
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000153target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -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 Deilyac25ca12013-10-18 20:41:16 -0700160 '10.9': ('clang', 'clang++'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000161}
162
Ned Deily4a96a372013-01-29 00:08:32 -0800163CC, CXX = target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000164
165PYTHON_3 = getVersionTuple() >= (3, 0)
166
Thomas Wouters89f507f2006-12-13 04:49:30 +0000167USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +0000177 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
178 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000179""")% globals()
180
Ned Deily4a96a372013-01-29 00:08:32 -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 = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000188
189# Instructions for building libraries that are necessary for building a
190# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000191# [The recipes are defined here for convenience but instantiated later after
192# command line options have been processed.]
193def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000194 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000195
Ned Deily4a96a372013-01-29 00:08:32 -0800196 LT_10_5 = bool(DEPTARGET < '10.5')
197
Ned Deilyaa6a2122013-11-23 03:30:11 -0800198# Disable for now
Ned Deily7e60f512014-04-07 12:10:21 -0700199 if False: # if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 5)):
Ned Deily5b3582c2013-10-25 00:41:46 -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 Deily94764b22013-10-27 19:49:29 -0700221 patches=[
222 "issue19373_tk_8_5_15_source.patch",
223 ],
Ned Deily5b3582c2013-10-25 00:41:46 -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 Deily4a96a372013-01-29 00:08:32 -0800240 if getVersionTuple() >= (3, 3):
241 result.extend([
242 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800243 name="XZ 5.0.5",
244 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
245 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deily4a96a372013-01-29 00:08:32 -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 Deilyc1195c72014-03-01 14:00:46 -0800288 name="SQLite 3.8.3.1",
289 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
290 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deily4a96a372013-01-29 00:08:32 -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
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000307 if DEPTARGET < '10.5':
308 result.extend([
309 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000310 name="Bzip2 1.0.6",
311 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
312 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000313 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800314 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
315 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000316 shellQuote(os.path.join(WORKDIR, 'libraries')),
317 ' -arch '.join(ARCHLIST),
318 SDKPATH,
319 ),
320 ),
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 Deily4a96a372013-01-29 00:08:32 -0800326 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
327 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +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 Deily4f7ff782011-01-15 05:29:12 +0000335 name="GNU Readline 6.1.2",
336 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
337 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000338 patchlevel='0',
339 patches=[
340 # The readline maintainers don't do actual micro releases, but
341 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -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'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000346 ]
347 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000348 ])
349
Ned Deily4f7ff782011-01-15 05:29:12 +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 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000363
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000364 return result
365
Thomas Wouters477c8d52006-05-27 19:21:47 +0000366
Thomas Wouters477c8d52006-05-27 19:21:47 +0000367# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000368def pkg_recipes():
369 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deily9978a932014-04-09 16:15:20 -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)]
Benjamin Petersond9b7d482010-03-19 21:42:45 +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",
383 selected='selected',
384 ),
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).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000393
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000394 It also installs a number of examples and demos.
395 """,
396 required=False,
397 selected='selected',
398 ),
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,
409 selected='selected',
410 ),
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 Deily4a96a372013-01-29 00:08:32 -0800418 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000419 """,
420 postflight="scripts/postflight.documentation",
421 required=False,
422 selected='selected',
423 ),
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.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000431
Benjamin Petersond9b7d482010-03-19 21:42:45 +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 Deily41ab6c32013-11-22 22:25:43 -0800440 selected=unselected_for_lt_python34,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000441 ),
442 ]
443
Ned Deily41ab6c32013-11-22 22:25:43 -0800444 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 Deily4a96a372013-01-29 00:08:32 -0800461 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +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,
475 selected=unselected_for_python3,
476 )
477 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800478
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000479 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000480
Thomas Wouters477c8d52006-05-27 19:21:47 +0000481def 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 Deily4a96a372013-01-29 00:08:32 -0800494 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000495
496def runCommand(commandline):
497 """
Ezio Melotti13925002011-03-16 11:05:33 +0200498 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000499 unless the command fails.
500 """
501 fd = os.popen(commandline, 'r')
502 data = fd.read()
503 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000504 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000505 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800506 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Peterson2a691a82008-03-31 01:51:45 +0000515 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000516 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800517 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000518
519 return data
520
Ronald Oussorenc45c3d92010-04-18 15:24:17 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000538def checkEnvironment():
539 """
540 Check that we're running on a supported system.
541 """
542
Ned Deilye59e4c52011-01-29 18:56:28 +0000543 if sys.version_info[0:2] < (2, 4):
544 fatal("This script must be run with Python 2.4 or later")
545
Thomas Wouters477c8d52006-05-27 19:21:47 +0000546 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000547 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000549 if int(platform.release().split('.')[0]) < 8:
550 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussorenc45c3d92010-04-18 15:24:17 +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 Deily4a96a372013-01-29 00:08:32 -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 Oussorenc45c3d92010-04-18 15:24:17 +0000564
Ned Deily4a96a372013-01-29 00:08:32 -0800565 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000566 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800567 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000568 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800569 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000570 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -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))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000576 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +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 Deily4a96a372013-01-29 00:08:32 -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 Oussorenc45c3d92010-04-18 15:24:17 +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 Deily4a96a372013-01-29 00:08:32 -0800599 print("INFO: deleting environment variable %s=%s" % (
600 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000601 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000602
Ned Deily4a96a372013-01-29 00:08:32 -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 Deily7e60f512014-04-07 12:10:21 -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')
Ned Deily9978a932014-04-09 16:15:20 -0700616 if getVersionTuple() >= (3, 4):
617 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000618
Thomas Wouters89f507f2006-12-13 04:49:30 +0000619def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000620 """
621 Parse arguments and update global settings.
622 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000623 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800624 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000625
626 if args is None:
627 args = sys.argv[1:]
628
629 try:
630 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000631 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
632 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800633 except getopt.GetoptError:
634 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000635 sys.exit(1)
636
637 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800638 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000639 sys.exit(1)
640
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000641 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000642 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000643 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800644 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +0000659 elif k in ('--dep-target', ):
660 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000661 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000662
663 elif k in ('--universal-archs', ):
664 if v in UNIVERSALOPTS:
665 UNIVERSALARCHS = v
666 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000667 if deptarget is None:
668 # Select alternate default deployment
669 # target
670 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000671 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800672 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000673
Thomas Wouters477c8d52006-05-27 19:21:47 +0000674 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800675 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -0800682 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000683
Ned Deily4a96a372013-01-29 00:08:32 -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("")
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily5b3582c2013-10-25 00:41:46 -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.
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily5b3582c2013-10-25 00:41:46 -0700715 if ((retval.startswith('tcl') or retval.startswith('tk'))
716 and retval.endswith('-src')):
717 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -0800744 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000745
746 return os.path.join(builddir, retval)
747
748 finally:
749 os.chdir(curdir)
750
Thomas Wouters477c8d52006-05-27 19:21:47 +0000751def downloadURL(url, fname):
752 """
753 Download the contents of the url into the file.
754 """
Ned Deily4a96a372013-01-29 00:08:32 -0800755 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -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
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -0800808 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
809 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +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)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000816
Ned Deily4a96a372013-01-29 00:08:32 -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)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000825 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
826 shellQuote(fn),))
827
Ned Deily4a96a372013-01-29 00:08:32 -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 Deily94764b22013-10-27 19:49:29 -0700842 if 'buildDir' in recipe:
843 os.chdir(recipe['buildDir'])
844
Thomas Wouters0e3f5912006-08-11 14:57:12 +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 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000852
Thomas Wouters0e3f5912006-08-11 14:57:12 +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)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000860
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000861 if recipe.get('useLDFlags', 1):
862 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800863 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
864 "-I%s/usr/local/include"%(
865 recipe.get('extra_cflags', ''),
866 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000867 ' -arch '.join(archList),
868 shellQuote(SDKPATH)[1:-1],
869 shellQuote(basedir)[1:-1],),
Ned Deily4a96a372013-01-29 00:08:32 -0800870 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
871 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000872 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000873 shellQuote(basedir)[1:-1],
874 ' -arch '.join(archList)),
875 ])
876 else:
877 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800878 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
879 "-I%s/usr/local/include"%(
880 recipe.get('extra_cflags', ''),
881 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000882 ' -arch '.join(archList),
883 shellQuote(SDKPATH)[1:-1],
884 shellQuote(basedir)[1:-1],),
885 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000886
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000887 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800888 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000889
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000890 configure_args.insert(0, configure)
891 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000892
Ned Deily4a96a372013-01-29 00:08:32 -0800893 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000894 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000895
Ned Deily4a96a372013-01-29 00:08:32 -0800896 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000897 runCommand('{ ' + install + ' ;} 2>&1')
898
Ned Deily4a96a372013-01-29 00:08:32 -0800899 print("Done %s"%(name,))
900 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000901
902 os.chdir(curdir)
903
904def buildLibraries():
905 """
906 Build our dependencies into $WORKDIR/libraries/usr/local
907 """
Ned Deily4a96a372013-01-29 00:08:32 -0800908 print("")
909 print("Building required libraries")
910 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +0000916 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000917 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000918
919
920
921def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000922 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000923 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800924 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000925 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000926 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000927 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000928 curDir = os.getcwd()
929 os.chdir(buildDir)
Ned Deily9978a932014-04-09 16:15:20 -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 Oussoren207b4c22009-03-30 17:20:30 +0000939 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000940 if not os.path.exists(docdir):
941 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000942 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000943
944
945def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800946 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4f7ff782011-01-15 05:29:12 +0000955 os.makedirs(buildDir)
956 os.makedirs(rootDir)
957 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussorenac4b39f2009-03-30 20:05:35 +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 Deily4a96a372013-01-29 00:08:32 -0800974 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000975 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000976 "--with-universal-archs=%s "
977 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800978 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000979 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700980 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000981 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
982 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000983 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily41ab6c32013-11-22 22:25:43 -0800984 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000985 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000986 shellQuote(WORKDIR)[1:-1]))
987
Ned Deily4a96a372013-01-29 00:08:32 -0800988 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000989 runCommand("make")
990
Ned Deily4a96a372013-01-29 00:08:32 -0800991 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000992 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000993 shellQuote(rootDir)))
994
Ned Deily4a96a372013-01-29 00:08:32 -0800995 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000996 runCommand("make frameworkinstallextras DESTDIR=%s"%(
997 shellQuote(rootDir)))
998
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000999 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001000 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001001 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1002 runCommand("mv %s/* %s"%(
1003 shellQuote(os.path.join(
1004 WORKDIR, 'libraries', 'Library', 'Frameworks',
1005 'Python.framework', 'Versions', getVersion(),
1006 'lib')),
1007 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1008 'Python.framework', 'Versions', getVersion(),
1009 'lib'))))
1010
Ned Deily050fcd52013-10-26 03:16:44 -07001011 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1012 'Python.framework', 'Versions',
1013 version, 'lib', 'python%s'%(version,))
1014
Ned Deily4a96a372013-01-29 00:08:32 -08001015 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001016 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001017 gid = grp.getgrnam('admin').gr_gid
1018
Ned Deily4a96a372013-01-29 00:08:32 -08001019 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001020 for dirpath, dirnames, filenames in os.walk(frmDir):
1021 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001022 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001023 os.chown(os.path.join(dirpath, dn), -1, gid)
1024
Thomas Wouters477c8d52006-05-27 19:21:47 +00001025 for fn in filenames:
1026 if os.path.islink(fn):
1027 continue
1028
1029 # "chmod g+w $fn"
1030 p = os.path.join(dirpath, fn)
1031 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001032 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1033 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001034
Ned Deily4a96a372013-01-29 00:08:32 -08001035 if fn in EXPECTED_SHARED_LIBS:
1036 # check to see that this file was linked with the
1037 # expected library path and version
1038 data = captureCommand("otool -L %s" % shellQuote(p))
1039 for sl in EXPECTED_SHARED_LIBS[fn]:
1040 if ("\t%s " % sl) not in data:
1041 print("Expected shared lib %s was not linked with %s"
1042 % (sl, p))
1043 shared_lib_error = True
1044
1045 if shared_lib_error:
1046 fatal("Unexpected shared library errors.")
1047
Ned Deilye59e4c52011-01-29 18:56:28 +00001048 if PYTHON_3:
1049 LDVERSION=None
1050 VERSION=None
1051 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001052
Ned Deilye59e4c52011-01-29 18:56:28 +00001053 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001054 for ln in fp:
1055 if ln.startswith('VERSION='):
1056 VERSION=ln.split()[1]
1057 if ln.startswith('ABIFLAGS='):
1058 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001059 if ln.startswith('LDVERSION='):
1060 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001061 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001062
Ned Deilye59e4c52011-01-29 18:56:28 +00001063 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1064 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1065 config_suffix = '-' + LDVERSION
1066 else:
1067 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001068
Thomas Wouters477c8d52006-05-27 19:21:47 +00001069 # We added some directories to the search path during the configure
1070 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001071 # the end-users system. Also remove the directories from _sysconfigdata.py
1072 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073
Ned Deilya4f6b002013-10-25 00:47:38 -07001074 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1075 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1076
Ned Deilya4f6b002013-10-25 00:47:38 -07001077 # fix Makefile
1078 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1079 fp = open(path, 'r')
1080 data = fp.read()
1081 fp.close()
1082
1083 for p in (include_path, lib_path):
1084 data = data.replace(" " + p, '')
1085 data = data.replace(p + " ", '')
1086
1087 fp = open(path, 'w')
1088 fp.write(data)
1089 fp.close()
1090
1091 # fix _sysconfigdata if it exists
1092 #
1093 # TODO: make this more robust! test_sysconfig_module of
1094 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1095 # the output from get_config_var in both sysconfig and
1096 # distutils.sysconfig is exactly the same for both CFLAGS and
1097 # LDFLAGS. The fixing up is now complicated by the pretty
1098 # printing in _sysconfigdata.py. Also, we are using the
1099 # pprint from the Python running the installer build which
1100 # may not cosmetically format the same as the pprint in the Python
1101 # being built (and which is used to originally generate
1102 # _sysconfigdata.py).
1103
1104 import pprint
1105 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1106 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001107 fp = open(path, 'r')
1108 data = fp.read()
1109 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001110 # create build_time_vars dict
1111 exec(data)
1112 vars = {}
1113 for k, v in build_time_vars.items():
1114 if type(v) == type(''):
1115 for p in (include_path, lib_path):
1116 v = v.replace(' ' + p, '')
1117 v = v.replace(p + ' ', '')
1118 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001119
Ned Deily4a96a372013-01-29 00:08:32 -08001120 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001121 # duplicated from sysconfig._generate_posix_vars()
1122 fp.write('# system configuration generated and used by'
1123 ' the sysconfig module\n')
1124 fp.write('build_time_vars = ')
1125 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001126 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001127
1128 # Add symlinks in /usr/local/bin, using relative links
1129 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1130 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1131 'Python.framework', 'Versions', version, 'bin')
1132 if os.path.exists(usr_local_bin):
1133 shutil.rmtree(usr_local_bin)
1134 os.makedirs(usr_local_bin)
1135 for fn in os.listdir(
1136 os.path.join(frmDir, 'Versions', version, 'bin')):
1137 os.symlink(os.path.join(to_framework, fn),
1138 os.path.join(usr_local_bin, fn))
1139
1140 os.chdir(curdir)
1141
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001142 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001143 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001144 # with an already installed version of python 2
1145 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1146 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001147
1148def patchFile(inPath, outPath):
1149 data = fileContents(inPath)
1150 data = data.replace('$FULL_VERSION', getFullVersion())
1151 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001152 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001153 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001155
1156 # This one is not handy as a template variable
1157 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001158 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 fp.write(data)
1160 fp.close()
1161
1162def patchScript(inPath, outPath):
1163 data = fileContents(inPath)
1164 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001165 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001166 fp.write(data)
1167 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001168 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001169
1170
1171
1172def packageFromRecipe(targetDir, recipe):
1173 curdir = os.getcwd()
1174 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001175 # The major version (such as 2.5) is included in the package name
1176 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001177 # common.
1178 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001179 srcdir = recipe.get('source')
1180 pkgroot = recipe.get('topdir', srcdir)
1181 postflight = recipe.get('postflight')
1182 readme = textwrap.dedent(recipe['readme'])
1183 isRequired = recipe.get('required', True)
1184
Ned Deily4a96a372013-01-29 00:08:32 -08001185 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001186
1187 # Substitute some variables
1188 textvars = dict(
1189 VER=getVersion(),
1190 FULLVER=getFullVersion(),
1191 )
1192 readme = readme % textvars
1193
1194 if pkgroot is not None:
1195 pkgroot = pkgroot % textvars
1196 else:
1197 pkgroot = '/'
1198
1199 if srcdir is not None:
1200 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1201 srcdir = srcdir % textvars
1202
1203 if postflight is not None:
1204 postflight = os.path.abspath(postflight)
1205
1206 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1207 os.makedirs(packageContents)
1208
1209 if srcdir is not None:
1210 os.chdir(srcdir)
1211 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1212 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1213 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1214
1215 fn = os.path.join(packageContents, 'PkgInfo')
1216 fp = open(fn, 'w')
1217 fp.write('pmkrpkg1')
1218 fp.close()
1219
1220 rsrcDir = os.path.join(packageContents, "Resources")
1221 os.mkdir(rsrcDir)
1222 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1223 fp.write(readme)
1224 fp.close()
1225
1226 if postflight is not None:
1227 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1228
1229 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001230 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001231 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001232 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1233 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1234 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001235 CFBundleShortVersionString=vers,
1236 IFMajorVersion=major,
1237 IFMinorVersion=minor,
1238 IFPkgFormatVersion=0.10000000149011612,
1239 IFPkgFlagAllowBackRev=False,
1240 IFPkgFlagAuthorizationAction="RootAuthorization",
1241 IFPkgFlagDefaultLocation=pkgroot,
1242 IFPkgFlagFollowLinks=True,
1243 IFPkgFlagInstallFat=True,
1244 IFPkgFlagIsRequired=isRequired,
1245 IFPkgFlagOverwritePermissions=False,
1246 IFPkgFlagRelocatable=False,
1247 IFPkgFlagRestartAction="NoRestart",
1248 IFPkgFlagRootVolumeOnly=True,
1249 IFPkgFlagUpdateInstalledLangauges=False,
1250 )
1251 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1252
1253 pl = Plist(
1254 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001255 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001256 IFPkgDescriptionVersion=vers,
1257 )
1258 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1259
1260 finally:
1261 os.chdir(curdir)
1262
1263
1264def makeMpkgPlist(path):
1265
1266 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001267 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001268
1269 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001270 CFBundleGetInfoString="Python %s"%(vers,),
1271 CFBundleIdentifier='org.python.Python',
1272 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001273 CFBundleShortVersionString=vers,
1274 IFMajorVersion=major,
1275 IFMinorVersion=minor,
1276 IFPkgFlagComponentDirectory="Contents/Packages",
1277 IFPkgFlagPackageList=[
1278 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001279 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001280 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001281 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001282 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001283 ],
1284 IFPkgFormatVersion=0.10000000149011612,
1285 IFPkgFlagBackgroundScaling="proportional",
1286 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001287 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001288 )
1289
1290 writePlist(pl, path)
1291
1292
1293def buildInstaller():
1294
1295 # Zap all compiled files
1296 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1297 for fn in filenames:
1298 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1299 os.unlink(os.path.join(dirpath, fn))
1300
1301 outdir = os.path.join(WORKDIR, 'installer')
1302 if os.path.exists(outdir):
1303 shutil.rmtree(outdir)
1304 os.mkdir(outdir)
1305
Ronald Oussoren1943f862009-03-30 19:39:14 +00001306 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001307 pkgcontents = os.path.join(pkgroot, 'Packages')
1308 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001309 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001310 packageFromRecipe(pkgcontents, recipe)
1311
1312 rsrcDir = os.path.join(pkgroot, 'Resources')
1313
1314 fn = os.path.join(pkgroot, 'PkgInfo')
1315 fp = open(fn, 'w')
1316 fp.write('pmkrpkg1')
1317 fp.close()
1318
1319 os.mkdir(rsrcDir)
1320
1321 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1322 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001323 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001324 IFPkgDescriptionVersion=getVersion(),
1325 )
1326
1327 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1328 for fn in os.listdir('resources'):
1329 if fn == '.svn': continue
1330 if fn.endswith('.jpg'):
1331 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1332 else:
1333 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1334
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001335 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001336
1337
1338def installSize(clear=False, _saved=[]):
1339 if clear:
1340 del _saved[:]
1341 if not _saved:
1342 data = captureCommand("du -ks %s"%(
1343 shellQuote(os.path.join(WORKDIR, '_root'))))
1344 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1345 return _saved[0]
1346
1347
1348def buildDMG():
1349 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001350 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001351 """
1352 outdir = os.path.join(WORKDIR, 'diskimage')
1353 if os.path.exists(outdir):
1354 shutil.rmtree(outdir)
1355
1356 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001357 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001358 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001359 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001360 imagepath = imagepath + '.dmg'
1361
1362 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001363 volname='Python %s'%(getFullVersion())
1364 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1365 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001366 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001367 shellQuote(imagepath + ".tmp.dmg" )))
1368
1369
1370 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1371 os.mkdir(os.path.join(WORKDIR, "mnt"))
1372 runCommand("hdiutil attach %s -mountroot %s"%(
1373 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1374
1375 # Custom icon for the DMG, shown when the DMG is mounted.
1376 shutil.copy("../Icons/Disk Image.icns",
1377 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001378 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001379 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1380
1381 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1382
1383 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1384 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1385 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1386 setIcon(imagepath, "../Icons/Disk Image.icns")
1387
1388 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001389
1390 return imagepath
1391
1392
1393def setIcon(filePath, icnsPath):
1394 """
1395 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001396 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001397
Ronald Oussoren70050672010-04-30 15:00:26 +00001398 dirPath = os.path.normpath(os.path.dirname(__file__))
1399 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001400 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1401 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1402 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001403 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1404 if not os.path.exists(appPath):
1405 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001406 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1407 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001408
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001409 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1410 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411
1412def main():
1413 # First parse options and check if we can perform our work
1414 parseOptions()
1415 checkEnvironment()
1416
Ronald Oussoren1943f862009-03-30 19:39:14 +00001417 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001418 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001419 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001420
1421 if os.path.exists(WORKDIR):
1422 shutil.rmtree(WORKDIR)
1423 os.mkdir(WORKDIR)
1424
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001425 os.environ['LC_ALL'] = 'C'
1426
Thomas Wouters477c8d52006-05-27 19:21:47 +00001427 # Then build third-party libraries such as sleepycat DB4.
1428 buildLibraries()
1429
1430 # Now build python itself
1431 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001432
1433 # And then build the documentation
1434 # Remove the Deployment Target from the shell
1435 # environment, it's no longer needed and
1436 # an unexpected build target can cause problems
1437 # when Sphinx and its dependencies need to
1438 # be (re-)installed.
1439 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001440 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001441
1442
1443 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001444 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001445 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001446 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001447
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001448 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001449 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001450 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001451 setIcon(folder, "../Icons/Python Folder.icns")
1452
1453 # Create the installer
1454 buildInstaller()
1455
1456 # And copy the readme into the directory containing the installer
1457 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1458
1459 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001460 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001461
1462 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001463 fp.write("# BUILD INFO\n")
1464 fp.write("# Date: %s\n" % time.ctime())
1465 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001466 fp.close()
1467
Thomas Wouters477c8d52006-05-27 19:21:47 +00001468 # And copy it to a DMG
1469 buildDMG()
1470
Thomas Wouters477c8d52006-05-27 19:21:47 +00001471if __name__ == "__main__":
1472 main()