blob: 498f8f0be2b1a94b7716251470ebf2edf7654afd [file] [log] [blame]
Ned Deily7d9cf832010-11-27 16:42:15 -08001#!/usr/bin/env python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
Ned Deilydfca8c92012-08-06 06:34:00 -07004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
11Python 2.4.
Thomas Wouters477c8d52006-05-27 19:21:47 +000012
Ned Deilydfca8c92012-08-06 06:34:00 -070013In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
14requires an installed version of hg and a third-party version of
15Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
16(for 10.6 or later) installed in /Library/Frameworks. When installed,
17the Python built by this script will attempt to dynamically link first to
18Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
19back to the ones in /System/Library/Framework. For the build, we recommend
20installing the most recent ActiveTcl 8.4 or 8.5 version.
21
2232-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
23and the installation of additional components, such as a newer Python
24(2.5 is needed for Python parser updates), hg, and svn (for the documentation
25build).
26
Thomas Wouters477c8d52006-05-27 19:21:47 +000027Usage: see USAGE variable in the script.
28"""
Ned Deily7d9cf832010-11-27 16:42:15 -080029import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
30try:
31 import urllib2 as urllib_request
32except ImportError:
33 import urllib.request as urllib_request
34
35STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
36 | stat.S_IRGRP | stat.S_IXGRP
37 | stat.S_IROTH | stat.S_IXOTH )
38
39STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
40 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
41 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000042
Thomas Wouters89f507f2006-12-13 04:49:30 +000043INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000045
46from plistlib import Plist
47
Thomas Wouters477c8d52006-05-27 19:21:47 +000048try:
49 from plistlib import writePlist
50except ImportError:
51 # We're run using python2.3
52 def writePlist(plist, path):
53 plist.write(path)
54
55def shellQuote(value):
56 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000057 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000058 a shell command.
59 """
60 return "'%s'"%(value.replace("'", "'\"'\"'"))
61
62def grepValue(fn, variable):
63 variable = variable + '='
64 for ln in open(fn, 'r'):
65 if ln.startswith(variable):
66 value = ln[len(variable):].strip()
67 return value[1:-1]
Ned Deily7d9cf832010-11-27 16:42:15 -080068 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000071
72def getVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080073 global _cache_getVersion
74 if _cache_getVersion is None:
75 _cache_getVersion = grepValue(
76 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
77 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
Benjamin Petersond9b7d482010-03-19 21:42:45 +000079def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
Ned Deily7d9cf832010-11-27 16:42:15 -080082def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
Thomas Wouters477c8d52006-05-27 19:21:47 +000087def getFullVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080088 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000091 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
Ned Deily7d9cf832010-11-27 16:42:15 -080094 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000097
Thomas Wouters89f507f2006-12-13 04:49:30 +000098# The directory we'll use to create the build (will be erased and recreated)
99WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100
Thomas Wouters89f507f2006-12-13 04:49:30 +0000101# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000105
106# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000107
108### There are some issues with the SDK selection below here,
109### The resulting binary doesn't work on all platforms that
110### it should. Always default to the 10.4u SDK until that
111### isue is resolved.
112###
113##if int(os.uname()[2].split('.')[0]) == 8:
114## # Explicitly use the 10.4u (universal) SDK when
115## # building on 10.4, the system headers are not
116## # useable for a universal build
117## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
118##else:
119## SDKPATH = "/"
120
Thomas Wouters89f507f2006-12-13 04:49:30 +0000121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000122
Ronald Oussoren1943f862009-03-30 19:39:14 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 'intel': ('i386', 'x86_64'),
126 '3-way': ('ppc', 'i386', 'x86_64'),
127 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
128default_target_map = {
129 '64-bit': '10.5',
130 '3-way': '10.5',
131 'intel': '10.5',
132 'all': '10.5',
133}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140
Ezio Melotti42da6632011-03-15 05:18:48 +0200141# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000142SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143 os.path.dirname(
144 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000145 os.path.abspath(__file__
146 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000147
Ronald Oussoren1943f862009-03-30 19:39:14 +0000148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000151target_cc_map = {
152 '10.3': 'gcc-4.0',
153 '10.4': 'gcc-4.0',
Ned Deily1bc276d2012-06-24 01:27:51 -0700154 '10.5': 'gcc-4.2',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000155 '10.6': 'gcc-4.2',
Ned Deily1bc276d2012-06-24 01:27:51 -0700156 '10.7': 'clang',
157 '10.8': 'clang',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000158}
159
160CC = target_cc_map[DEPTARGET]
161
162PYTHON_3 = getVersionTuple() >= (3, 0)
163
Thomas Wouters89f507f2006-12-13 04:49:30 +0000164USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000165 Usage: build_python [options]
166
167 Options:
168 -? or -h: Show this message
169 -b DIR
170 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
171 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
172 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
173 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000174 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
175 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000176""")% globals()
177
Ned Deily2910a7b2012-07-30 02:35:58 -0700178# Dict of object file names with shared library names to check after building.
179# This is to ensure that we ended up dynamically linking with the shared
180# library paths and versions we expected. For example:
181# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
182# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
183# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
184EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000185
186# Instructions for building libraries that are necessary for building a
187# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000188# [The recipes are defined here for convenience but instantiated later after
189# command line options have been processed.]
190def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000191 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000192
Ned Deily4d4c0ee2012-04-01 00:17:33 -0700193 result.extend([
194 dict(
195 name="XZ 5.0.3",
196 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
197 checksum='fefe52f9ecd521de2a8ce38c21a27574',
198 configure_pre=[
199 '--disable-dependency-tracking',
200 ]
201 )
202 ])
203
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000204 if DEPTARGET < '10.5':
205 result.extend([
206 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000207 name="Bzip2 1.0.6",
208 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
209 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000210 configure=None,
211 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
212 CC,
213 shellQuote(os.path.join(WORKDIR, 'libraries')),
214 ' -arch '.join(ARCHLIST),
215 SDKPATH,
216 ),
217 ),
218 dict(
219 name="ZLib 1.2.3",
220 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
221 checksum='debc62758716a169df9f62e6ab2bc634',
222 configure=None,
223 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
224 CC,
225 shellQuote(os.path.join(WORKDIR, 'libraries')),
226 ' -arch '.join(ARCHLIST),
227 SDKPATH,
228 ),
229 ),
230 dict(
231 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000232 name="GNU Readline 6.1.2",
233 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
234 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000235 patchlevel='0',
236 patches=[
237 # The readline maintainers don't do actual micro releases, but
238 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000239 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
240 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000241 ]
242 ),
243 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000244 name="SQLite 3.7.4",
245 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
246 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
247 configure_env=('CFLAGS="-Os'
248 ' -DSQLITE_ENABLE_FTS3'
249 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
250 ' -DSQLITE_ENABLE_RTREE'
251 ' -DSQLITE_TCL=0'
252 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000253 configure_pre=[
254 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000255 '--enable-shared=no',
256 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000257 '--disable-readline',
258 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000259 ]
260 ),
261 dict(
262 name="NCurses 5.5",
263 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
264 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
265 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000266 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000267 "--without-cxx",
268 "--without-ada",
269 "--without-progs",
270 "--without-curses-h",
271 "--enable-shared",
272 "--with-shared",
273 "--datadir=/usr/share",
274 "--sysconfdir=/etc",
275 "--sharedstatedir=/usr/com",
276 "--with-terminfo-dirs=/usr/share/terminfo",
277 "--with-default-terminfo-dir=/usr/share/terminfo",
278 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
279 "--enable-termcap",
280 ],
281 patches=[
282 "ncurses-5.5.patch",
283 ],
284 useLDFlags=False,
285 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
286 shellQuote(os.path.join(WORKDIR, 'libraries')),
287 shellQuote(os.path.join(WORKDIR, 'libraries')),
288 getVersion(),
289 ),
290 ),
291 ])
292
Ned Deily4f7ff782011-01-15 05:29:12 +0000293 if not PYTHON_3:
294 result.extend([
295 dict(
296 name="Sleepycat DB 4.7.25",
297 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
298 checksum='ec2b87e833779681a0c3a814aa71359e',
299 buildDir="build_unix",
300 configure="../dist/configure",
301 configure_pre=[
302 '--includedir=/usr/local/include/db4',
303 ]
304 ),
305 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000306
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000307 return result
308
Thomas Wouters477c8d52006-05-27 19:21:47 +0000309
Thomas Wouters477c8d52006-05-27 19:21:47 +0000310# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000311def pkg_recipes():
312 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
313 result = [
314 dict(
315 name="PythonFramework",
316 long_name="Python Framework",
317 source="/Library/Frameworks/Python.framework",
318 readme="""\
319 This package installs Python.framework, that is the python
320 interpreter and the standard library. This also includes Python
321 wrappers for lots of Mac OS X API's.
322 """,
323 postflight="scripts/postflight.framework",
324 selected='selected',
325 ),
326 dict(
327 name="PythonApplications",
328 long_name="GUI Applications",
329 source="/Applications/Python %(VER)s",
330 readme="""\
331 This package installs IDLE (an interactive Python IDE),
332 Python Launcher and Build Applet (create application bundles
333 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000334
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000335 It also installs a number of examples and demos.
336 """,
337 required=False,
338 selected='selected',
339 ),
340 dict(
341 name="PythonUnixTools",
342 long_name="UNIX command-line tools",
343 source="/usr/local/bin",
344 readme="""\
345 This package installs the unix tools in /usr/local/bin for
346 compatibility with older releases of Python. This package
347 is not necessary to use Python.
348 """,
349 required=False,
350 selected='selected',
351 ),
352 dict(
353 name="PythonDocumentation",
354 long_name="Python Documentation",
355 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
356 source="/pydocs",
357 readme="""\
358 This package installs the python documentation at a location
Ned Deilydfca8c92012-08-06 06:34:00 -0700359 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000360 """,
361 postflight="scripts/postflight.documentation",
362 required=False,
363 selected='selected',
364 ),
365 dict(
366 name="PythonProfileChanges",
367 long_name="Shell profile updater",
368 readme="""\
369 This packages updates your shell profile to make sure that
370 the Python tools are found by your shell in preference of
371 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000372
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000373 If you don't install this package you'll have to add
374 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
375 to your PATH by hand.
376 """,
377 postflight="scripts/postflight.patch-profile",
378 topdir="/Library/Frameworks/Python.framework",
379 source="/empty-dir",
380 required=False,
381 selected=unselected_for_python3,
382 ),
383 ]
384
Ned Deily430d7a32012-06-24 00:19:31 -0700385 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000386 result.append(
387 dict(
388 name="PythonSystemFixes",
389 long_name="Fix system Python",
390 readme="""\
391 This package updates the system python installation on
392 Mac OS X 10.3 to ensure that you can build new python extensions
393 using that copy of python after installing this version.
394 """,
395 postflight="../Tools/fixapplepython23.py",
396 topdir="/Library/Frameworks/Python.framework",
397 source="/empty-dir",
398 required=False,
399 selected=unselected_for_python3,
400 )
401 )
402 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000403
Thomas Wouters477c8d52006-05-27 19:21:47 +0000404def fatal(msg):
405 """
406 A fatal error, bail out.
407 """
408 sys.stderr.write('FATAL: ')
409 sys.stderr.write(msg)
410 sys.stderr.write('\n')
411 sys.exit(1)
412
413def fileContents(fn):
414 """
415 Return the contents of the named file
416 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800417 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000418
419def runCommand(commandline):
420 """
Ezio Melotti13925002011-03-16 11:05:33 +0200421 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000422 unless the command fails.
423 """
424 fd = os.popen(commandline, 'r')
425 data = fd.read()
426 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000427 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000428 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800429 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000430
431 if VERBOSE:
432 sys.stdout.write(data); sys.stdout.flush()
433
434def captureCommand(commandline):
435 fd = os.popen(commandline, 'r')
436 data = fd.read()
437 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000438 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000439 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800440 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000441
442 return data
443
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000444def getTclTkVersion(configfile, versionline):
445 """
446 search Tcl or Tk configuration file for version line
447 """
448 try:
449 f = open(configfile, "r")
450 except:
451 fatal("Framework configuration file not found: %s" % configfile)
452
453 for l in f:
454 if l.startswith(versionline):
455 f.close()
456 return l
457
458 fatal("Version variable %s not found in framework configuration file: %s"
459 % (versionline, configfile))
460
Thomas Wouters477c8d52006-05-27 19:21:47 +0000461def checkEnvironment():
462 """
463 Check that we're running on a supported system.
464 """
465
Ned Deilye59e4c52011-01-29 18:56:28 +0000466 if sys.version_info[0:2] < (2, 4):
467 fatal("This script must be run with Python 2.4 or later")
468
Thomas Wouters477c8d52006-05-27 19:21:47 +0000469 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000470 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000471
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000472 if int(platform.release().split('.')[0]) < 8:
473 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000474
475 if not os.path.exists(SDKPATH):
476 fatal("Please install the latest version of Xcode and the %s SDK"%(
477 os.path.basename(SDKPATH[:-4])))
478
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000479 # Because we only support dynamic load of only one major/minor version of
480 # Tcl/Tk, ensure:
481 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily2910a7b2012-07-30 02:35:58 -0700482 # higher than the Apple-supplied system version in
483 # SDKROOT/System/Library/Frameworks
484 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
485 # in) SDKROOT/Library/Frameworks with the same version as the system
486 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000487
Ned Deily2910a7b2012-07-30 02:35:58 -0700488 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000489 for framework in ['Tcl', 'Tk']:
Ned Deily2910a7b2012-07-30 02:35:58 -0700490 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000491 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily2910a7b2012-07-30 02:35:58 -0700492 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000493 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily2910a7b2012-07-30 02:35:58 -0700494 frameworks[framework] = os.readlink(sysfw)
495 if not os.path.exists(libfw):
496 fatal("Please install a link to a current %s %s as %s so "
497 "the user can override the system framework."
498 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000499 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000500 fatal("Version of %s must match %s" % (libfw, sysfw) )
501 if os.path.exists(usrfw):
502 fatal("Please rename %s to avoid possible dynamic load issues."
503 % usrfw)
504
Ned Deily2910a7b2012-07-30 02:35:58 -0700505 if frameworks['Tcl'] != frameworks['Tk']:
506 fatal("The Tcl and Tk frameworks are not the same version.")
507
508 # add files to check after build
509 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
510 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
511 % frameworks['Tcl'],
512 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
513 % frameworks['Tk'],
514 ]
515
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000516 # Remove inherited environment variables which might influence build
517 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
518 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
519 for ev in list(os.environ):
520 for prefix in environ_var_prefixes:
521 if ev.startswith(prefix) :
Ned Deily7d9cf832010-11-27 16:42:15 -0800522 print("INFO: deleting environment variable %s=%s" % (
523 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000524 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000525
Ned Deilydfca8c92012-08-06 06:34:00 -0700526 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
527 if 'SDK_TOOLS_BIN' in os.environ:
528 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
529 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
530 # add its fixed location here if it exists
531 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
532 if os.path.isdir(OLD_DEVELOPER_TOOLS):
533 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
534 os.environ['PATH'] = base_path
Ned Deily7d9cf832010-11-27 16:42:15 -0800535 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000536
Thomas Wouters477c8d52006-05-27 19:21:47 +0000537
Thomas Wouters89f507f2006-12-13 04:49:30 +0000538def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000539 """
540 Parse arguments and update global settings.
541 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000542 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000543 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000544
545 if args is None:
546 args = sys.argv[1:]
547
548 try:
549 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000550 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
551 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily7d9cf832010-11-27 16:42:15 -0800552 except getopt.GetoptError:
553 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000554 sys.exit(1)
555
556 if args:
Ned Deily7d9cf832010-11-27 16:42:15 -0800557 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000558 sys.exit(1)
559
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000560 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000561 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000562 if k in ('-h', '-?', '--help'):
Ned Deily7d9cf832010-11-27 16:42:15 -0800563 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000564 sys.exit(0)
565
566 elif k in ('-d', '--build-dir'):
567 WORKDIR=v
568
569 elif k in ('--third-party',):
570 DEPSRC=v
571
572 elif k in ('--sdk-path',):
573 SDKPATH=v
574
575 elif k in ('--src-dir',):
576 SRCDIR=v
577
Ronald Oussoren1943f862009-03-30 19:39:14 +0000578 elif k in ('--dep-target', ):
579 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000580 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000581
582 elif k in ('--universal-archs', ):
583 if v in UNIVERSALOPTS:
584 UNIVERSALARCHS = v
585 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000586 if deptarget is None:
587 # Select alternate default deployment
588 # target
589 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000590 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800591 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000592
Thomas Wouters477c8d52006-05-27 19:21:47 +0000593 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800594 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000595
596 SRCDIR=os.path.abspath(SRCDIR)
597 WORKDIR=os.path.abspath(WORKDIR)
598 SDKPATH=os.path.abspath(SDKPATH)
599 DEPSRC=os.path.abspath(DEPSRC)
600
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000601 CC=target_cc_map[DEPTARGET]
602
Ned Deily7d9cf832010-11-27 16:42:15 -0800603 print("Settings:")
604 print(" * Source directory:", SRCDIR)
605 print(" * Build directory: ", WORKDIR)
606 print(" * SDK location: ", SDKPATH)
607 print(" * Third-party source:", DEPSRC)
608 print(" * Deployment target:", DEPTARGET)
609 print(" * Universal architectures:", ARCHLIST)
610 print(" * C compiler:", CC)
611 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000612
613
614
615
616def extractArchive(builddir, archiveName):
617 """
618 Extract a source archive into 'builddir'. Returns the path of the
619 extracted archive.
620
621 XXX: This function assumes that archives contain a toplevel directory
622 that is has the same name as the basename of the archive. This is
623 save enough for anything we use.
624 """
625 curdir = os.getcwd()
626 try:
627 os.chdir(builddir)
628 if archiveName.endswith('.tar.gz'):
629 retval = os.path.basename(archiveName[:-7])
630 if os.path.exists(retval):
631 shutil.rmtree(retval)
632 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
633
634 elif archiveName.endswith('.tar.bz2'):
635 retval = os.path.basename(archiveName[:-8])
636 if os.path.exists(retval):
637 shutil.rmtree(retval)
638 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
639
640 elif archiveName.endswith('.tar'):
641 retval = os.path.basename(archiveName[:-4])
642 if os.path.exists(retval):
643 shutil.rmtree(retval)
644 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
645
646 elif archiveName.endswith('.zip'):
647 retval = os.path.basename(archiveName[:-4])
648 if os.path.exists(retval):
649 shutil.rmtree(retval)
650 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
651
652 data = fp.read()
653 xit = fp.close()
654 if xit is not None:
655 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800656 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000657
658 return os.path.join(builddir, retval)
659
660 finally:
661 os.chdir(curdir)
662
663KNOWNSIZES = {
664 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
665 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
666}
667
668def downloadURL(url, fname):
669 """
670 Download the contents of the url into the file.
671 """
672 try:
673 size = os.path.getsize(fname)
674 except OSError:
675 pass
676 else:
677 if KNOWNSIZES.get(url) == size:
Ned Deily7d9cf832010-11-27 16:42:15 -0800678 print("Using existing file for", url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000679 return
Ned Deily7d9cf832010-11-27 16:42:15 -0800680 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000681 fpOut = open(fname, 'wb')
682 block = fpIn.read(10240)
683 try:
684 while block:
685 fpOut.write(block)
686 block = fpIn.read(10240)
687 fpIn.close()
688 fpOut.close()
689 except:
690 try:
691 os.unlink(fname)
692 except:
693 pass
694
695def buildRecipe(recipe, basedir, archList):
696 """
697 Build software using a recipe. This function does the
698 'configure;make;make install' dance for C software, with a possibility
699 to customize this process, basically a poor-mans DarwinPorts.
700 """
701 curdir = os.getcwd()
702
703 name = recipe['name']
704 url = recipe['url']
705 configure = recipe.get('configure', './configure')
706 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
707 shellQuote(basedir)))
708
709 archiveName = os.path.split(url)[-1]
710 sourceArchive = os.path.join(DEPSRC, archiveName)
711
712 if not os.path.exists(DEPSRC):
713 os.mkdir(DEPSRC)
714
715
716 if os.path.exists(sourceArchive):
Ned Deily7d9cf832010-11-27 16:42:15 -0800717 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000718
719 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800720 print("Did not find local copy of %s"%(name,))
721 print("Downloading %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000722 downloadURL(url, sourceArchive)
Ned Deily7d9cf832010-11-27 16:42:15 -0800723 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000724
Ned Deily7d9cf832010-11-27 16:42:15 -0800725 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000726 buildDir=os.path.join(WORKDIR, '_bld')
727 if not os.path.exists(buildDir):
728 os.mkdir(buildDir)
729
730 workDir = extractArchive(buildDir, sourceArchive)
731 os.chdir(workDir)
732 if 'buildDir' in recipe:
733 os.chdir(recipe['buildDir'])
734
735
736 for fn in recipe.get('patches', ()):
737 if fn.startswith('http://'):
738 # Download the patch before applying it.
739 path = os.path.join(DEPSRC, os.path.basename(fn))
740 downloadURL(fn, path)
741 fn = path
742
743 fn = os.path.join(curdir, fn)
744 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
745 shellQuote(fn),))
746
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000747 if configure is not None:
748 configure_args = [
749 "--prefix=/usr/local",
750 "--enable-static",
751 "--disable-shared",
752 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
753 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000754
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000755 if 'configure_pre' in recipe:
756 args = list(recipe['configure_pre'])
757 if '--disable-static' in args:
758 configure_args.remove('--enable-static')
759 if '--enable-shared' in args:
760 configure_args.remove('--disable-shared')
761 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000762
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000763 if recipe.get('useLDFlags', 1):
764 configure_args.extend([
765 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
766 ' -arch '.join(archList),
767 shellQuote(SDKPATH)[1:-1],
768 shellQuote(basedir)[1:-1],),
769 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000770 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000771 shellQuote(basedir)[1:-1],
772 ' -arch '.join(archList)),
773 ])
774 else:
775 configure_args.extend([
776 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
777 ' -arch '.join(archList),
778 shellQuote(SDKPATH)[1:-1],
779 shellQuote(basedir)[1:-1],),
780 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000781
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000782 if 'configure_post' in recipe:
783 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000784
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000785 configure_args.insert(0, configure)
786 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000787
Ned Deily4f7ff782011-01-15 05:29:12 +0000788 if 'configure_env' in recipe:
789 configure_args.insert(0, recipe['configure_env'])
790
Ned Deily7d9cf832010-11-27 16:42:15 -0800791 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000792 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000793
Ned Deily7d9cf832010-11-27 16:42:15 -0800794 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000795 runCommand('{ ' + install + ' ;} 2>&1')
796
Ned Deily7d9cf832010-11-27 16:42:15 -0800797 print("Done %s"%(name,))
798 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000799
800 os.chdir(curdir)
801
802def buildLibraries():
803 """
804 Build our dependencies into $WORKDIR/libraries/usr/local
805 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800806 print("")
807 print("Building required libraries")
808 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 universal = os.path.join(WORKDIR, 'libraries')
810 os.mkdir(universal)
811 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
812 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
813
Ronald Oussoren1943f862009-03-30 19:39:14 +0000814 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000815 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000816
817
818
819def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000820 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000821 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily7d9cf832010-11-27 16:42:15 -0800822 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000823 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000824 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000825 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000826 curDir = os.getcwd()
827 os.chdir(buildDir)
828 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000829 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000830 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000831 if not os.path.exists(docdir):
832 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000833 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000834
835
836def buildPython():
Ned Deily7d9cf832010-11-27 16:42:15 -0800837 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000838
839 buildDir = os.path.join(WORKDIR, '_bld', 'python')
840 rootDir = os.path.join(WORKDIR, '_root')
841
842 if os.path.exists(buildDir):
843 shutil.rmtree(buildDir)
844 if os.path.exists(rootDir):
845 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000846 os.makedirs(buildDir)
847 os.makedirs(rootDir)
848 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000849 curdir = os.getcwd()
850 os.chdir(buildDir)
851
852 # Not sure if this is still needed, the original build script
853 # claims that parts of the install assume python.exe exists.
854 os.symlink('python', os.path.join(buildDir, 'python.exe'))
855
856 # Extract the version from the configure file, needed to calculate
857 # several paths.
858 version = getVersion()
859
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000860 # Since the extra libs are not in their installed framework location
861 # during the build, augment the library path so that the interpreter
862 # will find them during its extension import sanity checks.
863 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
864 'libraries', 'usr', 'local', 'lib')
Ned Deily7d9cf832010-11-27 16:42:15 -0800865 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000866 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000867 "--with-universal-archs=%s "
868 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000869 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
870 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
871 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
872 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000873 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000874 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000875 shellQuote(WORKDIR)[1:-1]))
876
Ned Deily7d9cf832010-11-27 16:42:15 -0800877 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000878 runCommand("make")
879
Ned Deily7d9cf832010-11-27 16:42:15 -0800880 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000881 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000882 shellQuote(rootDir)))
883
Ned Deily7d9cf832010-11-27 16:42:15 -0800884 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000885 runCommand("make frameworkinstallextras DESTDIR=%s"%(
886 shellQuote(rootDir)))
887
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000888 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily7d9cf832010-11-27 16:42:15 -0800889 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
891 runCommand("mv %s/* %s"%(
892 shellQuote(os.path.join(
893 WORKDIR, 'libraries', 'Library', 'Frameworks',
894 'Python.framework', 'Versions', getVersion(),
895 'lib')),
896 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
897 'Python.framework', 'Versions', getVersion(),
898 'lib'))))
899
Ned Deily7d9cf832010-11-27 16:42:15 -0800900 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000901 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000902 gid = grp.getgrnam('admin').gr_gid
903
Ned Deily2910a7b2012-07-30 02:35:58 -0700904 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905 for dirpath, dirnames, filenames in os.walk(frmDir):
906 for dn in dirnames:
Ned Deily7d9cf832010-11-27 16:42:15 -0800907 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000908 os.chown(os.path.join(dirpath, dn), -1, gid)
909
Thomas Wouters477c8d52006-05-27 19:21:47 +0000910 for fn in filenames:
911 if os.path.islink(fn):
912 continue
913
914 # "chmod g+w $fn"
915 p = os.path.join(dirpath, fn)
916 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000917 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
918 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000919
Ned Deily2910a7b2012-07-30 02:35:58 -0700920 if fn in EXPECTED_SHARED_LIBS:
921 # check to see that this file was linked with the
922 # expected library path and version
923 data = captureCommand("otool -L %s" % shellQuote(p))
924 for sl in EXPECTED_SHARED_LIBS[fn]:
925 if ("\t%s " % sl) not in data:
926 print("Expected shared lib %s was not linked with %s"
927 % (sl, p))
928 shared_lib_error = True
929
930 if shared_lib_error:
931 fatal("Unexpected shared library errors.")
932
Ned Deilye59e4c52011-01-29 18:56:28 +0000933 if PYTHON_3:
934 LDVERSION=None
935 VERSION=None
936 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000937
Ned Deilye59e4c52011-01-29 18:56:28 +0000938 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000939 for ln in fp:
940 if ln.startswith('VERSION='):
941 VERSION=ln.split()[1]
942 if ln.startswith('ABIFLAGS='):
943 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000944 if ln.startswith('LDVERSION='):
945 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000946 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000947
Ned Deilye59e4c52011-01-29 18:56:28 +0000948 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
949 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
950 config_suffix = '-' + LDVERSION
951 else:
952 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000953
Thomas Wouters477c8d52006-05-27 19:21:47 +0000954 # We added some directories to the search path during the configure
955 # phase. Remove those because those directories won't be there on
Ned Deilya606aef2012-07-21 10:48:09 -0700956 # the end-users system. Also remove the directories from _sysconfigdata.py
957 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000958
Ned Deilya606aef2012-07-21 10:48:09 -0700959 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
960 'Python.framework', 'Versions',
961 version, 'lib', 'python%s'%(version,))
962 paths = [os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile'),
963 os.path.join(path_to_lib, '_sysconfigdata.py')]
964 for path in paths:
965 if not os.path.exists(path):
966 continue
967 fp = open(path, 'r')
968 data = fp.read()
969 fp.close()
970
Ned Deily2c80e122012-07-22 00:46:46 -0700971 data = data.replace(' -L%s/libraries/usr/local/lib'%(WORKDIR,), '')
972 data = data.replace(' -I%s/libraries/usr/local/include'%(WORKDIR,), '')
Ned Deilya606aef2012-07-21 10:48:09 -0700973 fp = open(path, 'w')
974 fp.write(data)
975 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000976
977 # Add symlinks in /usr/local/bin, using relative links
978 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
979 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
980 'Python.framework', 'Versions', version, 'bin')
981 if os.path.exists(usr_local_bin):
982 shutil.rmtree(usr_local_bin)
983 os.makedirs(usr_local_bin)
984 for fn in os.listdir(
985 os.path.join(frmDir, 'Versions', version, 'bin')):
986 os.symlink(os.path.join(to_framework, fn),
987 os.path.join(usr_local_bin, fn))
988
989 os.chdir(curdir)
990
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000991 if PYTHON_3:
992 # Remove the 'Current' link, that way we don't accidently mess
993 # with an already installed version of python 2
994 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
995 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000996
997def patchFile(inPath, outPath):
998 data = fileContents(inPath)
999 data = data.replace('$FULL_VERSION', getFullVersion())
1000 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001001 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001002 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001003 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001004
1005 # This one is not handy as a template variable
1006 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily7d9cf832010-11-27 16:42:15 -08001007 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001008 fp.write(data)
1009 fp.close()
1010
1011def patchScript(inPath, outPath):
1012 data = fileContents(inPath)
1013 data = data.replace('@PYVER@', getVersion())
Ned Deily7d9cf832010-11-27 16:42:15 -08001014 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001015 fp.write(data)
1016 fp.close()
Ned Deily7d9cf832010-11-27 16:42:15 -08001017 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001018
1019
1020
1021def packageFromRecipe(targetDir, recipe):
1022 curdir = os.getcwd()
1023 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001024 # The major version (such as 2.5) is included in the package name
1025 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001026 # common.
1027 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001028 srcdir = recipe.get('source')
1029 pkgroot = recipe.get('topdir', srcdir)
1030 postflight = recipe.get('postflight')
1031 readme = textwrap.dedent(recipe['readme'])
1032 isRequired = recipe.get('required', True)
1033
Ned Deily7d9cf832010-11-27 16:42:15 -08001034 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001035
1036 # Substitute some variables
1037 textvars = dict(
1038 VER=getVersion(),
1039 FULLVER=getFullVersion(),
1040 )
1041 readme = readme % textvars
1042
1043 if pkgroot is not None:
1044 pkgroot = pkgroot % textvars
1045 else:
1046 pkgroot = '/'
1047
1048 if srcdir is not None:
1049 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1050 srcdir = srcdir % textvars
1051
1052 if postflight is not None:
1053 postflight = os.path.abspath(postflight)
1054
1055 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1056 os.makedirs(packageContents)
1057
1058 if srcdir is not None:
1059 os.chdir(srcdir)
1060 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1061 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1062 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1063
1064 fn = os.path.join(packageContents, 'PkgInfo')
1065 fp = open(fn, 'w')
1066 fp.write('pmkrpkg1')
1067 fp.close()
1068
1069 rsrcDir = os.path.join(packageContents, "Resources")
1070 os.mkdir(rsrcDir)
1071 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1072 fp.write(readme)
1073 fp.close()
1074
1075 if postflight is not None:
1076 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1077
1078 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001079 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001080 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001081 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1082 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1083 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001084 CFBundleShortVersionString=vers,
1085 IFMajorVersion=major,
1086 IFMinorVersion=minor,
1087 IFPkgFormatVersion=0.10000000149011612,
1088 IFPkgFlagAllowBackRev=False,
1089 IFPkgFlagAuthorizationAction="RootAuthorization",
1090 IFPkgFlagDefaultLocation=pkgroot,
1091 IFPkgFlagFollowLinks=True,
1092 IFPkgFlagInstallFat=True,
1093 IFPkgFlagIsRequired=isRequired,
1094 IFPkgFlagOverwritePermissions=False,
1095 IFPkgFlagRelocatable=False,
1096 IFPkgFlagRestartAction="NoRestart",
1097 IFPkgFlagRootVolumeOnly=True,
1098 IFPkgFlagUpdateInstalledLangauges=False,
1099 )
1100 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1101
1102 pl = Plist(
1103 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001104 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001105 IFPkgDescriptionVersion=vers,
1106 )
1107 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1108
1109 finally:
1110 os.chdir(curdir)
1111
1112
1113def makeMpkgPlist(path):
1114
1115 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001116 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001117
1118 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001119 CFBundleGetInfoString="Python %s"%(vers,),
1120 CFBundleIdentifier='org.python.Python',
1121 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001122 CFBundleShortVersionString=vers,
1123 IFMajorVersion=major,
1124 IFMinorVersion=minor,
1125 IFPkgFlagComponentDirectory="Contents/Packages",
1126 IFPkgFlagPackageList=[
1127 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001128 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001129 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001130 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001131 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001132 ],
1133 IFPkgFormatVersion=0.10000000149011612,
1134 IFPkgFlagBackgroundScaling="proportional",
1135 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001136 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001137 )
1138
1139 writePlist(pl, path)
1140
1141
1142def buildInstaller():
1143
1144 # Zap all compiled files
1145 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1146 for fn in filenames:
1147 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1148 os.unlink(os.path.join(dirpath, fn))
1149
1150 outdir = os.path.join(WORKDIR, 'installer')
1151 if os.path.exists(outdir):
1152 shutil.rmtree(outdir)
1153 os.mkdir(outdir)
1154
Ronald Oussoren1943f862009-03-30 19:39:14 +00001155 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001156 pkgcontents = os.path.join(pkgroot, 'Packages')
1157 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001158 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 packageFromRecipe(pkgcontents, recipe)
1160
1161 rsrcDir = os.path.join(pkgroot, 'Resources')
1162
1163 fn = os.path.join(pkgroot, 'PkgInfo')
1164 fp = open(fn, 'w')
1165 fp.write('pmkrpkg1')
1166 fp.close()
1167
1168 os.mkdir(rsrcDir)
1169
1170 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1171 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001172 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001173 IFPkgDescriptionVersion=getVersion(),
1174 )
1175
1176 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1177 for fn in os.listdir('resources'):
1178 if fn == '.svn': continue
1179 if fn.endswith('.jpg'):
1180 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1181 else:
1182 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1183
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001184 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001185
1186
1187def installSize(clear=False, _saved=[]):
1188 if clear:
1189 del _saved[:]
1190 if not _saved:
1191 data = captureCommand("du -ks %s"%(
1192 shellQuote(os.path.join(WORKDIR, '_root'))))
1193 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1194 return _saved[0]
1195
1196
1197def buildDMG():
1198 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001199 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001200 """
1201 outdir = os.path.join(WORKDIR, 'diskimage')
1202 if os.path.exists(outdir):
1203 shutil.rmtree(outdir)
1204
1205 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001206 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001207 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001208 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001209 imagepath = imagepath + '.dmg'
1210
1211 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001212 volname='Python %s'%(getFullVersion())
1213 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1214 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001215 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001216 shellQuote(imagepath + ".tmp.dmg" )))
1217
1218
1219 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1220 os.mkdir(os.path.join(WORKDIR, "mnt"))
1221 runCommand("hdiutil attach %s -mountroot %s"%(
1222 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1223
1224 # Custom icon for the DMG, shown when the DMG is mounted.
1225 shutil.copy("../Icons/Disk Image.icns",
1226 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deilydfca8c92012-08-06 06:34:00 -07001227 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001228 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1229
1230 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1231
1232 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1233 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1234 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1235 setIcon(imagepath, "../Icons/Disk Image.icns")
1236
1237 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001238
1239 return imagepath
1240
1241
1242def setIcon(filePath, icnsPath):
1243 """
1244 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001245 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001246
Ronald Oussoren70050672010-04-30 15:00:26 +00001247 dirPath = os.path.normpath(os.path.dirname(__file__))
1248 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001249 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1250 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1251 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001252 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1253 if not os.path.exists(appPath):
1254 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001255 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1256 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001257
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001258 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1259 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001260
1261def main():
1262 # First parse options and check if we can perform our work
1263 parseOptions()
1264 checkEnvironment()
1265
Ronald Oussoren1943f862009-03-30 19:39:14 +00001266 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001267 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001268
1269 if os.path.exists(WORKDIR):
1270 shutil.rmtree(WORKDIR)
1271 os.mkdir(WORKDIR)
1272
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001273 os.environ['LC_ALL'] = 'C'
1274
Thomas Wouters477c8d52006-05-27 19:21:47 +00001275 # Then build third-party libraries such as sleepycat DB4.
1276 buildLibraries()
1277
1278 # Now build python itself
1279 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001280
1281 # And then build the documentation
1282 # Remove the Deployment Target from the shell
1283 # environment, it's no longer needed and
1284 # an unexpected build target can cause problems
1285 # when Sphinx and its dependencies need to
1286 # be (re-)installed.
1287 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001288 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001289
1290
1291 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001292 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001293 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001294 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001295
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001296 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001297 getVersion(),))
Ned Deily7d9cf832010-11-27 16:42:15 -08001298 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001299 setIcon(folder, "../Icons/Python Folder.icns")
1300
1301 # Create the installer
1302 buildInstaller()
1303
1304 # And copy the readme into the directory containing the installer
1305 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1306
1307 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001308 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001309
1310 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily7d9cf832010-11-27 16:42:15 -08001311 fp.write("# BUILD INFO\n")
1312 fp.write("# Date: %s\n" % time.ctime())
1313 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001314 fp.close()
1315
Thomas Wouters477c8d52006-05-27 19:21:47 +00001316 # And copy it to a DMG
1317 buildDMG()
1318
Thomas Wouters477c8d52006-05-27 19:21:47 +00001319if __name__ == "__main__":
1320 main()