blob: 69f3ff7ee904db6f6f4560d9aca789af658cf4cf [file] [log] [blame]
Ned Deilye1c9794952013-01-29 00:07:46 -08001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deily53c460d2011-01-30 01:43:40 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deilye1c9794952013-01-29 00:07:46 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deily53c460d2011-01-30 01:43:40 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
Ned Deily53c460d2011-01-30 01:43:40 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
11Python 2.4.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000012
Ned Deilye1c9794952013-01-29 00:07:46 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000027Usage: see USAGE variable in the script.
28"""
Ned Deilye1c9794952013-01-29 00:07:46 -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 )
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000042
Ronald Oussoren158ad592006-11-07 16:00:34 +000043INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000045
46from plistlib import Plist
47
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000057 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -080068 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000071
72def getVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000078
Ned Deily53c460d2011-01-30 01:43:40 +000079def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
Ned Deilye1c9794952013-01-29 00:07:46 -080082def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000087def getFullVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080088 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000091 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
Ned Deilye1c9794952013-01-29 00:07:46 -080094 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000097
Ronald Oussorenaa560962006-11-07 15:53:38 +000098# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +000099WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000100
Ronald Oussorenaa560962006-11-07 15:53:38 +0000101# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000102# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000105
106# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +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 Melotti6d0f0f22013-08-26 01:31:30 +0300111### issue is resolved.
Ronald Oussoren209d4c32009-09-29 19:34:13 +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
121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000122
Ronald Oussoren508282e2009-03-30 19:34:51 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Oussoren508282e2009-03-30 19:34:51 +0000134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000140
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200141# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000142SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000143 os.path.dirname(
144 os.path.dirname(
145 os.path.abspath(__file__
146 ))))
147
Ronald Oussoren508282e2009-03-30 19:34:51 +0000148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000151target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily11f880a2013-10-18 20:40:23 -0700158 '10.9': ('clang', 'clang++'),
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000159}
160
Ned Deilye1c9794952013-01-29 00:07:46 -0800161CC, CXX = target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000162
Ned Deily53c460d2011-01-30 01:43:40 +0000163PYTHON_3 = getVersionTuple() >= (3, 0)
164
Ronald Oussoren158ad592006-11-07 16:00:34 +0000165USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000175 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
176 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000177""")% globals()
178
Ned Deilye1c9794952013-01-29 00:07:46 -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 = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000186
187# Instructions for building libraries that are necessary for building a
188# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000189# [The recipes are defined here for convenience but instantiated later after
190# command line options have been processed.]
191def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000192 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000193
Ned Deilye1c9794952013-01-29 00:07:46 -0800194 LT_10_5 = bool(DEPTARGET < '10.5')
195
Ned Deily0203a802013-10-25 00:40:07 -0700196 if DEPTARGET > '10.5':
197 result.extend([
198 dict(
199 name="Tcl 8.5.15",
200 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
201 checksum='f3df162f92c69b254079c4d0af7a690f',
202 buildDir="unix",
203 configure_pre=[
204 '--enable-shared',
205 '--enable-threads',
206 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
207 ],
208 useLDFlags=False,
209 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
210 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
211 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
212 },
213 ),
214 dict(
215 name="Tk 8.5.15",
216 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
217 checksum='55b8e33f903210a4e1c8bce0f820657f',
218 buildDir="unix",
219 configure_pre=[
220 '--enable-aqua',
221 '--enable-shared',
222 '--enable-threads',
223 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
224 ],
225 useLDFlags=False,
226 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'%{
227 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
228 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
229 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
230 },
231 ),
232 ])
233
Ned Deilye1c9794952013-01-29 00:07:46 -0800234 if getVersionTuple() >= (3, 3):
235 result.extend([
236 dict(
237 name="XZ 5.0.3",
238 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
239 checksum='fefe52f9ecd521de2a8ce38c21a27574',
240 configure_pre=[
241 '--disable-dependency-tracking',
242 ]
243 ),
244 ])
245
246 result.extend([
247 dict(
248 name="NCurses 5.9",
249 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
250 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
251 configure_pre=[
252 "--enable-widec",
253 "--without-cxx",
254 "--without-cxx-binding",
255 "--without-ada",
256 "--without-curses-h",
257 "--enable-shared",
258 "--with-shared",
259 "--without-debug",
260 "--without-normal",
261 "--without-tests",
262 "--without-manpages",
263 "--datadir=/usr/share",
264 "--sysconfdir=/etc",
265 "--sharedstatedir=/usr/com",
266 "--with-terminfo-dirs=/usr/share/terminfo",
267 "--with-default-terminfo-dir=/usr/share/terminfo",
268 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
269 ],
270 patchscripts=[
271 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
272 "f54bf02a349f96a7c4f0d00922f3a0d4"),
273 ],
274 useLDFlags=False,
275 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
276 shellQuote(os.path.join(WORKDIR, 'libraries')),
277 shellQuote(os.path.join(WORKDIR, 'libraries')),
278 getVersion(),
279 ),
280 ),
281 dict(
282 name="SQLite 3.7.13",
283 url="http://www.sqlite.org/sqlite-autoconf-3071300.tar.gz",
284 checksum='c97df403e8a3d5b67bb408fcd6aabd8e',
285 extra_cflags=('-Os '
286 '-DSQLITE_ENABLE_FTS4 '
287 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
288 '-DSQLITE_ENABLE_RTREE '
289 '-DSQLITE_TCL=0 '
290 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
291 configure_pre=[
292 '--enable-threadsafe',
293 '--enable-shared=no',
294 '--enable-static=yes',
295 '--disable-readline',
296 '--disable-dependency-tracking',
297 ]
298 ),
299 ])
300
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000301 if DEPTARGET < '10.5':
302 result.extend([
303 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000304 name="Bzip2 1.0.6",
305 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
306 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000307 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800308 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
309 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000310 shellQuote(os.path.join(WORKDIR, 'libraries')),
311 ' -arch '.join(ARCHLIST),
312 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000313 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000314 ),
315 dict(
316 name="ZLib 1.2.3",
317 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
318 checksum='debc62758716a169df9f62e6ab2bc634',
319 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800320 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
321 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000322 shellQuote(os.path.join(WORKDIR, 'libraries')),
323 ' -arch '.join(ARCHLIST),
324 SDKPATH,
325 ),
326 ),
327 dict(
328 # Note that GNU readline is GPL'd software
Ned Deily53c460d2011-01-30 01:43:40 +0000329 name="GNU Readline 6.1.2",
330 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
331 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000332 patchlevel='0',
333 patches=[
334 # The readline maintainers don't do actual micro releases, but
335 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -0800336 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
337 'c642f2e84d820884b0bf9fd176bc6c3f'),
338 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
339 '1a76781a1ea734e831588285db7ec9b1'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000340 ]
341 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000342 ])
343
Ned Deily53c460d2011-01-30 01:43:40 +0000344 if not PYTHON_3:
345 result.extend([
346 dict(
347 name="Sleepycat DB 4.7.25",
348 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
349 checksum='ec2b87e833779681a0c3a814aa71359e',
350 buildDir="build_unix",
351 configure="../dist/configure",
352 configure_pre=[
353 '--includedir=/usr/local/include/db4',
354 ]
355 ),
356 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000357
358 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000359
360
361# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000362def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000363 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000364 result = [
365 dict(
366 name="PythonFramework",
367 long_name="Python Framework",
368 source="/Library/Frameworks/Python.framework",
369 readme="""\
370 This package installs Python.framework, that is the python
371 interpreter and the standard library. This also includes Python
372 wrappers for lots of Mac OS X API's.
373 """,
374 postflight="scripts/postflight.framework",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000375 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000376 ),
377 dict(
378 name="PythonApplications",
379 long_name="GUI Applications",
380 source="/Applications/Python %(VER)s",
381 readme="""\
382 This package installs IDLE (an interactive Python IDE),
383 Python Launcher and Build Applet (create application bundles
384 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000385
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000386 It also installs a number of examples and demos.
387 """,
388 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000389 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000390 ),
391 dict(
392 name="PythonUnixTools",
393 long_name="UNIX command-line tools",
394 source="/usr/local/bin",
395 readme="""\
396 This package installs the unix tools in /usr/local/bin for
397 compatibility with older releases of Python. This package
398 is not necessary to use Python.
399 """,
400 required=False,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000401 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000402 ),
403 dict(
404 name="PythonDocumentation",
405 long_name="Python Documentation",
406 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
407 source="/pydocs",
408 readme="""\
409 This package installs the python documentation at a location
Ned Deilye1c9794952013-01-29 00:07:46 -0800410 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000411 """,
412 postflight="scripts/postflight.documentation",
413 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000414 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000415 ),
416 dict(
417 name="PythonProfileChanges",
418 long_name="Shell profile updater",
419 readme="""\
420 This packages updates your shell profile to make sure that
421 the Python tools are found by your shell in preference of
422 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000423
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000424 If you don't install this package you'll have to add
425 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
426 to your PATH by hand.
427 """,
428 postflight="scripts/postflight.patch-profile",
429 topdir="/Library/Frameworks/Python.framework",
430 source="/empty-dir",
431 required=False,
Ned Deily53c460d2011-01-30 01:43:40 +0000432 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000433 ),
434 ]
435
Ned Deilye1c9794952013-01-29 00:07:46 -0800436 if DEPTARGET < '10.4' and not PYTHON_3:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000437 result.append(
438 dict(
439 name="PythonSystemFixes",
440 long_name="Fix system Python",
441 readme="""\
442 This package updates the system python installation on
443 Mac OS X 10.3 to ensure that you can build new python extensions
444 using that copy of python after installing this version.
445 """,
446 postflight="../Tools/fixapplepython23.py",
447 topdir="/Library/Frameworks/Python.framework",
448 source="/empty-dir",
449 required=False,
Ned Deily53c460d2011-01-30 01:43:40 +0000450 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000451 )
452 )
453 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000454
455def fatal(msg):
456 """
457 A fatal error, bail out.
458 """
459 sys.stderr.write('FATAL: ')
460 sys.stderr.write(msg)
461 sys.stderr.write('\n')
462 sys.exit(1)
463
464def fileContents(fn):
465 """
466 Return the contents of the named file
467 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800468 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000469
470def runCommand(commandline):
471 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200472 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000473 unless the command fails.
474 """
475 fd = os.popen(commandline, 'r')
476 data = fd.read()
477 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000478 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000479 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800480 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000481
482 if VERBOSE:
483 sys.stdout.write(data); sys.stdout.flush()
484
485def captureCommand(commandline):
486 fd = os.popen(commandline, 'r')
487 data = fd.read()
488 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000489 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000490 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800491 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000492
493 return data
494
Ronald Oussoren287128a2010-04-18 14:01:05 +0000495def getTclTkVersion(configfile, versionline):
496 """
497 search Tcl or Tk configuration file for version line
498 """
499 try:
500 f = open(configfile, "r")
501 except:
502 fatal("Framework configuration file not found: %s" % configfile)
503
504 for l in f:
505 if l.startswith(versionline):
506 f.close()
507 return l
508
509 fatal("Version variable %s not found in framework configuration file: %s"
510 % (versionline, configfile))
511
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000512def checkEnvironment():
513 """
514 Check that we're running on a supported system.
515 """
516
Ned Deily53c460d2011-01-30 01:43:40 +0000517 if sys.version_info[0:2] < (2, 4):
518 fatal("This script must be run with Python 2.4 or later")
519
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000520 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000521 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000522
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000523 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000524 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000525
526 if not os.path.exists(SDKPATH):
527 fatal("Please install the latest version of Xcode and the %s SDK"%(
528 os.path.basename(SDKPATH[:-4])))
529
Ronald Oussoren287128a2010-04-18 14:01:05 +0000530 # Because we only support dynamic load of only one major/minor version of
531 # Tcl/Tk, ensure:
532 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deilye1c9794952013-01-29 00:07:46 -0800533 # higher than the Apple-supplied system version in
534 # SDKROOT/System/Library/Frameworks
535 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
536 # in) SDKROOT/Library/Frameworks with the same version as the system
537 # version. This allows users to choose to install a newer patch level.
Ronald Oussoren287128a2010-04-18 14:01:05 +0000538
Ned Deilye1c9794952013-01-29 00:07:46 -0800539 frameworks = {}
Ronald Oussoren287128a2010-04-18 14:01:05 +0000540 for framework in ['Tcl', 'Tk']:
Ned Deilye1c9794952013-01-29 00:07:46 -0800541 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily53c460d2011-01-30 01:43:40 +0000542 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800543 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000544 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800545 frameworks[framework] = os.readlink(sysfw)
546 if not os.path.exists(libfw):
547 fatal("Please install a link to a current %s %s as %s so "
548 "the user can override the system framework."
549 % (framework, frameworks[framework], libfw))
Ned Deily53c460d2011-01-30 01:43:40 +0000550 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +0000551 fatal("Version of %s must match %s" % (libfw, sysfw) )
552 if os.path.exists(usrfw):
553 fatal("Please rename %s to avoid possible dynamic load issues."
554 % usrfw)
555
Ned Deilye1c9794952013-01-29 00:07:46 -0800556 if frameworks['Tcl'] != frameworks['Tk']:
557 fatal("The Tcl and Tk frameworks are not the same version.")
558
559 # add files to check after build
560 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
561 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
562 % frameworks['Tcl'],
563 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
564 % frameworks['Tk'],
565 ]
566
Ned Deily0203a802013-10-25 00:40:07 -0700567 # For 10.6+ builds, we build two versions of _tkinter:
Ned Deily70f213a2013-10-26 03:16:06 -0700568 # - the traditional version (renamed to _tkinter_library.so) linked
Ned Deily0203a802013-10-25 00:40:07 -0700569 # with /Library/Frameworks/{Tcl,Tk}.framework
Ned Deily70f213a2013-10-26 03:16:06 -0700570 # - the default version linked with our builtin copies of Tcl and Tk
Ned Deily0203a802013-10-25 00:40:07 -0700571 if DEPTARGET > '10.5':
Ned Deily70f213a2013-10-26 03:16:06 -0700572 EXPECTED_SHARED_LIBS['_tkinter_library.so'] = \
Ned Deily0203a802013-10-25 00:40:07 -0700573 EXPECTED_SHARED_LIBS['_tkinter.so']
574 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
575 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib"
576 % (getVersion(), frameworks['Tcl']),
577 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtk%s.dylib"
578 % (getVersion(), frameworks['Tk']),
579 ]
580
Ronald Oussoren287128a2010-04-18 14:01:05 +0000581 # Remove inherited environment variables which might influence build
582 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
583 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
584 for ev in list(os.environ):
585 for prefix in environ_var_prefixes:
586 if ev.startswith(prefix) :
Ned Deilye1c9794952013-01-29 00:07:46 -0800587 print("INFO: deleting environment variable %s=%s" % (
588 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000589 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000590
Ned Deilye1c9794952013-01-29 00:07:46 -0800591 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
592 if 'SDK_TOOLS_BIN' in os.environ:
593 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
594 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
595 # add its fixed location here if it exists
596 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
597 if os.path.isdir(OLD_DEVELOPER_TOOLS):
598 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
599 os.environ['PATH'] = base_path
600 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussorenac4b7ad2010-04-20 05:50:44 +0000601
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000602
Ronald Oussoren158ad592006-11-07 16:00:34 +0000603def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000604 """
605 Parse arguments and update global settings.
606 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000607 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800608 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000609
610 if args is None:
611 args = sys.argv[1:]
612
613 try:
614 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000615 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
616 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800617 except getopt.GetoptError:
618 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000619 sys.exit(1)
620
621 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800622 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000623 sys.exit(1)
624
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000625 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000626 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000627 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800628 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000629 sys.exit(0)
630
631 elif k in ('-d', '--build-dir'):
632 WORKDIR=v
633
634 elif k in ('--third-party',):
635 DEPSRC=v
636
637 elif k in ('--sdk-path',):
638 SDKPATH=v
639
640 elif k in ('--src-dir',):
641 SRCDIR=v
642
Ronald Oussoren508282e2009-03-30 19:34:51 +0000643 elif k in ('--dep-target', ):
644 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000645 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000646
647 elif k in ('--universal-archs', ):
648 if v in UNIVERSALOPTS:
649 UNIVERSALARCHS = v
650 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000651 if deptarget is None:
652 # Select alternate default deployment
653 # target
654 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000655 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800656 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000657
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000658 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800659 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000660
661 SRCDIR=os.path.abspath(SRCDIR)
662 WORKDIR=os.path.abspath(WORKDIR)
663 SDKPATH=os.path.abspath(SDKPATH)
664 DEPSRC=os.path.abspath(DEPSRC)
665
Ned Deilye1c9794952013-01-29 00:07:46 -0800666 CC, CXX=target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000667
Ned Deilye1c9794952013-01-29 00:07:46 -0800668 print("Settings:")
669 print(" * Source directory:", SRCDIR)
670 print(" * Build directory: ", WORKDIR)
671 print(" * SDK location: ", SDKPATH)
672 print(" * Third-party source:", DEPSRC)
673 print(" * Deployment target:", DEPTARGET)
674 print(" * Universal architectures:", ARCHLIST)
675 print(" * C compiler:", CC)
676 print(" * C++ compiler:", CXX)
677 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000678
679
680
681
682def extractArchive(builddir, archiveName):
683 """
684 Extract a source archive into 'builddir'. Returns the path of the
685 extracted archive.
686
687 XXX: This function assumes that archives contain a toplevel directory
688 that is has the same name as the basename of the archive. This is
Ned Deily0203a802013-10-25 00:40:07 -0700689 safe enough for almost anything we use. Unfortunately, it does not
690 work for current Tcl and Tk source releases where the basename of
691 the archive ends with "-src" but the uncompressed directory does not.
692 For now, just special case Tcl and Tk tar.gz downloads.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000693 """
694 curdir = os.getcwd()
695 try:
696 os.chdir(builddir)
697 if archiveName.endswith('.tar.gz'):
698 retval = os.path.basename(archiveName[:-7])
Ned Deily0203a802013-10-25 00:40:07 -0700699 if ((retval.startswith('tcl') or retval.startswith('tk'))
700 and retval.endswith('-src')):
701 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000702 if os.path.exists(retval):
703 shutil.rmtree(retval)
704 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
705
706 elif archiveName.endswith('.tar.bz2'):
707 retval = os.path.basename(archiveName[:-8])
708 if os.path.exists(retval):
709 shutil.rmtree(retval)
710 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
711
712 elif archiveName.endswith('.tar'):
713 retval = os.path.basename(archiveName[:-4])
714 if os.path.exists(retval):
715 shutil.rmtree(retval)
716 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
717
718 elif archiveName.endswith('.zip'):
719 retval = os.path.basename(archiveName[:-4])
720 if os.path.exists(retval):
721 shutil.rmtree(retval)
722 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
723
724 data = fp.read()
725 xit = fp.close()
726 if xit is not None:
727 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800728 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000729
730 return os.path.join(builddir, retval)
731
732 finally:
733 os.chdir(curdir)
734
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000735def downloadURL(url, fname):
736 """
737 Download the contents of the url into the file.
738 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800739 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000740 fpOut = open(fname, 'wb')
741 block = fpIn.read(10240)
742 try:
743 while block:
744 fpOut.write(block)
745 block = fpIn.read(10240)
746 fpIn.close()
747 fpOut.close()
748 except:
749 try:
750 os.unlink(fname)
751 except:
752 pass
753
Ned Deilye1c9794952013-01-29 00:07:46 -0800754def verifyThirdPartyFile(url, checksum, fname):
755 """
756 Download file from url to filename fname if it does not already exist.
757 Abort if file contents does not match supplied md5 checksum.
758 """
759 name = os.path.basename(fname)
760 if os.path.exists(fname):
761 print("Using local copy of %s"%(name,))
762 else:
763 print("Did not find local copy of %s"%(name,))
764 print("Downloading %s"%(name,))
765 downloadURL(url, fname)
766 print("Archive for %s stored as %s"%(name, fname))
767 if os.system(
768 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
769 % (shellQuote(fname), checksum) ):
770 fatal('MD5 checksum mismatch for file %s' % fname)
771
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000772def buildRecipe(recipe, basedir, archList):
773 """
774 Build software using a recipe. This function does the
775 'configure;make;make install' dance for C software, with a possibility
776 to customize this process, basically a poor-mans DarwinPorts.
777 """
778 curdir = os.getcwd()
779
780 name = recipe['name']
781 url = recipe['url']
782 configure = recipe.get('configure', './configure')
783 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
784 shellQuote(basedir)))
785
786 archiveName = os.path.split(url)[-1]
787 sourceArchive = os.path.join(DEPSRC, archiveName)
788
789 if not os.path.exists(DEPSRC):
790 os.mkdir(DEPSRC)
791
Ned Deilye1c9794952013-01-29 00:07:46 -0800792 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
793 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000794 buildDir=os.path.join(WORKDIR, '_bld')
795 if not os.path.exists(buildDir):
796 os.mkdir(buildDir)
797
798 workDir = extractArchive(buildDir, sourceArchive)
799 os.chdir(workDir)
800 if 'buildDir' in recipe:
801 os.chdir(recipe['buildDir'])
802
Ned Deilye1c9794952013-01-29 00:07:46 -0800803 for patch in recipe.get('patches', ()):
804 if isinstance(patch, tuple):
805 url, checksum = patch
806 fn = os.path.join(DEPSRC, os.path.basename(url))
807 verifyThirdPartyFile(url, checksum, fn)
808 else:
809 # patch is a file in the source directory
810 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000811 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
812 shellQuote(fn),))
813
Ned Deilye1c9794952013-01-29 00:07:46 -0800814 for patchscript in recipe.get('patchscripts', ()):
815 if isinstance(patchscript, tuple):
816 url, checksum = patchscript
817 fn = os.path.join(DEPSRC, os.path.basename(url))
818 verifyThirdPartyFile(url, checksum, fn)
819 else:
820 # patch is a file in the source directory
821 fn = os.path.join(curdir, patchscript)
822 if fn.endswith('.bz2'):
823 runCommand('bunzip2 -fk %s' % shellQuote(fn))
824 fn = fn[:-4]
825 runCommand('sh %s' % shellQuote(fn))
826 os.unlink(fn)
827
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000828 if configure is not None:
829 configure_args = [
830 "--prefix=/usr/local",
831 "--enable-static",
832 "--disable-shared",
833 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
834 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000835
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000836 if 'configure_pre' in recipe:
837 args = list(recipe['configure_pre'])
838 if '--disable-static' in args:
839 configure_args.remove('--enable-static')
840 if '--enable-shared' in args:
841 configure_args.remove('--disable-shared')
842 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000843
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000844 if recipe.get('useLDFlags', 1):
845 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800846 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
847 "-I%s/usr/local/include"%(
848 recipe.get('extra_cflags', ''),
849 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000850 ' -arch '.join(archList),
851 shellQuote(SDKPATH)[1:-1],
852 shellQuote(basedir)[1:-1],),
Ned Deilye1c9794952013-01-29 00:07:46 -0800853 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
854 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000855 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000856 shellQuote(basedir)[1:-1],
857 ' -arch '.join(archList)),
858 ])
859 else:
860 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800861 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
862 "-I%s/usr/local/include"%(
863 recipe.get('extra_cflags', ''),
864 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000865 ' -arch '.join(archList),
866 shellQuote(SDKPATH)[1:-1],
867 shellQuote(basedir)[1:-1],),
868 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000869
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000870 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -0800871 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000872
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000873 configure_args.insert(0, configure)
874 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000875
Ned Deilye1c9794952013-01-29 00:07:46 -0800876 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000877 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000878
Ned Deilye1c9794952013-01-29 00:07:46 -0800879 print("Running install for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000880 runCommand('{ ' + install + ' ;} 2>&1')
881
Ned Deilye1c9794952013-01-29 00:07:46 -0800882 print("Done %s"%(name,))
883 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000884
885 os.chdir(curdir)
886
887def buildLibraries():
888 """
889 Build our dependencies into $WORKDIR/libraries/usr/local
890 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800891 print("")
892 print("Building required libraries")
893 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000894 universal = os.path.join(WORKDIR, 'libraries')
895 os.mkdir(universal)
896 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
897 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
898
Ronald Oussoren508282e2009-03-30 19:34:51 +0000899 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000900 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000901
902
903
904def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000905 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000906 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -0800907 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000908 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000909 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000910 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000911 curDir = os.getcwd()
912 os.chdir(buildDir)
913 runCommand('make update')
Martin v. Löwis896c4772010-04-22 11:34:36 +0000914 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000915 os.chdir(curDir)
916 if not os.path.exists(docdir):
917 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000918 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000919
920
921def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -0800922 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000923
924 buildDir = os.path.join(WORKDIR, '_bld', 'python')
925 rootDir = os.path.join(WORKDIR, '_root')
926
927 if os.path.exists(buildDir):
928 shutil.rmtree(buildDir)
929 if os.path.exists(rootDir):
930 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +0000931 os.makedirs(buildDir)
932 os.makedirs(rootDir)
933 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000934 curdir = os.getcwd()
935 os.chdir(buildDir)
936
937 # Not sure if this is still needed, the original build script
938 # claims that parts of the install assume python.exe exists.
939 os.symlink('python', os.path.join(buildDir, 'python.exe'))
940
941 # Extract the version from the configure file, needed to calculate
942 # several paths.
943 version = getVersion()
944
Ronald Oussoren008af852009-03-30 20:02:08 +0000945 # Since the extra libs are not in their installed framework location
946 # during the build, augment the library path so that the interpreter
947 # will find them during its extension import sanity checks.
948 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
949 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -0800950 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +0000951 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
952 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000953 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +0000954 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -0700955 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000956 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
957 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +0000958 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000959 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000960 shellQuote(WORKDIR)[1:-1]))
961
Ned Deilye1c9794952013-01-29 00:07:46 -0800962 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000963 runCommand("make")
964
Ned Deily0203a802013-10-25 00:40:07 -0700965 # For deployment targets of 10.6 and higher, we build our own version
966 # of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is
967 # out-of-date and has critical bugs. Save the _tkinter.so that was
968 # linked with /Library/Frameworks/{Tck,Tk}.framework and build
Ned Deily70f213a2013-10-26 03:16:06 -0700969 # another _tkinter.so linked with our builtin Tcl and Tk libs.
Ned Deily0203a802013-10-25 00:40:07 -0700970 if DEPTARGET > '10.5':
971 runCommand("find build -name '_tkinter.so' "
Ned Deily70f213a2013-10-26 03:16:06 -0700972 " -execdir mv '{}' _tkinter_library.so \;")
973 print("Running make to build builtin _tkinter")
Ned Deily0203a802013-10-25 00:40:07 -0700974 runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' "
975 "TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%(
976 shellQuote(WORKDIR)[1:-1],
977 shellQuote(WORKDIR)[1:-1]))
Ned Deily70f213a2013-10-26 03:16:06 -0700978 # make a copy which will be moved to lib-tkinter later
Ned Deily0203a802013-10-25 00:40:07 -0700979 runCommand("find build -name '_tkinter.so' "
Ned Deily70f213a2013-10-26 03:16:06 -0700980 " -execdir cp -p '{}' _tkinter_builtin.so \;")
Ned Deily0203a802013-10-25 00:40:07 -0700981
Ned Deilye1c9794952013-01-29 00:07:46 -0800982 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +0000983 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000984 shellQuote(rootDir)))
985
Ned Deilye1c9794952013-01-29 00:07:46 -0800986 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000987 runCommand("make frameworkinstallextras DESTDIR=%s"%(
988 shellQuote(rootDir)))
989
Ronald Oussoren008af852009-03-30 20:02:08 +0000990 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -0800991 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000992 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
993 runCommand("mv %s/* %s"%(
994 shellQuote(os.path.join(
995 WORKDIR, 'libraries', 'Library', 'Frameworks',
996 'Python.framework', 'Versions', getVersion(),
997 'lib')),
998 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
999 'Python.framework', 'Versions', getVersion(),
1000 'lib'))))
1001
Ned Deily70f213a2013-10-26 03:16:06 -07001002 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1003 'Python.framework', 'Versions',
1004 version, 'lib', 'python%s'%(version,))
1005
1006 # If we made multiple versions of _tkinter, move them to
1007 # their own directories under python lib. This allows
1008 # users to select which to import by manipulating sys.path
1009 # directly or with PYTHONPATH.
1010
1011 if DEPTARGET > '10.5':
1012 TKINTERS = ['builtin', 'library']
1013 tkinter_moves = [('_tkinter_' + tkn + '.so',
1014 os.path.join(path_to_lib, 'lib-tkinter', tkn))
1015 for tkn in TKINTERS]
1016 # Create the destination directories under lib-tkinter.
1017 # The permissions and uid/gid will be fixed up next.
1018 for tkm in tkinter_moves:
1019 os.makedirs(tkm[1])
1020
Ned Deilye1c9794952013-01-29 00:07:46 -08001021 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001022 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001023 gid = grp.getgrnam('admin').gr_gid
1024
Ned Deilye1c9794952013-01-29 00:07:46 -08001025 shared_lib_error = False
Ned Deily70f213a2013-10-26 03:16:06 -07001026 moves_list = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001027 for dirpath, dirnames, filenames in os.walk(frmDir):
1028 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001029 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001030 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001031
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001032 for fn in filenames:
1033 if os.path.islink(fn):
1034 continue
1035
1036 # "chmod g+w $fn"
1037 p = os.path.join(dirpath, fn)
1038 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001039 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1040 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001041
Ned Deilye1c9794952013-01-29 00:07:46 -08001042 if fn in EXPECTED_SHARED_LIBS:
1043 # check to see that this file was linked with the
1044 # expected library path and version
1045 data = captureCommand("otool -L %s" % shellQuote(p))
1046 for sl in EXPECTED_SHARED_LIBS[fn]:
1047 if ("\t%s " % sl) not in data:
1048 print("Expected shared lib %s was not linked with %s"
1049 % (sl, p))
1050 shared_lib_error = True
1051
Ned Deily70f213a2013-10-26 03:16:06 -07001052 # If this is a _tkinter variant, move it to its own directory
1053 # now that we have fixed its permissions and checked that it
1054 # was linked properly. The directory was created earlier.
1055 # The files are moved after the entire tree has been walked
1056 # since the shared library checking depends on the files
1057 # having unique names.
1058 if DEPTARGET > '10.5':
1059 for tkm in tkinter_moves:
1060 if fn == tkm[0]:
1061 moves_list.append(
1062 (p, os.path.join(tkm[1], '_tkinter.so')))
1063
Ned Deilye1c9794952013-01-29 00:07:46 -08001064 if shared_lib_error:
1065 fatal("Unexpected shared library errors.")
1066
Ned Deily70f213a2013-10-26 03:16:06 -07001067 # Now do the moves.
1068 for ml in moves_list:
1069 shutil.move(ml[0], ml[1])
1070
Ned Deily53c460d2011-01-30 01:43:40 +00001071 if PYTHON_3:
1072 LDVERSION=None
1073 VERSION=None
1074 ABIFLAGS=None
1075
1076 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1077 for ln in fp:
1078 if ln.startswith('VERSION='):
1079 VERSION=ln.split()[1]
1080 if ln.startswith('ABIFLAGS='):
1081 ABIFLAGS=ln.split()[1]
1082 if ln.startswith('LDVERSION='):
1083 LDVERSION=ln.split()[1]
1084 fp.close()
1085
1086 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1087 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1088 config_suffix = '-' + LDVERSION
1089 else:
1090 config_suffix = '' # Python 2.x
1091
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001092 # We added some directories to the search path during the configure
1093 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001094 # the end-users system. Also remove the directories from _sysconfigdata.py
1095 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001096
Ned Deilye6ef7022013-10-25 00:46:59 -07001097 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1098 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1099
Ned Deilye6ef7022013-10-25 00:46:59 -07001100 # fix Makefile
1101 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1102 fp = open(path, 'r')
1103 data = fp.read()
1104 fp.close()
1105
1106 for p in (include_path, lib_path):
1107 data = data.replace(" " + p, '')
1108 data = data.replace(p + " ", '')
1109
1110 fp = open(path, 'w')
1111 fp.write(data)
1112 fp.close()
1113
1114 # fix _sysconfigdata if it exists
1115 #
1116 # TODO: make this more robust! test_sysconfig_module of
1117 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1118 # the output from get_config_var in both sysconfig and
1119 # distutils.sysconfig is exactly the same for both CFLAGS and
1120 # LDFLAGS. The fixing up is now complicated by the pretty
1121 # printing in _sysconfigdata.py. Also, we are using the
1122 # pprint from the Python running the installer build which
1123 # may not cosmetically format the same as the pprint in the Python
1124 # being built (and which is used to originally generate
1125 # _sysconfigdata.py).
1126
1127 import pprint
1128 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1129 if os.path.exists(path):
Ned Deilye1c9794952013-01-29 00:07:46 -08001130 fp = open(path, 'r')
1131 data = fp.read()
1132 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -07001133 # create build_time_vars dict
1134 exec(data)
1135 vars = {}
1136 for k, v in build_time_vars.items():
1137 if type(v) == type(''):
1138 for p in (include_path, lib_path):
1139 v = v.replace(' ' + p, '')
1140 v = v.replace(p + ' ', '')
1141 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001142
Ned Deilye1c9794952013-01-29 00:07:46 -08001143 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -07001144 # duplicated from sysconfig._generate_posix_vars()
1145 fp.write('# system configuration generated and used by'
1146 ' the sysconfig module\n')
1147 fp.write('build_time_vars = ')
1148 pprint.pprint(vars, stream=fp)
Ned Deilye1c9794952013-01-29 00:07:46 -08001149 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001150
1151 # Add symlinks in /usr/local/bin, using relative links
1152 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1153 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1154 'Python.framework', 'Versions', version, 'bin')
1155 if os.path.exists(usr_local_bin):
1156 shutil.rmtree(usr_local_bin)
1157 os.makedirs(usr_local_bin)
1158 for fn in os.listdir(
1159 os.path.join(frmDir, 'Versions', version, 'bin')):
1160 os.symlink(os.path.join(to_framework, fn),
1161 os.path.join(usr_local_bin, fn))
1162
1163 os.chdir(curdir)
1164
Ned Deily53c460d2011-01-30 01:43:40 +00001165 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001166 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001167 # with an already installed version of python 2
1168 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1169 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001170
1171def patchFile(inPath, outPath):
1172 data = fileContents(inPath)
1173 data = data.replace('$FULL_VERSION', getFullVersion())
1174 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001175 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001176 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001177 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +00001178
1179 # This one is not handy as a template variable
1180 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001181 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001182 fp.write(data)
1183 fp.close()
1184
1185def patchScript(inPath, outPath):
1186 data = fileContents(inPath)
1187 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001188 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001189 fp.write(data)
1190 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001191 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001192
1193
1194
1195def packageFromRecipe(targetDir, recipe):
1196 curdir = os.getcwd()
1197 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001198 # The major version (such as 2.5) is included in the package name
1199 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001200 # common.
1201 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001202 srcdir = recipe.get('source')
1203 pkgroot = recipe.get('topdir', srcdir)
1204 postflight = recipe.get('postflight')
1205 readme = textwrap.dedent(recipe['readme'])
1206 isRequired = recipe.get('required', True)
1207
Ned Deilye1c9794952013-01-29 00:07:46 -08001208 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001209
1210 # Substitute some variables
1211 textvars = dict(
1212 VER=getVersion(),
1213 FULLVER=getFullVersion(),
1214 )
1215 readme = readme % textvars
1216
1217 if pkgroot is not None:
1218 pkgroot = pkgroot % textvars
1219 else:
1220 pkgroot = '/'
1221
1222 if srcdir is not None:
1223 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1224 srcdir = srcdir % textvars
1225
1226 if postflight is not None:
1227 postflight = os.path.abspath(postflight)
1228
1229 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1230 os.makedirs(packageContents)
1231
1232 if srcdir is not None:
1233 os.chdir(srcdir)
1234 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1235 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1236 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1237
1238 fn = os.path.join(packageContents, 'PkgInfo')
1239 fp = open(fn, 'w')
1240 fp.write('pmkrpkg1')
1241 fp.close()
1242
1243 rsrcDir = os.path.join(packageContents, "Resources")
1244 os.mkdir(rsrcDir)
1245 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1246 fp.write(readme)
1247 fp.close()
1248
1249 if postflight is not None:
1250 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1251
1252 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001253 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001254 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001255 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1256 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1257 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001258 CFBundleShortVersionString=vers,
1259 IFMajorVersion=major,
1260 IFMinorVersion=minor,
1261 IFPkgFormatVersion=0.10000000149011612,
1262 IFPkgFlagAllowBackRev=False,
1263 IFPkgFlagAuthorizationAction="RootAuthorization",
1264 IFPkgFlagDefaultLocation=pkgroot,
1265 IFPkgFlagFollowLinks=True,
1266 IFPkgFlagInstallFat=True,
1267 IFPkgFlagIsRequired=isRequired,
1268 IFPkgFlagOverwritePermissions=False,
1269 IFPkgFlagRelocatable=False,
1270 IFPkgFlagRestartAction="NoRestart",
1271 IFPkgFlagRootVolumeOnly=True,
1272 IFPkgFlagUpdateInstalledLangauges=False,
1273 )
1274 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1275
1276 pl = Plist(
1277 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001278 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001279 IFPkgDescriptionVersion=vers,
1280 )
1281 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1282
1283 finally:
1284 os.chdir(curdir)
1285
1286
1287def makeMpkgPlist(path):
1288
1289 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001290 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001291
1292 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001293 CFBundleGetInfoString="Python %s"%(vers,),
1294 CFBundleIdentifier='org.python.Python',
1295 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001296 CFBundleShortVersionString=vers,
1297 IFMajorVersion=major,
1298 IFMinorVersion=minor,
1299 IFPkgFlagComponentDirectory="Contents/Packages",
1300 IFPkgFlagPackageList=[
1301 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001302 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001303 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001304 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001305 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001306 ],
1307 IFPkgFormatVersion=0.10000000149011612,
1308 IFPkgFlagBackgroundScaling="proportional",
1309 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001310 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001311 )
1312
1313 writePlist(pl, path)
1314
1315
1316def buildInstaller():
1317
1318 # Zap all compiled files
1319 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1320 for fn in filenames:
1321 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1322 os.unlink(os.path.join(dirpath, fn))
1323
1324 outdir = os.path.join(WORKDIR, 'installer')
1325 if os.path.exists(outdir):
1326 shutil.rmtree(outdir)
1327 os.mkdir(outdir)
1328
Ronald Oussoren508282e2009-03-30 19:34:51 +00001329 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001330 pkgcontents = os.path.join(pkgroot, 'Packages')
1331 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001332 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001333 packageFromRecipe(pkgcontents, recipe)
1334
1335 rsrcDir = os.path.join(pkgroot, 'Resources')
1336
1337 fn = os.path.join(pkgroot, 'PkgInfo')
1338 fp = open(fn, 'w')
1339 fp.write('pmkrpkg1')
1340 fp.close()
1341
1342 os.mkdir(rsrcDir)
1343
1344 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1345 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001346 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001347 IFPkgDescriptionVersion=getVersion(),
1348 )
1349
1350 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1351 for fn in os.listdir('resources'):
1352 if fn == '.svn': continue
1353 if fn.endswith('.jpg'):
1354 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1355 else:
1356 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1357
Ronald Oussorenc5555542006-06-11 20:24:45 +00001358 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001359
1360
1361def installSize(clear=False, _saved=[]):
1362 if clear:
1363 del _saved[:]
1364 if not _saved:
1365 data = captureCommand("du -ks %s"%(
1366 shellQuote(os.path.join(WORKDIR, '_root'))))
1367 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1368 return _saved[0]
1369
1370
1371def buildDMG():
1372 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001373 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001374 """
1375 outdir = os.path.join(WORKDIR, 'diskimage')
1376 if os.path.exists(outdir):
1377 shutil.rmtree(outdir)
1378
1379 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001380 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001381 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001382 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001383 imagepath = imagepath + '.dmg'
1384
1385 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001386 volname='Python %s'%(getFullVersion())
1387 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1388 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001389 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001390 shellQuote(imagepath + ".tmp.dmg" )))
1391
1392
1393 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1394 os.mkdir(os.path.join(WORKDIR, "mnt"))
1395 runCommand("hdiutil attach %s -mountroot %s"%(
1396 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1397
1398 # Custom icon for the DMG, shown when the DMG is mounted.
1399 shutil.copy("../Icons/Disk Image.icns",
1400 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001401 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001402 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1403
1404 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1405
1406 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1407 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1408 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1409 setIcon(imagepath, "../Icons/Disk Image.icns")
1410
1411 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001412
1413 return imagepath
1414
1415
1416def setIcon(filePath, icnsPath):
1417 """
1418 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001419 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001420
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001421 dirPath = os.path.normpath(os.path.dirname(__file__))
1422 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001423 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1424 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1425 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001426 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1427 if not os.path.exists(appPath):
1428 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001429 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1430 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001431
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001432 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1433 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001434
1435def main():
1436 # First parse options and check if we can perform our work
1437 parseOptions()
1438 checkEnvironment()
1439
Ronald Oussoren508282e2009-03-30 19:34:51 +00001440 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001441 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001442 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001443
1444 if os.path.exists(WORKDIR):
1445 shutil.rmtree(WORKDIR)
1446 os.mkdir(WORKDIR)
1447
Ronald Oussoren287128a2010-04-18 14:01:05 +00001448 os.environ['LC_ALL'] = 'C'
1449
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001450 # Then build third-party libraries such as sleepycat DB4.
1451 buildLibraries()
1452
1453 # Now build python itself
1454 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001455
1456 # And then build the documentation
1457 # Remove the Deployment Target from the shell
1458 # environment, it's no longer needed and
1459 # an unexpected build target can cause problems
1460 # when Sphinx and its dependencies need to
1461 # be (re-)installed.
1462 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001463 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001464
1465
1466 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001467 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001468 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001469 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001470
Benjamin Petersonc3104762008-10-03 11:52:06 +00001471 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001472 getVersion(),))
Ned Deilye1c9794952013-01-29 00:07:46 -08001473 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001474 setIcon(folder, "../Icons/Python Folder.icns")
1475
1476 # Create the installer
1477 buildInstaller()
1478
1479 # And copy the readme into the directory containing the installer
1480 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1481
1482 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001483 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001484
1485 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001486 fp.write("# BUILD INFO\n")
1487 fp.write("# Date: %s\n" % time.ctime())
1488 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001489 fp.close()
1490
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001491 # And copy it to a DMG
1492 buildDMG()
1493
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001494if __name__ == "__main__":
1495 main()