blob: f01258731eb7bd4c1aa24aec8d0e48a53968e9cd [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
11Python 2.4.
Thomas Wouters477c8d52006-05-27 19:21:47 +000012
Ned Deily4a96a372013-01-29 00:08:32 -080013In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
14requires an installed version of hg and a third-party version of
15Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
16(for 10.6 or later) installed in /Library/Frameworks. When installed,
17the Python built by this script will attempt to dynamically link first to
18Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
19back to the ones in /System/Library/Framework. For the build, we recommend
20installing the most recent ActiveTcl 8.4 or 8.5 version.
21
2232-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
23and the installation of additional components, such as a newer Python
24(2.5 is needed for Python parser updates), hg, and svn (for the documentation
25build).
26
Thomas Wouters477c8d52006-05-27 19:21:47 +000027Usage: see USAGE variable in the script.
28"""
Ned Deily4a96a372013-01-29 00:08:32 -080029import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
30try:
31 import urllib2 as urllib_request
32except ImportError:
33 import urllib.request as urllib_request
34
35STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
36 | stat.S_IRGRP | stat.S_IXGRP
37 | stat.S_IROTH | stat.S_IXOTH )
38
39STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
40 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
41 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000042
Thomas Wouters89f507f2006-12-13 04:49:30 +000043INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000045
46from plistlib import Plist
47
Thomas Wouters477c8d52006-05-27 19:21:47 +000048try:
49 from plistlib import writePlist
50except ImportError:
51 # We're run using python2.3
52 def writePlist(plist, path):
53 plist.write(path)
54
55def shellQuote(value):
56 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000057 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000058 a shell command.
59 """
60 return "'%s'"%(value.replace("'", "'\"'\"'"))
61
62def grepValue(fn, variable):
63 variable = variable + '='
64 for ln in open(fn, 'r'):
65 if ln.startswith(variable):
66 value = ln[len(variable):].strip()
67 return value[1:-1]
Ned Deily4a96a372013-01-29 00:08:32 -080068 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000071
72def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080073 global _cache_getVersion
74 if _cache_getVersion is None:
75 _cache_getVersion = grepValue(
76 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
77 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
Benjamin Petersond9b7d482010-03-19 21:42:45 +000079def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
Ned Deily4a96a372013-01-29 00:08:32 -080082def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
Thomas Wouters477c8d52006-05-27 19:21:47 +000087def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080088 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000091 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080094 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000097
Thomas Wouters89f507f2006-12-13 04:49:30 +000098# The directory we'll use to create the build (will be erased and recreated)
99WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100
Thomas Wouters89f507f2006-12-13 04:49:30 +0000101# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000105
106# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000107
108### There are some issues with the SDK selection below here,
109### The resulting binary doesn't work on all platforms that
110### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300111### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000112###
113##if int(os.uname()[2].split('.')[0]) == 8:
114## # Explicitly use the 10.4u (universal) SDK when
115## # building on 10.4, the system headers are not
116## # useable for a universal build
117## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
118##else:
119## SDKPATH = "/"
120
Thomas Wouters89f507f2006-12-13 04:49:30 +0000121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000122
Ronald Oussoren1943f862009-03-30 19:39:14 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 'intel': ('i386', 'x86_64'),
126 '3-way': ('ppc', 'i386', 'x86_64'),
127 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
128default_target_map = {
129 '64-bit': '10.5',
130 '3-way': '10.5',
131 'intel': '10.5',
132 'all': '10.5',
133}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140
Ezio Melotti42da6632011-03-15 05:18:48 +0200141# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000142SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143 os.path.dirname(
144 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000145 os.path.abspath(__file__
146 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000147
Ronald Oussoren1943f862009-03-30 19:39:14 +0000148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000151target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800152 '10.3': ('gcc-4.0', 'g++-4.0'),
153 '10.4': ('gcc-4.0', 'g++-4.0'),
154 '10.5': ('gcc-4.2', 'g++-4.2'),
155 '10.6': ('gcc-4.2', 'g++-4.2'),
156 '10.7': ('clang', 'clang++'),
157 '10.8': ('clang', 'clang++'),
Ned Deilyac25ca12013-10-18 20:41:16 -0700158 '10.9': ('clang', 'clang++'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000159}
160
Ned Deily4a96a372013-01-29 00:08:32 -0800161CC, CXX = target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000162
163PYTHON_3 = getVersionTuple() >= (3, 0)
164
Thomas Wouters89f507f2006-12-13 04:49:30 +0000165USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000166 Usage: build_python [options]
167
168 Options:
169 -? or -h: Show this message
170 -b DIR
171 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
172 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
173 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
174 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000175 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
176 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000177""")% globals()
178
Ned Deily4a96a372013-01-29 00:08:32 -0800179# Dict of object file names with shared library names to check after building.
180# This is to ensure that we ended up dynamically linking with the shared
181# library paths and versions we expected. For example:
182# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
183# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
184# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
185EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000186
187# Instructions for building libraries that are necessary for building a
188# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000189# [The recipes are defined here for convenience but instantiated later after
190# command line options have been processed.]
191def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000192 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000193
Ned Deily4a96a372013-01-29 00:08:32 -0800194 LT_10_5 = bool(DEPTARGET < '10.5')
195
Ned Deilyaa6a2122013-11-23 03:30:11 -0800196# Disable for now
197 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 5)):
Ned Deily5b3582c2013-10-25 00:41:46 -0700198 result.extend([
199 dict(
200 name="Tcl 8.5.15",
201 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
202 checksum='f3df162f92c69b254079c4d0af7a690f',
203 buildDir="unix",
204 configure_pre=[
205 '--enable-shared',
206 '--enable-threads',
207 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
208 ],
209 useLDFlags=False,
210 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
211 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
212 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
213 },
214 ),
215 dict(
216 name="Tk 8.5.15",
217 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
218 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700219 patches=[
220 "issue19373_tk_8_5_15_source.patch",
221 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700222 buildDir="unix",
223 configure_pre=[
224 '--enable-aqua',
225 '--enable-shared',
226 '--enable-threads',
227 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
228 ],
229 useLDFlags=False,
230 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'%{
231 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
232 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
233 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
234 },
235 ),
236 ])
237
Ned Deily4a96a372013-01-29 00:08:32 -0800238 if getVersionTuple() >= (3, 3):
239 result.extend([
240 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800241 name="XZ 5.0.5",
242 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
243 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deily4a96a372013-01-29 00:08:32 -0800244 configure_pre=[
245 '--disable-dependency-tracking',
246 ]
247 ),
248 ])
249
250 result.extend([
251 dict(
252 name="NCurses 5.9",
253 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
254 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
255 configure_pre=[
256 "--enable-widec",
257 "--without-cxx",
258 "--without-cxx-binding",
259 "--without-ada",
260 "--without-curses-h",
261 "--enable-shared",
262 "--with-shared",
263 "--without-debug",
264 "--without-normal",
265 "--without-tests",
266 "--without-manpages",
267 "--datadir=/usr/share",
268 "--sysconfdir=/etc",
269 "--sharedstatedir=/usr/com",
270 "--with-terminfo-dirs=/usr/share/terminfo",
271 "--with-default-terminfo-dir=/usr/share/terminfo",
272 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
273 ],
274 patchscripts=[
275 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
276 "f54bf02a349f96a7c4f0d00922f3a0d4"),
277 ],
278 useLDFlags=False,
279 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
280 shellQuote(os.path.join(WORKDIR, 'libraries')),
281 shellQuote(os.path.join(WORKDIR, 'libraries')),
282 getVersion(),
283 ),
284 ),
285 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800286 name="SQLite 3.8.1",
287 url="http://www.sqlite.org/2013/sqlite-autoconf-3080100.tar.gz",
288 checksum='8b5a0a02dfcb0c7daf90856a5cfd485a',
Ned Deily4a96a372013-01-29 00:08:32 -0800289 extra_cflags=('-Os '
290 '-DSQLITE_ENABLE_FTS4 '
291 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
292 '-DSQLITE_ENABLE_RTREE '
293 '-DSQLITE_TCL=0 '
294 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
295 configure_pre=[
296 '--enable-threadsafe',
297 '--enable-shared=no',
298 '--enable-static=yes',
299 '--disable-readline',
300 '--disable-dependency-tracking',
301 ]
302 ),
303 ])
304
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000305 if DEPTARGET < '10.5':
306 result.extend([
307 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000308 name="Bzip2 1.0.6",
309 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
310 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000311 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800312 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
313 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000314 shellQuote(os.path.join(WORKDIR, 'libraries')),
315 ' -arch '.join(ARCHLIST),
316 SDKPATH,
317 ),
318 ),
319 dict(
320 name="ZLib 1.2.3",
321 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
322 checksum='debc62758716a169df9f62e6ab2bc634',
323 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800324 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
325 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000326 shellQuote(os.path.join(WORKDIR, 'libraries')),
327 ' -arch '.join(ARCHLIST),
328 SDKPATH,
329 ),
330 ),
331 dict(
332 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000333 name="GNU Readline 6.1.2",
334 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
335 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000336 patchlevel='0',
337 patches=[
338 # The readline maintainers don't do actual micro releases, but
339 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800340 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
341 'c642f2e84d820884b0bf9fd176bc6c3f'),
342 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
343 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000344 ]
345 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000346 ])
347
Ned Deily4f7ff782011-01-15 05:29:12 +0000348 if not PYTHON_3:
349 result.extend([
350 dict(
351 name="Sleepycat DB 4.7.25",
352 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
353 checksum='ec2b87e833779681a0c3a814aa71359e',
354 buildDir="build_unix",
355 configure="../dist/configure",
356 configure_pre=[
357 '--includedir=/usr/local/include/db4',
358 ]
359 ),
360 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000361
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000362 return result
363
Thomas Wouters477c8d52006-05-27 19:21:47 +0000364
Thomas Wouters477c8d52006-05-27 19:21:47 +0000365# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000366def pkg_recipes():
367 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deily41ab6c32013-11-22 22:25:43 -0800368 unselected_for_lt_python34 = ('selected', 'unselected')[getVersionTuple() < (3, 4)]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000369 result = [
370 dict(
371 name="PythonFramework",
372 long_name="Python Framework",
373 source="/Library/Frameworks/Python.framework",
374 readme="""\
375 This package installs Python.framework, that is the python
376 interpreter and the standard library. This also includes Python
377 wrappers for lots of Mac OS X API's.
378 """,
379 postflight="scripts/postflight.framework",
380 selected='selected',
381 ),
382 dict(
383 name="PythonApplications",
384 long_name="GUI Applications",
385 source="/Applications/Python %(VER)s",
386 readme="""\
387 This package installs IDLE (an interactive Python IDE),
388 Python Launcher and Build Applet (create application bundles
389 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000390
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000391 It also installs a number of examples and demos.
392 """,
393 required=False,
394 selected='selected',
395 ),
396 dict(
397 name="PythonUnixTools",
398 long_name="UNIX command-line tools",
399 source="/usr/local/bin",
400 readme="""\
401 This package installs the unix tools in /usr/local/bin for
402 compatibility with older releases of Python. This package
403 is not necessary to use Python.
404 """,
405 required=False,
406 selected='selected',
407 ),
408 dict(
409 name="PythonDocumentation",
410 long_name="Python Documentation",
411 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
412 source="/pydocs",
413 readme="""\
414 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800415 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000416 """,
417 postflight="scripts/postflight.documentation",
418 required=False,
419 selected='selected',
420 ),
421 dict(
422 name="PythonProfileChanges",
423 long_name="Shell profile updater",
424 readme="""\
425 This packages updates your shell profile to make sure that
426 the Python tools are found by your shell in preference of
427 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000428
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000429 If you don't install this package you'll have to add
430 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
431 to your PATH by hand.
432 """,
433 postflight="scripts/postflight.patch-profile",
434 topdir="/Library/Frameworks/Python.framework",
435 source="/empty-dir",
436 required=False,
Ned Deily41ab6c32013-11-22 22:25:43 -0800437 selected=unselected_for_lt_python34,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000438 ),
439 ]
440
Ned Deily41ab6c32013-11-22 22:25:43 -0800441 if getVersionTuple() >= (3, 4):
442 result.append(
443 dict(
444 name="PythonInstallPip",
445 long_name="Install or upgrade pip",
446 readme="""\
447 This package installs (or upgrades from an earlier version)
448 pip, a tool for installing and managing Python packages.
449 """,
450 postflight="scripts/postflight.ensurepip",
451 topdir="/Library/Frameworks/Python.framework",
452 source="/empty-dir",
453 required=False,
454 selected='selected',
455 )
456 )
457
Ned Deily4a96a372013-01-29 00:08:32 -0800458 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000459 result.append(
460 dict(
461 name="PythonSystemFixes",
462 long_name="Fix system Python",
463 readme="""\
464 This package updates the system python installation on
465 Mac OS X 10.3 to ensure that you can build new python extensions
466 using that copy of python after installing this version.
467 """,
468 postflight="../Tools/fixapplepython23.py",
469 topdir="/Library/Frameworks/Python.framework",
470 source="/empty-dir",
471 required=False,
472 selected=unselected_for_python3,
473 )
474 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800475
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000476 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478def fatal(msg):
479 """
480 A fatal error, bail out.
481 """
482 sys.stderr.write('FATAL: ')
483 sys.stderr.write(msg)
484 sys.stderr.write('\n')
485 sys.exit(1)
486
487def fileContents(fn):
488 """
489 Return the contents of the named file
490 """
Ned Deily4a96a372013-01-29 00:08:32 -0800491 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000492
493def runCommand(commandline):
494 """
Ezio Melotti13925002011-03-16 11:05:33 +0200495 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000496 unless the command fails.
497 """
498 fd = os.popen(commandline, 'r')
499 data = fd.read()
500 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000501 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000502 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800503 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000504
505 if VERBOSE:
506 sys.stdout.write(data); sys.stdout.flush()
507
508def captureCommand(commandline):
509 fd = os.popen(commandline, 'r')
510 data = fd.read()
511 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000512 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800514 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000515
516 return data
517
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000518def getTclTkVersion(configfile, versionline):
519 """
520 search Tcl or Tk configuration file for version line
521 """
522 try:
523 f = open(configfile, "r")
524 except:
525 fatal("Framework configuration file not found: %s" % configfile)
526
527 for l in f:
528 if l.startswith(versionline):
529 f.close()
530 return l
531
532 fatal("Version variable %s not found in framework configuration file: %s"
533 % (versionline, configfile))
534
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535def checkEnvironment():
536 """
537 Check that we're running on a supported system.
538 """
539
Ned Deilye59e4c52011-01-29 18:56:28 +0000540 if sys.version_info[0:2] < (2, 4):
541 fatal("This script must be run with Python 2.4 or later")
542
Thomas Wouters477c8d52006-05-27 19:21:47 +0000543 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000544 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000545
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000546 if int(platform.release().split('.')[0]) < 8:
547 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548
549 if not os.path.exists(SDKPATH):
550 fatal("Please install the latest version of Xcode and the %s SDK"%(
551 os.path.basename(SDKPATH[:-4])))
552
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000553 # Because we only support dynamic load of only one major/minor version of
554 # Tcl/Tk, ensure:
555 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800556 # higher than the Apple-supplied system version in
557 # SDKROOT/System/Library/Frameworks
558 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
559 # in) SDKROOT/Library/Frameworks with the same version as the system
560 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000561
Ned Deily4a96a372013-01-29 00:08:32 -0800562 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000563 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800564 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000565 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800566 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000567 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800568 frameworks[framework] = os.readlink(sysfw)
569 if not os.path.exists(libfw):
570 fatal("Please install a link to a current %s %s as %s so "
571 "the user can override the system framework."
572 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000573 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000574 fatal("Version of %s must match %s" % (libfw, sysfw) )
575 if os.path.exists(usrfw):
576 fatal("Please rename %s to avoid possible dynamic load issues."
577 % usrfw)
578
Ned Deily4a96a372013-01-29 00:08:32 -0800579 if frameworks['Tcl'] != frameworks['Tk']:
580 fatal("The Tcl and Tk frameworks are not the same version.")
581
582 # add files to check after build
583 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
584 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
585 % frameworks['Tcl'],
586 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
587 % frameworks['Tk'],
588 ]
589
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000590 # Remove inherited environment variables which might influence build
591 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
592 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
593 for ev in list(os.environ):
594 for prefix in environ_var_prefixes:
595 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800596 print("INFO: deleting environment variable %s=%s" % (
597 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000598 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000599
Ned Deily4a96a372013-01-29 00:08:32 -0800600 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
601 if 'SDK_TOOLS_BIN' in os.environ:
602 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
603 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
604 # add its fixed location here if it exists
605 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
606 if os.path.isdir(OLD_DEVELOPER_TOOLS):
607 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
608 os.environ['PATH'] = base_path
609 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000610
Thomas Wouters477c8d52006-05-27 19:21:47 +0000611
Thomas Wouters89f507f2006-12-13 04:49:30 +0000612def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000613 """
614 Parse arguments and update global settings.
615 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000616 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800617 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000618
619 if args is None:
620 args = sys.argv[1:]
621
622 try:
623 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000624 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
625 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800626 except getopt.GetoptError:
627 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000628 sys.exit(1)
629
630 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800631 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000632 sys.exit(1)
633
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000634 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000635 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000636 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800637 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000638 sys.exit(0)
639
640 elif k in ('-d', '--build-dir'):
641 WORKDIR=v
642
643 elif k in ('--third-party',):
644 DEPSRC=v
645
646 elif k in ('--sdk-path',):
647 SDKPATH=v
648
649 elif k in ('--src-dir',):
650 SRCDIR=v
651
Ronald Oussoren1943f862009-03-30 19:39:14 +0000652 elif k in ('--dep-target', ):
653 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000654 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000655
656 elif k in ('--universal-archs', ):
657 if v in UNIVERSALOPTS:
658 UNIVERSALARCHS = v
659 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000660 if deptarget is None:
661 # Select alternate default deployment
662 # target
663 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000664 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800665 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000666
Thomas Wouters477c8d52006-05-27 19:21:47 +0000667 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800668 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000669
670 SRCDIR=os.path.abspath(SRCDIR)
671 WORKDIR=os.path.abspath(WORKDIR)
672 SDKPATH=os.path.abspath(SDKPATH)
673 DEPSRC=os.path.abspath(DEPSRC)
674
Ned Deily4a96a372013-01-29 00:08:32 -0800675 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000676
Ned Deily4a96a372013-01-29 00:08:32 -0800677 print("Settings:")
678 print(" * Source directory:", SRCDIR)
679 print(" * Build directory: ", WORKDIR)
680 print(" * SDK location: ", SDKPATH)
681 print(" * Third-party source:", DEPSRC)
682 print(" * Deployment target:", DEPTARGET)
683 print(" * Universal architectures:", ARCHLIST)
684 print(" * C compiler:", CC)
685 print(" * C++ compiler:", CXX)
686 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000687
688
689
690
691def extractArchive(builddir, archiveName):
692 """
693 Extract a source archive into 'builddir'. Returns the path of the
694 extracted archive.
695
696 XXX: This function assumes that archives contain a toplevel directory
697 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700698 safe enough for almost anything we use. Unfortunately, it does not
699 work for current Tcl and Tk source releases where the basename of
700 the archive ends with "-src" but the uncompressed directory does not.
701 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000702 """
703 curdir = os.getcwd()
704 try:
705 os.chdir(builddir)
706 if archiveName.endswith('.tar.gz'):
707 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700708 if ((retval.startswith('tcl') or retval.startswith('tk'))
709 and retval.endswith('-src')):
710 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000711 if os.path.exists(retval):
712 shutil.rmtree(retval)
713 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
714
715 elif archiveName.endswith('.tar.bz2'):
716 retval = os.path.basename(archiveName[:-8])
717 if os.path.exists(retval):
718 shutil.rmtree(retval)
719 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
720
721 elif archiveName.endswith('.tar'):
722 retval = os.path.basename(archiveName[:-4])
723 if os.path.exists(retval):
724 shutil.rmtree(retval)
725 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
726
727 elif archiveName.endswith('.zip'):
728 retval = os.path.basename(archiveName[:-4])
729 if os.path.exists(retval):
730 shutil.rmtree(retval)
731 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
732
733 data = fp.read()
734 xit = fp.close()
735 if xit is not None:
736 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800737 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000738
739 return os.path.join(builddir, retval)
740
741 finally:
742 os.chdir(curdir)
743
Thomas Wouters477c8d52006-05-27 19:21:47 +0000744def downloadURL(url, fname):
745 """
746 Download the contents of the url into the file.
747 """
Ned Deily4a96a372013-01-29 00:08:32 -0800748 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000749 fpOut = open(fname, 'wb')
750 block = fpIn.read(10240)
751 try:
752 while block:
753 fpOut.write(block)
754 block = fpIn.read(10240)
755 fpIn.close()
756 fpOut.close()
757 except:
758 try:
759 os.unlink(fname)
760 except:
761 pass
762
Ned Deily4a96a372013-01-29 00:08:32 -0800763def verifyThirdPartyFile(url, checksum, fname):
764 """
765 Download file from url to filename fname if it does not already exist.
766 Abort if file contents does not match supplied md5 checksum.
767 """
768 name = os.path.basename(fname)
769 if os.path.exists(fname):
770 print("Using local copy of %s"%(name,))
771 else:
772 print("Did not find local copy of %s"%(name,))
773 print("Downloading %s"%(name,))
774 downloadURL(url, fname)
775 print("Archive for %s stored as %s"%(name, fname))
776 if os.system(
777 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
778 % (shellQuote(fname), checksum) ):
779 fatal('MD5 checksum mismatch for file %s' % fname)
780
Thomas Wouters477c8d52006-05-27 19:21:47 +0000781def buildRecipe(recipe, basedir, archList):
782 """
783 Build software using a recipe. This function does the
784 'configure;make;make install' dance for C software, with a possibility
785 to customize this process, basically a poor-mans DarwinPorts.
786 """
787 curdir = os.getcwd()
788
789 name = recipe['name']
790 url = recipe['url']
791 configure = recipe.get('configure', './configure')
792 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
793 shellQuote(basedir)))
794
795 archiveName = os.path.split(url)[-1]
796 sourceArchive = os.path.join(DEPSRC, archiveName)
797
798 if not os.path.exists(DEPSRC):
799 os.mkdir(DEPSRC)
800
Ned Deily4a96a372013-01-29 00:08:32 -0800801 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
802 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000803 buildDir=os.path.join(WORKDIR, '_bld')
804 if not os.path.exists(buildDir):
805 os.mkdir(buildDir)
806
807 workDir = extractArchive(buildDir, sourceArchive)
808 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809
Ned Deily4a96a372013-01-29 00:08:32 -0800810 for patch in recipe.get('patches', ()):
811 if isinstance(patch, tuple):
812 url, checksum = patch
813 fn = os.path.join(DEPSRC, os.path.basename(url))
814 verifyThirdPartyFile(url, checksum, fn)
815 else:
816 # patch is a file in the source directory
817 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
819 shellQuote(fn),))
820
Ned Deily4a96a372013-01-29 00:08:32 -0800821 for patchscript in recipe.get('patchscripts', ()):
822 if isinstance(patchscript, tuple):
823 url, checksum = patchscript
824 fn = os.path.join(DEPSRC, os.path.basename(url))
825 verifyThirdPartyFile(url, checksum, fn)
826 else:
827 # patch is a file in the source directory
828 fn = os.path.join(curdir, patchscript)
829 if fn.endswith('.bz2'):
830 runCommand('bunzip2 -fk %s' % shellQuote(fn))
831 fn = fn[:-4]
832 runCommand('sh %s' % shellQuote(fn))
833 os.unlink(fn)
834
Ned Deily94764b22013-10-27 19:49:29 -0700835 if 'buildDir' in recipe:
836 os.chdir(recipe['buildDir'])
837
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000838 if configure is not None:
839 configure_args = [
840 "--prefix=/usr/local",
841 "--enable-static",
842 "--disable-shared",
843 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
844 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000845
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000846 if 'configure_pre' in recipe:
847 args = list(recipe['configure_pre'])
848 if '--disable-static' in args:
849 configure_args.remove('--enable-static')
850 if '--enable-shared' in args:
851 configure_args.remove('--disable-shared')
852 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000853
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000854 if recipe.get('useLDFlags', 1):
855 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800856 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
857 "-I%s/usr/local/include"%(
858 recipe.get('extra_cflags', ''),
859 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000860 ' -arch '.join(archList),
861 shellQuote(SDKPATH)[1:-1],
862 shellQuote(basedir)[1:-1],),
Ned Deily4a96a372013-01-29 00:08:32 -0800863 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
864 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000865 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000866 shellQuote(basedir)[1:-1],
867 ' -arch '.join(archList)),
868 ])
869 else:
870 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800871 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
872 "-I%s/usr/local/include"%(
873 recipe.get('extra_cflags', ''),
874 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000875 ' -arch '.join(archList),
876 shellQuote(SDKPATH)[1:-1],
877 shellQuote(basedir)[1:-1],),
878 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000879
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000880 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800881 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000882
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000883 configure_args.insert(0, configure)
884 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000885
Ned Deily4a96a372013-01-29 00:08:32 -0800886 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000887 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000888
Ned Deily4a96a372013-01-29 00:08:32 -0800889 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890 runCommand('{ ' + install + ' ;} 2>&1')
891
Ned Deily4a96a372013-01-29 00:08:32 -0800892 print("Done %s"%(name,))
893 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000894
895 os.chdir(curdir)
896
897def buildLibraries():
898 """
899 Build our dependencies into $WORKDIR/libraries/usr/local
900 """
Ned Deily4a96a372013-01-29 00:08:32 -0800901 print("")
902 print("Building required libraries")
903 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000904 universal = os.path.join(WORKDIR, 'libraries')
905 os.mkdir(universal)
906 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
907 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
908
Ronald Oussoren1943f862009-03-30 19:39:14 +0000909 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000910 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000911
912
913
914def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000915 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000916 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800917 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000918 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000919 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000921 curDir = os.getcwd()
922 os.chdir(buildDir)
923 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000924 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000925 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000926 if not os.path.exists(docdir):
927 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000928 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929
930
931def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800932 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000933
934 buildDir = os.path.join(WORKDIR, '_bld', 'python')
935 rootDir = os.path.join(WORKDIR, '_root')
936
937 if os.path.exists(buildDir):
938 shutil.rmtree(buildDir)
939 if os.path.exists(rootDir):
940 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000941 os.makedirs(buildDir)
942 os.makedirs(rootDir)
943 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000944 curdir = os.getcwd()
945 os.chdir(buildDir)
946
947 # Not sure if this is still needed, the original build script
948 # claims that parts of the install assume python.exe exists.
949 os.symlink('python', os.path.join(buildDir, 'python.exe'))
950
951 # Extract the version from the configure file, needed to calculate
952 # several paths.
953 version = getVersion()
954
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000955 # Since the extra libs are not in their installed framework location
956 # during the build, augment the library path so that the interpreter
957 # will find them during its extension import sanity checks.
958 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
959 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800960 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000961 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000962 "--with-universal-archs=%s "
963 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800964 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000965 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700966 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000967 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
968 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000969 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily41ab6c32013-11-22 22:25:43 -0800970 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000971 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000972 shellQuote(WORKDIR)[1:-1]))
973
Ned Deily4a96a372013-01-29 00:08:32 -0800974 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000975 runCommand("make")
976
Ned Deily4a96a372013-01-29 00:08:32 -0800977 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000978 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000979 shellQuote(rootDir)))
980
Ned Deily4a96a372013-01-29 00:08:32 -0800981 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000982 runCommand("make frameworkinstallextras DESTDIR=%s"%(
983 shellQuote(rootDir)))
984
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000985 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800986 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000987 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
988 runCommand("mv %s/* %s"%(
989 shellQuote(os.path.join(
990 WORKDIR, 'libraries', 'Library', 'Frameworks',
991 'Python.framework', 'Versions', getVersion(),
992 'lib')),
993 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
994 'Python.framework', 'Versions', getVersion(),
995 'lib'))))
996
Ned Deily050fcd52013-10-26 03:16:44 -0700997 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
998 'Python.framework', 'Versions',
999 version, 'lib', 'python%s'%(version,))
1000
Ned Deily4a96a372013-01-29 00:08:32 -08001001 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001002 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001003 gid = grp.getgrnam('admin').gr_gid
1004
Ned Deily4a96a372013-01-29 00:08:32 -08001005 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001006 for dirpath, dirnames, filenames in os.walk(frmDir):
1007 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001008 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001009 os.chown(os.path.join(dirpath, dn), -1, gid)
1010
Thomas Wouters477c8d52006-05-27 19:21:47 +00001011 for fn in filenames:
1012 if os.path.islink(fn):
1013 continue
1014
1015 # "chmod g+w $fn"
1016 p = os.path.join(dirpath, fn)
1017 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001018 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1019 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001020
Ned Deily4a96a372013-01-29 00:08:32 -08001021 if fn in EXPECTED_SHARED_LIBS:
1022 # check to see that this file was linked with the
1023 # expected library path and version
1024 data = captureCommand("otool -L %s" % shellQuote(p))
1025 for sl in EXPECTED_SHARED_LIBS[fn]:
1026 if ("\t%s " % sl) not in data:
1027 print("Expected shared lib %s was not linked with %s"
1028 % (sl, p))
1029 shared_lib_error = True
1030
1031 if shared_lib_error:
1032 fatal("Unexpected shared library errors.")
1033
Ned Deilye59e4c52011-01-29 18:56:28 +00001034 if PYTHON_3:
1035 LDVERSION=None
1036 VERSION=None
1037 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001038
Ned Deilye59e4c52011-01-29 18:56:28 +00001039 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001040 for ln in fp:
1041 if ln.startswith('VERSION='):
1042 VERSION=ln.split()[1]
1043 if ln.startswith('ABIFLAGS='):
1044 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001045 if ln.startswith('LDVERSION='):
1046 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001047 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001048
Ned Deilye59e4c52011-01-29 18:56:28 +00001049 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1050 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1051 config_suffix = '-' + LDVERSION
1052 else:
1053 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001054
Thomas Wouters477c8d52006-05-27 19:21:47 +00001055 # We added some directories to the search path during the configure
1056 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001057 # the end-users system. Also remove the directories from _sysconfigdata.py
1058 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001059
Ned Deilya4f6b002013-10-25 00:47:38 -07001060 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1061 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1062
Ned Deilya4f6b002013-10-25 00:47:38 -07001063 # fix Makefile
1064 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1065 fp = open(path, 'r')
1066 data = fp.read()
1067 fp.close()
1068
1069 for p in (include_path, lib_path):
1070 data = data.replace(" " + p, '')
1071 data = data.replace(p + " ", '')
1072
1073 fp = open(path, 'w')
1074 fp.write(data)
1075 fp.close()
1076
1077 # fix _sysconfigdata if it exists
1078 #
1079 # TODO: make this more robust! test_sysconfig_module of
1080 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1081 # the output from get_config_var in both sysconfig and
1082 # distutils.sysconfig is exactly the same for both CFLAGS and
1083 # LDFLAGS. The fixing up is now complicated by the pretty
1084 # printing in _sysconfigdata.py. Also, we are using the
1085 # pprint from the Python running the installer build which
1086 # may not cosmetically format the same as the pprint in the Python
1087 # being built (and which is used to originally generate
1088 # _sysconfigdata.py).
1089
1090 import pprint
1091 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1092 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001093 fp = open(path, 'r')
1094 data = fp.read()
1095 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001096 # create build_time_vars dict
1097 exec(data)
1098 vars = {}
1099 for k, v in build_time_vars.items():
1100 if type(v) == type(''):
1101 for p in (include_path, lib_path):
1102 v = v.replace(' ' + p, '')
1103 v = v.replace(p + ' ', '')
1104 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001105
Ned Deily4a96a372013-01-29 00:08:32 -08001106 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001107 # duplicated from sysconfig._generate_posix_vars()
1108 fp.write('# system configuration generated and used by'
1109 ' the sysconfig module\n')
1110 fp.write('build_time_vars = ')
1111 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001112 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001113
1114 # Add symlinks in /usr/local/bin, using relative links
1115 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1116 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1117 'Python.framework', 'Versions', version, 'bin')
1118 if os.path.exists(usr_local_bin):
1119 shutil.rmtree(usr_local_bin)
1120 os.makedirs(usr_local_bin)
1121 for fn in os.listdir(
1122 os.path.join(frmDir, 'Versions', version, 'bin')):
1123 os.symlink(os.path.join(to_framework, fn),
1124 os.path.join(usr_local_bin, fn))
1125
1126 os.chdir(curdir)
1127
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001128 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001129 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001130 # with an already installed version of python 2
1131 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1132 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001133
1134def patchFile(inPath, outPath):
1135 data = fileContents(inPath)
1136 data = data.replace('$FULL_VERSION', getFullVersion())
1137 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001138 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001139 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001140 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001141
1142 # This one is not handy as a template variable
1143 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001144 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001145 fp.write(data)
1146 fp.close()
1147
1148def patchScript(inPath, outPath):
1149 data = fileContents(inPath)
1150 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001151 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001152 fp.write(data)
1153 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001154 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001155
1156
1157
1158def packageFromRecipe(targetDir, recipe):
1159 curdir = os.getcwd()
1160 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001161 # The major version (such as 2.5) is included in the package name
1162 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001163 # common.
1164 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001165 srcdir = recipe.get('source')
1166 pkgroot = recipe.get('topdir', srcdir)
1167 postflight = recipe.get('postflight')
1168 readme = textwrap.dedent(recipe['readme'])
1169 isRequired = recipe.get('required', True)
1170
Ned Deily4a96a372013-01-29 00:08:32 -08001171 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001172
1173 # Substitute some variables
1174 textvars = dict(
1175 VER=getVersion(),
1176 FULLVER=getFullVersion(),
1177 )
1178 readme = readme % textvars
1179
1180 if pkgroot is not None:
1181 pkgroot = pkgroot % textvars
1182 else:
1183 pkgroot = '/'
1184
1185 if srcdir is not None:
1186 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1187 srcdir = srcdir % textvars
1188
1189 if postflight is not None:
1190 postflight = os.path.abspath(postflight)
1191
1192 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1193 os.makedirs(packageContents)
1194
1195 if srcdir is not None:
1196 os.chdir(srcdir)
1197 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1198 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1199 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1200
1201 fn = os.path.join(packageContents, 'PkgInfo')
1202 fp = open(fn, 'w')
1203 fp.write('pmkrpkg1')
1204 fp.close()
1205
1206 rsrcDir = os.path.join(packageContents, "Resources")
1207 os.mkdir(rsrcDir)
1208 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1209 fp.write(readme)
1210 fp.close()
1211
1212 if postflight is not None:
1213 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1214
1215 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001216 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001217 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001218 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1219 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1220 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001221 CFBundleShortVersionString=vers,
1222 IFMajorVersion=major,
1223 IFMinorVersion=minor,
1224 IFPkgFormatVersion=0.10000000149011612,
1225 IFPkgFlagAllowBackRev=False,
1226 IFPkgFlagAuthorizationAction="RootAuthorization",
1227 IFPkgFlagDefaultLocation=pkgroot,
1228 IFPkgFlagFollowLinks=True,
1229 IFPkgFlagInstallFat=True,
1230 IFPkgFlagIsRequired=isRequired,
1231 IFPkgFlagOverwritePermissions=False,
1232 IFPkgFlagRelocatable=False,
1233 IFPkgFlagRestartAction="NoRestart",
1234 IFPkgFlagRootVolumeOnly=True,
1235 IFPkgFlagUpdateInstalledLangauges=False,
1236 )
1237 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1238
1239 pl = Plist(
1240 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001241 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001242 IFPkgDescriptionVersion=vers,
1243 )
1244 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1245
1246 finally:
1247 os.chdir(curdir)
1248
1249
1250def makeMpkgPlist(path):
1251
1252 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001253 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001254
1255 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001256 CFBundleGetInfoString="Python %s"%(vers,),
1257 CFBundleIdentifier='org.python.Python',
1258 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001259 CFBundleShortVersionString=vers,
1260 IFMajorVersion=major,
1261 IFMinorVersion=minor,
1262 IFPkgFlagComponentDirectory="Contents/Packages",
1263 IFPkgFlagPackageList=[
1264 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001265 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001266 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001267 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001268 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001269 ],
1270 IFPkgFormatVersion=0.10000000149011612,
1271 IFPkgFlagBackgroundScaling="proportional",
1272 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001273 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001274 )
1275
1276 writePlist(pl, path)
1277
1278
1279def buildInstaller():
1280
1281 # Zap all compiled files
1282 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1283 for fn in filenames:
1284 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1285 os.unlink(os.path.join(dirpath, fn))
1286
1287 outdir = os.path.join(WORKDIR, 'installer')
1288 if os.path.exists(outdir):
1289 shutil.rmtree(outdir)
1290 os.mkdir(outdir)
1291
Ronald Oussoren1943f862009-03-30 19:39:14 +00001292 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001293 pkgcontents = os.path.join(pkgroot, 'Packages')
1294 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001295 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001296 packageFromRecipe(pkgcontents, recipe)
1297
1298 rsrcDir = os.path.join(pkgroot, 'Resources')
1299
1300 fn = os.path.join(pkgroot, 'PkgInfo')
1301 fp = open(fn, 'w')
1302 fp.write('pmkrpkg1')
1303 fp.close()
1304
1305 os.mkdir(rsrcDir)
1306
1307 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1308 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001309 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001310 IFPkgDescriptionVersion=getVersion(),
1311 )
1312
1313 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1314 for fn in os.listdir('resources'):
1315 if fn == '.svn': continue
1316 if fn.endswith('.jpg'):
1317 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1318 else:
1319 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1320
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001321 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001322
1323
1324def installSize(clear=False, _saved=[]):
1325 if clear:
1326 del _saved[:]
1327 if not _saved:
1328 data = captureCommand("du -ks %s"%(
1329 shellQuote(os.path.join(WORKDIR, '_root'))))
1330 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1331 return _saved[0]
1332
1333
1334def buildDMG():
1335 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001336 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001337 """
1338 outdir = os.path.join(WORKDIR, 'diskimage')
1339 if os.path.exists(outdir):
1340 shutil.rmtree(outdir)
1341
1342 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001343 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001344 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001345 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001346 imagepath = imagepath + '.dmg'
1347
1348 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001349 volname='Python %s'%(getFullVersion())
1350 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1351 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001352 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001353 shellQuote(imagepath + ".tmp.dmg" )))
1354
1355
1356 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1357 os.mkdir(os.path.join(WORKDIR, "mnt"))
1358 runCommand("hdiutil attach %s -mountroot %s"%(
1359 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1360
1361 # Custom icon for the DMG, shown when the DMG is mounted.
1362 shutil.copy("../Icons/Disk Image.icns",
1363 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001364 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001365 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1366
1367 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1368
1369 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1370 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1371 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1372 setIcon(imagepath, "../Icons/Disk Image.icns")
1373
1374 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001375
1376 return imagepath
1377
1378
1379def setIcon(filePath, icnsPath):
1380 """
1381 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001382 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001383
Ronald Oussoren70050672010-04-30 15:00:26 +00001384 dirPath = os.path.normpath(os.path.dirname(__file__))
1385 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001386 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1387 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1388 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001389 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1390 if not os.path.exists(appPath):
1391 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001392 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1393 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001394
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001395 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1396 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001397
1398def main():
1399 # First parse options and check if we can perform our work
1400 parseOptions()
1401 checkEnvironment()
1402
Ronald Oussoren1943f862009-03-30 19:39:14 +00001403 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001404 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001405 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001406
1407 if os.path.exists(WORKDIR):
1408 shutil.rmtree(WORKDIR)
1409 os.mkdir(WORKDIR)
1410
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001411 os.environ['LC_ALL'] = 'C'
1412
Thomas Wouters477c8d52006-05-27 19:21:47 +00001413 # Then build third-party libraries such as sleepycat DB4.
1414 buildLibraries()
1415
1416 # Now build python itself
1417 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001418
1419 # And then build the documentation
1420 # Remove the Deployment Target from the shell
1421 # environment, it's no longer needed and
1422 # an unexpected build target can cause problems
1423 # when Sphinx and its dependencies need to
1424 # be (re-)installed.
1425 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001426 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001427
1428
1429 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001430 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001431 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001432 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001433
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001434 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001435 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001436 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001437 setIcon(folder, "../Icons/Python Folder.icns")
1438
1439 # Create the installer
1440 buildInstaller()
1441
1442 # And copy the readme into the directory containing the installer
1443 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1444
1445 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001446 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001447
1448 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001449 fp.write("# BUILD INFO\n")
1450 fp.write("# Date: %s\n" % time.ctime())
1451 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452 fp.close()
1453
Thomas Wouters477c8d52006-05-27 19:21:47 +00001454 # And copy it to a DMG
1455 buildDMG()
1456
Thomas Wouters477c8d52006-05-27 19:21:47 +00001457if __name__ == "__main__":
1458 main()