blob: a67a60662d20680c7fbecfd7fce52d4d89e3194a [file] [log] [blame]
Ned Deilye1c9794952013-01-29 00:07:46 -08001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deily53c460d2011-01-30 01:43:40 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deilye1c9794952013-01-29 00:07:46 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deily53c460d2011-01-30 01:43:40 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
Ned Deily53c460d2011-01-30 01:43:40 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
Ned Deilyebd63dc2014-04-09 16:12:11 -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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000014
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -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 Deilye1c9794952013-01-29 00:07:46 -080028
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000029Usage: see USAGE variable in the script.
30"""
Ned Deilye1c9794952013-01-29 00:07:46 -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 )
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000044
Ronald Oussoren158ad592006-11-07 16:00:34 +000045INCLUDE_TIMESTAMP = 1
46VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000047
48from plistlib import Plist
49
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000059 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -080070 raise RuntimeError("Cannot find variable %s" % variable[:-1])
71
72_cache_getVersion = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000073
74def getVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000080
Ned Deily53c460d2011-01-30 01:43:40 +000081def getVersionTuple():
82 return tuple([int(n) for n in getVersion().split('.')])
83
Ned Deilye1c9794952013-01-29 00:07:46 -080084def getVersionMajorMinor():
85 return tuple([int(n) for n in getVersion().split('.', 2)])
86
87_cache_getFullVersion = None
88
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000089def getFullVersion():
Ned Deilye1c9794952013-01-29 00:07:46 -080090 global _cache_getFullVersion
91 if _cache_getFullVersion is not None:
92 return _cache_getFullVersion
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000093 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
94 for ln in open(fn):
95 if 'PY_VERSION' in ln:
Ned Deilye1c9794952013-01-29 00:07:46 -080096 _cache_getFullVersion = ln.split()[-1][1:-1]
97 return _cache_getFullVersion
98 raise RuntimeError("Cannot find full version??")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000099
Ronald Oussorenaa560962006-11-07 15:53:38 +0000100# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000101WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000102
Ronald Oussorenaa560962006-11-07 15:53:38 +0000103# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000104# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +0000105DEPSRC = os.path.join(WORKDIR, 'third-party')
106DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000107
108# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +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 Melotti6d0f0f22013-08-26 01:31:30 +0300113### issue is resolved.
Ronald Oussoren209d4c32009-09-29 19:34:13 +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
123SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000124
Ronald Oussoren508282e2009-03-30 19:34:51 +0000125universal_opts_map = { '32-bit': ('i386', 'ppc',),
126 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Oussoren508282e2009-03-30 19:34:51 +0000136
137UNIVERSALOPTS = tuple(universal_opts_map.keys())
138
139UNIVERSALARCHS = '32-bit'
140
141ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000142
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200143# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000144SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000145 os.path.dirname(
146 os.path.dirname(
147 os.path.abspath(__file__
148 ))))
149
Ronald Oussoren508282e2009-03-30 19:34:51 +0000150# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
151DEPTARGET = '10.3'
152
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000153target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -0800154 '10.3': ('gcc-4.0', 'g++-4.0'),
155 '10.4': ('gcc-4.0', 'g++-4.0'),
156 '10.5': ('gcc-4.2', 'g++-4.2'),
157 '10.6': ('gcc-4.2', 'g++-4.2'),
158 '10.7': ('clang', 'clang++'),
159 '10.8': ('clang', 'clang++'),
Ned Deily11f880a2013-10-18 20:40:23 -0700160 '10.9': ('clang', 'clang++'),
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000161}
162
Ned Deilye1c9794952013-01-29 00:07:46 -0800163CC, CXX = target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000164
Ned Deily53c460d2011-01-30 01:43:40 +0000165PYTHON_3 = getVersionTuple() >= (3, 0)
166
Ronald Oussoren158ad592006-11-07 16:00:34 +0000167USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000168 Usage: build_python [options]
169
170 Options:
171 -? or -h: Show this message
172 -b DIR
173 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
174 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
175 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
176 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000177 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
178 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000179""")% globals()
180
Ned Deilye1c9794952013-01-29 00:07:46 -0800181# Dict of object file names with shared library names to check after building.
182# This is to ensure that we ended up dynamically linking with the shared
183# library paths and versions we expected. For example:
184# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
185# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
186# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
187EXPECTED_SHARED_LIBS = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000188
189# Instructions for building libraries that are necessary for building a
190# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000191# [The recipes are defined here for convenience but instantiated later after
192# command line options have been processed.]
193def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000194 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000195
Ned Deilye1c9794952013-01-29 00:07:46 -0800196 LT_10_5 = bool(DEPTARGET < '10.5')
197
Ned Deilyebd63dc2014-04-09 16:12:11 -0700198# Disable for now
199 if False: # if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 5)):
Ned Deily0203a802013-10-25 00:40:07 -0700200 result.extend([
201 dict(
202 name="Tcl 8.5.15",
203 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
204 checksum='f3df162f92c69b254079c4d0af7a690f',
205 buildDir="unix",
206 configure_pre=[
207 '--enable-shared',
208 '--enable-threads',
209 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
210 ],
211 useLDFlags=False,
212 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
213 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
214 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
215 },
216 ),
217 dict(
218 name="Tk 8.5.15",
219 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
220 checksum='55b8e33f903210a4e1c8bce0f820657f',
Ned Deilya6cbff02013-10-27 19:47:23 -0700221 patches=[
222 "issue19373_tk_8_5_15_source.patch",
223 ],
Ned Deily0203a802013-10-25 00:40:07 -0700224 buildDir="unix",
225 configure_pre=[
226 '--enable-aqua',
227 '--enable-shared',
228 '--enable-threads',
229 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
230 ],
231 useLDFlags=False,
232 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'%{
233 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
234 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
235 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
236 },
237 ),
238 ])
239
Ned Deilye1c9794952013-01-29 00:07:46 -0800240 if getVersionTuple() >= (3, 3):
241 result.extend([
242 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700243 name="XZ 5.0.5",
244 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
245 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deilye1c9794952013-01-29 00:07:46 -0800246 configure_pre=[
247 '--disable-dependency-tracking',
248 ]
249 ),
250 ])
251
252 result.extend([
253 dict(
254 name="NCurses 5.9",
255 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
256 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
257 configure_pre=[
258 "--enable-widec",
259 "--without-cxx",
260 "--without-cxx-binding",
261 "--without-ada",
262 "--without-curses-h",
263 "--enable-shared",
264 "--with-shared",
265 "--without-debug",
266 "--without-normal",
267 "--without-tests",
268 "--without-manpages",
269 "--datadir=/usr/share",
270 "--sysconfdir=/etc",
271 "--sharedstatedir=/usr/com",
272 "--with-terminfo-dirs=/usr/share/terminfo",
273 "--with-default-terminfo-dir=/usr/share/terminfo",
274 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
275 ],
276 patchscripts=[
277 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
278 "f54bf02a349f96a7c4f0d00922f3a0d4"),
279 ],
280 useLDFlags=False,
281 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
282 shellQuote(os.path.join(WORKDIR, 'libraries')),
283 shellQuote(os.path.join(WORKDIR, 'libraries')),
284 getVersion(),
285 ),
286 ),
287 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700288 name="SQLite 3.8.3.1",
289 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
290 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deilye1c9794952013-01-29 00:07:46 -0800291 extra_cflags=('-Os '
292 '-DSQLITE_ENABLE_FTS4 '
293 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
294 '-DSQLITE_ENABLE_RTREE '
295 '-DSQLITE_TCL=0 '
296 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
297 configure_pre=[
298 '--enable-threadsafe',
299 '--enable-shared=no',
300 '--enable-static=yes',
301 '--disable-readline',
302 '--disable-dependency-tracking',
303 ]
304 ),
305 ])
306
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000307 if DEPTARGET < '10.5':
308 result.extend([
309 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000310 name="Bzip2 1.0.6",
311 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
312 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000313 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800314 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
315 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000316 shellQuote(os.path.join(WORKDIR, 'libraries')),
317 ' -arch '.join(ARCHLIST),
318 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000319 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000320 ),
321 dict(
322 name="ZLib 1.2.3",
323 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
324 checksum='debc62758716a169df9f62e6ab2bc634',
325 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800326 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
327 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000328 shellQuote(os.path.join(WORKDIR, 'libraries')),
329 ' -arch '.join(ARCHLIST),
330 SDKPATH,
331 ),
332 ),
333 dict(
334 # Note that GNU readline is GPL'd software
Ned Deily53c460d2011-01-30 01:43:40 +0000335 name="GNU Readline 6.1.2",
336 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
337 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000338 patchlevel='0',
339 patches=[
340 # The readline maintainers don't do actual micro releases, but
341 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -0800342 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
343 'c642f2e84d820884b0bf9fd176bc6c3f'),
344 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
345 '1a76781a1ea734e831588285db7ec9b1'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000346 ]
347 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000348 ])
349
Ned Deily53c460d2011-01-30 01:43:40 +0000350 if not PYTHON_3:
351 result.extend([
352 dict(
353 name="Sleepycat DB 4.7.25",
354 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
355 checksum='ec2b87e833779681a0c3a814aa71359e',
356 buildDir="build_unix",
357 configure="../dist/configure",
358 configure_pre=[
359 '--includedir=/usr/local/include/db4',
360 ]
361 ),
362 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000363
364 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000365
366
367# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000368def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000369 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deilyebd63dc2014-04-09 16:12:11 -0700370 # unselected if 3.0 through 3.3, selected otherwise (2.x or >= 3.4)
371 unselected_for_lt_python34 = ('selected', 'unselected')[(3, 0) <= getVersionTuple() < (3, 4)]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000372 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",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000383 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000384 ),
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).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000393
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000394 It also installs a number of examples and demos.
395 """,
396 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000397 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000398 ),
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,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000409 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000410 ),
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 Deilye1c9794952013-01-29 00:07:46 -0800418 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000419 """,
420 postflight="scripts/postflight.documentation",
421 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000422 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000423 ),
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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000431
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deilyebd63dc2014-04-09 16:12:11 -0700440 selected=unselected_for_lt_python34,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000441 ),
442 ]
443
Ned Deilyebd63dc2014-04-09 16:12:11 -0700444 if getVersionTuple() >= (3, 4):
445 result.append(
446 dict(
447 name="PythonInstallPip",
448 long_name="Install or upgrade pip",
449 readme="""\
450 This package installs (or upgrades from an earlier version)
451 pip, a tool for installing and managing Python packages.
452 """,
453 postflight="scripts/postflight.ensurepip",
454 topdir="/Library/Frameworks/Python.framework",
455 source="/empty-dir",
456 required=False,
457 selected='selected',
458 )
459 )
460
Ned Deilye1c9794952013-01-29 00:07:46 -0800461 if DEPTARGET < '10.4' and not PYTHON_3:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000462 result.append(
463 dict(
464 name="PythonSystemFixes",
465 long_name="Fix system Python",
466 readme="""\
467 This package updates the system python installation on
468 Mac OS X 10.3 to ensure that you can build new python extensions
469 using that copy of python after installing this version.
470 """,
471 postflight="../Tools/fixapplepython23.py",
472 topdir="/Library/Frameworks/Python.framework",
473 source="/empty-dir",
474 required=False,
Ned Deily53c460d2011-01-30 01:43:40 +0000475 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000476 )
477 )
Ned Deilyebd63dc2014-04-09 16:12:11 -0700478
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000479 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000480
481def fatal(msg):
482 """
483 A fatal error, bail out.
484 """
485 sys.stderr.write('FATAL: ')
486 sys.stderr.write(msg)
487 sys.stderr.write('\n')
488 sys.exit(1)
489
490def fileContents(fn):
491 """
492 Return the contents of the named file
493 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800494 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000495
496def runCommand(commandline):
497 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200498 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000499 unless the command fails.
500 """
501 fd = os.popen(commandline, 'r')
502 data = fd.read()
503 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000504 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000505 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800506 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000507
508 if VERBOSE:
509 sys.stdout.write(data); sys.stdout.flush()
510
511def captureCommand(commandline):
512 fd = os.popen(commandline, 'r')
513 data = fd.read()
514 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000515 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000516 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800517 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000518
519 return data
520
Ronald Oussoren287128a2010-04-18 14:01:05 +0000521def getTclTkVersion(configfile, versionline):
522 """
523 search Tcl or Tk configuration file for version line
524 """
525 try:
526 f = open(configfile, "r")
527 except:
528 fatal("Framework configuration file not found: %s" % configfile)
529
530 for l in f:
531 if l.startswith(versionline):
532 f.close()
533 return l
534
535 fatal("Version variable %s not found in framework configuration file: %s"
536 % (versionline, configfile))
537
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000538def checkEnvironment():
539 """
540 Check that we're running on a supported system.
541 """
542
Ned Deily53c460d2011-01-30 01:43:40 +0000543 if sys.version_info[0:2] < (2, 4):
544 fatal("This script must be run with Python 2.4 or later")
545
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000546 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000547 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000548
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000549 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000550 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000551
552 if not os.path.exists(SDKPATH):
553 fatal("Please install the latest version of Xcode and the %s SDK"%(
554 os.path.basename(SDKPATH[:-4])))
555
Ronald Oussoren287128a2010-04-18 14:01:05 +0000556 # Because we only support dynamic load of only one major/minor version of
557 # Tcl/Tk, ensure:
558 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deilye1c9794952013-01-29 00:07:46 -0800559 # higher than the Apple-supplied system version in
560 # SDKROOT/System/Library/Frameworks
561 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
562 # in) SDKROOT/Library/Frameworks with the same version as the system
563 # version. This allows users to choose to install a newer patch level.
Ronald Oussoren287128a2010-04-18 14:01:05 +0000564
Ned Deilye1c9794952013-01-29 00:07:46 -0800565 frameworks = {}
Ronald Oussoren287128a2010-04-18 14:01:05 +0000566 for framework in ['Tcl', 'Tk']:
Ned Deilye1c9794952013-01-29 00:07:46 -0800567 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily53c460d2011-01-30 01:43:40 +0000568 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800569 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000570 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800571 frameworks[framework] = os.readlink(sysfw)
572 if not os.path.exists(libfw):
573 fatal("Please install a link to a current %s %s as %s so "
574 "the user can override the system framework."
575 % (framework, frameworks[framework], libfw))
Ned Deily53c460d2011-01-30 01:43:40 +0000576 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +0000577 fatal("Version of %s must match %s" % (libfw, sysfw) )
578 if os.path.exists(usrfw):
579 fatal("Please rename %s to avoid possible dynamic load issues."
580 % usrfw)
581
Ned Deilye1c9794952013-01-29 00:07:46 -0800582 if frameworks['Tcl'] != frameworks['Tk']:
583 fatal("The Tcl and Tk frameworks are not the same version.")
584
585 # add files to check after build
586 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
587 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
588 % frameworks['Tcl'],
589 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
590 % frameworks['Tk'],
591 ]
592
Ronald Oussoren287128a2010-04-18 14:01:05 +0000593 # Remove inherited environment variables which might influence build
594 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
595 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
596 for ev in list(os.environ):
597 for prefix in environ_var_prefixes:
598 if ev.startswith(prefix) :
Ned Deilye1c9794952013-01-29 00:07:46 -0800599 print("INFO: deleting environment variable %s=%s" % (
600 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000601 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000602
Ned Deilye1c9794952013-01-29 00:07:46 -0800603 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
604 if 'SDK_TOOLS_BIN' in os.environ:
605 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
606 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
607 # add its fixed location here if it exists
608 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
609 if os.path.isdir(OLD_DEVELOPER_TOOLS):
610 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
611 os.environ['PATH'] = base_path
612 print("Setting default PATH: %s"%(os.environ['PATH']))
Ned Deilyebd63dc2014-04-09 16:12:11 -0700613 # Ensure ws have access to hg and to sphinx-build.
614 # You may have to create links in /usr/bin for them.
615 runCommand('hg --version')
616 if getVersionTuple() >= (3, 4):
617 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000618
Ronald Oussoren158ad592006-11-07 16:00:34 +0000619def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000620 """
621 Parse arguments and update global settings.
622 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000623 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800624 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000625
626 if args is None:
627 args = sys.argv[1:]
628
629 try:
630 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000631 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
632 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800633 except getopt.GetoptError:
634 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000635 sys.exit(1)
636
637 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800638 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000639 sys.exit(1)
640
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000641 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000642 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000643 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800644 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000645 sys.exit(0)
646
647 elif k in ('-d', '--build-dir'):
648 WORKDIR=v
649
650 elif k in ('--third-party',):
651 DEPSRC=v
652
653 elif k in ('--sdk-path',):
654 SDKPATH=v
655
656 elif k in ('--src-dir',):
657 SRCDIR=v
658
Ronald Oussoren508282e2009-03-30 19:34:51 +0000659 elif k in ('--dep-target', ):
660 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000661 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000662
663 elif k in ('--universal-archs', ):
664 if v in UNIVERSALOPTS:
665 UNIVERSALARCHS = v
666 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000667 if deptarget is None:
668 # Select alternate default deployment
669 # target
670 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000671 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800672 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000673
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000674 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800675 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000676
677 SRCDIR=os.path.abspath(SRCDIR)
678 WORKDIR=os.path.abspath(WORKDIR)
679 SDKPATH=os.path.abspath(SDKPATH)
680 DEPSRC=os.path.abspath(DEPSRC)
681
Ned Deilye1c9794952013-01-29 00:07:46 -0800682 CC, CXX=target_cc_map[DEPTARGET]
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000683
Ned Deilye1c9794952013-01-29 00:07:46 -0800684 print("Settings:")
685 print(" * Source directory:", SRCDIR)
686 print(" * Build directory: ", WORKDIR)
687 print(" * SDK location: ", SDKPATH)
688 print(" * Third-party source:", DEPSRC)
689 print(" * Deployment target:", DEPTARGET)
690 print(" * Universal architectures:", ARCHLIST)
691 print(" * C compiler:", CC)
692 print(" * C++ compiler:", CXX)
693 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000694
695
696
697
698def extractArchive(builddir, archiveName):
699 """
700 Extract a source archive into 'builddir'. Returns the path of the
701 extracted archive.
702
703 XXX: This function assumes that archives contain a toplevel directory
704 that is has the same name as the basename of the archive. This is
Ned Deily0203a802013-10-25 00:40:07 -0700705 safe enough for almost anything we use. Unfortunately, it does not
706 work for current Tcl and Tk source releases where the basename of
707 the archive ends with "-src" but the uncompressed directory does not.
708 For now, just special case Tcl and Tk tar.gz downloads.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000709 """
710 curdir = os.getcwd()
711 try:
712 os.chdir(builddir)
713 if archiveName.endswith('.tar.gz'):
714 retval = os.path.basename(archiveName[:-7])
Ned Deily0203a802013-10-25 00:40:07 -0700715 if ((retval.startswith('tcl') or retval.startswith('tk'))
716 and retval.endswith('-src')):
717 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000718 if os.path.exists(retval):
719 shutil.rmtree(retval)
720 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
721
722 elif archiveName.endswith('.tar.bz2'):
723 retval = os.path.basename(archiveName[:-8])
724 if os.path.exists(retval):
725 shutil.rmtree(retval)
726 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
727
728 elif archiveName.endswith('.tar'):
729 retval = os.path.basename(archiveName[:-4])
730 if os.path.exists(retval):
731 shutil.rmtree(retval)
732 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
733
734 elif archiveName.endswith('.zip'):
735 retval = os.path.basename(archiveName[:-4])
736 if os.path.exists(retval):
737 shutil.rmtree(retval)
738 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
739
740 data = fp.read()
741 xit = fp.close()
742 if xit is not None:
743 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800744 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000745
746 return os.path.join(builddir, retval)
747
748 finally:
749 os.chdir(curdir)
750
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000751def downloadURL(url, fname):
752 """
753 Download the contents of the url into the file.
754 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800755 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000756 fpOut = open(fname, 'wb')
757 block = fpIn.read(10240)
758 try:
759 while block:
760 fpOut.write(block)
761 block = fpIn.read(10240)
762 fpIn.close()
763 fpOut.close()
764 except:
765 try:
766 os.unlink(fname)
767 except:
768 pass
769
Ned Deilye1c9794952013-01-29 00:07:46 -0800770def verifyThirdPartyFile(url, checksum, fname):
771 """
772 Download file from url to filename fname if it does not already exist.
773 Abort if file contents does not match supplied md5 checksum.
774 """
775 name = os.path.basename(fname)
776 if os.path.exists(fname):
777 print("Using local copy of %s"%(name,))
778 else:
779 print("Did not find local copy of %s"%(name,))
780 print("Downloading %s"%(name,))
781 downloadURL(url, fname)
782 print("Archive for %s stored as %s"%(name, fname))
783 if os.system(
784 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
785 % (shellQuote(fname), checksum) ):
786 fatal('MD5 checksum mismatch for file %s' % fname)
787
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000788def buildRecipe(recipe, basedir, archList):
789 """
790 Build software using a recipe. This function does the
791 'configure;make;make install' dance for C software, with a possibility
792 to customize this process, basically a poor-mans DarwinPorts.
793 """
794 curdir = os.getcwd()
795
796 name = recipe['name']
797 url = recipe['url']
798 configure = recipe.get('configure', './configure')
799 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
800 shellQuote(basedir)))
801
802 archiveName = os.path.split(url)[-1]
803 sourceArchive = os.path.join(DEPSRC, archiveName)
804
805 if not os.path.exists(DEPSRC):
806 os.mkdir(DEPSRC)
807
Ned Deilye1c9794952013-01-29 00:07:46 -0800808 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
809 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000810 buildDir=os.path.join(WORKDIR, '_bld')
811 if not os.path.exists(buildDir):
812 os.mkdir(buildDir)
813
814 workDir = extractArchive(buildDir, sourceArchive)
815 os.chdir(workDir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000816
Ned Deilye1c9794952013-01-29 00:07:46 -0800817 for patch in recipe.get('patches', ()):
818 if isinstance(patch, tuple):
819 url, checksum = patch
820 fn = os.path.join(DEPSRC, os.path.basename(url))
821 verifyThirdPartyFile(url, checksum, fn)
822 else:
823 # patch is a file in the source directory
824 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000825 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
826 shellQuote(fn),))
827
Ned Deilye1c9794952013-01-29 00:07:46 -0800828 for patchscript in recipe.get('patchscripts', ()):
829 if isinstance(patchscript, tuple):
830 url, checksum = patchscript
831 fn = os.path.join(DEPSRC, os.path.basename(url))
832 verifyThirdPartyFile(url, checksum, fn)
833 else:
834 # patch is a file in the source directory
835 fn = os.path.join(curdir, patchscript)
836 if fn.endswith('.bz2'):
837 runCommand('bunzip2 -fk %s' % shellQuote(fn))
838 fn = fn[:-4]
839 runCommand('sh %s' % shellQuote(fn))
840 os.unlink(fn)
841
Ned Deilya6cbff02013-10-27 19:47:23 -0700842 if 'buildDir' in recipe:
843 os.chdir(recipe['buildDir'])
844
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000845 if configure is not None:
846 configure_args = [
847 "--prefix=/usr/local",
848 "--enable-static",
849 "--disable-shared",
850 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
851 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000852
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000853 if 'configure_pre' in recipe:
854 args = list(recipe['configure_pre'])
855 if '--disable-static' in args:
856 configure_args.remove('--enable-static')
857 if '--enable-shared' in args:
858 configure_args.remove('--disable-shared')
859 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000860
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000861 if recipe.get('useLDFlags', 1):
862 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800863 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
864 "-I%s/usr/local/include"%(
865 recipe.get('extra_cflags', ''),
866 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000867 ' -arch '.join(archList),
868 shellQuote(SDKPATH)[1:-1],
869 shellQuote(basedir)[1:-1],),
Ned Deilyc177b1c2014-04-09 16:13:46 -0700870 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800871 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000872 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000873 shellQuote(basedir)[1:-1],
874 ' -arch '.join(archList)),
875 ])
876 else:
877 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800878 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
879 "-I%s/usr/local/include"%(
880 recipe.get('extra_cflags', ''),
881 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000882 ' -arch '.join(archList),
883 shellQuote(SDKPATH)[1:-1],
884 shellQuote(basedir)[1:-1],),
885 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000886
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000887 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -0800888 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000889
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000890 configure_args.insert(0, configure)
891 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000892
Ned Deilye1c9794952013-01-29 00:07:46 -0800893 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000894 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000895
Ned Deilye1c9794952013-01-29 00:07:46 -0800896 print("Running install for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000897 runCommand('{ ' + install + ' ;} 2>&1')
898
Ned Deilye1c9794952013-01-29 00:07:46 -0800899 print("Done %s"%(name,))
900 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000901
902 os.chdir(curdir)
903
904def buildLibraries():
905 """
906 Build our dependencies into $WORKDIR/libraries/usr/local
907 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800908 print("")
909 print("Building required libraries")
910 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000911 universal = os.path.join(WORKDIR, 'libraries')
912 os.mkdir(universal)
913 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
914 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
915
Ronald Oussoren508282e2009-03-30 19:34:51 +0000916 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000917 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000918
919
920
921def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000922 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000923 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -0800924 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000925 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000926 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000927 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000928 curDir = os.getcwd()
929 os.chdir(buildDir)
Ned Deilyebd63dc2014-04-09 16:12:11 -0700930 # The Doc build changed for 3.4 (technically, for 3.4.1)
931 if getVersionTuple() < (3, 4):
932 # This step does an svn checkout of sphinx and its dependencies
933 runCommand('make update')
934 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
935 else:
936 runCommand('make clean')
937 # Assume sphinx-build is on our PATH, checked in checkEnvironment
938 runCommand('make html')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000939 os.chdir(curDir)
940 if not os.path.exists(docdir):
941 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000942 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000943
944
945def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -0800946 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000947
948 buildDir = os.path.join(WORKDIR, '_bld', 'python')
949 rootDir = os.path.join(WORKDIR, '_root')
950
951 if os.path.exists(buildDir):
952 shutil.rmtree(buildDir)
953 if os.path.exists(rootDir):
954 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +0000955 os.makedirs(buildDir)
956 os.makedirs(rootDir)
957 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000958 curdir = os.getcwd()
959 os.chdir(buildDir)
960
961 # Not sure if this is still needed, the original build script
962 # claims that parts of the install assume python.exe exists.
963 os.symlink('python', os.path.join(buildDir, 'python.exe'))
964
965 # Extract the version from the configure file, needed to calculate
966 # several paths.
967 version = getVersion()
968
Ronald Oussoren008af852009-03-30 20:02:08 +0000969 # Since the extra libs are not in their installed framework location
970 # during the build, augment the library path so that the interpreter
971 # will find them during its extension import sanity checks.
972 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
973 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -0800974 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +0000975 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
976 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000977 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -0700978 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +0000979 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -0700980 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000981 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
982 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +0000983 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyebd63dc2014-04-09 16:12:11 -0700984 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000985 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000986 shellQuote(WORKDIR)[1:-1]))
987
Ned Deilye1c9794952013-01-29 00:07:46 -0800988 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000989 runCommand("make")
990
Ned Deilye1c9794952013-01-29 00:07:46 -0800991 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +0000992 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000993 shellQuote(rootDir)))
994
Ned Deilye1c9794952013-01-29 00:07:46 -0800995 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000996 runCommand("make frameworkinstallextras DESTDIR=%s"%(
997 shellQuote(rootDir)))
998
Ronald Oussoren008af852009-03-30 20:02:08 +0000999 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -08001000 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001001 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1002 runCommand("mv %s/* %s"%(
1003 shellQuote(os.path.join(
1004 WORKDIR, 'libraries', 'Library', 'Frameworks',
1005 'Python.framework', 'Versions', getVersion(),
1006 'lib')),
1007 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1008 'Python.framework', 'Versions', getVersion(),
1009 'lib'))))
1010
Ned Deily70f213a2013-10-26 03:16:06 -07001011 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1012 'Python.framework', 'Versions',
1013 version, 'lib', 'python%s'%(version,))
1014
Ned Deilye1c9794952013-01-29 00:07:46 -08001015 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001016 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001017 gid = grp.getgrnam('admin').gr_gid
1018
Ned Deilye1c9794952013-01-29 00:07:46 -08001019 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001020 for dirpath, dirnames, filenames in os.walk(frmDir):
1021 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001022 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001023 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001024
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001025 for fn in filenames:
1026 if os.path.islink(fn):
1027 continue
1028
1029 # "chmod g+w $fn"
1030 p = os.path.join(dirpath, fn)
1031 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001032 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1033 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001034
Ned Deilye1c9794952013-01-29 00:07:46 -08001035 if fn in EXPECTED_SHARED_LIBS:
1036 # check to see that this file was linked with the
1037 # expected library path and version
1038 data = captureCommand("otool -L %s" % shellQuote(p))
1039 for sl in EXPECTED_SHARED_LIBS[fn]:
1040 if ("\t%s " % sl) not in data:
1041 print("Expected shared lib %s was not linked with %s"
1042 % (sl, p))
1043 shared_lib_error = True
1044
1045 if shared_lib_error:
1046 fatal("Unexpected shared library errors.")
1047
Ned Deily53c460d2011-01-30 01:43:40 +00001048 if PYTHON_3:
1049 LDVERSION=None
1050 VERSION=None
1051 ABIFLAGS=None
1052
1053 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1054 for ln in fp:
1055 if ln.startswith('VERSION='):
1056 VERSION=ln.split()[1]
1057 if ln.startswith('ABIFLAGS='):
1058 ABIFLAGS=ln.split()[1]
1059 if ln.startswith('LDVERSION='):
1060 LDVERSION=ln.split()[1]
1061 fp.close()
1062
1063 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1064 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1065 config_suffix = '-' + LDVERSION
1066 else:
1067 config_suffix = '' # Python 2.x
1068
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001069 # We added some directories to the search path during the configure
1070 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001071 # the end-users system. Also remove the directories from _sysconfigdata.py
1072 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001073
Ned Deilye6ef7022013-10-25 00:46:59 -07001074 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1075 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1076
Ned Deilye6ef7022013-10-25 00:46:59 -07001077 # fix Makefile
1078 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1079 fp = open(path, 'r')
1080 data = fp.read()
1081 fp.close()
1082
1083 for p in (include_path, lib_path):
1084 data = data.replace(" " + p, '')
1085 data = data.replace(p + " ", '')
1086
1087 fp = open(path, 'w')
1088 fp.write(data)
1089 fp.close()
1090
1091 # fix _sysconfigdata if it exists
1092 #
1093 # TODO: make this more robust! test_sysconfig_module of
1094 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1095 # the output from get_config_var in both sysconfig and
1096 # distutils.sysconfig is exactly the same for both CFLAGS and
1097 # LDFLAGS. The fixing up is now complicated by the pretty
1098 # printing in _sysconfigdata.py. Also, we are using the
1099 # pprint from the Python running the installer build which
1100 # may not cosmetically format the same as the pprint in the Python
1101 # being built (and which is used to originally generate
1102 # _sysconfigdata.py).
1103
1104 import pprint
1105 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1106 if os.path.exists(path):
Ned Deilye1c9794952013-01-29 00:07:46 -08001107 fp = open(path, 'r')
1108 data = fp.read()
1109 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -07001110 # create build_time_vars dict
1111 exec(data)
1112 vars = {}
1113 for k, v in build_time_vars.items():
1114 if type(v) == type(''):
1115 for p in (include_path, lib_path):
1116 v = v.replace(' ' + p, '')
1117 v = v.replace(p + ' ', '')
1118 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001119
Ned Deilye1c9794952013-01-29 00:07:46 -08001120 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -07001121 # duplicated from sysconfig._generate_posix_vars()
1122 fp.write('# system configuration generated and used by'
1123 ' the sysconfig module\n')
1124 fp.write('build_time_vars = ')
1125 pprint.pprint(vars, stream=fp)
Ned Deilye1c9794952013-01-29 00:07:46 -08001126 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001127
1128 # Add symlinks in /usr/local/bin, using relative links
1129 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1130 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1131 'Python.framework', 'Versions', version, 'bin')
1132 if os.path.exists(usr_local_bin):
1133 shutil.rmtree(usr_local_bin)
1134 os.makedirs(usr_local_bin)
1135 for fn in os.listdir(
1136 os.path.join(frmDir, 'Versions', version, 'bin')):
1137 os.symlink(os.path.join(to_framework, fn),
1138 os.path.join(usr_local_bin, fn))
1139
1140 os.chdir(curdir)
1141
Ned Deily53c460d2011-01-30 01:43:40 +00001142 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001143 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001144 # with an already installed version of python 2
1145 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1146 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001147
1148def patchFile(inPath, outPath):
1149 data = fileContents(inPath)
1150 data = data.replace('$FULL_VERSION', getFullVersion())
1151 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001152 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001153 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001154 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +00001155
1156 # This one is not handy as a template variable
1157 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001158 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001159 fp.write(data)
1160 fp.close()
1161
1162def patchScript(inPath, outPath):
1163 data = fileContents(inPath)
1164 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001165 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001166 fp.write(data)
1167 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001168 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001169
1170
1171
1172def packageFromRecipe(targetDir, recipe):
1173 curdir = os.getcwd()
1174 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001175 # The major version (such as 2.5) is included in the package name
1176 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001177 # common.
1178 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001179 srcdir = recipe.get('source')
1180 pkgroot = recipe.get('topdir', srcdir)
1181 postflight = recipe.get('postflight')
1182 readme = textwrap.dedent(recipe['readme'])
1183 isRequired = recipe.get('required', True)
1184
Ned Deilye1c9794952013-01-29 00:07:46 -08001185 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001186
1187 # Substitute some variables
1188 textvars = dict(
1189 VER=getVersion(),
1190 FULLVER=getFullVersion(),
1191 )
1192 readme = readme % textvars
1193
1194 if pkgroot is not None:
1195 pkgroot = pkgroot % textvars
1196 else:
1197 pkgroot = '/'
1198
1199 if srcdir is not None:
1200 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1201 srcdir = srcdir % textvars
1202
1203 if postflight is not None:
1204 postflight = os.path.abspath(postflight)
1205
1206 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1207 os.makedirs(packageContents)
1208
1209 if srcdir is not None:
1210 os.chdir(srcdir)
1211 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1212 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1213 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1214
1215 fn = os.path.join(packageContents, 'PkgInfo')
1216 fp = open(fn, 'w')
1217 fp.write('pmkrpkg1')
1218 fp.close()
1219
1220 rsrcDir = os.path.join(packageContents, "Resources")
1221 os.mkdir(rsrcDir)
1222 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1223 fp.write(readme)
1224 fp.close()
1225
1226 if postflight is not None:
1227 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1228
1229 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001230 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001231 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001232 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1233 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1234 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001235 CFBundleShortVersionString=vers,
1236 IFMajorVersion=major,
1237 IFMinorVersion=minor,
1238 IFPkgFormatVersion=0.10000000149011612,
1239 IFPkgFlagAllowBackRev=False,
1240 IFPkgFlagAuthorizationAction="RootAuthorization",
1241 IFPkgFlagDefaultLocation=pkgroot,
1242 IFPkgFlagFollowLinks=True,
1243 IFPkgFlagInstallFat=True,
1244 IFPkgFlagIsRequired=isRequired,
1245 IFPkgFlagOverwritePermissions=False,
1246 IFPkgFlagRelocatable=False,
1247 IFPkgFlagRestartAction="NoRestart",
1248 IFPkgFlagRootVolumeOnly=True,
1249 IFPkgFlagUpdateInstalledLangauges=False,
1250 )
1251 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1252
1253 pl = Plist(
1254 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001255 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001256 IFPkgDescriptionVersion=vers,
1257 )
1258 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1259
1260 finally:
1261 os.chdir(curdir)
1262
1263
1264def makeMpkgPlist(path):
1265
1266 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001267 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001268
1269 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001270 CFBundleGetInfoString="Python %s"%(vers,),
1271 CFBundleIdentifier='org.python.Python',
1272 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001273 CFBundleShortVersionString=vers,
1274 IFMajorVersion=major,
1275 IFMinorVersion=minor,
1276 IFPkgFlagComponentDirectory="Contents/Packages",
1277 IFPkgFlagPackageList=[
1278 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001279 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001280 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001281 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001282 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001283 ],
1284 IFPkgFormatVersion=0.10000000149011612,
1285 IFPkgFlagBackgroundScaling="proportional",
1286 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001287 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001288 )
1289
1290 writePlist(pl, path)
1291
1292
1293def buildInstaller():
1294
1295 # Zap all compiled files
1296 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1297 for fn in filenames:
1298 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1299 os.unlink(os.path.join(dirpath, fn))
1300
1301 outdir = os.path.join(WORKDIR, 'installer')
1302 if os.path.exists(outdir):
1303 shutil.rmtree(outdir)
1304 os.mkdir(outdir)
1305
Ronald Oussoren508282e2009-03-30 19:34:51 +00001306 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001307 pkgcontents = os.path.join(pkgroot, 'Packages')
1308 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001309 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001310 packageFromRecipe(pkgcontents, recipe)
1311
1312 rsrcDir = os.path.join(pkgroot, 'Resources')
1313
1314 fn = os.path.join(pkgroot, 'PkgInfo')
1315 fp = open(fn, 'w')
1316 fp.write('pmkrpkg1')
1317 fp.close()
1318
1319 os.mkdir(rsrcDir)
1320
1321 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1322 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001323 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001324 IFPkgDescriptionVersion=getVersion(),
1325 )
1326
1327 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1328 for fn in os.listdir('resources'):
1329 if fn == '.svn': continue
1330 if fn.endswith('.jpg'):
1331 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1332 else:
1333 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1334
Ronald Oussorenc5555542006-06-11 20:24:45 +00001335 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001336
1337
1338def installSize(clear=False, _saved=[]):
1339 if clear:
1340 del _saved[:]
1341 if not _saved:
1342 data = captureCommand("du -ks %s"%(
1343 shellQuote(os.path.join(WORKDIR, '_root'))))
1344 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1345 return _saved[0]
1346
1347
1348def buildDMG():
1349 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001350 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001351 """
1352 outdir = os.path.join(WORKDIR, 'diskimage')
1353 if os.path.exists(outdir):
1354 shutil.rmtree(outdir)
1355
1356 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001357 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001358 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001359 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001360 imagepath = imagepath + '.dmg'
1361
1362 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001363 volname='Python %s'%(getFullVersion())
1364 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1365 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001366 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001367 shellQuote(imagepath + ".tmp.dmg" )))
1368
1369
1370 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1371 os.mkdir(os.path.join(WORKDIR, "mnt"))
1372 runCommand("hdiutil attach %s -mountroot %s"%(
1373 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1374
1375 # Custom icon for the DMG, shown when the DMG is mounted.
1376 shutil.copy("../Icons/Disk Image.icns",
1377 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001378 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001379 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1380
1381 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1382
1383 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1384 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1385 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1386 setIcon(imagepath, "../Icons/Disk Image.icns")
1387
1388 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001389
1390 return imagepath
1391
1392
1393def setIcon(filePath, icnsPath):
1394 """
1395 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001396 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001397
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001398 dirPath = os.path.normpath(os.path.dirname(__file__))
1399 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001400 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1401 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1402 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001403 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1404 if not os.path.exists(appPath):
1405 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001406 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1407 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001408
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001409 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1410 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001411
1412def main():
1413 # First parse options and check if we can perform our work
1414 parseOptions()
1415 checkEnvironment()
1416
Ronald Oussoren508282e2009-03-30 19:34:51 +00001417 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001418 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001419 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001420
1421 if os.path.exists(WORKDIR):
1422 shutil.rmtree(WORKDIR)
1423 os.mkdir(WORKDIR)
1424
Ronald Oussoren287128a2010-04-18 14:01:05 +00001425 os.environ['LC_ALL'] = 'C'
1426
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001427 # Then build third-party libraries such as sleepycat DB4.
1428 buildLibraries()
1429
1430 # Now build python itself
1431 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001432
1433 # And then build the documentation
1434 # Remove the Deployment Target from the shell
1435 # environment, it's no longer needed and
1436 # an unexpected build target can cause problems
1437 # when Sphinx and its dependencies need to
1438 # be (re-)installed.
1439 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001440 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001441
1442
1443 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001444 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001445 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001446 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001447
Benjamin Petersonc3104762008-10-03 11:52:06 +00001448 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001449 getVersion(),))
Ned Deilye1c9794952013-01-29 00:07:46 -08001450 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001451 setIcon(folder, "../Icons/Python Folder.icns")
1452
1453 # Create the installer
1454 buildInstaller()
1455
1456 # And copy the readme into the directory containing the installer
1457 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1458
1459 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001460 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001461
1462 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001463 fp.write("# BUILD INFO\n")
1464 fp.write("# Date: %s\n" % time.ctime())
1465 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001466 fp.close()
1467
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001468 # And copy it to a DMG
1469 buildDMG()
1470
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001471if __name__ == "__main__":
1472 main()