blob: 710e84d1e6e41a908b83a65e00650fe4e1867a28 [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
Ned Deily1f70b872014-06-25 13:33:57 -0700153def getDeptargetTuple():
154 return tuple([int(n) for n in DEPTARGET.split('.')[0:2]])
155
156def getTargetCompilers():
157 target_cc_map = {
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily1f70b872014-06-25 13:33:57 -0700162 }
163 return target_cc_map.get(DEPTARGET, ('clang', 'clang++') )
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000164
Ned Deily1f70b872014-06-25 13:33:57 -0700165CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000166
Ned Deily53c460d2011-01-30 01:43:40 +0000167PYTHON_3 = getVersionTuple() >= (3, 0)
168
Ronald Oussoren158ad592006-11-07 16:00:34 +0000169USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000179 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
180 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000181""")% globals()
182
Ned Deilye1c9794952013-01-29 00:07:46 -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 = {}
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000190
191# Instructions for building libraries that are necessary for building a
192# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000193# [The recipes are defined here for convenience but instantiated later after
194# command line options have been processed.]
195def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000196 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000197
Ned Deily1f70b872014-06-25 13:33:57 -0700198 LT_10_5 = bool(getDeptargetTuple() < (10, 5))
Ned Deilye1c9794952013-01-29 00:07:46 -0800199
Ned Deilyebd63dc2014-04-09 16:12:11 -0700200# Disable for now
Ned Deily1f70b872014-06-25 13:33:57 -0700201 if False: # if (getDeptargetTuple() > (10, 5)) and (getVersionTuple() >= (3, 5)):
Ned Deily0203a802013-10-25 00:40:07 -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 Deilya6cbff02013-10-27 19:47:23 -0700223 patches=[
224 "issue19373_tk_8_5_15_source.patch",
225 ],
Ned Deily0203a802013-10-25 00:40:07 -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 Deilye1c9794952013-01-29 00:07:46 -0800242 if getVersionTuple() >= (3, 3):
243 result.extend([
244 dict(
Ned Deilyebd63dc2014-04-09 16:12:11 -0700245 name="XZ 5.0.5",
246 url="http://tukaani.org/xz/xz-5.0.5.tar.gz",
247 checksum='19d924e066b6fff0bc9d1981b4e53196',
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -0700290 name="SQLite 3.8.3.1",
291 url="http://www.sqlite.org/2014/sqlite-autoconf-3080301.tar.gz",
292 checksum='509ff98d8dc9729b618b7e96612079c6',
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily1f70b872014-06-25 13:33:57 -0700309 if getDeptargetTuple() < (10, 5):
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000310 result.extend([
311 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000312 name="Bzip2 1.0.6",
313 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
314 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000315 configure=None,
Ned Deilye1c9794952013-01-29 00:07:46 -0800316 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
317 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000318 shellQuote(os.path.join(WORKDIR, 'libraries')),
319 ' -arch '.join(ARCHLIST),
320 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000321 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000322 ),
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 Deilye1c9794952013-01-29 00:07:46 -0800328 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
329 CC, CXX,
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deily53c460d2011-01-30 01:43:40 +0000337 name="GNU Readline 6.1.2",
338 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
339 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000340 patchlevel='0',
341 patches=[
342 # The readline maintainers don't do actual micro releases, but
343 # just ship a set of patches.
Ned Deilye1c9794952013-01-29 00:07:46 -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'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000348 ]
349 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000350 ])
351
Ned Deily53c460d2011-01-30 01:43:40 +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 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000365
366 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000367
368
369# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000370def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000371 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ned Deilyebd63dc2014-04-09 16:12:11 -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)]
Ronald Oussorenc66ced32009-09-20 20:16:11 +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",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000385 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000386 ),
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).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000395
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000396 It also installs a number of examples and demos.
397 """,
398 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000399 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000400 ),
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,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000411 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000412 ),
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 Deilye1c9794952013-01-29 00:07:46 -0800420 that is useable for pydoc and IDLE.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000421 """,
422 postflight="scripts/postflight.documentation",
423 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000424 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000425 ),
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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000433
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deilyebd63dc2014-04-09 16:12:11 -0700442 selected=unselected_for_lt_python34,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000443 ),
444 ]
445
Ned Deilyebd63dc2014-04-09 16:12:11 -0700446 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 Deily1f70b872014-06-25 13:33:57 -0700463 if getDeptargetTuple() < (10, 4) and not PYTHON_3:
Ronald Oussorenc66ced32009-09-20 20:16:11 +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,
Ned Deily53c460d2011-01-30 01:43:40 +0000477 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000478 )
479 )
Ned Deilyebd63dc2014-04-09 16:12:11 -0700480
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000481 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000482
483def 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 Deilye1c9794952013-01-29 00:07:46 -0800496 return open(fn, 'r').read()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000497
498def runCommand(commandline):
499 """
Ezio Melottic2077b02011-03-16 12:34:31 +0200500 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000501 unless the command fails.
502 """
503 fd = os.popen(commandline, 'r')
504 data = fd.read()
505 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000506 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000507 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800508 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Peterson5b63acd2008-03-29 15:24:25 +0000517 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000518 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800519 raise RuntimeError("command failed: %s"%(commandline,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000520
521 return data
522
Ronald Oussoren287128a2010-04-18 14:01:05 +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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000540def checkEnvironment():
541 """
542 Check that we're running on a supported system.
543 """
544
Ned Deily53c460d2011-01-30 01:43:40 +0000545 if sys.version_info[0:2] < (2, 4):
546 fatal("This script must be run with Python 2.4 or later")
547
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000548 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000549 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000550
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000551 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000552 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -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 Oussoren287128a2010-04-18 14:01:05 +0000566
Ned Deilye1c9794952013-01-29 00:07:46 -0800567 frameworks = {}
Ronald Oussoren287128a2010-04-18 14:01:05 +0000568 for framework in ['Tcl', 'Tk']:
Ned Deilye1c9794952013-01-29 00:07:46 -0800569 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ned Deily53c460d2011-01-30 01:43:40 +0000570 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -0800571 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussoren287128a2010-04-18 14:01:05 +0000572 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deilye1c9794952013-01-29 00:07:46 -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))
Ned Deily53c460d2011-01-30 01:43:40 +0000578 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -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 Oussoren287128a2010-04-18 14:01:05 +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 Deilye1c9794952013-01-29 00:07:46 -0800601 print("INFO: deleting environment variable %s=%s" % (
602 ev, os.environ[ev]))
Ronald Oussoren287128a2010-04-18 14:01:05 +0000603 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000604
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilyebd63dc2014-04-09 16:12:11 -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 Deily5ceffa12014-09-05 15:51:54 -0700618 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000619
Ronald Oussoren158ad592006-11-07 16:00:34 +0000620def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000621 """
622 Parse arguments and update global settings.
623 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000624 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800625 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000626
627 if args is None:
628 args = sys.argv[1:]
629
630 try:
631 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000632 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
633 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800634 except getopt.GetoptError:
635 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000636 sys.exit(1)
637
638 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800639 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000640 sys.exit(1)
641
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000642 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000643 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000644 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800645 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000660 elif k in ('--dep-target', ):
661 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000662 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000663
664 elif k in ('--universal-archs', ):
665 if v in UNIVERSALOPTS:
666 UNIVERSALARCHS = v
667 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000668 if deptarget is None:
669 # Select alternate default deployment
670 # target
671 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000672 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800673 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000674
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000675 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800676 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily1f70b872014-06-25 13:33:57 -0700683 CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000684
Ned Deilye1c9794952013-01-29 00:07:46 -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("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily0203a802013-10-25 00:40:07 -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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily0203a802013-10-25 00:40:07 -0700716 if ((retval.startswith('tcl') or retval.startswith('tk'))
717 and retval.endswith('-src')):
718 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -0800745 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000746
747 return os.path.join(builddir, retval)
748
749 finally:
750 os.chdir(curdir)
751
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000752def downloadURL(url, fname):
753 """
754 Download the contents of the url into the file.
755 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800756 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -0800809 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
810 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000817
Ned Deilye1c9794952013-01-29 00:07:46 -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)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000826 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
827 shellQuote(fn),))
828
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deilya6cbff02013-10-27 19:47:23 -0700843 if 'buildDir' in recipe:
844 os.chdir(recipe['buildDir'])
845
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000853
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000861
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000862 if recipe.get('useLDFlags', 1):
863 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800864 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
865 "-I%s/usr/local/include"%(
866 recipe.get('extra_cflags', ''),
867 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000868 ' -arch '.join(archList),
869 shellQuote(SDKPATH)[1:-1],
870 shellQuote(basedir)[1:-1],),
Ned Deilyc177b1c2014-04-09 16:13:46 -0700871 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800872 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000873 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000874 shellQuote(basedir)[1:-1],
875 ' -arch '.join(archList)),
876 ])
877 else:
878 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800879 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
880 "-I%s/usr/local/include"%(
881 recipe.get('extra_cflags', ''),
882 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000883 ' -arch '.join(archList),
884 shellQuote(SDKPATH)[1:-1],
885 shellQuote(basedir)[1:-1],),
886 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000887
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000888 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -0800889 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000890
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000891 configure_args.insert(0, configure)
892 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000893
Ned Deilye1c9794952013-01-29 00:07:46 -0800894 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000895 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000896
Ned Deilye1c9794952013-01-29 00:07:46 -0800897 print("Running install for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000898 runCommand('{ ' + install + ' ;} 2>&1')
899
Ned Deilye1c9794952013-01-29 00:07:46 -0800900 print("Done %s"%(name,))
901 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000902
903 os.chdir(curdir)
904
905def buildLibraries():
906 """
907 Build our dependencies into $WORKDIR/libraries/usr/local
908 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800909 print("")
910 print("Building required libraries")
911 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000917 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000918 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000919
920
921
922def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000923 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000924 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -0800925 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000926 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000927 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000928 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000929 curDir = os.getcwd()
930 os.chdir(buildDir)
Ned Deily5ceffa12014-09-05 15:51:54 -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 Oussoren648a4fe2009-03-30 17:15:29 +0000935 os.chdir(curDir)
936 if not os.path.exists(docdir):
937 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000938 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000939
940
941def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -0800942 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily53c460d2011-01-30 01:43:40 +0000951 os.makedirs(buildDir)
952 os.makedirs(rootDir)
953 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren008af852009-03-30 20:02:08 +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 Deilye1c9794952013-01-29 00:07:46 -0800970 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +0000971 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
972 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000973 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -0700974 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +0000975 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -0700976 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000977 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
978 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +0000979 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyebd63dc2014-04-09 16:12:11 -0700980 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000981 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000982 shellQuote(WORKDIR)[1:-1]))
983
Ned Deilyfbb60d52014-05-22 15:27:01 -0700984 print("Running make touch")
985 runCommand("make touch")
986
Ned Deilye1c9794952013-01-29 00:07:46 -0800987 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000988 runCommand("make")
989
Ned Deilye1c9794952013-01-29 00:07:46 -0800990 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +0000991 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000992 shellQuote(rootDir)))
993
Ned Deilye1c9794952013-01-29 00:07:46 -0800994 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000995 runCommand("make frameworkinstallextras DESTDIR=%s"%(
996 shellQuote(rootDir)))
997
Ronald Oussoren008af852009-03-30 20:02:08 +0000998 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -0800999 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily70f213a2013-10-26 03:16:06 -07001010 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1011 'Python.framework', 'Versions',
1012 version, 'lib', 'python%s'%(version,))
1013
Ned Deilye1c9794952013-01-29 00:07:46 -08001014 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001015 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001016 gid = grp.getgrnam('admin').gr_gid
1017
Ned Deilye1c9794952013-01-29 00:07:46 -08001018 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001019 for dirpath, dirnames, filenames in os.walk(frmDir):
1020 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001021 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001022 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001023
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001031 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1032 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001033
Ned Deilye1c9794952013-01-29 00:07:46 -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 Deily53c460d2011-01-30 01:43:40 +00001047 if PYTHON_3:
1048 LDVERSION=None
1049 VERSION=None
1050 ABIFLAGS=None
1051
1052 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1053 for ln in fp:
1054 if ln.startswith('VERSION='):
1055 VERSION=ln.split()[1]
1056 if ln.startswith('ABIFLAGS='):
1057 ABIFLAGS=ln.split()[1]
1058 if ln.startswith('LDVERSION='):
1059 LDVERSION=ln.split()[1]
1060 fp.close()
1061
1062 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1063 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1064 config_suffix = '-' + LDVERSION
1065 else:
1066 config_suffix = '' # Python 2.x
1067
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -08001070 # the end-users system. Also remove the directories from _sysconfigdata.py
1071 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001072
Ned Deilye6ef7022013-10-25 00:46:59 -07001073 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1074 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1075
Ned Deilye6ef7022013-10-25 00:46:59 -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 Deilye1c9794952013-01-29 00:07:46 -08001106 fp = open(path, 'r')
1107 data = fp.read()
1108 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -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 Deilye1c9794952013-01-29 00:07:46 -08001118
Ned Deilye1c9794952013-01-29 00:07:46 -08001119 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -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 Deilye1c9794952013-01-29 00:07:46 -08001125 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
Ned Deily53c460d2011-01-30 01:43:40 +00001141 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001142 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001143 # with an already installed version of python 2
1144 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1145 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001146
1147def patchFile(inPath, outPath):
1148 data = fileContents(inPath)
1149 data = data.replace('$FULL_VERSION', getFullVersion())
1150 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001151 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001152 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001153 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +00001154
1155 # This one is not handy as a template variable
1156 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001157 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001158 fp.write(data)
1159 fp.close()
1160
1161def patchScript(inPath, outPath):
1162 data = fileContents(inPath)
1163 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001164 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001165 fp.write(data)
1166 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001167 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001168
1169
1170
1171def packageFromRecipe(targetDir, recipe):
1172 curdir = os.getcwd()
1173 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +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
Ronald Oussorenc5555542006-06-11 20:24:45 +00001176 # common.
1177 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -08001184 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -08001229 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001230 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001231 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1232 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1233 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001254 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deilye1c9794952013-01-29 00:07:46 -08001266 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001267
1268 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001269 CFBundleGetInfoString="Python %s"%(vers,),
1270 CFBundleIdentifier='org.python.Python',
1271 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001272 CFBundleShortVersionString=vers,
1273 IFMajorVersion=major,
1274 IFMinorVersion=minor,
1275 IFPkgFlagComponentDirectory="Contents/Packages",
1276 IFPkgFlagPackageList=[
1277 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001278 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001279 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001280 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001281 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001282 ],
1283 IFPkgFormatVersion=0.10000000149011612,
1284 IFPkgFlagBackgroundScaling="proportional",
1285 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001286 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001305 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001306 pkgcontents = os.path.join(pkgroot, 'Packages')
1307 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001308 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001322 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
Ronald Oussorenc5555542006-06-11 20:24:45 +00001334 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001349 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001356 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001357 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001358 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001359 imagepath = imagepath + '.dmg'
1360
1361 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001362 volname='Python %s'%(getFullVersion())
1363 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1364 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001365 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +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 Deilye1c9794952013-01-29 00:07:46 -08001377 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +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")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001388
1389 return imagepath
1390
1391
1392def setIcon(filePath, icnsPath):
1393 """
1394 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001395 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001396
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001397 dirPath = os.path.normpath(os.path.dirname(__file__))
1398 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +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 Oussoren5d18cc62010-04-30 14:58:39 +00001402 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1403 if not os.path.exists(appPath):
1404 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001405 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1406 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001407
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001408 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1409 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001410
1411def main():
1412 # First parse options and check if we can perform our work
1413 parseOptions()
1414 checkEnvironment()
1415
Ronald Oussoren508282e2009-03-30 19:34:51 +00001416 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001417 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001418 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001419
1420 if os.path.exists(WORKDIR):
1421 shutil.rmtree(WORKDIR)
1422 os.mkdir(WORKDIR)
1423
Ronald Oussoren287128a2010-04-18 14:01:05 +00001424 os.environ['LC_ALL'] = 'C'
1425
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001426 # Then build third-party libraries such as sleepycat DB4.
1427 buildLibraries()
1428
1429 # Now build python itself
1430 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +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']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001439 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001440
1441
1442 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001443 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001444 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001445 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001446
Benjamin Petersonc3104762008-10-03 11:52:06 +00001447 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001448 getVersion(),))
Ned Deilye1c9794952013-01-29 00:07:46 -08001449 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001459 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001460
1461 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -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)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001465 fp.close()
1466
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001467 # And copy it to a DMG
1468 buildDMG()
1469
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001470if __name__ == "__main__":
1471 main()