blob: 710e84d1e6e41a908b83a65e00650fe4e1867a28 [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 Deily1ff32a92014-09-05 15:57:05 -0700618 runCommand('sphinx-build --version')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000619
Thomas Wouters89f507f2006-12-13 04:49:30 +0000620def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000621 """
622 Parse arguments and update global settings.
623 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000624 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800625 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000626
627 if args is None:
628 args = sys.argv[1:]
629
630 try:
631 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000632 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
633 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800634 except getopt.GetoptError:
635 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000636 sys.exit(1)
637
638 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800639 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000640 sys.exit(1)
641
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000642 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000643 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000644 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800645 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000646 sys.exit(0)
647
648 elif k in ('-d', '--build-dir'):
649 WORKDIR=v
650
651 elif k in ('--third-party',):
652 DEPSRC=v
653
654 elif k in ('--sdk-path',):
655 SDKPATH=v
656
657 elif k in ('--src-dir',):
658 SRCDIR=v
659
Ronald Oussoren1943f862009-03-30 19:39:14 +0000660 elif k in ('--dep-target', ):
661 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000662 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000663
664 elif k in ('--universal-archs', ):
665 if v in UNIVERSALOPTS:
666 UNIVERSALARCHS = v
667 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000668 if deptarget is None:
669 # Select alternate default deployment
670 # target
671 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000672 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800673 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000674
Thomas Wouters477c8d52006-05-27 19:21:47 +0000675 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800676 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677
678 SRCDIR=os.path.abspath(SRCDIR)
679 WORKDIR=os.path.abspath(WORKDIR)
680 SDKPATH=os.path.abspath(SDKPATH)
681 DEPSRC=os.path.abspath(DEPSRC)
682
Ned Deily04cdfa12014-06-25 13:36:14 -0700683 CC, CXX = getTargetCompilers()
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000684
Ned Deily4a96a372013-01-29 00:08:32 -0800685 print("Settings:")
686 print(" * Source directory:", SRCDIR)
687 print(" * Build directory: ", WORKDIR)
688 print(" * SDK location: ", SDKPATH)
689 print(" * Third-party source:", DEPSRC)
690 print(" * Deployment target:", DEPTARGET)
691 print(" * Universal architectures:", ARCHLIST)
692 print(" * C compiler:", CC)
693 print(" * C++ compiler:", CXX)
694 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000695
696
697
698
699def extractArchive(builddir, archiveName):
700 """
701 Extract a source archive into 'builddir'. Returns the path of the
702 extracted archive.
703
704 XXX: This function assumes that archives contain a toplevel directory
705 that is has the same name as the basename of the archive. This is
Ned Deily5b3582c2013-10-25 00:41:46 -0700706 safe enough for almost anything we use. Unfortunately, it does not
707 work for current Tcl and Tk source releases where the basename of
708 the archive ends with "-src" but the uncompressed directory does not.
709 For now, just special case Tcl and Tk tar.gz downloads.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000710 """
711 curdir = os.getcwd()
712 try:
713 os.chdir(builddir)
714 if archiveName.endswith('.tar.gz'):
715 retval = os.path.basename(archiveName[:-7])
Ned Deily5b3582c2013-10-25 00:41:46 -0700716 if ((retval.startswith('tcl') or retval.startswith('tk'))
717 and retval.endswith('-src')):
718 retval = retval[:-4]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000719 if os.path.exists(retval):
720 shutil.rmtree(retval)
721 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
722
723 elif archiveName.endswith('.tar.bz2'):
724 retval = os.path.basename(archiveName[:-8])
725 if os.path.exists(retval):
726 shutil.rmtree(retval)
727 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
728
729 elif archiveName.endswith('.tar'):
730 retval = os.path.basename(archiveName[:-4])
731 if os.path.exists(retval):
732 shutil.rmtree(retval)
733 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
734
735 elif archiveName.endswith('.zip'):
736 retval = os.path.basename(archiveName[:-4])
737 if os.path.exists(retval):
738 shutil.rmtree(retval)
739 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
740
741 data = fp.read()
742 xit = fp.close()
743 if xit is not None:
744 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800745 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000746
747 return os.path.join(builddir, retval)
748
749 finally:
750 os.chdir(curdir)
751
Thomas Wouters477c8d52006-05-27 19:21:47 +0000752def downloadURL(url, fname):
753 """
754 Download the contents of the url into the file.
755 """
Ned Deily4a96a372013-01-29 00:08:32 -0800756 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000757 fpOut = open(fname, 'wb')
758 block = fpIn.read(10240)
759 try:
760 while block:
761 fpOut.write(block)
762 block = fpIn.read(10240)
763 fpIn.close()
764 fpOut.close()
765 except:
766 try:
767 os.unlink(fname)
768 except:
769 pass
770
Ned Deily4a96a372013-01-29 00:08:32 -0800771def verifyThirdPartyFile(url, checksum, fname):
772 """
773 Download file from url to filename fname if it does not already exist.
774 Abort if file contents does not match supplied md5 checksum.
775 """
776 name = os.path.basename(fname)
777 if os.path.exists(fname):
778 print("Using local copy of %s"%(name,))
779 else:
780 print("Did not find local copy of %s"%(name,))
781 print("Downloading %s"%(name,))
782 downloadURL(url, fname)
783 print("Archive for %s stored as %s"%(name, fname))
784 if os.system(
785 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
786 % (shellQuote(fname), checksum) ):
787 fatal('MD5 checksum mismatch for file %s' % fname)
788
Thomas Wouters477c8d52006-05-27 19:21:47 +0000789def buildRecipe(recipe, basedir, archList):
790 """
791 Build software using a recipe. This function does the
792 'configure;make;make install' dance for C software, with a possibility
793 to customize this process, basically a poor-mans DarwinPorts.
794 """
795 curdir = os.getcwd()
796
797 name = recipe['name']
798 url = recipe['url']
799 configure = recipe.get('configure', './configure')
800 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
801 shellQuote(basedir)))
802
803 archiveName = os.path.split(url)[-1]
804 sourceArchive = os.path.join(DEPSRC, archiveName)
805
806 if not os.path.exists(DEPSRC):
807 os.mkdir(DEPSRC)
808
Ned Deily4a96a372013-01-29 00:08:32 -0800809 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
810 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000811 buildDir=os.path.join(WORKDIR, '_bld')
812 if not os.path.exists(buildDir):
813 os.mkdir(buildDir)
814
815 workDir = extractArchive(buildDir, sourceArchive)
816 os.chdir(workDir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000817
Ned Deily4a96a372013-01-29 00:08:32 -0800818 for patch in recipe.get('patches', ()):
819 if isinstance(patch, tuple):
820 url, checksum = patch
821 fn = os.path.join(DEPSRC, os.path.basename(url))
822 verifyThirdPartyFile(url, checksum, fn)
823 else:
824 # patch is a file in the source directory
825 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000826 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
827 shellQuote(fn),))
828
Ned Deily4a96a372013-01-29 00:08:32 -0800829 for patchscript in recipe.get('patchscripts', ()):
830 if isinstance(patchscript, tuple):
831 url, checksum = patchscript
832 fn = os.path.join(DEPSRC, os.path.basename(url))
833 verifyThirdPartyFile(url, checksum, fn)
834 else:
835 # patch is a file in the source directory
836 fn = os.path.join(curdir, patchscript)
837 if fn.endswith('.bz2'):
838 runCommand('bunzip2 -fk %s' % shellQuote(fn))
839 fn = fn[:-4]
840 runCommand('sh %s' % shellQuote(fn))
841 os.unlink(fn)
842
Ned Deily94764b22013-10-27 19:49:29 -0700843 if 'buildDir' in recipe:
844 os.chdir(recipe['buildDir'])
845
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000846 if configure is not None:
847 configure_args = [
848 "--prefix=/usr/local",
849 "--enable-static",
850 "--disable-shared",
851 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
852 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000853
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000854 if 'configure_pre' in recipe:
855 args = list(recipe['configure_pre'])
856 if '--disable-static' in args:
857 configure_args.remove('--enable-static')
858 if '--enable-shared' in args:
859 configure_args.remove('--disable-shared')
860 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000861
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000862 if recipe.get('useLDFlags', 1):
863 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800864 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
865 "-I%s/usr/local/include"%(
866 recipe.get('extra_cflags', ''),
867 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000868 ' -arch '.join(archList),
869 shellQuote(SDKPATH)[1:-1],
870 shellQuote(basedir)[1:-1],),
Ned Deily0134a352014-04-09 16:16:08 -0700871 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deily4a96a372013-01-29 00:08:32 -0800872 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000873 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000874 shellQuote(basedir)[1:-1],
875 ' -arch '.join(archList)),
876 ])
877 else:
878 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800879 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
880 "-I%s/usr/local/include"%(
881 recipe.get('extra_cflags', ''),
882 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000883 ' -arch '.join(archList),
884 shellQuote(SDKPATH)[1:-1],
885 shellQuote(basedir)[1:-1],),
886 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000887
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000888 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800889 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000891 configure_args.insert(0, configure)
892 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000893
Ned Deily4a96a372013-01-29 00:08:32 -0800894 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000895 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000896
Ned Deily4a96a372013-01-29 00:08:32 -0800897 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000898 runCommand('{ ' + install + ' ;} 2>&1')
899
Ned Deily4a96a372013-01-29 00:08:32 -0800900 print("Done %s"%(name,))
901 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000902
903 os.chdir(curdir)
904
905def buildLibraries():
906 """
907 Build our dependencies into $WORKDIR/libraries/usr/local
908 """
Ned Deily4a96a372013-01-29 00:08:32 -0800909 print("")
910 print("Building required libraries")
911 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000912 universal = os.path.join(WORKDIR, 'libraries')
913 os.mkdir(universal)
914 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
915 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
916
Ronald Oussoren1943f862009-03-30 19:39:14 +0000917 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000918 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000919
920
921
922def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000923 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000924 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800925 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000926 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000927 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000928 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000929 curDir = os.getcwd()
930 os.chdir(buildDir)
Ned Deily1ff32a92014-09-05 15:57:05 -0700931 # The Doc build changed for 3.4 (technically, for 3.4.1) and for 2.7.9
932 runCommand('make clean')
933 # Assume sphinx-build is on our PATH, checked in checkEnvironment
934 runCommand('make html')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000935 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000936 if not os.path.exists(docdir):
937 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000938 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000939
940
941def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800942 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000943
944 buildDir = os.path.join(WORKDIR, '_bld', 'python')
945 rootDir = os.path.join(WORKDIR, '_root')
946
947 if os.path.exists(buildDir):
948 shutil.rmtree(buildDir)
949 if os.path.exists(rootDir):
950 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000951 os.makedirs(buildDir)
952 os.makedirs(rootDir)
953 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000954 curdir = os.getcwd()
955 os.chdir(buildDir)
956
957 # Not sure if this is still needed, the original build script
958 # claims that parts of the install assume python.exe exists.
959 os.symlink('python', os.path.join(buildDir, 'python.exe'))
960
961 # Extract the version from the configure file, needed to calculate
962 # several paths.
963 version = getVersion()
964
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000965 # Since the extra libs are not in their installed framework location
966 # during the build, augment the library path so that the interpreter
967 # will find them during its extension import sanity checks.
968 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
969 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800970 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000971 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000972 "--with-universal-archs=%s "
973 "%s "
Ned Deily41ab6c32013-11-22 22:25:43 -0800974 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000975 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deily4b7a0232013-10-25 00:46:02 -0700976 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000977 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
978 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000979 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deily41ab6c32013-11-22 22:25:43 -0800980 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000981 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000982 shellQuote(WORKDIR)[1:-1]))
983
Ned Deily729148b2014-05-22 15:28:06 -0700984 print("Running make touch")
985 runCommand("make touch")
986
Ned Deily4a96a372013-01-29 00:08:32 -0800987 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000988 runCommand("make")
989
Ned Deily4a96a372013-01-29 00:08:32 -0800990 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000991 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992 shellQuote(rootDir)))
993
Ned Deily4a96a372013-01-29 00:08:32 -0800994 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000995 runCommand("make frameworkinstallextras DESTDIR=%s"%(
996 shellQuote(rootDir)))
997
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000998 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800999 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001000 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1001 runCommand("mv %s/* %s"%(
1002 shellQuote(os.path.join(
1003 WORKDIR, 'libraries', 'Library', 'Frameworks',
1004 'Python.framework', 'Versions', getVersion(),
1005 'lib')),
1006 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1007 'Python.framework', 'Versions', getVersion(),
1008 'lib'))))
1009
Ned Deily050fcd52013-10-26 03:16:44 -07001010 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1011 'Python.framework', 'Versions',
1012 version, 'lib', 'python%s'%(version,))
1013
Ned Deily4a96a372013-01-29 00:08:32 -08001014 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001015 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +00001016 gid = grp.getgrnam('admin').gr_gid
1017
Ned Deily4a96a372013-01-29 00:08:32 -08001018 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +00001019 for dirpath, dirnames, filenames in os.walk(frmDir):
1020 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -08001021 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001022 os.chown(os.path.join(dirpath, dn), -1, gid)
1023
Thomas Wouters477c8d52006-05-27 19:21:47 +00001024 for fn in filenames:
1025 if os.path.islink(fn):
1026 continue
1027
1028 # "chmod g+w $fn"
1029 p = os.path.join(dirpath, fn)
1030 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +00001031 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1032 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001033
Ned Deily4a96a372013-01-29 00:08:32 -08001034 if fn in EXPECTED_SHARED_LIBS:
1035 # check to see that this file was linked with the
1036 # expected library path and version
1037 data = captureCommand("otool -L %s" % shellQuote(p))
1038 for sl in EXPECTED_SHARED_LIBS[fn]:
1039 if ("\t%s " % sl) not in data:
1040 print("Expected shared lib %s was not linked with %s"
1041 % (sl, p))
1042 shared_lib_error = True
1043
1044 if shared_lib_error:
1045 fatal("Unexpected shared library errors.")
1046
Ned Deilye59e4c52011-01-29 18:56:28 +00001047 if PYTHON_3:
1048 LDVERSION=None
1049 VERSION=None
1050 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001051
Ned Deilye59e4c52011-01-29 18:56:28 +00001052 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001053 for ln in fp:
1054 if ln.startswith('VERSION='):
1055 VERSION=ln.split()[1]
1056 if ln.startswith('ABIFLAGS='):
1057 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001058 if ln.startswith('LDVERSION='):
1059 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +00001060 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001061
Ned Deilye59e4c52011-01-29 18:56:28 +00001062 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1063 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1064 config_suffix = '-' + LDVERSION
1065 else:
1066 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +00001067
Thomas Wouters477c8d52006-05-27 19:21:47 +00001068 # We added some directories to the search path during the configure
1069 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -08001070 # the end-users system. Also remove the directories from _sysconfigdata.py
1071 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001072
Ned Deilya4f6b002013-10-25 00:47:38 -07001073 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1074 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1075
Ned Deilya4f6b002013-10-25 00:47:38 -07001076 # fix Makefile
1077 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1078 fp = open(path, 'r')
1079 data = fp.read()
1080 fp.close()
1081
1082 for p in (include_path, lib_path):
1083 data = data.replace(" " + p, '')
1084 data = data.replace(p + " ", '')
1085
1086 fp = open(path, 'w')
1087 fp.write(data)
1088 fp.close()
1089
1090 # fix _sysconfigdata if it exists
1091 #
1092 # TODO: make this more robust! test_sysconfig_module of
1093 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1094 # the output from get_config_var in both sysconfig and
1095 # distutils.sysconfig is exactly the same for both CFLAGS and
1096 # LDFLAGS. The fixing up is now complicated by the pretty
1097 # printing in _sysconfigdata.py. Also, we are using the
1098 # pprint from the Python running the installer build which
1099 # may not cosmetically format the same as the pprint in the Python
1100 # being built (and which is used to originally generate
1101 # _sysconfigdata.py).
1102
1103 import pprint
1104 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1105 if os.path.exists(path):
Ned Deily4a96a372013-01-29 00:08:32 -08001106 fp = open(path, 'r')
1107 data = fp.read()
1108 fp.close()
Ned Deilya4f6b002013-10-25 00:47:38 -07001109 # create build_time_vars dict
1110 exec(data)
1111 vars = {}
1112 for k, v in build_time_vars.items():
1113 if type(v) == type(''):
1114 for p in (include_path, lib_path):
1115 v = v.replace(' ' + p, '')
1116 v = v.replace(p + ' ', '')
1117 vars[k] = v
Ned Deily4a96a372013-01-29 00:08:32 -08001118
Ned Deily4a96a372013-01-29 00:08:32 -08001119 fp = open(path, 'w')
Ned Deilya4f6b002013-10-25 00:47:38 -07001120 # duplicated from sysconfig._generate_posix_vars()
1121 fp.write('# system configuration generated and used by'
1122 ' the sysconfig module\n')
1123 fp.write('build_time_vars = ')
1124 pprint.pprint(vars, stream=fp)
Ned Deily4a96a372013-01-29 00:08:32 -08001125 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001126
1127 # Add symlinks in /usr/local/bin, using relative links
1128 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1129 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1130 'Python.framework', 'Versions', version, 'bin')
1131 if os.path.exists(usr_local_bin):
1132 shutil.rmtree(usr_local_bin)
1133 os.makedirs(usr_local_bin)
1134 for fn in os.listdir(
1135 os.path.join(frmDir, 'Versions', version, 'bin')):
1136 os.symlink(os.path.join(to_framework, fn),
1137 os.path.join(usr_local_bin, fn))
1138
1139 os.chdir(curdir)
1140
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001141 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001142 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001143 # with an already installed version of python 2
1144 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1145 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001146
1147def patchFile(inPath, outPath):
1148 data = fileContents(inPath)
1149 data = data.replace('$FULL_VERSION', getFullVersion())
1150 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001151 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001152 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001153 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001154
1155 # This one is not handy as a template variable
1156 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001157 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001158 fp.write(data)
1159 fp.close()
1160
1161def patchScript(inPath, outPath):
1162 data = fileContents(inPath)
1163 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001164 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001165 fp.write(data)
1166 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001167 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001168
1169
1170
1171def packageFromRecipe(targetDir, recipe):
1172 curdir = os.getcwd()
1173 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001174 # The major version (such as 2.5) is included in the package name
1175 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001176 # common.
1177 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001178 srcdir = recipe.get('source')
1179 pkgroot = recipe.get('topdir', srcdir)
1180 postflight = recipe.get('postflight')
1181 readme = textwrap.dedent(recipe['readme'])
1182 isRequired = recipe.get('required', True)
1183
Ned Deily4a96a372013-01-29 00:08:32 -08001184 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001185
1186 # Substitute some variables
1187 textvars = dict(
1188 VER=getVersion(),
1189 FULLVER=getFullVersion(),
1190 )
1191 readme = readme % textvars
1192
1193 if pkgroot is not None:
1194 pkgroot = pkgroot % textvars
1195 else:
1196 pkgroot = '/'
1197
1198 if srcdir is not None:
1199 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1200 srcdir = srcdir % textvars
1201
1202 if postflight is not None:
1203 postflight = os.path.abspath(postflight)
1204
1205 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1206 os.makedirs(packageContents)
1207
1208 if srcdir is not None:
1209 os.chdir(srcdir)
1210 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1211 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1212 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1213
1214 fn = os.path.join(packageContents, 'PkgInfo')
1215 fp = open(fn, 'w')
1216 fp.write('pmkrpkg1')
1217 fp.close()
1218
1219 rsrcDir = os.path.join(packageContents, "Resources")
1220 os.mkdir(rsrcDir)
1221 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1222 fp.write(readme)
1223 fp.close()
1224
1225 if postflight is not None:
1226 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1227
1228 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001229 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001230 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001231 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1232 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1233 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001234 CFBundleShortVersionString=vers,
1235 IFMajorVersion=major,
1236 IFMinorVersion=minor,
1237 IFPkgFormatVersion=0.10000000149011612,
1238 IFPkgFlagAllowBackRev=False,
1239 IFPkgFlagAuthorizationAction="RootAuthorization",
1240 IFPkgFlagDefaultLocation=pkgroot,
1241 IFPkgFlagFollowLinks=True,
1242 IFPkgFlagInstallFat=True,
1243 IFPkgFlagIsRequired=isRequired,
1244 IFPkgFlagOverwritePermissions=False,
1245 IFPkgFlagRelocatable=False,
1246 IFPkgFlagRestartAction="NoRestart",
1247 IFPkgFlagRootVolumeOnly=True,
1248 IFPkgFlagUpdateInstalledLangauges=False,
1249 )
1250 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1251
1252 pl = Plist(
1253 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001254 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001255 IFPkgDescriptionVersion=vers,
1256 )
1257 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1258
1259 finally:
1260 os.chdir(curdir)
1261
1262
1263def makeMpkgPlist(path):
1264
1265 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001266 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001267
1268 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001269 CFBundleGetInfoString="Python %s"%(vers,),
1270 CFBundleIdentifier='org.python.Python',
1271 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001272 CFBundleShortVersionString=vers,
1273 IFMajorVersion=major,
1274 IFMinorVersion=minor,
1275 IFPkgFlagComponentDirectory="Contents/Packages",
1276 IFPkgFlagPackageList=[
1277 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001278 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001279 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001280 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001281 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001282 ],
1283 IFPkgFormatVersion=0.10000000149011612,
1284 IFPkgFlagBackgroundScaling="proportional",
1285 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001286 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001287 )
1288
1289 writePlist(pl, path)
1290
1291
1292def buildInstaller():
1293
1294 # Zap all compiled files
1295 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1296 for fn in filenames:
1297 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1298 os.unlink(os.path.join(dirpath, fn))
1299
1300 outdir = os.path.join(WORKDIR, 'installer')
1301 if os.path.exists(outdir):
1302 shutil.rmtree(outdir)
1303 os.mkdir(outdir)
1304
Ronald Oussoren1943f862009-03-30 19:39:14 +00001305 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001306 pkgcontents = os.path.join(pkgroot, 'Packages')
1307 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001308 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001309 packageFromRecipe(pkgcontents, recipe)
1310
1311 rsrcDir = os.path.join(pkgroot, 'Resources')
1312
1313 fn = os.path.join(pkgroot, 'PkgInfo')
1314 fp = open(fn, 'w')
1315 fp.write('pmkrpkg1')
1316 fp.close()
1317
1318 os.mkdir(rsrcDir)
1319
1320 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1321 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001322 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001323 IFPkgDescriptionVersion=getVersion(),
1324 )
1325
1326 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1327 for fn in os.listdir('resources'):
1328 if fn == '.svn': continue
1329 if fn.endswith('.jpg'):
1330 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1331 else:
1332 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1333
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001334 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001335
1336
1337def installSize(clear=False, _saved=[]):
1338 if clear:
1339 del _saved[:]
1340 if not _saved:
1341 data = captureCommand("du -ks %s"%(
1342 shellQuote(os.path.join(WORKDIR, '_root'))))
1343 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1344 return _saved[0]
1345
1346
1347def buildDMG():
1348 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001349 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001350 """
1351 outdir = os.path.join(WORKDIR, 'diskimage')
1352 if os.path.exists(outdir):
1353 shutil.rmtree(outdir)
1354
1355 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001356 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001357 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001358 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001359 imagepath = imagepath + '.dmg'
1360
1361 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001362 volname='Python %s'%(getFullVersion())
1363 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1364 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001365 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001366 shellQuote(imagepath + ".tmp.dmg" )))
1367
1368
1369 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1370 os.mkdir(os.path.join(WORKDIR, "mnt"))
1371 runCommand("hdiutil attach %s -mountroot %s"%(
1372 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1373
1374 # Custom icon for the DMG, shown when the DMG is mounted.
1375 shutil.copy("../Icons/Disk Image.icns",
1376 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001377 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001378 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1379
1380 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1381
1382 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1383 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1384 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1385 setIcon(imagepath, "../Icons/Disk Image.icns")
1386
1387 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001388
1389 return imagepath
1390
1391
1392def setIcon(filePath, icnsPath):
1393 """
1394 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001395 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001396
Ronald Oussoren70050672010-04-30 15:00:26 +00001397 dirPath = os.path.normpath(os.path.dirname(__file__))
1398 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001399 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1400 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1401 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001402 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1403 if not os.path.exists(appPath):
1404 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001405 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1406 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001407
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001408 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1409 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001410
1411def main():
1412 # First parse options and check if we can perform our work
1413 parseOptions()
1414 checkEnvironment()
1415
Ronald Oussoren1943f862009-03-30 19:39:14 +00001416 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001417 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001418 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001419
1420 if os.path.exists(WORKDIR):
1421 shutil.rmtree(WORKDIR)
1422 os.mkdir(WORKDIR)
1423
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001424 os.environ['LC_ALL'] = 'C'
1425
Thomas Wouters477c8d52006-05-27 19:21:47 +00001426 # Then build third-party libraries such as sleepycat DB4.
1427 buildLibraries()
1428
1429 # Now build python itself
1430 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001431
1432 # And then build the documentation
1433 # Remove the Deployment Target from the shell
1434 # environment, it's no longer needed and
1435 # an unexpected build target can cause problems
1436 # when Sphinx and its dependencies need to
1437 # be (re-)installed.
1438 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001439 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001440
1441
1442 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001443 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001444 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001445 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001446
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001447 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001448 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001449 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001450 setIcon(folder, "../Icons/Python Folder.icns")
1451
1452 # Create the installer
1453 buildInstaller()
1454
1455 # And copy the readme into the directory containing the installer
1456 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1457
1458 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001459 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001460
1461 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001462 fp.write("# BUILD INFO\n")
1463 fp.write("# Date: %s\n" % time.ctime())
1464 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001465 fp.close()
1466
Thomas Wouters477c8d52006-05-27 19:21:47 +00001467 # And copy it to a DMG
1468 buildDMG()
1469
Thomas Wouters477c8d52006-05-27 19:21:47 +00001470if __name__ == "__main__":
1471 main()