blob: 46e715a62eb6fa5a81742caf336f906dbe6cc156 [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 Deilyed730102014-11-14 18:55:05 -0800201 if False: # if getDeptargetTuple() > (10, 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 Deilyed730102014-11-14 18:55:05 -0800242 if PYTHON_3:
Ned Deily4a96a372013-01-29 00:08:32 -0800243 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]
372 result = [
373 dict(
374 name="PythonFramework",
375 long_name="Python Framework",
376 source="/Library/Frameworks/Python.framework",
377 readme="""\
378 This package installs Python.framework, that is the python
379 interpreter and the standard library. This also includes Python
380 wrappers for lots of Mac OS X API's.
381 """,
382 postflight="scripts/postflight.framework",
383 selected='selected',
384 ),
385 dict(
386 name="PythonApplications",
387 long_name="GUI Applications",
388 source="/Applications/Python %(VER)s",
389 readme="""\
390 This package installs IDLE (an interactive Python IDE),
391 Python Launcher and Build Applet (create application bundles
392 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000393
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000394 It also installs a number of examples and demos.
395 """,
396 required=False,
397 selected='selected',
398 ),
399 dict(
400 name="PythonUnixTools",
401 long_name="UNIX command-line tools",
402 source="/usr/local/bin",
403 readme="""\
404 This package installs the unix tools in /usr/local/bin for
405 compatibility with older releases of Python. This package
406 is not necessary to use Python.
407 """,
408 required=False,
409 selected='selected',
410 ),
411 dict(
412 name="PythonDocumentation",
413 long_name="Python Documentation",
414 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
415 source="/pydocs",
416 readme="""\
417 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800418 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000419 """,
420 postflight="scripts/postflight.documentation",
421 required=False,
422 selected='selected',
423 ),
424 dict(
425 name="PythonProfileChanges",
426 long_name="Shell profile updater",
427 readme="""\
428 This packages updates your shell profile to make sure that
429 the Python tools are found by your shell in preference of
430 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000431
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000432 If you don't install this package you'll have to add
433 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
434 to your PATH by hand.
435 """,
436 postflight="scripts/postflight.patch-profile",
437 topdir="/Library/Frameworks/Python.framework",
438 source="/empty-dir",
439 required=False,
Ned Deilyed730102014-11-14 18:55:05 -0800440 selected='selected',
441 ),
442 dict(
443 name="PythonInstallPip",
444 long_name="Install or upgrade pip",
445 readme="""\
446 This package installs (or upgrades from an earlier version)
447 pip, a tool for installing and managing Python packages.
448 """,
449 postflight="scripts/postflight.ensurepip",
450 topdir="/Library/Frameworks/Python.framework",
451 source="/empty-dir",
452 required=False,
453 selected='selected',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000454 ),
455 ]
456
Ned Deily04cdfa12014-06-25 13:36:14 -0700457 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000458 result.append(
459 dict(
460 name="PythonSystemFixes",
461 long_name="Fix system Python",
462 readme="""\
463 This package updates the system python installation on
464 Mac OS X 10.3 to ensure that you can build new python extensions
465 using that copy of python after installing this version.
466 """,
467 postflight="../Tools/fixapplepython23.py",
468 topdir="/Library/Frameworks/Python.framework",
469 source="/empty-dir",
470 required=False,
471 selected=unselected_for_python3,
472 )
473 )
Ned Deily41ab6c32013-11-22 22:25:43 -0800474
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000475 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000476
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477def fatal(msg):
478 """
479 A fatal error, bail out.
480 """
481 sys.stderr.write('FATAL: ')
482 sys.stderr.write(msg)
483 sys.stderr.write('\n')
484 sys.exit(1)
485
486def fileContents(fn):
487 """
488 Return the contents of the named file
489 """
Ned Deily4a96a372013-01-29 00:08:32 -0800490 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000491
492def runCommand(commandline):
493 """
Ezio Melotti13925002011-03-16 11:05:33 +0200494 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000495 unless the command fails.
496 """
497 fd = os.popen(commandline, 'r')
498 data = fd.read()
499 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000500 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000501 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800502 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000503
504 if VERBOSE:
505 sys.stdout.write(data); sys.stdout.flush()
506
507def captureCommand(commandline):
508 fd = os.popen(commandline, 'r')
509 data = fd.read()
510 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000511 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000512 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800513 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000514
515 return data
516
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000517def getTclTkVersion(configfile, versionline):
518 """
519 search Tcl or Tk configuration file for version line
520 """
521 try:
522 f = open(configfile, "r")
523 except:
524 fatal("Framework configuration file not found: %s" % configfile)
525
526 for l in f:
527 if l.startswith(versionline):
528 f.close()
529 return l
530
531 fatal("Version variable %s not found in framework configuration file: %s"
532 % (versionline, configfile))
533
Thomas Wouters477c8d52006-05-27 19:21:47 +0000534def checkEnvironment():
535 """
536 Check that we're running on a supported system.
537 """
538
Ned Deilye59e4c52011-01-29 18:56:28 +0000539 if sys.version_info[0:2] < (2, 4):
540 fatal("This script must be run with Python 2.4 or later")
541
Thomas Wouters477c8d52006-05-27 19:21:47 +0000542 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000543 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000544
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000545 if int(platform.release().split('.')[0]) < 8:
546 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000547
548 if not os.path.exists(SDKPATH):
549 fatal("Please install the latest version of Xcode and the %s SDK"%(
550 os.path.basename(SDKPATH[:-4])))
551
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000552 # Because we only support dynamic load of only one major/minor version of
553 # Tcl/Tk, ensure:
554 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800555 # higher than the Apple-supplied system version in
556 # SDKROOT/System/Library/Frameworks
557 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
558 # in) SDKROOT/Library/Frameworks with the same version as the system
559 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000560
Ned Deily4a96a372013-01-29 00:08:32 -0800561 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000562 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800563 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000564 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800565 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000566 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800567 frameworks[framework] = os.readlink(sysfw)
568 if not os.path.exists(libfw):
569 fatal("Please install a link to a current %s %s as %s so "
570 "the user can override the system framework."
571 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000572 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000573 fatal("Version of %s must match %s" % (libfw, sysfw) )
574 if os.path.exists(usrfw):
575 fatal("Please rename %s to avoid possible dynamic load issues."
576 % usrfw)
577
Ned Deily4a96a372013-01-29 00:08:32 -0800578 if frameworks['Tcl'] != frameworks['Tk']:
579 fatal("The Tcl and Tk frameworks are not the same version.")
580
581 # add files to check after build
582 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
583 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
584 % frameworks['Tcl'],
585 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
586 % frameworks['Tk'],
587 ]
588
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000589 # Remove inherited environment variables which might influence build
590 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
591 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
592 for ev in list(os.environ):
593 for prefix in environ_var_prefixes:
594 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800595 print("INFO: deleting environment variable %s=%s" % (
596 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000597 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000598
Ned Deily4a96a372013-01-29 00:08:32 -0800599 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
600 if 'SDK_TOOLS_BIN' in os.environ:
601 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
602 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
603 # add its fixed location here if it exists
604 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
605 if os.path.isdir(OLD_DEVELOPER_TOOLS):
606 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
607 os.environ['PATH'] = base_path
608 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deily7e60f512014-04-07 12:10:21 -0700609 # Ensure ws have access to hg and to sphinx-build.
610 # You may have to create links in /usr/bin for them.
611 runCommand('hg --version')
Ned Deily1ff32a92014-09-05 15:57:05 -0700612 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000613
Thomas Wouters89f507f2006-12-13 04:49:30 +0000614def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000615 """
616 Parse arguments and update global settings.
617 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000618 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800619 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000620
621 if args is None:
622 args = sys.argv[1:]
623
624 try:
625 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000626 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
627 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800628 except getopt.GetoptError:
629 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000630 sys.exit(1)
631
632 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800633 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 sys.exit(1)
635
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000636 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000638 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800639 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000640 sys.exit(0)
641
642 elif k in ('-d', '--build-dir'):
643 WORKDIR=v
644
645 elif k in ('--third-party',):
646 DEPSRC=v
647
648 elif k in ('--sdk-path',):
649 SDKPATH=v
650
651 elif k in ('--src-dir',):
652 SRCDIR=v
653
Ronald Oussoren1943f862009-03-30 19:39:14 +0000654 elif k in ('--dep-target', ):
655 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000656 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000657
658 elif k in ('--universal-archs', ):
659 if v in UNIVERSALOPTS:
660 UNIVERSALARCHS = v
661 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000662 if deptarget is None:
663 # Select alternate default deployment
664 # target
665 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000666 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800667 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000668
Thomas Wouters477c8d52006-05-27 19:21:47 +0000669 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800670 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000671
672 SRCDIR=os.path.abspath(SRCDIR)
673 WORKDIR=os.path.abspath(WORKDIR)
674 SDKPATH=os.path.abspath(SDKPATH)
675 DEPSRC=os.path.abspath(DEPSRC)
676
Ned Deily04cdfa12014-06-25 13:36:14 -0700677 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000678
Ned Deily4a96a372013-01-29 00:08:32 -0800679 print("Settings:")
680 print(" * Source directory:", SRCDIR)
681 print(" * Build directory: ", WORKDIR)
682 print(" * SDK location: ", SDKPATH)
683 print(" * Third-party source:", DEPSRC)
684 print(" * Deployment target:", DEPTARGET)
685 print(" * Universal architectures:", ARCHLIST)
686 print(" * C compiler:", CC)
687 print(" * C++ compiler:", CXX)
688 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000689
690
691
692
693def extractArchive(builddir, archiveName):
694 """
695 Extract a source archive into 'builddir'. Returns the path of the
696 extracted archive.
697
698 XXX: This function assumes that archives contain a toplevel directory
699 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700700 safe enough for almost anything we use. Unfortunately, it does not
701 work for current Tcl and Tk source releases where the basename of
702 the archive ends with "-src" but the uncompressed directory does not.
703 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704 """
705 curdir = os.getcwd()
706 try:
707 os.chdir(builddir)
708 if archiveName.endswith('.tar.gz'):
709 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700710 if ((retval.startswith('tcl') or retval.startswith('tk'))
711 and retval.endswith('-src')):
712 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000713 if os.path.exists(retval):
714 shutil.rmtree(retval)
715 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
716
717 elif archiveName.endswith('.tar.bz2'):
718 retval = os.path.basename(archiveName[:-8])
719 if os.path.exists(retval):
720 shutil.rmtree(retval)
721 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
722
723 elif archiveName.endswith('.tar'):
724 retval = os.path.basename(archiveName[:-4])
725 if os.path.exists(retval):
726 shutil.rmtree(retval)
727 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
728
729 elif archiveName.endswith('.zip'):
730 retval = os.path.basename(archiveName[:-4])
731 if os.path.exists(retval):
732 shutil.rmtree(retval)
733 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
734
735 data = fp.read()
736 xit = fp.close()
737 if xit is not None:
738 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800739 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000740
741 return os.path.join(builddir, retval)
742
743 finally:
744 os.chdir(curdir)
745
Thomas Wouters477c8d52006-05-27 19:21:47 +0000746def downloadURL(url, fname):
747 """
748 Download the contents of the url into the file.
749 """
Ned Deily4a96a372013-01-29 00:08:32 -0800750 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000751 fpOut = open(fname, 'wb')
752 block = fpIn.read(10240)
753 try:
754 while block:
755 fpOut.write(block)
756 block = fpIn.read(10240)
757 fpIn.close()
758 fpOut.close()
759 except:
760 try:
761 os.unlink(fname)
762 except:
763 pass
764
Ned Deily4a96a372013-01-29 00:08:32 -0800765def verifyThirdPartyFile(url, checksum, fname):
766 """
767 Download file from url to filename fname if it does not already exist.
768 Abort if file contents does not match supplied md5 checksum.
769 """
770 name = os.path.basename(fname)
771 if os.path.exists(fname):
772 print("Using local copy of %s"%(name,))
773 else:
774 print("Did not find local copy of %s"%(name,))
775 print("Downloading %s"%(name,))
776 downloadURL(url, fname)
777 print("Archive for %s stored as %s"%(name, fname))
778 if os.system(
779 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
780 % (shellQuote(fname), checksum) ):
781 fatal('MD5 checksum mismatch for file %s' % fname)
782
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783def buildRecipe(recipe, basedir, archList):
784 """
785 Build software using a recipe. This function does the
786 'configure;make;make install' dance for C software, with a possibility
787 to customize this process, basically a poor-mans DarwinPorts.
788 """
789 curdir = os.getcwd()
790
791 name = recipe['name']
792 url = recipe['url']
793 configure = recipe.get('configure', './configure')
794 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
795 shellQuote(basedir)))
796
797 archiveName = os.path.split(url)[-1]
798 sourceArchive = os.path.join(DEPSRC, archiveName)
799
800 if not os.path.exists(DEPSRC):
801 os.mkdir(DEPSRC)
802
Ned Deily4a96a372013-01-29 00:08:32 -0800803 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
804 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000805 buildDir=os.path.join(WORKDIR, '_bld')
806 if not os.path.exists(buildDir):
807 os.mkdir(buildDir)
808
809 workDir = extractArchive(buildDir, sourceArchive)
810 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000811
Ned Deily4a96a372013-01-29 00:08:32 -0800812 for patch in recipe.get('patches', ()):
813 if isinstance(patch, tuple):
814 url, checksum = patch
815 fn = os.path.join(DEPSRC, os.path.basename(url))
816 verifyThirdPartyFile(url, checksum, fn)
817 else:
818 # patch is a file in the source directory
819 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000820 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
821 shellQuote(fn),))
822
Ned Deily4a96a372013-01-29 00:08:32 -0800823 for patchscript in recipe.get('patchscripts', ()):
824 if isinstance(patchscript, tuple):
825 url, checksum = patchscript
826 fn = os.path.join(DEPSRC, os.path.basename(url))
827 verifyThirdPartyFile(url, checksum, fn)
828 else:
829 # patch is a file in the source directory
830 fn = os.path.join(curdir, patchscript)
831 if fn.endswith('.bz2'):
832 runCommand('bunzip2 -fk %s' % shellQuote(fn))
833 fn = fn[:-4]
834 runCommand('sh %s' % shellQuote(fn))
835 os.unlink(fn)
836
Ned Deily94764b22013-10-27 19:49:29 -0700837 if 'buildDir' in recipe:
838 os.chdir(recipe['buildDir'])
839
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000840 if configure is not None:
841 configure_args = [
842 "--prefix=/usr/local",
843 "--enable-static",
844 "--disable-shared",
845 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
846 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000847
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000848 if 'configure_pre' in recipe:
849 args = list(recipe['configure_pre'])
850 if '--disable-static' in args:
851 configure_args.remove('--enable-static')
852 if '--enable-shared' in args:
853 configure_args.remove('--disable-shared')
854 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000855
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000856 if recipe.get('useLDFlags', 1):
857 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800858 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
859 "-I%s/usr/local/include"%(
860 recipe.get('extra_cflags', ''),
861 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000862 ' -arch '.join(archList),
863 shellQuote(SDKPATH)[1:-1],
864 shellQuote(basedir)[1:-1],),
Ned Deily0134a352014-04-09 16:16:08 -0700865 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800866 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000867 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000868 shellQuote(basedir)[1:-1],
869 ' -arch '.join(archList)),
870 ])
871 else:
872 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800873 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
874 "-I%s/usr/local/include"%(
875 recipe.get('extra_cflags', ''),
876 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000877 ' -arch '.join(archList),
878 shellQuote(SDKPATH)[1:-1],
879 shellQuote(basedir)[1:-1],),
880 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000881
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000882 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800883 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000884
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000885 configure_args.insert(0, configure)
886 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000887
Ned Deily4a96a372013-01-29 00:08:32 -0800888 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000889 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890
Ned Deily4a96a372013-01-29 00:08:32 -0800891 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000892 runCommand('{ ' + install + ' ;} 2>&1')
893
Ned Deily4a96a372013-01-29 00:08:32 -0800894 print("Done %s"%(name,))
895 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000896
897 os.chdir(curdir)
898
899def buildLibraries():
900 """
901 Build our dependencies into $WORKDIR/libraries/usr/local
902 """
Ned Deily4a96a372013-01-29 00:08:32 -0800903 print("")
904 print("Building required libraries")
905 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000906 universal = os.path.join(WORKDIR, 'libraries')
907 os.mkdir(universal)
908 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
909 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
910
Ronald Oussoren1943f862009-03-30 19:39:14 +0000911 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000912 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000913
914
915
916def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000917 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000918 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800919 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000921 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000922 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000923 curDir = os.getcwd()
924 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -0700925 # The Doc build changed for 3.4 (technically, for 3.4.1) and for 2.7.9
926 runCommand('make clean')
927 # Assume sphinx-build is on our PATH, checked in checkEnvironment
928 runCommand('make html')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000929 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000930 if not os.path.exists(docdir):
931 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000932 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000933
934
935def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800936 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000937
938 buildDir = os.path.join(WORKDIR, '_bld', 'python')
939 rootDir = os.path.join(WORKDIR, '_root')
940
941 if os.path.exists(buildDir):
942 shutil.rmtree(buildDir)
943 if os.path.exists(rootDir):
944 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000945 os.makedirs(buildDir)
946 os.makedirs(rootDir)
947 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000948 curdir = os.getcwd()
949 os.chdir(buildDir)
950
951 # Not sure if this is still needed, the original build script
952 # claims that parts of the install assume python.exe exists.
953 os.symlink('python', os.path.join(buildDir, 'python.exe'))
954
955 # Extract the version from the configure file, needed to calculate
956 # several paths.
957 version = getVersion()
958
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000959 # Since the extra libs are not in their installed framework location
960 # during the build, augment the library path so that the interpreter
961 # will find them during its extension import sanity checks.
962 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
963 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800964 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000965 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000966 "--with-universal-archs=%s "
967 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800968 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000969 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700970 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000971 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
972 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000973 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyed730102014-11-14 18:55:05 -0800974 (' ', '--without-ensurepip ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000975 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000976 shellQuote(WORKDIR)[1:-1]))
977
Ned Deily729148b2014-05-22 15:28:06 -0700978 print("Running make touch")
979 runCommand("make touch")
980
Ned Deily4a96a372013-01-29 00:08:32 -0800981 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000982 runCommand("make")
983
Ned Deily4a96a372013-01-29 00:08:32 -0800984 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000985 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000986 shellQuote(rootDir)))
987
Ned Deily4a96a372013-01-29 00:08:32 -0800988 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000989 runCommand("make frameworkinstallextras DESTDIR=%s"%(
990 shellQuote(rootDir)))
991
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000992 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800993 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000994 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
995 runCommand("mv %s/* %s"%(
996 shellQuote(os.path.join(
997 WORKDIR, 'libraries', 'Library', 'Frameworks',
998 'Python.framework', 'Versions', getVersion(),
999 'lib')),
1000 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1001 'Python.framework', 'Versions', getVersion(),
1002 'lib'))))
1003
Ned Deily050fcd52013-10-26 03:16:44 -07001004 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1005 'Python.framework', 'Versions',
1006 version, 'lib', 'python%s'%(version,))
1007
Ned Deily4a96a372013-01-29 00:08:32 -08001008 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001009 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001010 gid = grp.getgrnam('admin').gr_gid
1011
Ned Deily4a96a372013-01-29 00:08:32 -08001012 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001013 for dirpath, dirnames, filenames in os.walk(frmDir):
1014 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001015 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001016 os.chown(os.path.join(dirpath, dn), -1, gid)
1017
Thomas Wouters477c8d52006-05-27 19:21:47 +00001018 for fn in filenames:
1019 if os.path.islink(fn):
1020 continue
1021
1022 # "chmod g+w $fn"
1023 p = os.path.join(dirpath, fn)
1024 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001025 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1026 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001027
Ned Deily4a96a372013-01-29 00:08:32 -08001028 if fn in EXPECTED_SHARED_LIBS:
1029 # check to see that this file was linked with the
1030 # expected library path and version
1031 data = captureCommand("otool -L %s" % shellQuote(p))
1032 for sl in EXPECTED_SHARED_LIBS[fn]:
1033 if ("\t%s " % sl) not in data:
1034 print("Expected shared lib %s was not linked with %s"
1035 % (sl, p))
1036 shared_lib_error = True
1037
1038 if shared_lib_error:
1039 fatal("Unexpected shared library errors.")
1040
Ned Deilye59e4c52011-01-29 18:56:28 +00001041 if PYTHON_3:
1042 LDVERSION=None
1043 VERSION=None
1044 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001045
Ned Deilye59e4c52011-01-29 18:56:28 +00001046 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001047 for ln in fp:
1048 if ln.startswith('VERSION='):
1049 VERSION=ln.split()[1]
1050 if ln.startswith('ABIFLAGS='):
1051 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001052 if ln.startswith('LDVERSION='):
1053 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001054 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001055
Ned Deilye59e4c52011-01-29 18:56:28 +00001056 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1057 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1058 config_suffix = '-' + LDVERSION
1059 else:
1060 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001061
Thomas Wouters477c8d52006-05-27 19:21:47 +00001062 # We added some directories to the search path during the configure
1063 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001064 # the end-users system. Also remove the directories from _sysconfigdata.py
1065 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001066
Ned Deilya4f6b002013-10-25 00:47:38 -07001067 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1068 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1069
Ned Deilya4f6b002013-10-25 00:47:38 -07001070 # fix Makefile
1071 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1072 fp = open(path, 'r')
1073 data = fp.read()
1074 fp.close()
1075
1076 for p in (include_path, lib_path):
1077 data = data.replace(" " + p, '')
1078 data = data.replace(p + " ", '')
1079
1080 fp = open(path, 'w')
1081 fp.write(data)
1082 fp.close()
1083
1084 # fix _sysconfigdata if it exists
1085 #
1086 # TODO: make this more robust! test_sysconfig_module of
1087 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1088 # the output from get_config_var in both sysconfig and
1089 # distutils.sysconfig is exactly the same for both CFLAGS and
1090 # LDFLAGS. The fixing up is now complicated by the pretty
1091 # printing in _sysconfigdata.py. Also, we are using the
1092 # pprint from the Python running the installer build which
1093 # may not cosmetically format the same as the pprint in the Python
1094 # being built (and which is used to originally generate
1095 # _sysconfigdata.py).
1096
1097 import pprint
1098 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1099 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001100 fp = open(path, 'r')
1101 data = fp.read()
1102 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001103 # create build_time_vars dict
1104 exec(data)
1105 vars = {}
1106 for k, v in build_time_vars.items():
1107 if type(v) == type(''):
1108 for p in (include_path, lib_path):
1109 v = v.replace(' ' + p, '')
1110 v = v.replace(p + ' ', '')
1111 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001112
Ned Deily4a96a372013-01-29 00:08:32 -08001113 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001114 # duplicated from sysconfig._generate_posix_vars()
1115 fp.write('# system configuration generated and used by'
1116 ' the sysconfig module\n')
1117 fp.write('build_time_vars = ')
1118 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001119 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001120
1121 # Add symlinks in /usr/local/bin, using relative links
1122 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1123 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1124 'Python.framework', 'Versions', version, 'bin')
1125 if os.path.exists(usr_local_bin):
1126 shutil.rmtree(usr_local_bin)
1127 os.makedirs(usr_local_bin)
1128 for fn in os.listdir(
1129 os.path.join(frmDir, 'Versions', version, 'bin')):
1130 os.symlink(os.path.join(to_framework, fn),
1131 os.path.join(usr_local_bin, fn))
1132
1133 os.chdir(curdir)
1134
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001135 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001136 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001137 # with an already installed version of python 2
1138 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1139 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001140
1141def patchFile(inPath, outPath):
1142 data = fileContents(inPath)
1143 data = data.replace('$FULL_VERSION', getFullVersion())
1144 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001145 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001146 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001147 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001148
1149 # This one is not handy as a template variable
1150 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001151 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001152 fp.write(data)
1153 fp.close()
1154
1155def patchScript(inPath, outPath):
Ned Deilyed730102014-11-14 18:55:05 -08001156 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 data = fileContents(inPath)
Ned Deilyed730102014-11-14 18:55:05 -08001158 data = data.replace('@PYMAJOR@', str(major))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001160 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001161 fp.write(data)
1162 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001163 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164
1165
1166
1167def packageFromRecipe(targetDir, recipe):
1168 curdir = os.getcwd()
1169 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001170 # The major version (such as 2.5) is included in the package name
1171 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001172 # common.
1173 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001174 srcdir = recipe.get('source')
1175 pkgroot = recipe.get('topdir', srcdir)
1176 postflight = recipe.get('postflight')
1177 readme = textwrap.dedent(recipe['readme'])
1178 isRequired = recipe.get('required', True)
1179
Ned Deily4a96a372013-01-29 00:08:32 -08001180 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001181
1182 # Substitute some variables
1183 textvars = dict(
1184 VER=getVersion(),
1185 FULLVER=getFullVersion(),
1186 )
1187 readme = readme % textvars
1188
1189 if pkgroot is not None:
1190 pkgroot = pkgroot % textvars
1191 else:
1192 pkgroot = '/'
1193
1194 if srcdir is not None:
1195 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1196 srcdir = srcdir % textvars
1197
1198 if postflight is not None:
1199 postflight = os.path.abspath(postflight)
1200
1201 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1202 os.makedirs(packageContents)
1203
1204 if srcdir is not None:
1205 os.chdir(srcdir)
1206 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1207 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1208 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1209
1210 fn = os.path.join(packageContents, 'PkgInfo')
1211 fp = open(fn, 'w')
1212 fp.write('pmkrpkg1')
1213 fp.close()
1214
1215 rsrcDir = os.path.join(packageContents, "Resources")
1216 os.mkdir(rsrcDir)
1217 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1218 fp.write(readme)
1219 fp.close()
1220
1221 if postflight is not None:
1222 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1223
1224 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001225 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001226 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001227 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1228 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1229 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001230 CFBundleShortVersionString=vers,
1231 IFMajorVersion=major,
1232 IFMinorVersion=minor,
1233 IFPkgFormatVersion=0.10000000149011612,
1234 IFPkgFlagAllowBackRev=False,
1235 IFPkgFlagAuthorizationAction="RootAuthorization",
1236 IFPkgFlagDefaultLocation=pkgroot,
1237 IFPkgFlagFollowLinks=True,
1238 IFPkgFlagInstallFat=True,
1239 IFPkgFlagIsRequired=isRequired,
1240 IFPkgFlagOverwritePermissions=False,
1241 IFPkgFlagRelocatable=False,
1242 IFPkgFlagRestartAction="NoRestart",
1243 IFPkgFlagRootVolumeOnly=True,
1244 IFPkgFlagUpdateInstalledLangauges=False,
1245 )
1246 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1247
1248 pl = Plist(
1249 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001250 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001251 IFPkgDescriptionVersion=vers,
1252 )
1253 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1254
1255 finally:
1256 os.chdir(curdir)
1257
1258
1259def makeMpkgPlist(path):
1260
1261 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001262 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001263
1264 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001265 CFBundleGetInfoString="Python %s"%(vers,),
1266 CFBundleIdentifier='org.python.Python',
1267 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001268 CFBundleShortVersionString=vers,
1269 IFMajorVersion=major,
1270 IFMinorVersion=minor,
1271 IFPkgFlagComponentDirectory="Contents/Packages",
1272 IFPkgFlagPackageList=[
1273 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001274 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001275 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001276 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001277 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001278 ],
1279 IFPkgFormatVersion=0.10000000149011612,
1280 IFPkgFlagBackgroundScaling="proportional",
1281 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001282 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001283 )
1284
1285 writePlist(pl, path)
1286
1287
1288def buildInstaller():
1289
1290 # Zap all compiled files
1291 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1292 for fn in filenames:
1293 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1294 os.unlink(os.path.join(dirpath, fn))
1295
1296 outdir = os.path.join(WORKDIR, 'installer')
1297 if os.path.exists(outdir):
1298 shutil.rmtree(outdir)
1299 os.mkdir(outdir)
1300
Ronald Oussoren1943f862009-03-30 19:39:14 +00001301 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001302 pkgcontents = os.path.join(pkgroot, 'Packages')
1303 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001304 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001305 packageFromRecipe(pkgcontents, recipe)
1306
1307 rsrcDir = os.path.join(pkgroot, 'Resources')
1308
1309 fn = os.path.join(pkgroot, 'PkgInfo')
1310 fp = open(fn, 'w')
1311 fp.write('pmkrpkg1')
1312 fp.close()
1313
1314 os.mkdir(rsrcDir)
1315
1316 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1317 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001318 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001319 IFPkgDescriptionVersion=getVersion(),
1320 )
1321
1322 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1323 for fn in os.listdir('resources'):
1324 if fn == '.svn': continue
1325 if fn.endswith('.jpg'):
1326 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1327 else:
1328 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1329
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001330 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001331
1332
1333def installSize(clear=False, _saved=[]):
1334 if clear:
1335 del _saved[:]
1336 if not _saved:
1337 data = captureCommand("du -ks %s"%(
1338 shellQuote(os.path.join(WORKDIR, '_root'))))
1339 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1340 return _saved[0]
1341
1342
1343def buildDMG():
1344 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001345 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001346 """
1347 outdir = os.path.join(WORKDIR, 'diskimage')
1348 if os.path.exists(outdir):
1349 shutil.rmtree(outdir)
1350
1351 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001352 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001353 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001354 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001355 imagepath = imagepath + '.dmg'
1356
1357 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001358 volname='Python %s'%(getFullVersion())
1359 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1360 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001361 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001362 shellQuote(imagepath + ".tmp.dmg" )))
1363
1364
1365 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1366 os.mkdir(os.path.join(WORKDIR, "mnt"))
1367 runCommand("hdiutil attach %s -mountroot %s"%(
1368 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1369
1370 # Custom icon for the DMG, shown when the DMG is mounted.
1371 shutil.copy("../Icons/Disk Image.icns",
1372 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001373 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001374 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1375
1376 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1377
1378 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1379 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1380 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1381 setIcon(imagepath, "../Icons/Disk Image.icns")
1382
1383 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001384
1385 return imagepath
1386
1387
1388def setIcon(filePath, icnsPath):
1389 """
1390 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001391 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001392
Ronald Oussoren70050672010-04-30 15:00:26 +00001393 dirPath = os.path.normpath(os.path.dirname(__file__))
1394 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001395 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1396 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1397 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001398 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1399 if not os.path.exists(appPath):
1400 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001401 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1402 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001403
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001404 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1405 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001406
1407def main():
1408 # First parse options and check if we can perform our work
1409 parseOptions()
1410 checkEnvironment()
1411
Ronald Oussoren1943f862009-03-30 19:39:14 +00001412 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001413 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001414 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001415
1416 if os.path.exists(WORKDIR):
1417 shutil.rmtree(WORKDIR)
1418 os.mkdir(WORKDIR)
1419
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001420 os.environ['LC_ALL'] = 'C'
1421
Thomas Wouters477c8d52006-05-27 19:21:47 +00001422 # Then build third-party libraries such as sleepycat DB4.
1423 buildLibraries()
1424
1425 # Now build python itself
1426 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001427
1428 # And then build the documentation
1429 # Remove the Deployment Target from the shell
1430 # environment, it's no longer needed and
1431 # an unexpected build target can cause problems
1432 # when Sphinx and its dependencies need to
1433 # be (re-)installed.
1434 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001435 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001436
1437
1438 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001439 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001440 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001441 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001442
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001443 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001444 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001445 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001446 setIcon(folder, "../Icons/Python Folder.icns")
1447
1448 # Create the installer
1449 buildInstaller()
1450
1451 # And copy the readme into the directory containing the installer
1452 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1453
1454 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001455 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001456
1457 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001458 fp.write("# BUILD INFO\n")
1459 fp.write("# Date: %s\n" % time.ctime())
1460 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001461 fp.close()
1462
Thomas Wouters477c8d52006-05-27 19:21:47 +00001463 # And copy it to a DMG
1464 buildDMG()
1465
Thomas Wouters477c8d52006-05-27 19:21:47 +00001466if __name__ == "__main__":
1467 main()