blob: 74c274646a9b0e072183ef40b32aca8fc35b7d95 [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 Deily0134a352014-04-09 16:16:08 -0700870 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800871 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 Deily729148b2014-05-22 15:28:06 -0700988 print("Running make touch")
989 runCommand("make touch")
990
Ned Deily4a96a372013-01-29 00:08:32 -0800991 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992 runCommand("make")
993
Ned Deily4a96a372013-01-29 00:08:32 -0800994 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000995 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000996 shellQuote(rootDir)))
997
Ned Deily4a96a372013-01-29 00:08:32 -0800998 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000999 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1000 shellQuote(rootDir)))
1001
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001002 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001003 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily050fcd52013-10-26 03:16:44 -07001014 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1015 'Python.framework', 'Versions',
1016 version, 'lib', 'python%s'%(version,))
1017
Ned Deily4a96a372013-01-29 00:08:32 -08001018 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001019 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001020 gid = grp.getgrnam('admin').gr_gid
1021
Ned Deily4a96a372013-01-29 00:08:32 -08001022 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001023 for dirpath, dirnames, filenames in os.walk(frmDir):
1024 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001025 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001026 os.chown(os.path.join(dirpath, dn), -1, gid)
1027
Thomas Wouters477c8d52006-05-27 19:21:47 +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)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001035 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1036 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001037
Ned Deily4a96a372013-01-29 00:08:32 -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 Deilye59e4c52011-01-29 18:56:28 +00001051 if PYTHON_3:
1052 LDVERSION=None
1053 VERSION=None
1054 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001055
Ned Deilye59e4c52011-01-29 18:56:28 +00001056 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001057 for ln in fp:
1058 if ln.startswith('VERSION='):
1059 VERSION=ln.split()[1]
1060 if ln.startswith('ABIFLAGS='):
1061 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001062 if ln.startswith('LDVERSION='):
1063 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001064 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001065
Ned Deilye59e4c52011-01-29 18:56:28 +00001066 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1067 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1068 config_suffix = '-' + LDVERSION
1069 else:
1070 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001071
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -08001074 # the end-users system. Also remove the directories from _sysconfigdata.py
1075 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076
Ned Deilya4f6b002013-10-25 00:47:38 -07001077 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1078 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1079
Ned Deilya4f6b002013-10-25 00:47:38 -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 Deily4a96a372013-01-29 00:08:32 -08001110 fp = open(path, 'r')
1111 data = fp.read()
1112 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -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 Deily4a96a372013-01-29 00:08:32 -08001122
Ned Deily4a96a372013-01-29 00:08:32 -08001123 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -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 Deily4a96a372013-01-29 00:08:32 -08001129 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +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
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001145 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001146 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001147 # with an already installed version of python 2
1148 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1149 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001150
1151def patchFile(inPath, outPath):
1152 data = fileContents(inPath)
1153 data = data.replace('$FULL_VERSION', getFullVersion())
1154 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001155 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001156 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001158
1159 # This one is not handy as a template variable
1160 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001161 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001162 fp.write(data)
1163 fp.close()
1164
1165def patchScript(inPath, outPath):
1166 data = fileContents(inPath)
1167 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001168 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001169 fp.write(data)
1170 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001171 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001172
1173
1174
1175def packageFromRecipe(targetDir, recipe):
1176 curdir = os.getcwd()
1177 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +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
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001180 # common.
1181 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -08001188 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -08001233 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001234 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001235 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1236 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1237 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +00001258 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Deily4a96a372013-01-29 00:08:32 -08001270 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001271
1272 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001273 CFBundleGetInfoString="Python %s"%(vers,),
1274 CFBundleIdentifier='org.python.Python',
1275 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001276 CFBundleShortVersionString=vers,
1277 IFMajorVersion=major,
1278 IFMinorVersion=minor,
1279 IFPkgFlagComponentDirectory="Contents/Packages",
1280 IFPkgFlagPackageList=[
1281 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001282 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001283 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001284 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001285 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001286 ],
1287 IFPkgFormatVersion=0.10000000149011612,
1288 IFPkgFlagBackgroundScaling="proportional",
1289 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001290 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +00001309 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001310 pkgcontents = os.path.join(pkgroot, 'Packages')
1311 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001312 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +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 Oussoren1943f862009-03-30 19:39:14 +00001326 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +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
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001338 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +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 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001353 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +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,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001360 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001361 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001362 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001363 imagepath = imagepath + '.dmg'
1364
1365 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001366 volname='Python %s'%(getFullVersion())
1367 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1368 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001369 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +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 Deily4a96a372013-01-29 00:08:32 -08001381 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +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")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001392
1393 return imagepath
1394
1395
1396def setIcon(filePath, icnsPath):
1397 """
1398 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001399 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001400
Ronald Oussoren70050672010-04-30 15:00:26 +00001401 dirPath = os.path.normpath(os.path.dirname(__file__))
1402 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +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 Oussoren70050672010-04-30 15:00:26 +00001406 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1407 if not os.path.exists(appPath):
1408 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001409 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1410 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001412 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1413 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001414
1415def main():
1416 # First parse options and check if we can perform our work
1417 parseOptions()
1418 checkEnvironment()
1419
Ronald Oussoren1943f862009-03-30 19:39:14 +00001420 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001421 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001422 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001423
1424 if os.path.exists(WORKDIR):
1425 shutil.rmtree(WORKDIR)
1426 os.mkdir(WORKDIR)
1427
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001428 os.environ['LC_ALL'] = 'C'
1429
Thomas Wouters477c8d52006-05-27 19:21:47 +00001430 # Then build third-party libraries such as sleepycat DB4.
1431 buildLibraries()
1432
1433 # Now build python itself
1434 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +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']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001443 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001444
1445
1446 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001447 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001448 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001449 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001450
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001451 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001453 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +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.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001463 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001464
1465 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -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)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001469 fp.close()
1470
Thomas Wouters477c8d52006-05-27 19:21:47 +00001471 # And copy it to a DMG
1472 buildDMG()
1473
Thomas Wouters477c8d52006-05-27 19:21:47 +00001474if __name__ == "__main__":
1475 main()