blob: 7891b6b21edd09256c84fe956f84962de69906a7 [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.
4It requires at least Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK for
532-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
13Usage: see USAGE variable in the script.
14"""
Ned Deily7d9cf832010-11-27 16:42:15 -080015import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
16try:
17 import urllib2 as urllib_request
18except ImportError:
19 import urllib.request as urllib_request
20
21STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
22 | stat.S_IRGRP | stat.S_IXGRP
23 | stat.S_IROTH | stat.S_IXOTH )
24
25STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
26 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
27 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000028
Thomas Wouters89f507f2006-12-13 04:49:30 +000029INCLUDE_TIMESTAMP = 1
30VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000031
32from plistlib import Plist
33
Thomas Wouters477c8d52006-05-27 19:21:47 +000034try:
35 from plistlib import writePlist
36except ImportError:
37 # We're run using python2.3
38 def writePlist(plist, path):
39 plist.write(path)
40
41def shellQuote(value):
42 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000043 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000044 a shell command.
45 """
46 return "'%s'"%(value.replace("'", "'\"'\"'"))
47
48def grepValue(fn, variable):
49 variable = variable + '='
50 for ln in open(fn, 'r'):
51 if ln.startswith(variable):
52 value = ln[len(variable):].strip()
53 return value[1:-1]
Ned Deily7d9cf832010-11-27 16:42:15 -080054 raise RuntimeError("Cannot find variable %s" % variable[:-1])
55
56_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000057
58def getVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080059 global _cache_getVersion
60 if _cache_getVersion is None:
61 _cache_getVersion = grepValue(
62 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
63 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000064
Benjamin Petersond9b7d482010-03-19 21:42:45 +000065def getVersionTuple():
66 return tuple([int(n) for n in getVersion().split('.')])
67
Ned Deily7d9cf832010-11-27 16:42:15 -080068def getVersionMajorMinor():
69 return tuple([int(n) for n in getVersion().split('.', 2)])
70
71_cache_getFullVersion = None
72
Thomas Wouters477c8d52006-05-27 19:21:47 +000073def getFullVersion():
Ned Deily7d9cf832010-11-27 16:42:15 -080074 global _cache_getFullVersion
75 if _cache_getFullVersion is not None:
76 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000077 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
78 for ln in open(fn):
79 if 'PY_VERSION' in ln:
Ned Deily7d9cf832010-11-27 16:42:15 -080080 _cache_getFullVersion = ln.split()[-1][1:-1]
81 return _cache_getFullVersion
82 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000083
Thomas Wouters89f507f2006-12-13 04:49:30 +000084# The directory we'll use to create the build (will be erased and recreated)
85WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000086
Thomas Wouters89f507f2006-12-13 04:49:30 +000087# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000088# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000089DEPSRC = os.path.join(WORKDIR, 'third-party')
90DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000091
92# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +000093
94### There are some issues with the SDK selection below here,
95### The resulting binary doesn't work on all platforms that
96### it should. Always default to the 10.4u SDK until that
97### isue is resolved.
98###
99##if int(os.uname()[2].split('.')[0]) == 8:
100## # Explicitly use the 10.4u (universal) SDK when
101## # building on 10.4, the system headers are not
102## # useable for a universal build
103## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
104##else:
105## SDKPATH = "/"
106
Thomas Wouters89f507f2006-12-13 04:49:30 +0000107SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000108
Ronald Oussoren1943f862009-03-30 19:39:14 +0000109universal_opts_map = { '32-bit': ('i386', 'ppc',),
110 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000111 'intel': ('i386', 'x86_64'),
112 '3-way': ('ppc', 'i386', 'x86_64'),
113 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
114default_target_map = {
115 '64-bit': '10.5',
116 '3-way': '10.5',
117 'intel': '10.5',
118 'all': '10.5',
119}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000120
121UNIVERSALOPTS = tuple(universal_opts_map.keys())
122
123UNIVERSALARCHS = '32-bit'
124
125ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126
Ezio Melotti42da6632011-03-15 05:18:48 +0200127# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000128SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000129 os.path.dirname(
130 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000131 os.path.abspath(__file__
132 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000133
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
135DEPTARGET = '10.3'
136
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000137target_cc_map = {
138 '10.3': 'gcc-4.0',
139 '10.4': 'gcc-4.0',
Ned Deily1bc276d2012-06-24 01:27:51 -0700140 '10.5': 'gcc-4.2',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000141 '10.6': 'gcc-4.2',
Ned Deily1bc276d2012-06-24 01:27:51 -0700142 '10.7': 'clang',
143 '10.8': 'clang',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000144}
145
146CC = target_cc_map[DEPTARGET]
147
148PYTHON_3 = getVersionTuple() >= (3, 0)
149
Thomas Wouters89f507f2006-12-13 04:49:30 +0000150USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000151 Usage: build_python [options]
152
153 Options:
154 -? or -h: Show this message
155 -b DIR
156 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
157 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
158 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
159 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000160 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
161 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000162""")% globals()
163
164
165# Instructions for building libraries that are necessary for building a
166# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000167# [The recipes are defined here for convenience but instantiated later after
168# command line options have been processed.]
169def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000170 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000171
Ned Deily4d4c0ee2012-04-01 00:17:33 -0700172 result.extend([
173 dict(
174 name="XZ 5.0.3",
175 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
176 checksum='fefe52f9ecd521de2a8ce38c21a27574',
177 configure_pre=[
178 '--disable-dependency-tracking',
179 ]
180 )
181 ])
182
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000183 if DEPTARGET < '10.5':
184 result.extend([
185 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000186 name="Bzip2 1.0.6",
187 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
188 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000189 configure=None,
190 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
191 CC,
192 shellQuote(os.path.join(WORKDIR, 'libraries')),
193 ' -arch '.join(ARCHLIST),
194 SDKPATH,
195 ),
196 ),
197 dict(
198 name="ZLib 1.2.3",
199 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
200 checksum='debc62758716a169df9f62e6ab2bc634',
201 configure=None,
202 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
203 CC,
204 shellQuote(os.path.join(WORKDIR, 'libraries')),
205 ' -arch '.join(ARCHLIST),
206 SDKPATH,
207 ),
208 ),
209 dict(
210 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000211 name="GNU Readline 6.1.2",
212 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
213 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000214 patchlevel='0',
215 patches=[
216 # The readline maintainers don't do actual micro releases, but
217 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000218 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
219 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000220 ]
221 ),
222 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000223 name="SQLite 3.7.4",
224 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
225 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
226 configure_env=('CFLAGS="-Os'
227 ' -DSQLITE_ENABLE_FTS3'
228 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
229 ' -DSQLITE_ENABLE_RTREE'
230 ' -DSQLITE_TCL=0'
231 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000232 configure_pre=[
233 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000234 '--enable-shared=no',
235 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000236 '--disable-readline',
237 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000238 ]
239 ),
240 dict(
241 name="NCurses 5.5",
242 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
243 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
244 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000245 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000246 "--without-cxx",
247 "--without-ada",
248 "--without-progs",
249 "--without-curses-h",
250 "--enable-shared",
251 "--with-shared",
252 "--datadir=/usr/share",
253 "--sysconfdir=/etc",
254 "--sharedstatedir=/usr/com",
255 "--with-terminfo-dirs=/usr/share/terminfo",
256 "--with-default-terminfo-dir=/usr/share/terminfo",
257 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
258 "--enable-termcap",
259 ],
260 patches=[
261 "ncurses-5.5.patch",
262 ],
263 useLDFlags=False,
264 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
265 shellQuote(os.path.join(WORKDIR, 'libraries')),
266 shellQuote(os.path.join(WORKDIR, 'libraries')),
267 getVersion(),
268 ),
269 ),
270 ])
271
Ned Deily4f7ff782011-01-15 05:29:12 +0000272 if not PYTHON_3:
273 result.extend([
274 dict(
275 name="Sleepycat DB 4.7.25",
276 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
277 checksum='ec2b87e833779681a0c3a814aa71359e',
278 buildDir="build_unix",
279 configure="../dist/configure",
280 configure_pre=[
281 '--includedir=/usr/local/include/db4',
282 ]
283 ),
284 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000285
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000286 return result
287
Thomas Wouters477c8d52006-05-27 19:21:47 +0000288
Thomas Wouters477c8d52006-05-27 19:21:47 +0000289# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000290def pkg_recipes():
291 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
292 result = [
293 dict(
294 name="PythonFramework",
295 long_name="Python Framework",
296 source="/Library/Frameworks/Python.framework",
297 readme="""\
298 This package installs Python.framework, that is the python
299 interpreter and the standard library. This also includes Python
300 wrappers for lots of Mac OS X API's.
301 """,
302 postflight="scripts/postflight.framework",
303 selected='selected',
304 ),
305 dict(
306 name="PythonApplications",
307 long_name="GUI Applications",
308 source="/Applications/Python %(VER)s",
309 readme="""\
310 This package installs IDLE (an interactive Python IDE),
311 Python Launcher and Build Applet (create application bundles
312 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000313
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000314 It also installs a number of examples and demos.
315 """,
316 required=False,
317 selected='selected',
318 ),
319 dict(
320 name="PythonUnixTools",
321 long_name="UNIX command-line tools",
322 source="/usr/local/bin",
323 readme="""\
324 This package installs the unix tools in /usr/local/bin for
325 compatibility with older releases of Python. This package
326 is not necessary to use Python.
327 """,
328 required=False,
329 selected='selected',
330 ),
331 dict(
332 name="PythonDocumentation",
333 long_name="Python Documentation",
334 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
335 source="/pydocs",
336 readme="""\
337 This package installs the python documentation at a location
338 that is useable for pydoc and IDLE. If you have installed Xcode
339 it will also install a link to the documentation in
340 /Developer/Documentation/Python
341 """,
342 postflight="scripts/postflight.documentation",
343 required=False,
344 selected='selected',
345 ),
346 dict(
347 name="PythonProfileChanges",
348 long_name="Shell profile updater",
349 readme="""\
350 This packages updates your shell profile to make sure that
351 the Python tools are found by your shell in preference of
352 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000353
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000354 If you don't install this package you'll have to add
355 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
356 to your PATH by hand.
357 """,
358 postflight="scripts/postflight.patch-profile",
359 topdir="/Library/Frameworks/Python.framework",
360 source="/empty-dir",
361 required=False,
362 selected=unselected_for_python3,
363 ),
364 ]
365
Ned Deily430d7a32012-06-24 00:19:31 -0700366 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000367 result.append(
368 dict(
369 name="PythonSystemFixes",
370 long_name="Fix system Python",
371 readme="""\
372 This package updates the system python installation on
373 Mac OS X 10.3 to ensure that you can build new python extensions
374 using that copy of python after installing this version.
375 """,
376 postflight="../Tools/fixapplepython23.py",
377 topdir="/Library/Frameworks/Python.framework",
378 source="/empty-dir",
379 required=False,
380 selected=unselected_for_python3,
381 )
382 )
383 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000384
Thomas Wouters477c8d52006-05-27 19:21:47 +0000385def fatal(msg):
386 """
387 A fatal error, bail out.
388 """
389 sys.stderr.write('FATAL: ')
390 sys.stderr.write(msg)
391 sys.stderr.write('\n')
392 sys.exit(1)
393
394def fileContents(fn):
395 """
396 Return the contents of the named file
397 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800398 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000399
400def runCommand(commandline):
401 """
Ezio Melotti13925002011-03-16 11:05:33 +0200402 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000403 unless the command fails.
404 """
405 fd = os.popen(commandline, 'r')
406 data = fd.read()
407 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000408 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000409 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800410 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000411
412 if VERBOSE:
413 sys.stdout.write(data); sys.stdout.flush()
414
415def captureCommand(commandline):
416 fd = os.popen(commandline, 'r')
417 data = fd.read()
418 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000419 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000420 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800421 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000422
423 return data
424
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000425def getTclTkVersion(configfile, versionline):
426 """
427 search Tcl or Tk configuration file for version line
428 """
429 try:
430 f = open(configfile, "r")
431 except:
432 fatal("Framework configuration file not found: %s" % configfile)
433
434 for l in f:
435 if l.startswith(versionline):
436 f.close()
437 return l
438
439 fatal("Version variable %s not found in framework configuration file: %s"
440 % (versionline, configfile))
441
Thomas Wouters477c8d52006-05-27 19:21:47 +0000442def checkEnvironment():
443 """
444 Check that we're running on a supported system.
445 """
446
Ned Deilye59e4c52011-01-29 18:56:28 +0000447 if sys.version_info[0:2] < (2, 4):
448 fatal("This script must be run with Python 2.4 or later")
449
Thomas Wouters477c8d52006-05-27 19:21:47 +0000450 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000451 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000452
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000453 if int(platform.release().split('.')[0]) < 8:
454 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000455
456 if not os.path.exists(SDKPATH):
457 fatal("Please install the latest version of Xcode and the %s SDK"%(
458 os.path.basename(SDKPATH[:-4])))
459
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000460 # Because we only support dynamic load of only one major/minor version of
461 # Tcl/Tk, ensure:
462 # 1. there are no user-installed frameworks of Tcl/Tk with version
463 # higher than the Apple-supplied system version
464 # 2. there is a user-installed framework in /Library/Frameworks with the
465 # same version as the system version. This allows users to choose
466 # to install a newer patch level.
467
468 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000469 #fw = dict(lower=framework.lower(),
470 # upper=framework.upper(),
471 # cap=framework.capitalize())
472 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
473 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
474 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000475 libfw = os.path.join('/', fwpth)
476 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000477 #version = "%(upper)s_VERSION" % fw
478 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000479 fatal("Version of %s must match %s" % (libfw, sysfw) )
480 if os.path.exists(usrfw):
481 fatal("Please rename %s to avoid possible dynamic load issues."
482 % usrfw)
483
484 # Remove inherited environment variables which might influence build
485 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
486 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
487 for ev in list(os.environ):
488 for prefix in environ_var_prefixes:
489 if ev.startswith(prefix) :
Ned Deily7d9cf832010-11-27 16:42:15 -0800490 print("INFO: deleting environment variable %s=%s" % (
491 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000492 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000493
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000494 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
Ned Deily7d9cf832010-11-27 16:42:15 -0800495 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000496
Thomas Wouters477c8d52006-05-27 19:21:47 +0000497
Thomas Wouters89f507f2006-12-13 04:49:30 +0000498def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000499 """
500 Parse arguments and update global settings.
501 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000502 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000503 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000504
505 if args is None:
506 args = sys.argv[1:]
507
508 try:
509 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000510 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
511 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily7d9cf832010-11-27 16:42:15 -0800512 except getopt.GetoptError:
513 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000514 sys.exit(1)
515
516 if args:
Ned Deily7d9cf832010-11-27 16:42:15 -0800517 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000518 sys.exit(1)
519
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000520 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000521 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000522 if k in ('-h', '-?', '--help'):
Ned Deily7d9cf832010-11-27 16:42:15 -0800523 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000524 sys.exit(0)
525
526 elif k in ('-d', '--build-dir'):
527 WORKDIR=v
528
529 elif k in ('--third-party',):
530 DEPSRC=v
531
532 elif k in ('--sdk-path',):
533 SDKPATH=v
534
535 elif k in ('--src-dir',):
536 SRCDIR=v
537
Ronald Oussoren1943f862009-03-30 19:39:14 +0000538 elif k in ('--dep-target', ):
539 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000540 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000541
542 elif k in ('--universal-archs', ):
543 if v in UNIVERSALOPTS:
544 UNIVERSALARCHS = v
545 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000546 if deptarget is None:
547 # Select alternate default deployment
548 # target
549 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000550 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800551 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000552
Thomas Wouters477c8d52006-05-27 19:21:47 +0000553 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800554 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555
556 SRCDIR=os.path.abspath(SRCDIR)
557 WORKDIR=os.path.abspath(WORKDIR)
558 SDKPATH=os.path.abspath(SDKPATH)
559 DEPSRC=os.path.abspath(DEPSRC)
560
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000561 CC=target_cc_map[DEPTARGET]
562
Ned Deily7d9cf832010-11-27 16:42:15 -0800563 print("Settings:")
564 print(" * Source directory:", SRCDIR)
565 print(" * Build directory: ", WORKDIR)
566 print(" * SDK location: ", SDKPATH)
567 print(" * Third-party source:", DEPSRC)
568 print(" * Deployment target:", DEPTARGET)
569 print(" * Universal architectures:", ARCHLIST)
570 print(" * C compiler:", CC)
571 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000572
573
574
575
576def extractArchive(builddir, archiveName):
577 """
578 Extract a source archive into 'builddir'. Returns the path of the
579 extracted archive.
580
581 XXX: This function assumes that archives contain a toplevel directory
582 that is has the same name as the basename of the archive. This is
583 save enough for anything we use.
584 """
585 curdir = os.getcwd()
586 try:
587 os.chdir(builddir)
588 if archiveName.endswith('.tar.gz'):
589 retval = os.path.basename(archiveName[:-7])
590 if os.path.exists(retval):
591 shutil.rmtree(retval)
592 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
593
594 elif archiveName.endswith('.tar.bz2'):
595 retval = os.path.basename(archiveName[:-8])
596 if os.path.exists(retval):
597 shutil.rmtree(retval)
598 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
599
600 elif archiveName.endswith('.tar'):
601 retval = os.path.basename(archiveName[:-4])
602 if os.path.exists(retval):
603 shutil.rmtree(retval)
604 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
605
606 elif archiveName.endswith('.zip'):
607 retval = os.path.basename(archiveName[:-4])
608 if os.path.exists(retval):
609 shutil.rmtree(retval)
610 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
611
612 data = fp.read()
613 xit = fp.close()
614 if xit is not None:
615 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800616 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000617
618 return os.path.join(builddir, retval)
619
620 finally:
621 os.chdir(curdir)
622
623KNOWNSIZES = {
624 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
625 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
626}
627
628def downloadURL(url, fname):
629 """
630 Download the contents of the url into the file.
631 """
632 try:
633 size = os.path.getsize(fname)
634 except OSError:
635 pass
636 else:
637 if KNOWNSIZES.get(url) == size:
Ned Deily7d9cf832010-11-27 16:42:15 -0800638 print("Using existing file for", url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000639 return
Ned Deily7d9cf832010-11-27 16:42:15 -0800640 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000641 fpOut = open(fname, 'wb')
642 block = fpIn.read(10240)
643 try:
644 while block:
645 fpOut.write(block)
646 block = fpIn.read(10240)
647 fpIn.close()
648 fpOut.close()
649 except:
650 try:
651 os.unlink(fname)
652 except:
653 pass
654
655def buildRecipe(recipe, basedir, archList):
656 """
657 Build software using a recipe. This function does the
658 'configure;make;make install' dance for C software, with a possibility
659 to customize this process, basically a poor-mans DarwinPorts.
660 """
661 curdir = os.getcwd()
662
663 name = recipe['name']
664 url = recipe['url']
665 configure = recipe.get('configure', './configure')
666 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
667 shellQuote(basedir)))
668
669 archiveName = os.path.split(url)[-1]
670 sourceArchive = os.path.join(DEPSRC, archiveName)
671
672 if not os.path.exists(DEPSRC):
673 os.mkdir(DEPSRC)
674
675
676 if os.path.exists(sourceArchive):
Ned Deily7d9cf832010-11-27 16:42:15 -0800677 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000678
679 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800680 print("Did not find local copy of %s"%(name,))
681 print("Downloading %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000682 downloadURL(url, sourceArchive)
Ned Deily7d9cf832010-11-27 16:42:15 -0800683 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000684
Ned Deily7d9cf832010-11-27 16:42:15 -0800685 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000686 buildDir=os.path.join(WORKDIR, '_bld')
687 if not os.path.exists(buildDir):
688 os.mkdir(buildDir)
689
690 workDir = extractArchive(buildDir, sourceArchive)
691 os.chdir(workDir)
692 if 'buildDir' in recipe:
693 os.chdir(recipe['buildDir'])
694
695
696 for fn in recipe.get('patches', ()):
697 if fn.startswith('http://'):
698 # Download the patch before applying it.
699 path = os.path.join(DEPSRC, os.path.basename(fn))
700 downloadURL(fn, path)
701 fn = path
702
703 fn = os.path.join(curdir, fn)
704 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
705 shellQuote(fn),))
706
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000707 if configure is not None:
708 configure_args = [
709 "--prefix=/usr/local",
710 "--enable-static",
711 "--disable-shared",
712 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
713 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000714
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000715 if 'configure_pre' in recipe:
716 args = list(recipe['configure_pre'])
717 if '--disable-static' in args:
718 configure_args.remove('--enable-static')
719 if '--enable-shared' in args:
720 configure_args.remove('--disable-shared')
721 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000722
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000723 if recipe.get('useLDFlags', 1):
724 configure_args.extend([
725 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
726 ' -arch '.join(archList),
727 shellQuote(SDKPATH)[1:-1],
728 shellQuote(basedir)[1:-1],),
729 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000730 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000731 shellQuote(basedir)[1:-1],
732 ' -arch '.join(archList)),
733 ])
734 else:
735 configure_args.extend([
736 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
737 ' -arch '.join(archList),
738 shellQuote(SDKPATH)[1:-1],
739 shellQuote(basedir)[1:-1],),
740 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000741
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000742 if 'configure_post' in recipe:
743 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000744
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000745 configure_args.insert(0, configure)
746 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000747
Ned Deily4f7ff782011-01-15 05:29:12 +0000748 if 'configure_env' in recipe:
749 configure_args.insert(0, recipe['configure_env'])
750
Ned Deily7d9cf832010-11-27 16:42:15 -0800751 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000752 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000753
Ned Deily7d9cf832010-11-27 16:42:15 -0800754 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000755 runCommand('{ ' + install + ' ;} 2>&1')
756
Ned Deily7d9cf832010-11-27 16:42:15 -0800757 print("Done %s"%(name,))
758 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000759
760 os.chdir(curdir)
761
762def buildLibraries():
763 """
764 Build our dependencies into $WORKDIR/libraries/usr/local
765 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800766 print("")
767 print("Building required libraries")
768 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000769 universal = os.path.join(WORKDIR, 'libraries')
770 os.mkdir(universal)
771 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
772 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
773
Ronald Oussoren1943f862009-03-30 19:39:14 +0000774 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000775 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000776
777
778
779def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000780 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000781 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily7d9cf832010-11-27 16:42:15 -0800782 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000784 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000785 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000786 curDir = os.getcwd()
787 os.chdir(buildDir)
788 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000789 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000790 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000791 if not os.path.exists(docdir):
792 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000793 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000794
795
796def buildPython():
Ned Deily7d9cf832010-11-27 16:42:15 -0800797 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000798
799 buildDir = os.path.join(WORKDIR, '_bld', 'python')
800 rootDir = os.path.join(WORKDIR, '_root')
801
802 if os.path.exists(buildDir):
803 shutil.rmtree(buildDir)
804 if os.path.exists(rootDir):
805 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000806 os.makedirs(buildDir)
807 os.makedirs(rootDir)
808 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 curdir = os.getcwd()
810 os.chdir(buildDir)
811
812 # Not sure if this is still needed, the original build script
813 # claims that parts of the install assume python.exe exists.
814 os.symlink('python', os.path.join(buildDir, 'python.exe'))
815
816 # Extract the version from the configure file, needed to calculate
817 # several paths.
818 version = getVersion()
819
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000820 # Since the extra libs are not in their installed framework location
821 # during the build, augment the library path so that the interpreter
822 # will find them during its extension import sanity checks.
823 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
824 'libraries', 'usr', 'local', 'lib')
Ned Deily7d9cf832010-11-27 16:42:15 -0800825 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000826 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000827 "--with-universal-archs=%s "
828 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000829 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
830 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
831 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
832 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000833 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000834 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000835 shellQuote(WORKDIR)[1:-1]))
836
Ned Deily7d9cf832010-11-27 16:42:15 -0800837 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000838 runCommand("make")
839
Ned Deily7d9cf832010-11-27 16:42:15 -0800840 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000841 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000842 shellQuote(rootDir)))
843
Ned Deily7d9cf832010-11-27 16:42:15 -0800844 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000845 runCommand("make frameworkinstallextras DESTDIR=%s"%(
846 shellQuote(rootDir)))
847
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000848 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily7d9cf832010-11-27 16:42:15 -0800849 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000850 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
851 runCommand("mv %s/* %s"%(
852 shellQuote(os.path.join(
853 WORKDIR, 'libraries', 'Library', 'Frameworks',
854 'Python.framework', 'Versions', getVersion(),
855 'lib')),
856 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
857 'Python.framework', 'Versions', getVersion(),
858 'lib'))))
859
Ned Deily7d9cf832010-11-27 16:42:15 -0800860 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000861 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000862 gid = grp.getgrnam('admin').gr_gid
863
Thomas Wouters477c8d52006-05-27 19:21:47 +0000864 for dirpath, dirnames, filenames in os.walk(frmDir):
865 for dn in dirnames:
Ned Deily7d9cf832010-11-27 16:42:15 -0800866 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000867 os.chown(os.path.join(dirpath, dn), -1, gid)
868
Thomas Wouters477c8d52006-05-27 19:21:47 +0000869
870 for fn in filenames:
871 if os.path.islink(fn):
872 continue
873
874 # "chmod g+w $fn"
875 p = os.path.join(dirpath, fn)
876 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000877 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
878 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000879
Ned Deilye59e4c52011-01-29 18:56:28 +0000880 if PYTHON_3:
881 LDVERSION=None
882 VERSION=None
883 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000884
Ned Deilye59e4c52011-01-29 18:56:28 +0000885 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000886 for ln in fp:
887 if ln.startswith('VERSION='):
888 VERSION=ln.split()[1]
889 if ln.startswith('ABIFLAGS='):
890 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000891 if ln.startswith('LDVERSION='):
892 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000893 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000894
Ned Deilye59e4c52011-01-29 18:56:28 +0000895 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
896 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
897 config_suffix = '-' + LDVERSION
898 else:
899 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000900
Thomas Wouters477c8d52006-05-27 19:21:47 +0000901 # We added some directories to the search path during the configure
902 # phase. Remove those because those directories won't be there on
Ned Deilya606aef2012-07-21 10:48:09 -0700903 # the end-users system. Also remove the directories from _sysconfigdata.py
904 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905
Ned Deilya606aef2012-07-21 10:48:09 -0700906 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
907 'Python.framework', 'Versions',
908 version, 'lib', 'python%s'%(version,))
909 paths = [os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile'),
910 os.path.join(path_to_lib, '_sysconfigdata.py')]
911 for path in paths:
912 if not os.path.exists(path):
913 continue
914 fp = open(path, 'r')
915 data = fp.read()
916 fp.close()
917
918 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
919 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
920 fp = open(path, 'w')
921 fp.write(data)
922 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000923
924 # Add symlinks in /usr/local/bin, using relative links
925 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
926 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
927 'Python.framework', 'Versions', version, 'bin')
928 if os.path.exists(usr_local_bin):
929 shutil.rmtree(usr_local_bin)
930 os.makedirs(usr_local_bin)
931 for fn in os.listdir(
932 os.path.join(frmDir, 'Versions', version, 'bin')):
933 os.symlink(os.path.join(to_framework, fn),
934 os.path.join(usr_local_bin, fn))
935
936 os.chdir(curdir)
937
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000938 if PYTHON_3:
939 # Remove the 'Current' link, that way we don't accidently mess
940 # with an already installed version of python 2
941 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
942 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000943
944def patchFile(inPath, outPath):
945 data = fileContents(inPath)
946 data = data.replace('$FULL_VERSION', getFullVersion())
947 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000948 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000949 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000950 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000951
952 # This one is not handy as a template variable
953 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily7d9cf832010-11-27 16:42:15 -0800954 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000955 fp.write(data)
956 fp.close()
957
958def patchScript(inPath, outPath):
959 data = fileContents(inPath)
960 data = data.replace('@PYVER@', getVersion())
Ned Deily7d9cf832010-11-27 16:42:15 -0800961 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000962 fp.write(data)
963 fp.close()
Ned Deily7d9cf832010-11-27 16:42:15 -0800964 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000965
966
967
968def packageFromRecipe(targetDir, recipe):
969 curdir = os.getcwd()
970 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000971 # The major version (such as 2.5) is included in the package name
972 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000973 # common.
974 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000975 srcdir = recipe.get('source')
976 pkgroot = recipe.get('topdir', srcdir)
977 postflight = recipe.get('postflight')
978 readme = textwrap.dedent(recipe['readme'])
979 isRequired = recipe.get('required', True)
980
Ned Deily7d9cf832010-11-27 16:42:15 -0800981 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000982
983 # Substitute some variables
984 textvars = dict(
985 VER=getVersion(),
986 FULLVER=getFullVersion(),
987 )
988 readme = readme % textvars
989
990 if pkgroot is not None:
991 pkgroot = pkgroot % textvars
992 else:
993 pkgroot = '/'
994
995 if srcdir is not None:
996 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
997 srcdir = srcdir % textvars
998
999 if postflight is not None:
1000 postflight = os.path.abspath(postflight)
1001
1002 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1003 os.makedirs(packageContents)
1004
1005 if srcdir is not None:
1006 os.chdir(srcdir)
1007 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1008 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1009 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1010
1011 fn = os.path.join(packageContents, 'PkgInfo')
1012 fp = open(fn, 'w')
1013 fp.write('pmkrpkg1')
1014 fp.close()
1015
1016 rsrcDir = os.path.join(packageContents, "Resources")
1017 os.mkdir(rsrcDir)
1018 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1019 fp.write(readme)
1020 fp.close()
1021
1022 if postflight is not None:
1023 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1024
1025 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001026 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001027 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001028 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1029 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1030 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001031 CFBundleShortVersionString=vers,
1032 IFMajorVersion=major,
1033 IFMinorVersion=minor,
1034 IFPkgFormatVersion=0.10000000149011612,
1035 IFPkgFlagAllowBackRev=False,
1036 IFPkgFlagAuthorizationAction="RootAuthorization",
1037 IFPkgFlagDefaultLocation=pkgroot,
1038 IFPkgFlagFollowLinks=True,
1039 IFPkgFlagInstallFat=True,
1040 IFPkgFlagIsRequired=isRequired,
1041 IFPkgFlagOverwritePermissions=False,
1042 IFPkgFlagRelocatable=False,
1043 IFPkgFlagRestartAction="NoRestart",
1044 IFPkgFlagRootVolumeOnly=True,
1045 IFPkgFlagUpdateInstalledLangauges=False,
1046 )
1047 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1048
1049 pl = Plist(
1050 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001051 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001052 IFPkgDescriptionVersion=vers,
1053 )
1054 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1055
1056 finally:
1057 os.chdir(curdir)
1058
1059
1060def makeMpkgPlist(path):
1061
1062 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001063 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001064
1065 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001066 CFBundleGetInfoString="Python %s"%(vers,),
1067 CFBundleIdentifier='org.python.Python',
1068 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001069 CFBundleShortVersionString=vers,
1070 IFMajorVersion=major,
1071 IFMinorVersion=minor,
1072 IFPkgFlagComponentDirectory="Contents/Packages",
1073 IFPkgFlagPackageList=[
1074 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001075 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001076 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001077 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001078 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001079 ],
1080 IFPkgFormatVersion=0.10000000149011612,
1081 IFPkgFlagBackgroundScaling="proportional",
1082 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001083 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001084 )
1085
1086 writePlist(pl, path)
1087
1088
1089def buildInstaller():
1090
1091 # Zap all compiled files
1092 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1093 for fn in filenames:
1094 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1095 os.unlink(os.path.join(dirpath, fn))
1096
1097 outdir = os.path.join(WORKDIR, 'installer')
1098 if os.path.exists(outdir):
1099 shutil.rmtree(outdir)
1100 os.mkdir(outdir)
1101
Ronald Oussoren1943f862009-03-30 19:39:14 +00001102 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001103 pkgcontents = os.path.join(pkgroot, 'Packages')
1104 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001105 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001106 packageFromRecipe(pkgcontents, recipe)
1107
1108 rsrcDir = os.path.join(pkgroot, 'Resources')
1109
1110 fn = os.path.join(pkgroot, 'PkgInfo')
1111 fp = open(fn, 'w')
1112 fp.write('pmkrpkg1')
1113 fp.close()
1114
1115 os.mkdir(rsrcDir)
1116
1117 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1118 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001119 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001120 IFPkgDescriptionVersion=getVersion(),
1121 )
1122
1123 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1124 for fn in os.listdir('resources'):
1125 if fn == '.svn': continue
1126 if fn.endswith('.jpg'):
1127 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1128 else:
1129 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1130
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001131 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001132
1133
1134def installSize(clear=False, _saved=[]):
1135 if clear:
1136 del _saved[:]
1137 if not _saved:
1138 data = captureCommand("du -ks %s"%(
1139 shellQuote(os.path.join(WORKDIR, '_root'))))
1140 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1141 return _saved[0]
1142
1143
1144def buildDMG():
1145 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001146 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001147 """
1148 outdir = os.path.join(WORKDIR, 'diskimage')
1149 if os.path.exists(outdir):
1150 shutil.rmtree(outdir)
1151
1152 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001153 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001155 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001156 imagepath = imagepath + '.dmg'
1157
1158 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001159 volname='Python %s'%(getFullVersion())
1160 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1161 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001162 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001163 shellQuote(imagepath + ".tmp.dmg" )))
1164
1165
1166 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1167 os.mkdir(os.path.join(WORKDIR, "mnt"))
1168 runCommand("hdiutil attach %s -mountroot %s"%(
1169 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1170
1171 # Custom icon for the DMG, shown when the DMG is mounted.
1172 shutil.copy("../Icons/Disk Image.icns",
1173 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1174 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1175 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1176
1177 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1178
1179 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1180 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1181 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1182 setIcon(imagepath, "../Icons/Disk Image.icns")
1183
1184 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001185
1186 return imagepath
1187
1188
1189def setIcon(filePath, icnsPath):
1190 """
1191 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001192 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001193
Ronald Oussoren70050672010-04-30 15:00:26 +00001194 dirPath = os.path.normpath(os.path.dirname(__file__))
1195 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001196 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1197 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1198 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001199 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1200 if not os.path.exists(appPath):
1201 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001202 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1203 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001204
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001205 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1206 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001207
1208def main():
1209 # First parse options and check if we can perform our work
1210 parseOptions()
1211 checkEnvironment()
1212
Ronald Oussoren1943f862009-03-30 19:39:14 +00001213 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001214 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001215
1216 if os.path.exists(WORKDIR):
1217 shutil.rmtree(WORKDIR)
1218 os.mkdir(WORKDIR)
1219
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001220 os.environ['LC_ALL'] = 'C'
1221
Thomas Wouters477c8d52006-05-27 19:21:47 +00001222 # Then build third-party libraries such as sleepycat DB4.
1223 buildLibraries()
1224
1225 # Now build python itself
1226 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001227
1228 # And then build the documentation
1229 # Remove the Deployment Target from the shell
1230 # environment, it's no longer needed and
1231 # an unexpected build target can cause problems
1232 # when Sphinx and its dependencies need to
1233 # be (re-)installed.
1234 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001235 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001236
1237
1238 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001239 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001240 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001241 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001242
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001243 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001244 getVersion(),))
Ned Deily7d9cf832010-11-27 16:42:15 -08001245 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001246 setIcon(folder, "../Icons/Python Folder.icns")
1247
1248 # Create the installer
1249 buildInstaller()
1250
1251 # And copy the readme into the directory containing the installer
1252 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1253
1254 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001255 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001256
1257 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily7d9cf832010-11-27 16:42:15 -08001258 fp.write("# BUILD INFO\n")
1259 fp.write("# Date: %s\n" % time.ctime())
1260 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001261 fp.close()
1262
Thomas Wouters477c8d52006-05-27 19:21:47 +00001263 # And copy it to a DMG
1264 buildDMG()
1265
Thomas Wouters477c8d52006-05-27 19:21:47 +00001266if __name__ == "__main__":
1267 main()