blob: 199b560b6544db006f3bf7d99e65cd22cbd6f9c4 [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')
618 if getVersionTuple() >= (3, 4):
619 runCommand('sphinx-build --version')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000620
Ronald Oussoren158ad592006-11-07 16:00:34 +0000621def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000622 """
623 Parse arguments and update global settings.
624 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000625 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deilye1c9794952013-01-29 00:07:46 -0800626 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000627
628 if args is None:
629 args = sys.argv[1:]
630
631 try:
632 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000633 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
634 'dep-target=', 'universal-archs=', 'help' ])
Ned Deilye1c9794952013-01-29 00:07:46 -0800635 except getopt.GetoptError:
636 print(sys.exc_info()[1])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000637 sys.exit(1)
638
639 if args:
Ned Deilye1c9794952013-01-29 00:07:46 -0800640 print("Additional arguments")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000641 sys.exit(1)
642
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000643 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000644 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000645 if k in ('-h', '-?', '--help'):
Ned Deilye1c9794952013-01-29 00:07:46 -0800646 print(USAGE)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000647 sys.exit(0)
648
649 elif k in ('-d', '--build-dir'):
650 WORKDIR=v
651
652 elif k in ('--third-party',):
653 DEPSRC=v
654
655 elif k in ('--sdk-path',):
656 SDKPATH=v
657
658 elif k in ('--src-dir',):
659 SRCDIR=v
660
Ronald Oussoren508282e2009-03-30 19:34:51 +0000661 elif k in ('--dep-target', ):
662 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000663 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000664
665 elif k in ('--universal-archs', ):
666 if v in UNIVERSALOPTS:
667 UNIVERSALARCHS = v
668 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000669 if deptarget is None:
670 # Select alternate default deployment
671 # target
672 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000673 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800674 raise NotImplementedError(v)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000675
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000676 else:
Ned Deilye1c9794952013-01-29 00:07:46 -0800677 raise NotImplementedError(k)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000678
679 SRCDIR=os.path.abspath(SRCDIR)
680 WORKDIR=os.path.abspath(WORKDIR)
681 SDKPATH=os.path.abspath(SDKPATH)
682 DEPSRC=os.path.abspath(DEPSRC)
683
Ned Deily1f70b872014-06-25 13:33:57 -0700684 CC, CXX = getTargetCompilers()
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000685
Ned Deilye1c9794952013-01-29 00:07:46 -0800686 print("Settings:")
687 print(" * Source directory:", SRCDIR)
688 print(" * Build directory: ", WORKDIR)
689 print(" * SDK location: ", SDKPATH)
690 print(" * Third-party source:", DEPSRC)
691 print(" * Deployment target:", DEPTARGET)
692 print(" * Universal architectures:", ARCHLIST)
693 print(" * C compiler:", CC)
694 print(" * C++ compiler:", CXX)
695 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000696
697
698
699
700def extractArchive(builddir, archiveName):
701 """
702 Extract a source archive into 'builddir'. Returns the path of the
703 extracted archive.
704
705 XXX: This function assumes that archives contain a toplevel directory
706 that is has the same name as the basename of the archive. This is
Ned Deily0203a802013-10-25 00:40:07 -0700707 safe enough for almost anything we use. Unfortunately, it does not
708 work for current Tcl and Tk source releases where the basename of
709 the archive ends with "-src" but the uncompressed directory does not.
710 For now, just special case Tcl and Tk tar.gz downloads.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000711 """
712 curdir = os.getcwd()
713 try:
714 os.chdir(builddir)
715 if archiveName.endswith('.tar.gz'):
716 retval = os.path.basename(archiveName[:-7])
Ned Deily0203a802013-10-25 00:40:07 -0700717 if ((retval.startswith('tcl') or retval.startswith('tk'))
718 and retval.endswith('-src')):
719 retval = retval[:-4]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000720 if os.path.exists(retval):
721 shutil.rmtree(retval)
722 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
723
724 elif archiveName.endswith('.tar.bz2'):
725 retval = os.path.basename(archiveName[:-8])
726 if os.path.exists(retval):
727 shutil.rmtree(retval)
728 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
729
730 elif archiveName.endswith('.tar'):
731 retval = os.path.basename(archiveName[:-4])
732 if os.path.exists(retval):
733 shutil.rmtree(retval)
734 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
735
736 elif archiveName.endswith('.zip'):
737 retval = os.path.basename(archiveName[:-4])
738 if os.path.exists(retval):
739 shutil.rmtree(retval)
740 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
741
742 data = fp.read()
743 xit = fp.close()
744 if xit is not None:
745 sys.stdout.write(data)
Ned Deilye1c9794952013-01-29 00:07:46 -0800746 raise RuntimeError("Cannot extract %s"%(archiveName,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000747
748 return os.path.join(builddir, retval)
749
750 finally:
751 os.chdir(curdir)
752
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000753def downloadURL(url, fname):
754 """
755 Download the contents of the url into the file.
756 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800757 fpIn = urllib_request.urlopen(url)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000758 fpOut = open(fname, 'wb')
759 block = fpIn.read(10240)
760 try:
761 while block:
762 fpOut.write(block)
763 block = fpIn.read(10240)
764 fpIn.close()
765 fpOut.close()
766 except:
767 try:
768 os.unlink(fname)
769 except:
770 pass
771
Ned Deilye1c9794952013-01-29 00:07:46 -0800772def verifyThirdPartyFile(url, checksum, fname):
773 """
774 Download file from url to filename fname if it does not already exist.
775 Abort if file contents does not match supplied md5 checksum.
776 """
777 name = os.path.basename(fname)
778 if os.path.exists(fname):
779 print("Using local copy of %s"%(name,))
780 else:
781 print("Did not find local copy of %s"%(name,))
782 print("Downloading %s"%(name,))
783 downloadURL(url, fname)
784 print("Archive for %s stored as %s"%(name, fname))
785 if os.system(
786 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
787 % (shellQuote(fname), checksum) ):
788 fatal('MD5 checksum mismatch for file %s' % fname)
789
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000790def buildRecipe(recipe, basedir, archList):
791 """
792 Build software using a recipe. This function does the
793 'configure;make;make install' dance for C software, with a possibility
794 to customize this process, basically a poor-mans DarwinPorts.
795 """
796 curdir = os.getcwd()
797
798 name = recipe['name']
799 url = recipe['url']
800 configure = recipe.get('configure', './configure')
801 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
802 shellQuote(basedir)))
803
804 archiveName = os.path.split(url)[-1]
805 sourceArchive = os.path.join(DEPSRC, archiveName)
806
807 if not os.path.exists(DEPSRC):
808 os.mkdir(DEPSRC)
809
Ned Deilye1c9794952013-01-29 00:07:46 -0800810 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
811 print("Extracting archive for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000812 buildDir=os.path.join(WORKDIR, '_bld')
813 if not os.path.exists(buildDir):
814 os.mkdir(buildDir)
815
816 workDir = extractArchive(buildDir, sourceArchive)
817 os.chdir(workDir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000818
Ned Deilye1c9794952013-01-29 00:07:46 -0800819 for patch in recipe.get('patches', ()):
820 if isinstance(patch, tuple):
821 url, checksum = patch
822 fn = os.path.join(DEPSRC, os.path.basename(url))
823 verifyThirdPartyFile(url, checksum, fn)
824 else:
825 # patch is a file in the source directory
826 fn = os.path.join(curdir, patch)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000827 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
828 shellQuote(fn),))
829
Ned Deilye1c9794952013-01-29 00:07:46 -0800830 for patchscript in recipe.get('patchscripts', ()):
831 if isinstance(patchscript, tuple):
832 url, checksum = patchscript
833 fn = os.path.join(DEPSRC, os.path.basename(url))
834 verifyThirdPartyFile(url, checksum, fn)
835 else:
836 # patch is a file in the source directory
837 fn = os.path.join(curdir, patchscript)
838 if fn.endswith('.bz2'):
839 runCommand('bunzip2 -fk %s' % shellQuote(fn))
840 fn = fn[:-4]
841 runCommand('sh %s' % shellQuote(fn))
842 os.unlink(fn)
843
Ned Deilya6cbff02013-10-27 19:47:23 -0700844 if 'buildDir' in recipe:
845 os.chdir(recipe['buildDir'])
846
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000847 if configure is not None:
848 configure_args = [
849 "--prefix=/usr/local",
850 "--enable-static",
851 "--disable-shared",
852 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
853 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000854
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000855 if 'configure_pre' in recipe:
856 args = list(recipe['configure_pre'])
857 if '--disable-static' in args:
858 configure_args.remove('--enable-static')
859 if '--enable-shared' in args:
860 configure_args.remove('--disable-shared')
861 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000862
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000863 if recipe.get('useLDFlags', 1):
864 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800865 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
866 "-I%s/usr/local/include"%(
867 recipe.get('extra_cflags', ''),
868 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000869 ' -arch '.join(archList),
870 shellQuote(SDKPATH)[1:-1],
871 shellQuote(basedir)[1:-1],),
Ned Deilyc177b1c2014-04-09 16:13:46 -0700872 "LDFLAGS=-mmacosx-version-min=%s -isysroot %s -L%s/usr/local/lib -arch %s"%(
Ned Deilye1c9794952013-01-29 00:07:46 -0800873 DEPTARGET,
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000874 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000875 shellQuote(basedir)[1:-1],
876 ' -arch '.join(archList)),
877 ])
878 else:
879 configure_args.extend([
Ned Deilye1c9794952013-01-29 00:07:46 -0800880 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
881 "-I%s/usr/local/include"%(
882 recipe.get('extra_cflags', ''),
883 DEPTARGET,
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000884 ' -arch '.join(archList),
885 shellQuote(SDKPATH)[1:-1],
886 shellQuote(basedir)[1:-1],),
887 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000888
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000889 if 'configure_post' in recipe:
Ned Deilye1c9794952013-01-29 00:07:46 -0800890 configure_args = configure_args + list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000891
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000892 configure_args.insert(0, configure)
893 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000894
Ned Deilye1c9794952013-01-29 00:07:46 -0800895 print("Running configure for %s"%(name,))
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000896 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000897
Ned Deilye1c9794952013-01-29 00:07:46 -0800898 print("Running install for %s"%(name,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000899 runCommand('{ ' + install + ' ;} 2>&1')
900
Ned Deilye1c9794952013-01-29 00:07:46 -0800901 print("Done %s"%(name,))
902 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000903
904 os.chdir(curdir)
905
906def buildLibraries():
907 """
908 Build our dependencies into $WORKDIR/libraries/usr/local
909 """
Ned Deilye1c9794952013-01-29 00:07:46 -0800910 print("")
911 print("Building required libraries")
912 print("")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000913 universal = os.path.join(WORKDIR, 'libraries')
914 os.mkdir(universal)
915 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
916 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
917
Ronald Oussoren508282e2009-03-30 19:34:51 +0000918 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000919 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000920
921
922
923def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000924 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000925 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deilye1c9794952013-01-29 00:07:46 -0800926 print("Install python documentation")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000927 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000928 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000929 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000930 curDir = os.getcwd()
931 os.chdir(buildDir)
Ned Deilyebd63dc2014-04-09 16:12:11 -0700932 # The Doc build changed for 3.4 (technically, for 3.4.1)
933 if getVersionTuple() < (3, 4):
934 # This step does an svn checkout of sphinx and its dependencies
935 runCommand('make update')
936 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
937 else:
938 runCommand('make clean')
939 # Assume sphinx-build is on our PATH, checked in checkEnvironment
940 runCommand('make html')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000941 os.chdir(curDir)
942 if not os.path.exists(docdir):
943 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000944 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000945
946
947def buildPython():
Ned Deilye1c9794952013-01-29 00:07:46 -0800948 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000949
950 buildDir = os.path.join(WORKDIR, '_bld', 'python')
951 rootDir = os.path.join(WORKDIR, '_root')
952
953 if os.path.exists(buildDir):
954 shutil.rmtree(buildDir)
955 if os.path.exists(rootDir):
956 shutil.rmtree(rootDir)
Ned Deily53c460d2011-01-30 01:43:40 +0000957 os.makedirs(buildDir)
958 os.makedirs(rootDir)
959 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000960 curdir = os.getcwd()
961 os.chdir(buildDir)
962
963 # Not sure if this is still needed, the original build script
964 # claims that parts of the install assume python.exe exists.
965 os.symlink('python', os.path.join(buildDir, 'python.exe'))
966
967 # Extract the version from the configure file, needed to calculate
968 # several paths.
969 version = getVersion()
970
Ronald Oussoren008af852009-03-30 20:02:08 +0000971 # Since the extra libs are not in their installed framework location
972 # during the build, augment the library path so that the interpreter
973 # will find them during its extension import sanity checks.
974 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
975 'libraries', 'usr', 'local', 'lib')
Ned Deilye1c9794952013-01-29 00:07:46 -0800976 print("Running configure...")
Ronald Oussoren508282e2009-03-30 19:34:51 +0000977 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
978 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000979 "%s "
Ned Deilyebd63dc2014-04-09 16:12:11 -0700980 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +0000981 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
Ned Deilyf84b5312013-10-25 00:44:46 -0700982 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000983 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
984 UNIVERSALARCHS,
Ned Deily53c460d2011-01-30 01:43:40 +0000985 (' ', '--with-computed-gotos ')[PYTHON_3],
Ned Deilyebd63dc2014-04-09 16:12:11 -0700986 (' ', '--without-ensurepip ')[getVersionTuple() >= (3, 4)],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000987 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000988 shellQuote(WORKDIR)[1:-1]))
989
Ned Deilyfbb60d52014-05-22 15:27:01 -0700990 print("Running make touch")
991 runCommand("make touch")
992
Ned Deilye1c9794952013-01-29 00:07:46 -0800993 print("Running make")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000994 runCommand("make")
995
Ned Deilye1c9794952013-01-29 00:07:46 -0800996 print("Running make install")
Ned Deily53c460d2011-01-30 01:43:40 +0000997 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000998 shellQuote(rootDir)))
999
Ned Deilye1c9794952013-01-29 00:07:46 -08001000 print("Running make frameworkinstallextras")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001001 runCommand("make frameworkinstallextras DESTDIR=%s"%(
1002 shellQuote(rootDir)))
1003
Ronald Oussoren008af852009-03-30 20:02:08 +00001004 del os.environ['DYLD_LIBRARY_PATH']
Ned Deilye1c9794952013-01-29 00:07:46 -08001005 print("Copying required shared libraries")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001006 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
1007 runCommand("mv %s/* %s"%(
1008 shellQuote(os.path.join(
1009 WORKDIR, 'libraries', 'Library', 'Frameworks',
1010 'Python.framework', 'Versions', getVersion(),
1011 'lib')),
1012 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1013 'Python.framework', 'Versions', getVersion(),
1014 'lib'))))
1015
Ned Deily70f213a2013-10-26 03:16:06 -07001016 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1017 'Python.framework', 'Versions',
1018 version, 'lib', 'python%s'%(version,))
1019
Ned Deilye1c9794952013-01-29 00:07:46 -08001020 print("Fix file modes")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001021 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001022 gid = grp.getgrnam('admin').gr_gid
1023
Ned Deilye1c9794952013-01-29 00:07:46 -08001024 shared_lib_error = False
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001025 for dirpath, dirnames, filenames in os.walk(frmDir):
1026 for dn in dirnames:
Ned Deilye1c9794952013-01-29 00:07:46 -08001027 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001028 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +00001029
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001030 for fn in filenames:
1031 if os.path.islink(fn):
1032 continue
1033
1034 # "chmod g+w $fn"
1035 p = os.path.join(dirpath, fn)
1036 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +00001037 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1038 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001039
Ned Deilye1c9794952013-01-29 00:07:46 -08001040 if fn in EXPECTED_SHARED_LIBS:
1041 # check to see that this file was linked with the
1042 # expected library path and version
1043 data = captureCommand("otool -L %s" % shellQuote(p))
1044 for sl in EXPECTED_SHARED_LIBS[fn]:
1045 if ("\t%s " % sl) not in data:
1046 print("Expected shared lib %s was not linked with %s"
1047 % (sl, p))
1048 shared_lib_error = True
1049
1050 if shared_lib_error:
1051 fatal("Unexpected shared library errors.")
1052
Ned Deily53c460d2011-01-30 01:43:40 +00001053 if PYTHON_3:
1054 LDVERSION=None
1055 VERSION=None
1056 ABIFLAGS=None
1057
1058 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1059 for ln in fp:
1060 if ln.startswith('VERSION='):
1061 VERSION=ln.split()[1]
1062 if ln.startswith('ABIFLAGS='):
1063 ABIFLAGS=ln.split()[1]
1064 if ln.startswith('LDVERSION='):
1065 LDVERSION=ln.split()[1]
1066 fp.close()
1067
1068 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1069 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1070 config_suffix = '-' + LDVERSION
1071 else:
1072 config_suffix = '' # Python 2.x
1073
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001074 # We added some directories to the search path during the configure
1075 # phase. Remove those because those directories won't be there on
Ned Deilye1c9794952013-01-29 00:07:46 -08001076 # the end-users system. Also remove the directories from _sysconfigdata.py
1077 # (added in 3.3) if it exists.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001078
Ned Deilye6ef7022013-10-25 00:46:59 -07001079 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1080 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1081
Ned Deilye6ef7022013-10-25 00:46:59 -07001082 # fix Makefile
1083 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1084 fp = open(path, 'r')
1085 data = fp.read()
1086 fp.close()
1087
1088 for p in (include_path, lib_path):
1089 data = data.replace(" " + p, '')
1090 data = data.replace(p + " ", '')
1091
1092 fp = open(path, 'w')
1093 fp.write(data)
1094 fp.close()
1095
1096 # fix _sysconfigdata if it exists
1097 #
1098 # TODO: make this more robust! test_sysconfig_module of
1099 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1100 # the output from get_config_var in both sysconfig and
1101 # distutils.sysconfig is exactly the same for both CFLAGS and
1102 # LDFLAGS. The fixing up is now complicated by the pretty
1103 # printing in _sysconfigdata.py. Also, we are using the
1104 # pprint from the Python running the installer build which
1105 # may not cosmetically format the same as the pprint in the Python
1106 # being built (and which is used to originally generate
1107 # _sysconfigdata.py).
1108
1109 import pprint
1110 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1111 if os.path.exists(path):
Ned Deilye1c9794952013-01-29 00:07:46 -08001112 fp = open(path, 'r')
1113 data = fp.read()
1114 fp.close()
Ned Deilye6ef7022013-10-25 00:46:59 -07001115 # create build_time_vars dict
1116 exec(data)
1117 vars = {}
1118 for k, v in build_time_vars.items():
1119 if type(v) == type(''):
1120 for p in (include_path, lib_path):
1121 v = v.replace(' ' + p, '')
1122 v = v.replace(p + ' ', '')
1123 vars[k] = v
Ned Deilye1c9794952013-01-29 00:07:46 -08001124
Ned Deilye1c9794952013-01-29 00:07:46 -08001125 fp = open(path, 'w')
Ned Deilye6ef7022013-10-25 00:46:59 -07001126 # duplicated from sysconfig._generate_posix_vars()
1127 fp.write('# system configuration generated and used by'
1128 ' the sysconfig module\n')
1129 fp.write('build_time_vars = ')
1130 pprint.pprint(vars, stream=fp)
Ned Deilye1c9794952013-01-29 00:07:46 -08001131 fp.close()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001132
1133 # Add symlinks in /usr/local/bin, using relative links
1134 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1135 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1136 'Python.framework', 'Versions', version, 'bin')
1137 if os.path.exists(usr_local_bin):
1138 shutil.rmtree(usr_local_bin)
1139 os.makedirs(usr_local_bin)
1140 for fn in os.listdir(
1141 os.path.join(frmDir, 'Versions', version, 'bin')):
1142 os.symlink(os.path.join(to_framework, fn),
1143 os.path.join(usr_local_bin, fn))
1144
1145 os.chdir(curdir)
1146
Ned Deily53c460d2011-01-30 01:43:40 +00001147 if PYTHON_3:
Ezio Melotti6d0f0f22013-08-26 01:31:30 +03001148 # Remove the 'Current' link, that way we don't accidentally mess
Ned Deily53c460d2011-01-30 01:43:40 +00001149 # with an already installed version of python 2
1150 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1151 'Python.framework', 'Versions', 'Current'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001152
1153def patchFile(inPath, outPath):
1154 data = fileContents(inPath)
1155 data = data.replace('$FULL_VERSION', getFullVersion())
1156 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +00001157 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +00001158 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001159 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +00001160
1161 # This one is not handy as a template variable
1162 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deilye1c9794952013-01-29 00:07:46 -08001163 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001164 fp.write(data)
1165 fp.close()
1166
1167def patchScript(inPath, outPath):
1168 data = fileContents(inPath)
1169 data = data.replace('@PYVER@', getVersion())
Ned Deilye1c9794952013-01-29 00:07:46 -08001170 fp = open(outPath, 'w')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001171 fp.write(data)
1172 fp.close()
Ned Deilye1c9794952013-01-29 00:07:46 -08001173 os.chmod(outPath, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001174
1175
1176
1177def packageFromRecipe(targetDir, recipe):
1178 curdir = os.getcwd()
1179 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +00001180 # The major version (such as 2.5) is included in the package name
1181 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +00001182 # common.
1183 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001184 srcdir = recipe.get('source')
1185 pkgroot = recipe.get('topdir', srcdir)
1186 postflight = recipe.get('postflight')
1187 readme = textwrap.dedent(recipe['readme'])
1188 isRequired = recipe.get('required', True)
1189
Ned Deilye1c9794952013-01-29 00:07:46 -08001190 print("- building package %s"%(pkgname,))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001191
1192 # Substitute some variables
1193 textvars = dict(
1194 VER=getVersion(),
1195 FULLVER=getFullVersion(),
1196 )
1197 readme = readme % textvars
1198
1199 if pkgroot is not None:
1200 pkgroot = pkgroot % textvars
1201 else:
1202 pkgroot = '/'
1203
1204 if srcdir is not None:
1205 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1206 srcdir = srcdir % textvars
1207
1208 if postflight is not None:
1209 postflight = os.path.abspath(postflight)
1210
1211 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1212 os.makedirs(packageContents)
1213
1214 if srcdir is not None:
1215 os.chdir(srcdir)
1216 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1217 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1218 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1219
1220 fn = os.path.join(packageContents, 'PkgInfo')
1221 fp = open(fn, 'w')
1222 fp.write('pmkrpkg1')
1223 fp.close()
1224
1225 rsrcDir = os.path.join(packageContents, "Resources")
1226 os.mkdir(rsrcDir)
1227 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1228 fp.write(readme)
1229 fp.close()
1230
1231 if postflight is not None:
1232 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1233
1234 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001235 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001236 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001237 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1238 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1239 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001240 CFBundleShortVersionString=vers,
1241 IFMajorVersion=major,
1242 IFMinorVersion=minor,
1243 IFPkgFormatVersion=0.10000000149011612,
1244 IFPkgFlagAllowBackRev=False,
1245 IFPkgFlagAuthorizationAction="RootAuthorization",
1246 IFPkgFlagDefaultLocation=pkgroot,
1247 IFPkgFlagFollowLinks=True,
1248 IFPkgFlagInstallFat=True,
1249 IFPkgFlagIsRequired=isRequired,
1250 IFPkgFlagOverwritePermissions=False,
1251 IFPkgFlagRelocatable=False,
1252 IFPkgFlagRestartAction="NoRestart",
1253 IFPkgFlagRootVolumeOnly=True,
1254 IFPkgFlagUpdateInstalledLangauges=False,
1255 )
1256 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1257
1258 pl = Plist(
1259 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +00001260 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001261 IFPkgDescriptionVersion=vers,
1262 )
1263 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1264
1265 finally:
1266 os.chdir(curdir)
1267
1268
1269def makeMpkgPlist(path):
1270
1271 vers = getFullVersion()
Ned Deilye1c9794952013-01-29 00:07:46 -08001272 major, minor = getVersionMajorMinor()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001273
1274 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001275 CFBundleGetInfoString="Python %s"%(vers,),
1276 CFBundleIdentifier='org.python.Python',
1277 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001278 CFBundleShortVersionString=vers,
1279 IFMajorVersion=major,
1280 IFMinorVersion=minor,
1281 IFPkgFlagComponentDirectory="Contents/Packages",
1282 IFPkgFlagPackageList=[
1283 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001284 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001285 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001286 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001287 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001288 ],
1289 IFPkgFormatVersion=0.10000000149011612,
1290 IFPkgFlagBackgroundScaling="proportional",
1291 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001292 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001293 )
1294
1295 writePlist(pl, path)
1296
1297
1298def buildInstaller():
1299
1300 # Zap all compiled files
1301 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1302 for fn in filenames:
1303 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1304 os.unlink(os.path.join(dirpath, fn))
1305
1306 outdir = os.path.join(WORKDIR, 'installer')
1307 if os.path.exists(outdir):
1308 shutil.rmtree(outdir)
1309 os.mkdir(outdir)
1310
Ronald Oussoren508282e2009-03-30 19:34:51 +00001311 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001312 pkgcontents = os.path.join(pkgroot, 'Packages')
1313 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001314 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001315 packageFromRecipe(pkgcontents, recipe)
1316
1317 rsrcDir = os.path.join(pkgroot, 'Resources')
1318
1319 fn = os.path.join(pkgroot, 'PkgInfo')
1320 fp = open(fn, 'w')
1321 fp.write('pmkrpkg1')
1322 fp.close()
1323
1324 os.mkdir(rsrcDir)
1325
1326 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1327 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001328 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001329 IFPkgDescriptionVersion=getVersion(),
1330 )
1331
1332 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1333 for fn in os.listdir('resources'):
1334 if fn == '.svn': continue
1335 if fn.endswith('.jpg'):
1336 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1337 else:
1338 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1339
Ronald Oussorenc5555542006-06-11 20:24:45 +00001340 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001341
1342
1343def installSize(clear=False, _saved=[]):
1344 if clear:
1345 del _saved[:]
1346 if not _saved:
1347 data = captureCommand("du -ks %s"%(
1348 shellQuote(os.path.join(WORKDIR, '_root'))))
1349 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1350 return _saved[0]
1351
1352
1353def buildDMG():
1354 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001355 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001356 """
1357 outdir = os.path.join(WORKDIR, 'diskimage')
1358 if os.path.exists(outdir):
1359 shutil.rmtree(outdir)
1360
1361 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001362 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001363 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001364 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001365 imagepath = imagepath + '.dmg'
1366
1367 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001368 volname='Python %s'%(getFullVersion())
1369 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1370 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001371 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001372 shellQuote(imagepath + ".tmp.dmg" )))
1373
1374
1375 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1376 os.mkdir(os.path.join(WORKDIR, "mnt"))
1377 runCommand("hdiutil attach %s -mountroot %s"%(
1378 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1379
1380 # Custom icon for the DMG, shown when the DMG is mounted.
1381 shutil.copy("../Icons/Disk Image.icns",
1382 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilye1c9794952013-01-29 00:07:46 -08001383 runCommand("SetFile -a C %s/"%(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001384 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1385
1386 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1387
1388 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1389 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1390 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1391 setIcon(imagepath, "../Icons/Disk Image.icns")
1392
1393 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001394
1395 return imagepath
1396
1397
1398def setIcon(filePath, icnsPath):
1399 """
1400 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001401 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001402
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001403 dirPath = os.path.normpath(os.path.dirname(__file__))
1404 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001405 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1406 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1407 # to connections to the window server.
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001408 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1409 if not os.path.exists(appPath):
1410 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001411 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1412 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001413
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001414 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1415 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001416
1417def main():
1418 # First parse options and check if we can perform our work
1419 parseOptions()
1420 checkEnvironment()
1421
Ronald Oussoren508282e2009-03-30 19:34:51 +00001422 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001423 os.environ['CC'] = CC
Ned Deilye1c9794952013-01-29 00:07:46 -08001424 os.environ['CXX'] = CXX
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001425
1426 if os.path.exists(WORKDIR):
1427 shutil.rmtree(WORKDIR)
1428 os.mkdir(WORKDIR)
1429
Ronald Oussoren287128a2010-04-18 14:01:05 +00001430 os.environ['LC_ALL'] = 'C'
1431
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001432 # Then build third-party libraries such as sleepycat DB4.
1433 buildLibraries()
1434
1435 # Now build python itself
1436 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001437
1438 # And then build the documentation
1439 # Remove the Deployment Target from the shell
1440 # environment, it's no longer needed and
1441 # an unexpected build target can cause problems
1442 # when Sphinx and its dependencies need to
1443 # be (re-)installed.
1444 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001445 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001446
1447
1448 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001449 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001450 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001451 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001452
Benjamin Petersonc3104762008-10-03 11:52:06 +00001453 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001454 getVersion(),))
Ned Deilye1c9794952013-01-29 00:07:46 -08001455 os.chmod(folder, STAT_0o755)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001456 setIcon(folder, "../Icons/Python Folder.icns")
1457
1458 # Create the installer
1459 buildInstaller()
1460
1461 # And copy the readme into the directory containing the installer
1462 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1463
1464 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001465 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001466
1467 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deilye1c9794952013-01-29 00:07:46 -08001468 fp.write("# BUILD INFO\n")
1469 fp.write("# Date: %s\n" % time.ctime())
1470 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001471 fp.close()
1472
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001473 # And copy it to a DMG
1474 buildDMG()
1475
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001476if __name__ == "__main__":
1477 main()