blob: 199b560b6544db006f3bf7d99e65cd22cbd6f9c4 [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deily4a96a372013-01-29 00:08:32 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
Ned Deily9978a932014-04-09 16:15:20 -070011Python 2.4. However, as of Python 3.4.1, Doc builds require an external
12sphinx-build and the current versions of Sphinx now require at least
13Python 2.6.
Thomas Wouters477c8d52006-05-27 19:21:47 +000014
Ned Deily4a96a372013-01-29 00:08:32 -080015In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
16requires an installed version of hg and a third-party version of
17Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
18(for 10.6 or later) installed in /Library/Frameworks. When installed,
19the Python built by this script will attempt to dynamically link first to
20Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
21back to the ones in /System/Library/Framework. For the build, we recommend
22installing the most recent ActiveTcl 8.4 or 8.5 version.
23
2432-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
25and the installation of additional components, such as a newer Python
Ned Deily9978a932014-04-09 16:15:20 -070026(2.5 is needed for Python parser updates), hg, and for the documentation
27build either svn (pre-3.4.1) or sphinx-build (3.4.1 and later).
Ned Deily4a96a372013-01-29 00:08:32 -080028
Thomas Wouters477c8d52006-05-27 19:21:47 +000029Usage: see USAGE variable in the script.
30"""
Ned Deily4a96a372013-01-29 00:08:32 -080031import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
32try:
33 import urllib2 as urllib_request
34except ImportError:
35 import urllib.request as urllib_request
36
37STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38 | stat.S_IRGRP | stat.S_IXGRP
39 | stat.S_IROTH | stat.S_IXOTH )
40
41STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
42 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
43 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
Thomas Wouters89f507f2006-12-13 04:49:30 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000047
48from plistlib import Plist
49
Thomas Wouters477c8d52006-05-27 19:21:47 +000050try:
51 from plistlib import writePlist
52except ImportError:
53 # We're run using python2.3
54 def writePlist(plist, path):
55 plist.write(path)
56
57def shellQuote(value):
58 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000059 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000060 a shell command.
61 """
62 return "'%s'"%(value.replace("'", "'\"'\"'"))
63
64def grepValue(fn, variable):
65 variable = variable + '='
66 for ln in open(fn, 'r'):
67 if ln.startswith(variable):
68 value = ln[len(variable):].strip()
69 return value[1:-1]
Ned Deily4a96a372013-01-29 00:08:32 -080070 raise RuntimeError("Cannot find variable %s" % variable[:-1])
71
72_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000073
74def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080075 global _cache_getVersion
76 if _cache_getVersion is None:
77 _cache_getVersion = grepValue(
78 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
79 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000080
Benjamin Petersond9b7d482010-03-19 21:42:45 +000081def getVersionTuple():
82 return tuple([int(n) for n in getVersion().split('.')])
83
Ned Deily4a96a372013-01-29 00:08:32 -080084def getVersionMajorMinor():
85 return tuple([int(n) for n in getVersion().split('.', 2)])
86
87_cache_getFullVersion = None
88
Thomas Wouters477c8d52006-05-27 19:21:47 +000089def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080090 global _cache_getFullVersion
91 if _cache_getFullVersion is not None:
92 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000093 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
94 for ln in open(fn):
95 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080096 _cache_getFullVersion = ln.split()[-1][1:-1]
97 return _cache_getFullVersion
98 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000099
Thomas Wouters89f507f2006-12-13 04:49:30 +0000100# The directory we'll use to create the build (will be erased and recreated)
101WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000104# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000105DEPSRC = os.path.join(WORKDIR, 'third-party')
106DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000107
108# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000109
110### There are some issues with the SDK selection below here,
111### The resulting binary doesn't work on all platforms that
112### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300113### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000114###
115##if int(os.uname()[2].split('.')[0]) == 8:
116## # Explicitly use the 10.4u (universal) SDK when
117## # building on 10.4, the system headers are not
118## # useable for a universal build
119## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
120##else:
121## SDKPATH = "/"
122
Thomas Wouters89f507f2006-12-13 04:49:30 +0000123SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000124
Ronald Oussoren1943f862009-03-30 19:39:14 +0000125universal_opts_map = { '32-bit': ('i386', 'ppc',),
126 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000127 'intel': ('i386', 'x86_64'),
128 '3-way': ('ppc', 'i386', 'x86_64'),
129 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
130default_target_map = {
131 '64-bit': '10.5',
132 '3-way': '10.5',
133 'intel': '10.5',
134 'all': '10.5',
135}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000136
137UNIVERSALOPTS = tuple(universal_opts_map.keys())
138
139UNIVERSALARCHS = '32-bit'
140
141ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142
Ezio Melotti42da6632011-03-15 05:18:48 +0200143# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000144SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000145 os.path.dirname(
146 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000147 os.path.abspath(__file__
148 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000149
Ronald Oussoren1943f862009-03-30 19:39:14 +0000150# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
151DEPTARGET = '10.3'
152
Ned Deily04cdfa12014-06-25 13:36:14 -0700153def getDeptargetTuple():
154 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
155
156def getTargetCompilers():
157 target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800158 '10.3': ('gcc-4.0', 'g++-4.0'),
159 '10.4': ('gcc-4.0', 'g++-4.0'),
160 '10.5': ('gcc-4.2', 'g++-4.2'),
161 '10.6': ('gcc-4.2', 'g++-4.2'),
Ned Deily04cdfa12014-06-25 13:36:14 -0700162 }
163 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000164
Ned Deily04cdfa12014-06-25 13:36:14 -0700165CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000166
167PYTHON_3 = getVersionTuple() >= (3, 0)
168
Thomas Wouters89f507f2006-12-13 04:49:30 +0000169USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000170 Usage: build_python [options]
171
172 Options:
173 -? or -h: Show this message
174 -b DIR
175 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
176 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
177 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
178 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000179 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
180 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000181""")% globals()
182
Ned Deily4a96a372013-01-29 00:08:32 -0800183# Dict of object file names with shared library names to check after building.
184# This is to ensure that we ended up dynamically linking with the shared
185# library paths and versions we expected. For example:
186# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
187# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
188# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
189EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000190
191# Instructions for building libraries that are necessary for building a
192# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000193# [The recipes are defined here for convenience but instantiated later after
194# command line options have been processed.]
195def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000196 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000197
Ned Deily04cdfa12014-06-25 13:36:14 -0700198 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deily4a96a372013-01-29 00:08:32 -0800199
Ned Deilyaa6a2122013-11-23 03:30:11 -0800200# Disable for now
Ned Deily04cdfa12014-06-25 13:36:14 -0700201 if False: # if (getDeptargetTuple() > (10, 5)) and (getVersionTuple() >= (3, 5)):
Ned Deily5b3582c2013-10-25 00:41:46 -0700202 result.extend([
203 dict(
204 name="Tcl 8.5.15",
205 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
206 checksum='f3df162f92c69b254079c4d0af7a690f',
207 buildDir="unix",
208 configure_pre=[
209 '--enable-shared',
210 '--enable-threads',
211 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
212 ],
213 useLDFlags=False,
214 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
215 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
216 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
217 },
218 ),
219 dict(
220 name="Tk 8.5.15",
221 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
222 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deily94764b22013-10-27 19:49:29 -0700223 patches=[
224 "issue19373_tk_8_5_15_source.patch",
225 ],
Ned Deily5b3582c2013-10-25 00:41:46 -0700226 buildDir="unix",
227 configure_pre=[
228 '--enable-aqua',
229 '--enable-shared',
230 '--enable-threads',
231 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
232 ],
233 useLDFlags=False,
234 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'%{
235 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
236 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
237 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
238 },
239 ),
240 ])
241
Ned Deily4a96a372013-01-29 00:08:32 -0800242 if getVersionTuple() >= (3, 3):
243 result.extend([
244 dict(
Ned Deily9fa4ced2013-11-22 22:54:02 -0800245 name="XZ 5.0.5",
246 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
247 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deily4a96a372013-01-29 00:08:32 -0800248 configure_pre=[
249 '--disable-dependency-tracking',
250 ]
251 ),
252 ])
253
254 result.extend([
255 dict(
256 name="NCurses 5.9",
257 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
258 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
259 configure_pre=[
260 "--enable-widec",
261 "--without-cxx",
262 "--without-cxx-binding",
263 "--without-ada",
264 "--without-curses-h",
265 "--enable-shared",
266 "--with-shared",
267 "--without-debug",
268 "--without-normal",
269 "--without-tests",
270 "--without-manpages",
271 "--datadir=/usr/share",
272 "--sysconfdir=/etc",
273 "--sharedstatedir=/usr/com",
274 "--with-terminfo-dirs=/usr/share/terminfo",
275 "--with-default-terminfo-dir=/usr/share/terminfo",
276 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
277 ],
278 patchscripts=[
279 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
280 "f54bf02a349f96a7c4f0d00922f3a0d4"),
281 ],
282 useLDFlags=False,
283 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
284 shellQuote(os.path.join(WORKDIR, 'libraries')),
285 shellQuote(os.path.join(WORKDIR, 'libraries')),
286 getVersion(),
287 ),
288 ),
289 dict(
Ned Deilyc1195c72014-03-01 14:00:46 -0800290 name="SQLite 3.8.3.1",
291 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
292 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deily4a96a372013-01-29 00:08:32 -0800293 extra_cflags=('-Os '
294 '-DSQLITE_ENABLE_FTS4 '
295 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
296 '-DSQLITE_ENABLE_RTREE '
297 '-DSQLITE_TCL=0 '
298 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
299 configure_pre=[
300 '--enable-threadsafe',
301 '--enable-shared=no',
302 '--enable-static=yes',
303 '--disable-readline',
304 '--disable-dependency-tracking',
305 ]
306 ),
307 ])
308
Ned Deily04cdfa12014-06-25 13:36:14 -0700309 if getDeptargetTuple() < (10, 5):
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000310 result.extend([
311 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000312 name="Bzip2 1.0.6",
313 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
314 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000315 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800316 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
317 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000318 shellQuote(os.path.join(WORKDIR, 'libraries')),
319 ' -arch '.join(ARCHLIST),
320 SDKPATH,
321 ),
322 ),
323 dict(
324 name="ZLib 1.2.3",
325 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
326 checksum='debc62758716a169df9f62e6ab2bc634',
327 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800328 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
329 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000330 shellQuote(os.path.join(WORKDIR, 'libraries')),
331 ' -arch '.join(ARCHLIST),
332 SDKPATH,
333 ),
334 ),
335 dict(
336 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000337 name="GNU Readline 6.1.2",
338 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
339 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000340 patchlevel='0',
341 patches=[
342 # The readline maintainers don't do actual micro releases, but
343 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800344 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
345 'c642f2e84d820884b0bf9fd176bc6c3f'),
346 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
347 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000348 ]
349 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000350 ])
351
Ned Deily4f7ff782011-01-15 05:29:12 +0000352 if not PYTHON_3:
353 result.extend([
354 dict(
355 name="Sleepycat DB 4.7.25",
356 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
357 checksum='ec2b87e833779681a0c3a814aa71359e',
358 buildDir="build_unix",
359 configure="../dist/configure",
360 configure_pre=[
361 '--includedir=/usr/local/include/db4',
362 ]
363 ),
364 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000365
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000366 return result
367
Thomas Wouters477c8d52006-05-27 19:21:47 +0000368
Thomas Wouters477c8d52006-05-27 19:21:47 +0000369# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000370def pkg_recipes():
371 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deily9978a932014-04-09 16:15:20 -0700372 # unselected if 3.0 through 3.3, selected otherwise (2.x or >= 3.4)
373 unselected_for_lt_python34 = ('selected', 'unselected')[(3, 0) <= getVersionTuple() < (3, 4)]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000374 result = [
375 dict(
376 name="PythonFramework",
377 long_name="Python Framework",
378 source="/Library/Frameworks/Python.framework",
379 readme="""\
380 This package installs Python.framework, that is the python
381 interpreter and the standard library. This also includes Python
382 wrappers for lots of Mac OS X API's.
383 """,
384 postflight="scripts/postflight.framework",
385 selected='selected',
386 ),
387 dict(
388 name="PythonApplications",
389 long_name="GUI Applications",
390 source="/Applications/Python %(VER)s",
391 readme="""\
392 This package installs IDLE (an interactive Python IDE),
393 Python Launcher and Build Applet (create application bundles
394 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000395
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000396 It also installs a number of examples and demos.
397 """,
398 required=False,
399 selected='selected',
400 ),
401 dict(
402 name="PythonUnixTools",
403 long_name="UNIX command-line tools",
404 source="/usr/local/bin",
405 readme="""\
406 This package installs the unix tools in /usr/local/bin for
407 compatibility with older releases of Python. This package
408 is not necessary to use Python.
409 """,
410 required=False,
411 selected='selected',
412 ),
413 dict(
414 name="PythonDocumentation",
415 long_name="Python Documentation",
416 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
417 source="/pydocs",
418 readme="""\
419 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800420 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000421 """,
422 postflight="scripts/postflight.documentation",
423 required=False,
424 selected='selected',
425 ),
426 dict(
427 name="PythonProfileChanges",
428 long_name="Shell profile updater",
429 readme="""\
430 This packages updates your shell profile to make sure that
431 the Python tools are found by your shell in preference of
432 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000433
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000434 If you don't install this package you'll have to add
435 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
436 to your PATH by hand.
437 """,
438 postflight="scripts/postflight.patch-profile",
439 topdir="/Library/Frameworks/Python.framework",
440 source="/empty-dir",
441 required=False,
Ned Deily41ab6c32013-11-22 22:25:43 -0800442 selected=unselected_for_lt_python34,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000443 ),
444 ]
445
Ned Deily41ab6c32013-11-22 22:25:43 -0800446 if getVersionTuple() >= (3, 4):
447 result.append(
448 dict(
449 name="PythonInstallPip",
450 long_name="Install or upgrade pip",
451 readme="""\
452 This package installs (or upgrades from an earlier version)
453 pip, a tool for installing and managing Python packages.
454 """,
455 postflight="scripts/postflight.ensurepip",
456 topdir="/Library/Frameworks/Python.framework",
457 source="/empty-dir",
458 required=False,
459 selected='selected',
460 )
461 )
462
Ned Deily04cdfa12014-06-25 13:36:14 -0700463 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000464 result.append(
465 dict(
466 name="PythonSystemFixes",
467 long_name="Fix system Python",
468 readme="""\
469 This package updates the system python installation on
470 Mac OS X 10.3 to ensure that you can build new python extensions
471 using that copy of python after installing this version.
472 """,
473 postflight="../Tools/fixapplepython23.py",
474 topdir="/Library/Frameworks/Python.framework",
475 source="/empty-dir",
476 required=False,
477 selected=unselected_for_python3,
478 )
479 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800480
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000481 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482
Thomas Wouters477c8d52006-05-27 19:21:47 +0000483def fatal(msg):
484 """
485 A fatal error, bail out.
486 """
487 sys.stderr.write('FATAL: ')
488 sys.stderr.write(msg)
489 sys.stderr.write('\n')
490 sys.exit(1)
491
492def fileContents(fn):
493 """
494 Return the contents of the named file
495 """
Ned Deily4a96a372013-01-29 00:08:32 -0800496 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000497
498def runCommand(commandline):
499 """
Ezio Melotti13925002011-03-16 11:05:33 +0200500 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000501 unless the command fails.
502 """
503 fd = os.popen(commandline, 'r')
504 data = fd.read()
505 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000506 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000507 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800508 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000509
510 if VERBOSE:
511 sys.stdout.write(data); sys.stdout.flush()
512
513def captureCommand(commandline):
514 fd = os.popen(commandline, 'r')
515 data = fd.read()
516 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000517 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000518 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800519 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000520
521 return data
522
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000523def getTclTkVersion(configfile, versionline):
524 """
525 search Tcl or Tk configuration file for version line
526 """
527 try:
528 f = open(configfile, "r")
529 except:
530 fatal("Framework configuration file not found: %s" % configfile)
531
532 for l in f:
533 if l.startswith(versionline):
534 f.close()
535 return l
536
537 fatal("Version variable %s not found in framework configuration file: %s"
538 % (versionline, configfile))
539
Thomas Wouters477c8d52006-05-27 19:21:47 +0000540def checkEnvironment():
541 """
542 Check that we're running on a supported system.
543 """
544
Ned Deilye59e4c52011-01-29 18:56:28 +0000545 if sys.version_info[0:2] < (2, 4):
546 fatal("This script must be run with Python 2.4 or later")
547
Thomas Wouters477c8d52006-05-27 19:21:47 +0000548 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000549 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000550
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000551 if int(platform.release().split('.')[0]) < 8:
552 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000553
554 if not os.path.exists(SDKPATH):
555 fatal("Please install the latest version of Xcode and the %s SDK"%(
556 os.path.basename(SDKPATH[:-4])))
557
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000558 # Because we only support dynamic load of only one major/minor version of
559 # Tcl/Tk, ensure:
560 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800561 # higher than the Apple-supplied system version in
562 # SDKROOT/System/Library/Frameworks
563 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
564 # in) SDKROOT/Library/Frameworks with the same version as the system
565 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000566
Ned Deily4a96a372013-01-29 00:08:32 -0800567 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000568 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800569 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000570 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800571 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000572 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800573 frameworks[framework] = os.readlink(sysfw)
574 if not os.path.exists(libfw):
575 fatal("Please install a link to a current %s %s as %s so "
576 "the user can override the system framework."
577 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000578 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000579 fatal("Version of %s must match %s" % (libfw, sysfw) )
580 if os.path.exists(usrfw):
581 fatal("Please rename %s to avoid possible dynamic load issues."
582 % usrfw)
583
Ned Deily4a96a372013-01-29 00:08:32 -0800584 if frameworks['Tcl'] != frameworks['Tk']:
585 fatal("The Tcl and Tk frameworks are not the same version.")
586
587 # add files to check after build
588 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
589 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
590 % frameworks['Tcl'],
591 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
592 % frameworks['Tk'],
593 ]
594
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000595 # Remove inherited environment variables which might influence build
596 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
597 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
598 for ev in list(os.environ):
599 for prefix in environ_var_prefixes:
600 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800601 print("INFO: deleting environment variable %s=%s" % (
602 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000603 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000604
Ned Deily4a96a372013-01-29 00:08:32 -0800605 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
606 if 'SDK_TOOLS_BIN' in os.environ:
607 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
608 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
609 # add its fixed location here if it exists
610 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
611 if os.path.isdir(OLD_DEVELOPER_TOOLS):
612 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
613 os.environ['PATH'] = base_path
614 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deily7e60f512014-04-07 12:10:21 -0700615 # Ensure ws have access to hg and to sphinx-build.
616 # You may have to create links in /usr/bin for them.
617 runCommand('hg --version')
Ned Deily9978a932014-04-09 16:15:20 -0700618 if getVersionTuple() >= (3, 4):
619 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000620
Thomas Wouters89f507f2006-12-13 04:49:30 +0000621def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000622 """
623 Parse arguments and update global settings.
624 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000625 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800626 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000627
628 if args is None:
629 args = sys.argv[1:]
630
631 try:
632 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000633 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
634 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800635 except getopt.GetoptError:
636 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637 sys.exit(1)
638
639 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800640 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000641 sys.exit(1)
642
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000643 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000644 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000645 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800646 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000647 sys.exit(0)
648
649 elif k in ('-d', '--build-dir'):
650 WORKDIR=v
651
652 elif k in ('--third-party',):
653 DEPSRC=v
654
655 elif k in ('--sdk-path',):
656 SDKPATH=v
657
658 elif k in ('--src-dir',):
659 SRCDIR=v
660
Ronald Oussoren1943f862009-03-30 19:39:14 +0000661 elif k in ('--dep-target', ):
662 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000663 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000664
665 elif k in ('--universal-archs', ):
666 if v in UNIVERSALOPTS:
667 UNIVERSALARCHS = v
668 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000669 if deptarget is None:
670 # Select alternate default deployment
671 # target
672 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000673 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800674 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000675
Thomas Wouters477c8d52006-05-27 19:21:47 +0000676 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800677 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000678
679 SRCDIR=os.path.abspath(SRCDIR)
680 WORKDIR=os.path.abspath(WORKDIR)
681 SDKPATH=os.path.abspath(SDKPATH)
682 DEPSRC=os.path.abspath(DEPSRC)
683
Ned Deily04cdfa12014-06-25 13:36:14 -0700684 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000685
Ned Deily4a96a372013-01-29 00:08:32 -0800686 print("Settings:")
687 print(" * Source directory:", SRCDIR)
688 print(" * Build directory: ", WORKDIR)
689 print(" * SDK location: ", SDKPATH)
690 print(" * Third-party source:", DEPSRC)
691 print(" * Deployment target:", DEPTARGET)
692 print(" * Universal architectures:", ARCHLIST)
693 print(" * C compiler:", CC)
694 print(" * C++ compiler:", CXX)
695 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000696
697
698
699
700def extractArchive(builddir, archiveName):
701 """
702 Extract a source archive into 'builddir'. Returns the path of the
703 extracted archive.
704
705 XXX: This function assumes that archives contain a toplevel directory
706 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700707 safe enough for almost anything we use. Unfortunately, it does not
708 work for current Tcl and Tk source releases where the basename of
709 the archive ends with "-src" but the uncompressed directory does not.
710 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000711 """
712 curdir = os.getcwd()
713 try:
714 os.chdir(builddir)
715 if archiveName.endswith('.tar.gz'):
716 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700717 if ((retval.startswith('tcl') or retval.startswith('tk'))
718 and retval.endswith('-src')):
719 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000720 if os.path.exists(retval):
721 shutil.rmtree(retval)
722 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
723
724 elif archiveName.endswith('.tar.bz2'):
725 retval = os.path.basename(archiveName[:-8])
726 if os.path.exists(retval):
727 shutil.rmtree(retval)
728 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
729
730 elif archiveName.endswith('.tar'):
731 retval = os.path.basename(archiveName[:-4])
732 if os.path.exists(retval):
733 shutil.rmtree(retval)
734 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
735
736 elif archiveName.endswith('.zip'):
737 retval = os.path.basename(archiveName[:-4])
738 if os.path.exists(retval):
739 shutil.rmtree(retval)
740 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
741
742 data = fp.read()
743 xit = fp.close()
744 if xit is not None:
745 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800746 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000747
748 return os.path.join(builddir, retval)
749
750 finally:
751 os.chdir(curdir)
752
Thomas Wouters477c8d52006-05-27 19:21:47 +0000753def downloadURL(url, fname):
754 """
755 Download the contents of the url into the file.
756 """
Ned Deily4a96a372013-01-29 00:08:32 -0800757 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000758 fpOut = open(fname, 'wb')
759 block = fpIn.read(10240)
760 try:
761 while block:
762 fpOut.write(block)
763 block = fpIn.read(10240)
764 fpIn.close()
765 fpOut.close()
766 except:
767 try:
768 os.unlink(fname)
769 except:
770 pass
771
Ned Deily4a96a372013-01-29 00:08:32 -0800772def verifyThirdPartyFile(url, checksum, fname):
773 """
774 Download file from url to filename fname if it does not already exist.
775 Abort if file contents does not match supplied md5 checksum.
776 """
777 name = os.path.basename(fname)
778 if os.path.exists(fname):
779 print("Using local copy of %s"%(name,))
780 else:
781 print("Did not find local copy of %s"%(name,))
782 print("Downloading %s"%(name,))
783 downloadURL(url, fname)
784 print("Archive for %s stored as %s"%(name, fname))
785 if os.system(
786 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
787 % (shellQuote(fname), checksum) ):
788 fatal('MD5 checksum mismatch for file %s' % fname)
789
Thomas Wouters477c8d52006-05-27 19:21:47 +0000790def buildRecipe(recipe, basedir, archList):
791 """
792 Build software using a recipe. This function does the
793 'configure;make;make install' dance for C software, with a possibility
794 to customize this process, basically a poor-mans DarwinPorts.
795 """
796 curdir = os.getcwd()
797
798 name = recipe['name']
799 url = recipe['url']
800 configure = recipe.get('configure', './configure')
801 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
802 shellQuote(basedir)))
803
804 archiveName = os.path.split(url)[-1]
805 sourceArchive = os.path.join(DEPSRC, archiveName)
806
807 if not os.path.exists(DEPSRC):
808 os.mkdir(DEPSRC)
809
Ned Deily4a96a372013-01-29 00:08:32 -0800810 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
811 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000812 buildDir=os.path.join(WORKDIR, '_bld')
813 if not os.path.exists(buildDir):
814 os.mkdir(buildDir)
815
816 workDir = extractArchive(buildDir, sourceArchive)
817 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818
Ned Deily4a96a372013-01-29 00:08:32 -0800819 for patch in recipe.get('patches', ()):
820 if isinstance(patch, tuple):
821 url, checksum = patch
822 fn = os.path.join(DEPSRC, os.path.basename(url))
823 verifyThirdPartyFile(url, checksum, fn)
824 else:
825 # patch is a file in the source directory
826 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
828 shellQuote(fn),))
829
Ned Deily4a96a372013-01-29 00:08:32 -0800830 for patchscript in recipe.get('patchscripts', ()):
831 if isinstance(patchscript, tuple):
832 url, checksum = patchscript
833 fn = os.path.join(DEPSRC, os.path.basename(url))
834 verifyThirdPartyFile(url, checksum, fn)
835 else:
836 # patch is a file in the source directory
837 fn = os.path.join(curdir, patchscript)
838 if fn.endswith('.bz2'):
839 runCommand('bunzip2 -fk %s' % shellQuote(fn))
840 fn = fn[:-4]
841 runCommand('sh %s' % shellQuote(fn))
842 os.unlink(fn)
843
Ned Deily94764b22013-10-27 19:49:29 -0700844 if 'buildDir' in recipe:
845 os.chdir(recipe['buildDir'])
846
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000847 if configure is not None:
848 configure_args = [
849 "--prefix=/usr/local",
850 "--enable-static",
851 "--disable-shared",
852 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
853 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000854
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000855 if 'configure_pre' in recipe:
856 args = list(recipe['configure_pre'])
857 if '--disable-static' in args:
858 configure_args.remove('--enable-static')
859 if '--enable-shared' in args:
860 configure_args.remove('--disable-shared')
861 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000862
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000863 if recipe.get('useLDFlags', 1):
864 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800865 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
866 "-I%s/usr/local/include"%(
867 recipe.get('extra_cflags', ''),
868 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000869 ' -arch '.join(archList),
870 shellQuote(SDKPATH)[1:-1],
871 shellQuote(basedir)[1:-1],),
Ned Deily0134a352014-04-09 16:16:08 -0700872 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800873 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000874 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000875 shellQuote(basedir)[1:-1],
876 ' -arch '.join(archList)),
877 ])
878 else:
879 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800880 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
881 "-I%s/usr/local/include"%(
882 recipe.get('extra_cflags', ''),
883 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000884 ' -arch '.join(archList),
885 shellQuote(SDKPATH)[1:-1],
886 shellQuote(basedir)[1:-1],),
887 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000888
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000889 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800890 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000891
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000892 configure_args.insert(0, configure)
893 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000894
Ned Deily4a96a372013-01-29 00:08:32 -0800895 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000896 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000897
Ned Deily4a96a372013-01-29 00:08:32 -0800898 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899 runCommand('{ ' + install + ' ;} 2>&1')
900
Ned Deily4a96a372013-01-29 00:08:32 -0800901 print("Done %s"%(name,))
902 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000903
904 os.chdir(curdir)
905
906def buildLibraries():
907 """
908 Build our dependencies into $WORKDIR/libraries/usr/local
909 """
Ned Deily4a96a372013-01-29 00:08:32 -0800910 print("")
911 print("Building required libraries")
912 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000913 universal = os.path.join(WORKDIR, 'libraries')
914 os.mkdir(universal)
915 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
916 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
917
Ronald Oussoren1943f862009-03-30 19:39:14 +0000918 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000919 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920
921
922
923def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000924 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000925 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800926 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000927 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000928 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000930 curDir = os.getcwd()
931 os.chdir(buildDir)
Ned Deily9978a932014-04-09 16:15:20 -0700932 # The Doc build changed for 3.4 (technically, for 3.4.1)
933 if getVersionTuple() < (3, 4):
934 # This step does an svn checkout of sphinx and its dependencies
935 runCommand('make update')
936 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
937 else:
938 runCommand('make clean')
939 # Assume sphinx-build is on our PATH, checked in checkEnvironment
940 runCommand('make html')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000941 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000942 if not os.path.exists(docdir):
943 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000944 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000945
946
947def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800948 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000949
950 buildDir = os.path.join(WORKDIR, '_bld', 'python')
951 rootDir = os.path.join(WORKDIR, '_root')
952
953 if os.path.exists(buildDir):
954 shutil.rmtree(buildDir)
955 if os.path.exists(rootDir):
956 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000957 os.makedirs(buildDir)
958 os.makedirs(rootDir)
959 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000960 curdir = os.getcwd()
961 os.chdir(buildDir)
962
963 # Not sure if this is still needed, the original build script
964 # claims that parts of the install assume python.exe exists.
965 os.symlink('python', os.path.join(buildDir, 'python.exe'))
966
967 # Extract the version from the configure file, needed to calculate
968 # several paths.
969 version = getVersion()
970
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000971 # Since the extra libs are not in their installed framework location
972 # during the build, augment the library path so that the interpreter
973 # will find them during its extension import sanity checks.
974 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
975 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800976 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000977 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000978 "--with-universal-archs=%s "
979 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800980 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000981 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700982 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000983 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
984 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000985 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily41ab6c32013-11-22 22:25:43 -0800986 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000987 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 shellQuote(WORKDIR)[1:-1]))
989
Ned Deily729148b2014-05-22 15:28:06 -0700990 print("Running make touch")
991 runCommand("make touch")
992
Ned Deily4a96a372013-01-29 00:08:32 -0800993 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000994 runCommand("make")
995
Ned Deily4a96a372013-01-29 00:08:32 -0800996 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000997 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000998 shellQuote(rootDir)))
999
Ned Deily4a96a372013-01-29 00:08:32 -08001000 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001001 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1002 shellQuote(rootDir)))
1003
Ronald Oussorenac4b39f2009-03-30 20:05:35 +00001004 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -08001005 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001006 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1007 runCommand("mv %s/* %s"%(
1008 shellQuote(os.path.join(
1009 WORKDIR, 'libraries', 'Library', 'Frameworks',
1010 'Python.framework', 'Versions', getVersion(),
1011 'lib')),
1012 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1013 'Python.framework', 'Versions', getVersion(),
1014 'lib'))))
1015
Ned Deily050fcd52013-10-26 03:16:44 -07001016 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1017 'Python.framework', 'Versions',
1018 version, 'lib', 'python%s'%(version,))
1019
Ned Deily4a96a372013-01-29 00:08:32 -08001020 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001021 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001022 gid = grp.getgrnam('admin').gr_gid
1023
Ned Deily4a96a372013-01-29 00:08:32 -08001024 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001025 for dirpath, dirnames, filenames in os.walk(frmDir):
1026 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001027 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001028 os.chown(os.path.join(dirpath, dn), -1, gid)
1029
Thomas Wouters477c8d52006-05-27 19:21:47 +00001030 for fn in filenames:
1031 if os.path.islink(fn):
1032 continue
1033
1034 # "chmod g+w $fn"
1035 p = os.path.join(dirpath, fn)
1036 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001037 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1038 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001039
Ned Deily4a96a372013-01-29 00:08:32 -08001040 if fn in EXPECTED_SHARED_LIBS:
1041 # check to see that this file was linked with the
1042 # expected library path and version
1043 data = captureCommand("otool -L %s" % shellQuote(p))
1044 for sl in EXPECTED_SHARED_LIBS[fn]:
1045 if ("\t%s " % sl) not in data:
1046 print("Expected shared lib %s was not linked with %s"
1047 % (sl, p))
1048 shared_lib_error = True
1049
1050 if shared_lib_error:
1051 fatal("Unexpected shared library errors.")
1052
Ned Deilye59e4c52011-01-29 18:56:28 +00001053 if PYTHON_3:
1054 LDVERSION=None
1055 VERSION=None
1056 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001057
Ned Deilye59e4c52011-01-29 18:56:28 +00001058 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001059 for ln in fp:
1060 if ln.startswith('VERSION='):
1061 VERSION=ln.split()[1]
1062 if ln.startswith('ABIFLAGS='):
1063 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001064 if ln.startswith('LDVERSION='):
1065 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001066 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001067
Ned Deilye59e4c52011-01-29 18:56:28 +00001068 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1069 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1070 config_suffix = '-' + LDVERSION
1071 else:
1072 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001073
Thomas Wouters477c8d52006-05-27 19:21:47 +00001074 # We added some directories to the search path during the configure
1075 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001076 # the end-users system. Also remove the directories from _sysconfigdata.py
1077 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001078
Ned Deilya4f6b002013-10-25 00:47:38 -07001079 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1080 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1081
Ned Deilya4f6b002013-10-25 00:47:38 -07001082 # fix Makefile
1083 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1084 fp = open(path, 'r')
1085 data = fp.read()
1086 fp.close()
1087
1088 for p in (include_path, lib_path):
1089 data = data.replace(" " + p, '')
1090 data = data.replace(p + " ", '')
1091
1092 fp = open(path, 'w')
1093 fp.write(data)
1094 fp.close()
1095
1096 # fix _sysconfigdata if it exists
1097 #
1098 # TODO: make this more robust! test_sysconfig_module of
1099 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1100 # the output from get_config_var in both sysconfig and
1101 # distutils.sysconfig is exactly the same for both CFLAGS and
1102 # LDFLAGS. The fixing up is now complicated by the pretty
1103 # printing in _sysconfigdata.py. Also, we are using the
1104 # pprint from the Python running the installer build which
1105 # may not cosmetically format the same as the pprint in the Python
1106 # being built (and which is used to originally generate
1107 # _sysconfigdata.py).
1108
1109 import pprint
1110 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1111 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001112 fp = open(path, 'r')
1113 data = fp.read()
1114 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001115 # create build_time_vars dict
1116 exec(data)
1117 vars = {}
1118 for k, v in build_time_vars.items():
1119 if type(v) == type(''):
1120 for p in (include_path, lib_path):
1121 v = v.replace(' ' + p, '')
1122 v = v.replace(p + ' ', '')
1123 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001124
Ned Deily4a96a372013-01-29 00:08:32 -08001125 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001126 # duplicated from sysconfig._generate_posix_vars()
1127 fp.write('# system configuration generated and used by'
1128 ' the sysconfig module\n')
1129 fp.write('build_time_vars = ')
1130 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001131 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001132
1133 # Add symlinks in /usr/local/bin, using relative links
1134 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1135 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1136 'Python.framework', 'Versions', version, 'bin')
1137 if os.path.exists(usr_local_bin):
1138 shutil.rmtree(usr_local_bin)
1139 os.makedirs(usr_local_bin)
1140 for fn in os.listdir(
1141 os.path.join(frmDir, 'Versions', version, 'bin')):
1142 os.symlink(os.path.join(to_framework, fn),
1143 os.path.join(usr_local_bin, fn))
1144
1145 os.chdir(curdir)
1146
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001147 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001148 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001149 # with an already installed version of python 2
1150 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1151 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001152
1153def patchFile(inPath, outPath):
1154 data = fileContents(inPath)
1155 data = data.replace('$FULL_VERSION', getFullVersion())
1156 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001157 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001158 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001160
1161 # This one is not handy as a template variable
1162 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001163 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164 fp.write(data)
1165 fp.close()
1166
1167def patchScript(inPath, outPath):
1168 data = fileContents(inPath)
1169 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001170 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001171 fp.write(data)
1172 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001173 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001174
1175
1176
1177def packageFromRecipe(targetDir, recipe):
1178 curdir = os.getcwd()
1179 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001180 # The major version (such as 2.5) is included in the package name
1181 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001182 # common.
1183 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001184 srcdir = recipe.get('source')
1185 pkgroot = recipe.get('topdir', srcdir)
1186 postflight = recipe.get('postflight')
1187 readme = textwrap.dedent(recipe['readme'])
1188 isRequired = recipe.get('required', True)
1189
Ned Deily4a96a372013-01-29 00:08:32 -08001190 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001191
1192 # Substitute some variables
1193 textvars = dict(
1194 VER=getVersion(),
1195 FULLVER=getFullVersion(),
1196 )
1197 readme = readme % textvars
1198
1199 if pkgroot is not None:
1200 pkgroot = pkgroot % textvars
1201 else:
1202 pkgroot = '/'
1203
1204 if srcdir is not None:
1205 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1206 srcdir = srcdir % textvars
1207
1208 if postflight is not None:
1209 postflight = os.path.abspath(postflight)
1210
1211 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1212 os.makedirs(packageContents)
1213
1214 if srcdir is not None:
1215 os.chdir(srcdir)
1216 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1217 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1218 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1219
1220 fn = os.path.join(packageContents, 'PkgInfo')
1221 fp = open(fn, 'w')
1222 fp.write('pmkrpkg1')
1223 fp.close()
1224
1225 rsrcDir = os.path.join(packageContents, "Resources")
1226 os.mkdir(rsrcDir)
1227 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1228 fp.write(readme)
1229 fp.close()
1230
1231 if postflight is not None:
1232 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1233
1234 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001235 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001236 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001237 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1238 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1239 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001240 CFBundleShortVersionString=vers,
1241 IFMajorVersion=major,
1242 IFMinorVersion=minor,
1243 IFPkgFormatVersion=0.10000000149011612,
1244 IFPkgFlagAllowBackRev=False,
1245 IFPkgFlagAuthorizationAction="RootAuthorization",
1246 IFPkgFlagDefaultLocation=pkgroot,
1247 IFPkgFlagFollowLinks=True,
1248 IFPkgFlagInstallFat=True,
1249 IFPkgFlagIsRequired=isRequired,
1250 IFPkgFlagOverwritePermissions=False,
1251 IFPkgFlagRelocatable=False,
1252 IFPkgFlagRestartAction="NoRestart",
1253 IFPkgFlagRootVolumeOnly=True,
1254 IFPkgFlagUpdateInstalledLangauges=False,
1255 )
1256 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1257
1258 pl = Plist(
1259 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001260 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001261 IFPkgDescriptionVersion=vers,
1262 )
1263 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1264
1265 finally:
1266 os.chdir(curdir)
1267
1268
1269def makeMpkgPlist(path):
1270
1271 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001272 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001273
1274 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001275 CFBundleGetInfoString="Python %s"%(vers,),
1276 CFBundleIdentifier='org.python.Python',
1277 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001278 CFBundleShortVersionString=vers,
1279 IFMajorVersion=major,
1280 IFMinorVersion=minor,
1281 IFPkgFlagComponentDirectory="Contents/Packages",
1282 IFPkgFlagPackageList=[
1283 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001284 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001285 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001286 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001287 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001288 ],
1289 IFPkgFormatVersion=0.10000000149011612,
1290 IFPkgFlagBackgroundScaling="proportional",
1291 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001292 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001293 )
1294
1295 writePlist(pl, path)
1296
1297
1298def buildInstaller():
1299
1300 # Zap all compiled files
1301 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1302 for fn in filenames:
1303 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1304 os.unlink(os.path.join(dirpath, fn))
1305
1306 outdir = os.path.join(WORKDIR, 'installer')
1307 if os.path.exists(outdir):
1308 shutil.rmtree(outdir)
1309 os.mkdir(outdir)
1310
Ronald Oussoren1943f862009-03-30 19:39:14 +00001311 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001312 pkgcontents = os.path.join(pkgroot, 'Packages')
1313 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001314 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001315 packageFromRecipe(pkgcontents, recipe)
1316
1317 rsrcDir = os.path.join(pkgroot, 'Resources')
1318
1319 fn = os.path.join(pkgroot, 'PkgInfo')
1320 fp = open(fn, 'w')
1321 fp.write('pmkrpkg1')
1322 fp.close()
1323
1324 os.mkdir(rsrcDir)
1325
1326 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1327 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001328 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001329 IFPkgDescriptionVersion=getVersion(),
1330 )
1331
1332 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1333 for fn in os.listdir('resources'):
1334 if fn == '.svn': continue
1335 if fn.endswith('.jpg'):
1336 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1337 else:
1338 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1339
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001340 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001341
1342
1343def installSize(clear=False, _saved=[]):
1344 if clear:
1345 del _saved[:]
1346 if not _saved:
1347 data = captureCommand("du -ks %s"%(
1348 shellQuote(os.path.join(WORKDIR, '_root'))))
1349 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1350 return _saved[0]
1351
1352
1353def buildDMG():
1354 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001355 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001356 """
1357 outdir = os.path.join(WORKDIR, 'diskimage')
1358 if os.path.exists(outdir):
1359 shutil.rmtree(outdir)
1360
1361 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001362 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001363 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001364 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001365 imagepath = imagepath + '.dmg'
1366
1367 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001368 volname='Python %s'%(getFullVersion())
1369 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1370 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001371 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001372 shellQuote(imagepath + ".tmp.dmg" )))
1373
1374
1375 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1376 os.mkdir(os.path.join(WORKDIR, "mnt"))
1377 runCommand("hdiutil attach %s -mountroot %s"%(
1378 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1379
1380 # Custom icon for the DMG, shown when the DMG is mounted.
1381 shutil.copy("../Icons/Disk Image.icns",
1382 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001383 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001384 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1385
1386 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1387
1388 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1389 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1390 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1391 setIcon(imagepath, "../Icons/Disk Image.icns")
1392
1393 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001394
1395 return imagepath
1396
1397
1398def setIcon(filePath, icnsPath):
1399 """
1400 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001401 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001402
Ronald Oussoren70050672010-04-30 15:00:26 +00001403 dirPath = os.path.normpath(os.path.dirname(__file__))
1404 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001405 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1406 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1407 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001408 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1409 if not os.path.exists(appPath):
1410 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001411 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1412 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001413
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001414 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1415 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001416
1417def main():
1418 # First parse options and check if we can perform our work
1419 parseOptions()
1420 checkEnvironment()
1421
Ronald Oussoren1943f862009-03-30 19:39:14 +00001422 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001423 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001424 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001425
1426 if os.path.exists(WORKDIR):
1427 shutil.rmtree(WORKDIR)
1428 os.mkdir(WORKDIR)
1429
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001430 os.environ['LC_ALL'] = 'C'
1431
Thomas Wouters477c8d52006-05-27 19:21:47 +00001432 # Then build third-party libraries such as sleepycat DB4.
1433 buildLibraries()
1434
1435 # Now build python itself
1436 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001437
1438 # And then build the documentation
1439 # Remove the Deployment Target from the shell
1440 # environment, it's no longer needed and
1441 # an unexpected build target can cause problems
1442 # when Sphinx and its dependencies need to
1443 # be (re-)installed.
1444 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001445 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001446
1447
1448 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001449 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001450 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001451 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001452
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001453 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001454 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001455 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001456 setIcon(folder, "../Icons/Python Folder.icns")
1457
1458 # Create the installer
1459 buildInstaller()
1460
1461 # And copy the readme into the directory containing the installer
1462 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1463
1464 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001465 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001466
1467 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001468 fp.write("# BUILD INFO\n")
1469 fp.write("# Date: %s\n" % time.ctime())
1470 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001471 fp.close()
1472
Thomas Wouters477c8d52006-05-27 19:21:47 +00001473 # And copy it to a DMG
1474 buildDMG()
1475
Thomas Wouters477c8d52006-05-27 19:21:47 +00001476if __name__ == "__main__":
1477 main()