blob: 9f8d0259c1953f4de3e0cd389083895db66d7d2f [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',
140 '10.5': 'gcc-4.0',
141 '10.6': 'gcc-4.2',
142}
143
144CC = target_cc_map[DEPTARGET]
145
146PYTHON_3 = getVersionTuple() >= (3, 0)
147
Thomas Wouters89f507f2006-12-13 04:49:30 +0000148USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000149 Usage: build_python [options]
150
151 Options:
152 -? or -h: Show this message
153 -b DIR
154 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
155 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
156 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
157 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000158 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
159 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000160""")% globals()
161
162
163# Instructions for building libraries that are necessary for building a
164# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000165# [The recipes are defined here for convenience but instantiated later after
166# command line options have been processed.]
167def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000168 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000169
Ned Deily4d4c0ee2012-04-01 00:17:33 -0700170 result.extend([
171 dict(
172 name="XZ 5.0.3",
173 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
174 checksum='fefe52f9ecd521de2a8ce38c21a27574',
175 configure_pre=[
176 '--disable-dependency-tracking',
177 ]
178 )
179 ])
180
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000181 if DEPTARGET < '10.5':
182 result.extend([
183 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000184 name="Bzip2 1.0.6",
185 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
186 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000187 configure=None,
188 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
189 CC,
190 shellQuote(os.path.join(WORKDIR, 'libraries')),
191 ' -arch '.join(ARCHLIST),
192 SDKPATH,
193 ),
194 ),
195 dict(
196 name="ZLib 1.2.3",
197 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
198 checksum='debc62758716a169df9f62e6ab2bc634',
199 configure=None,
200 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
201 CC,
202 shellQuote(os.path.join(WORKDIR, 'libraries')),
203 ' -arch '.join(ARCHLIST),
204 SDKPATH,
205 ),
206 ),
207 dict(
208 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000209 name="GNU Readline 6.1.2",
210 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
211 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000212 patchlevel='0',
213 patches=[
214 # The readline maintainers don't do actual micro releases, but
215 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000216 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
217 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000218 ]
219 ),
220 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000221 name="SQLite 3.7.4",
222 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
223 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
224 configure_env=('CFLAGS="-Os'
225 ' -DSQLITE_ENABLE_FTS3'
226 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
227 ' -DSQLITE_ENABLE_RTREE'
228 ' -DSQLITE_TCL=0'
229 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000230 configure_pre=[
231 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000232 '--enable-shared=no',
233 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000234 '--disable-readline',
235 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000236 ]
237 ),
238 dict(
239 name="NCurses 5.5",
240 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
241 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
242 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000243 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000244 "--without-cxx",
245 "--without-ada",
246 "--without-progs",
247 "--without-curses-h",
248 "--enable-shared",
249 "--with-shared",
250 "--datadir=/usr/share",
251 "--sysconfdir=/etc",
252 "--sharedstatedir=/usr/com",
253 "--with-terminfo-dirs=/usr/share/terminfo",
254 "--with-default-terminfo-dir=/usr/share/terminfo",
255 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
256 "--enable-termcap",
257 ],
258 patches=[
259 "ncurses-5.5.patch",
260 ],
261 useLDFlags=False,
262 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
263 shellQuote(os.path.join(WORKDIR, 'libraries')),
264 shellQuote(os.path.join(WORKDIR, 'libraries')),
265 getVersion(),
266 ),
267 ),
268 ])
269
Ned Deily4f7ff782011-01-15 05:29:12 +0000270 if not PYTHON_3:
271 result.extend([
272 dict(
273 name="Sleepycat DB 4.7.25",
274 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
275 checksum='ec2b87e833779681a0c3a814aa71359e',
276 buildDir="build_unix",
277 configure="../dist/configure",
278 configure_pre=[
279 '--includedir=/usr/local/include/db4',
280 ]
281 ),
282 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000283
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000284 return result
285
Thomas Wouters477c8d52006-05-27 19:21:47 +0000286
Thomas Wouters477c8d52006-05-27 19:21:47 +0000287# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000288def pkg_recipes():
289 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
290 result = [
291 dict(
292 name="PythonFramework",
293 long_name="Python Framework",
294 source="/Library/Frameworks/Python.framework",
295 readme="""\
296 This package installs Python.framework, that is the python
297 interpreter and the standard library. This also includes Python
298 wrappers for lots of Mac OS X API's.
299 """,
300 postflight="scripts/postflight.framework",
301 selected='selected',
302 ),
303 dict(
304 name="PythonApplications",
305 long_name="GUI Applications",
306 source="/Applications/Python %(VER)s",
307 readme="""\
308 This package installs IDLE (an interactive Python IDE),
309 Python Launcher and Build Applet (create application bundles
310 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000311
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000312 It also installs a number of examples and demos.
313 """,
314 required=False,
315 selected='selected',
316 ),
317 dict(
318 name="PythonUnixTools",
319 long_name="UNIX command-line tools",
320 source="/usr/local/bin",
321 readme="""\
322 This package installs the unix tools in /usr/local/bin for
323 compatibility with older releases of Python. This package
324 is not necessary to use Python.
325 """,
326 required=False,
327 selected='selected',
328 ),
329 dict(
330 name="PythonDocumentation",
331 long_name="Python Documentation",
332 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
333 source="/pydocs",
334 readme="""\
335 This package installs the python documentation at a location
336 that is useable for pydoc and IDLE. If you have installed Xcode
337 it will also install a link to the documentation in
338 /Developer/Documentation/Python
339 """,
340 postflight="scripts/postflight.documentation",
341 required=False,
342 selected='selected',
343 ),
344 dict(
345 name="PythonProfileChanges",
346 long_name="Shell profile updater",
347 readme="""\
348 This packages updates your shell profile to make sure that
349 the Python tools are found by your shell in preference of
350 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000351
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000352 If you don't install this package you'll have to add
353 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
354 to your PATH by hand.
355 """,
356 postflight="scripts/postflight.patch-profile",
357 topdir="/Library/Frameworks/Python.framework",
358 source="/empty-dir",
359 required=False,
360 selected=unselected_for_python3,
361 ),
362 ]
363
Ned Deily430d7a32012-06-24 00:19:31 -0700364 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000365 result.append(
366 dict(
367 name="PythonSystemFixes",
368 long_name="Fix system Python",
369 readme="""\
370 This package updates the system python installation on
371 Mac OS X 10.3 to ensure that you can build new python extensions
372 using that copy of python after installing this version.
373 """,
374 postflight="../Tools/fixapplepython23.py",
375 topdir="/Library/Frameworks/Python.framework",
376 source="/empty-dir",
377 required=False,
378 selected=unselected_for_python3,
379 )
380 )
381 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000382
Thomas Wouters477c8d52006-05-27 19:21:47 +0000383def fatal(msg):
384 """
385 A fatal error, bail out.
386 """
387 sys.stderr.write('FATAL: ')
388 sys.stderr.write(msg)
389 sys.stderr.write('\n')
390 sys.exit(1)
391
392def fileContents(fn):
393 """
394 Return the contents of the named file
395 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800396 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000397
398def runCommand(commandline):
399 """
Ezio Melotti13925002011-03-16 11:05:33 +0200400 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000401 unless the command fails.
402 """
403 fd = os.popen(commandline, 'r')
404 data = fd.read()
405 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000406 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000407 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800408 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000409
410 if VERBOSE:
411 sys.stdout.write(data); sys.stdout.flush()
412
413def captureCommand(commandline):
414 fd = os.popen(commandline, 'r')
415 data = fd.read()
416 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000417 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000418 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800419 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000420
421 return data
422
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000423def getTclTkVersion(configfile, versionline):
424 """
425 search Tcl or Tk configuration file for version line
426 """
427 try:
428 f = open(configfile, "r")
429 except:
430 fatal("Framework configuration file not found: %s" % configfile)
431
432 for l in f:
433 if l.startswith(versionline):
434 f.close()
435 return l
436
437 fatal("Version variable %s not found in framework configuration file: %s"
438 % (versionline, configfile))
439
Thomas Wouters477c8d52006-05-27 19:21:47 +0000440def checkEnvironment():
441 """
442 Check that we're running on a supported system.
443 """
444
Ned Deilye59e4c52011-01-29 18:56:28 +0000445 if sys.version_info[0:2] < (2, 4):
446 fatal("This script must be run with Python 2.4 or later")
447
Thomas Wouters477c8d52006-05-27 19:21:47 +0000448 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000449 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000450
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000451 if int(platform.release().split('.')[0]) < 8:
452 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000453
454 if not os.path.exists(SDKPATH):
455 fatal("Please install the latest version of Xcode and the %s SDK"%(
456 os.path.basename(SDKPATH[:-4])))
457
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000458 # Because we only support dynamic load of only one major/minor version of
459 # Tcl/Tk, ensure:
460 # 1. there are no user-installed frameworks of Tcl/Tk with version
461 # higher than the Apple-supplied system version
462 # 2. there is a user-installed framework in /Library/Frameworks with the
463 # same version as the system version. This allows users to choose
464 # to install a newer patch level.
465
466 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000467 #fw = dict(lower=framework.lower(),
468 # upper=framework.upper(),
469 # cap=framework.capitalize())
470 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
471 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
472 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000473 libfw = os.path.join('/', fwpth)
474 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000475 #version = "%(upper)s_VERSION" % fw
476 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000477 fatal("Version of %s must match %s" % (libfw, sysfw) )
478 if os.path.exists(usrfw):
479 fatal("Please rename %s to avoid possible dynamic load issues."
480 % usrfw)
481
482 # Remove inherited environment variables which might influence build
483 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
484 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
485 for ev in list(os.environ):
486 for prefix in environ_var_prefixes:
487 if ev.startswith(prefix) :
Ned Deily7d9cf832010-11-27 16:42:15 -0800488 print("INFO: deleting environment variable %s=%s" % (
489 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000490 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000491
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000492 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
Ned Deily7d9cf832010-11-27 16:42:15 -0800493 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000494
Thomas Wouters477c8d52006-05-27 19:21:47 +0000495
Thomas Wouters89f507f2006-12-13 04:49:30 +0000496def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000497 """
498 Parse arguments and update global settings.
499 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000500 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000501 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000502
503 if args is None:
504 args = sys.argv[1:]
505
506 try:
507 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000508 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
509 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily7d9cf832010-11-27 16:42:15 -0800510 except getopt.GetoptError:
511 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000512 sys.exit(1)
513
514 if args:
Ned Deily7d9cf832010-11-27 16:42:15 -0800515 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000516 sys.exit(1)
517
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000518 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000519 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000520 if k in ('-h', '-?', '--help'):
Ned Deily7d9cf832010-11-27 16:42:15 -0800521 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000522 sys.exit(0)
523
524 elif k in ('-d', '--build-dir'):
525 WORKDIR=v
526
527 elif k in ('--third-party',):
528 DEPSRC=v
529
530 elif k in ('--sdk-path',):
531 SDKPATH=v
532
533 elif k in ('--src-dir',):
534 SRCDIR=v
535
Ronald Oussoren1943f862009-03-30 19:39:14 +0000536 elif k in ('--dep-target', ):
537 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000538 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000539
540 elif k in ('--universal-archs', ):
541 if v in UNIVERSALOPTS:
542 UNIVERSALARCHS = v
543 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000544 if deptarget is None:
545 # Select alternate default deployment
546 # target
547 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000548 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800549 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000550
Thomas Wouters477c8d52006-05-27 19:21:47 +0000551 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800552 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000553
554 SRCDIR=os.path.abspath(SRCDIR)
555 WORKDIR=os.path.abspath(WORKDIR)
556 SDKPATH=os.path.abspath(SDKPATH)
557 DEPSRC=os.path.abspath(DEPSRC)
558
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000559 CC=target_cc_map[DEPTARGET]
560
Ned Deily7d9cf832010-11-27 16:42:15 -0800561 print("Settings:")
562 print(" * Source directory:", SRCDIR)
563 print(" * Build directory: ", WORKDIR)
564 print(" * SDK location: ", SDKPATH)
565 print(" * Third-party source:", DEPSRC)
566 print(" * Deployment target:", DEPTARGET)
567 print(" * Universal architectures:", ARCHLIST)
568 print(" * C compiler:", CC)
569 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000570
571
572
573
574def extractArchive(builddir, archiveName):
575 """
576 Extract a source archive into 'builddir'. Returns the path of the
577 extracted archive.
578
579 XXX: This function assumes that archives contain a toplevel directory
580 that is has the same name as the basename of the archive. This is
581 save enough for anything we use.
582 """
583 curdir = os.getcwd()
584 try:
585 os.chdir(builddir)
586 if archiveName.endswith('.tar.gz'):
587 retval = os.path.basename(archiveName[:-7])
588 if os.path.exists(retval):
589 shutil.rmtree(retval)
590 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
591
592 elif archiveName.endswith('.tar.bz2'):
593 retval = os.path.basename(archiveName[:-8])
594 if os.path.exists(retval):
595 shutil.rmtree(retval)
596 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
597
598 elif archiveName.endswith('.tar'):
599 retval = os.path.basename(archiveName[:-4])
600 if os.path.exists(retval):
601 shutil.rmtree(retval)
602 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
603
604 elif archiveName.endswith('.zip'):
605 retval = os.path.basename(archiveName[:-4])
606 if os.path.exists(retval):
607 shutil.rmtree(retval)
608 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
609
610 data = fp.read()
611 xit = fp.close()
612 if xit is not None:
613 sys.stdout.write(data)
Ned Deily7d9cf832010-11-27 16:42:15 -0800614 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000615
616 return os.path.join(builddir, retval)
617
618 finally:
619 os.chdir(curdir)
620
621KNOWNSIZES = {
622 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
623 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
624}
625
626def downloadURL(url, fname):
627 """
628 Download the contents of the url into the file.
629 """
630 try:
631 size = os.path.getsize(fname)
632 except OSError:
633 pass
634 else:
635 if KNOWNSIZES.get(url) == size:
Ned Deily7d9cf832010-11-27 16:42:15 -0800636 print("Using existing file for", url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000637 return
Ned Deily7d9cf832010-11-27 16:42:15 -0800638 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000639 fpOut = open(fname, 'wb')
640 block = fpIn.read(10240)
641 try:
642 while block:
643 fpOut.write(block)
644 block = fpIn.read(10240)
645 fpIn.close()
646 fpOut.close()
647 except:
648 try:
649 os.unlink(fname)
650 except:
651 pass
652
653def buildRecipe(recipe, basedir, archList):
654 """
655 Build software using a recipe. This function does the
656 'configure;make;make install' dance for C software, with a possibility
657 to customize this process, basically a poor-mans DarwinPorts.
658 """
659 curdir = os.getcwd()
660
661 name = recipe['name']
662 url = recipe['url']
663 configure = recipe.get('configure', './configure')
664 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
665 shellQuote(basedir)))
666
667 archiveName = os.path.split(url)[-1]
668 sourceArchive = os.path.join(DEPSRC, archiveName)
669
670 if not os.path.exists(DEPSRC):
671 os.mkdir(DEPSRC)
672
673
674 if os.path.exists(sourceArchive):
Ned Deily7d9cf832010-11-27 16:42:15 -0800675 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000676
677 else:
Ned Deily7d9cf832010-11-27 16:42:15 -0800678 print("Did not find local copy of %s"%(name,))
679 print("Downloading %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000680 downloadURL(url, sourceArchive)
Ned Deily7d9cf832010-11-27 16:42:15 -0800681 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000682
Ned Deily7d9cf832010-11-27 16:42:15 -0800683 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000684 buildDir=os.path.join(WORKDIR, '_bld')
685 if not os.path.exists(buildDir):
686 os.mkdir(buildDir)
687
688 workDir = extractArchive(buildDir, sourceArchive)
689 os.chdir(workDir)
690 if 'buildDir' in recipe:
691 os.chdir(recipe['buildDir'])
692
693
694 for fn in recipe.get('patches', ()):
695 if fn.startswith('http://'):
696 # Download the patch before applying it.
697 path = os.path.join(DEPSRC, os.path.basename(fn))
698 downloadURL(fn, path)
699 fn = path
700
701 fn = os.path.join(curdir, fn)
702 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
703 shellQuote(fn),))
704
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000705 if configure is not None:
706 configure_args = [
707 "--prefix=/usr/local",
708 "--enable-static",
709 "--disable-shared",
710 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
711 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000712
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000713 if 'configure_pre' in recipe:
714 args = list(recipe['configure_pre'])
715 if '--disable-static' in args:
716 configure_args.remove('--enable-static')
717 if '--enable-shared' in args:
718 configure_args.remove('--disable-shared')
719 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000720
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000721 if recipe.get('useLDFlags', 1):
722 configure_args.extend([
723 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
724 ' -arch '.join(archList),
725 shellQuote(SDKPATH)[1:-1],
726 shellQuote(basedir)[1:-1],),
727 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000728 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000729 shellQuote(basedir)[1:-1],
730 ' -arch '.join(archList)),
731 ])
732 else:
733 configure_args.extend([
734 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
735 ' -arch '.join(archList),
736 shellQuote(SDKPATH)[1:-1],
737 shellQuote(basedir)[1:-1],),
738 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000740 if 'configure_post' in recipe:
741 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000742
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000743 configure_args.insert(0, configure)
744 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000745
Ned Deily4f7ff782011-01-15 05:29:12 +0000746 if 'configure_env' in recipe:
747 configure_args.insert(0, recipe['configure_env'])
748
Ned Deily7d9cf832010-11-27 16:42:15 -0800749 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000750 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000751
Ned Deily7d9cf832010-11-27 16:42:15 -0800752 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000753 runCommand('{ ' + install + ' ;} 2>&1')
754
Ned Deily7d9cf832010-11-27 16:42:15 -0800755 print("Done %s"%(name,))
756 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000757
758 os.chdir(curdir)
759
760def buildLibraries():
761 """
762 Build our dependencies into $WORKDIR/libraries/usr/local
763 """
Ned Deily7d9cf832010-11-27 16:42:15 -0800764 print("")
765 print("Building required libraries")
766 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000767 universal = os.path.join(WORKDIR, 'libraries')
768 os.mkdir(universal)
769 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
770 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
771
Ronald Oussoren1943f862009-03-30 19:39:14 +0000772 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000773 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000774
775
776
777def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000778 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000779 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily7d9cf832010-11-27 16:42:15 -0800780 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000781 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000782 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000784 curDir = os.getcwd()
785 os.chdir(buildDir)
786 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000787 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000788 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000789 if not os.path.exists(docdir):
790 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000791 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000792
793
794def buildPython():
Ned Deily7d9cf832010-11-27 16:42:15 -0800795 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000796
797 buildDir = os.path.join(WORKDIR, '_bld', 'python')
798 rootDir = os.path.join(WORKDIR, '_root')
799
800 if os.path.exists(buildDir):
801 shutil.rmtree(buildDir)
802 if os.path.exists(rootDir):
803 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000804 os.makedirs(buildDir)
805 os.makedirs(rootDir)
806 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000807 curdir = os.getcwd()
808 os.chdir(buildDir)
809
810 # Not sure if this is still needed, the original build script
811 # claims that parts of the install assume python.exe exists.
812 os.symlink('python', os.path.join(buildDir, 'python.exe'))
813
814 # Extract the version from the configure file, needed to calculate
815 # several paths.
816 version = getVersion()
817
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000818 # Since the extra libs are not in their installed framework location
819 # during the build, augment the library path so that the interpreter
820 # will find them during its extension import sanity checks.
821 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
822 'libraries', 'usr', 'local', 'lib')
Ned Deily7d9cf832010-11-27 16:42:15 -0800823 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000824 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000825 "--with-universal-archs=%s "
826 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000827 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
828 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
829 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
830 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000831 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000832 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000833 shellQuote(WORKDIR)[1:-1]))
834
Ned Deily7d9cf832010-11-27 16:42:15 -0800835 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000836 runCommand("make")
837
Ned Deily7d9cf832010-11-27 16:42:15 -0800838 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000839 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000840 shellQuote(rootDir)))
841
Ned Deily7d9cf832010-11-27 16:42:15 -0800842 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000843 runCommand("make frameworkinstallextras DESTDIR=%s"%(
844 shellQuote(rootDir)))
845
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000846 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily7d9cf832010-11-27 16:42:15 -0800847 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000848 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
849 runCommand("mv %s/* %s"%(
850 shellQuote(os.path.join(
851 WORKDIR, 'libraries', 'Library', 'Frameworks',
852 'Python.framework', 'Versions', getVersion(),
853 'lib')),
854 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
855 'Python.framework', 'Versions', getVersion(),
856 'lib'))))
857
Ned Deily7d9cf832010-11-27 16:42:15 -0800858 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000859 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000860 gid = grp.getgrnam('admin').gr_gid
861
Thomas Wouters477c8d52006-05-27 19:21:47 +0000862 for dirpath, dirnames, filenames in os.walk(frmDir):
863 for dn in dirnames:
Ned Deily7d9cf832010-11-27 16:42:15 -0800864 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000865 os.chown(os.path.join(dirpath, dn), -1, gid)
866
Thomas Wouters477c8d52006-05-27 19:21:47 +0000867
868 for fn in filenames:
869 if os.path.islink(fn):
870 continue
871
872 # "chmod g+w $fn"
873 p = os.path.join(dirpath, fn)
874 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000875 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
876 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000877
Ned Deilye59e4c52011-01-29 18:56:28 +0000878 if PYTHON_3:
879 LDVERSION=None
880 VERSION=None
881 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000882
Ned Deilye59e4c52011-01-29 18:56:28 +0000883 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000884 for ln in fp:
885 if ln.startswith('VERSION='):
886 VERSION=ln.split()[1]
887 if ln.startswith('ABIFLAGS='):
888 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000889 if ln.startswith('LDVERSION='):
890 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000891 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000892
Ned Deilye59e4c52011-01-29 18:56:28 +0000893 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
894 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
895 config_suffix = '-' + LDVERSION
896 else:
897 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000898
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899 # We added some directories to the search path during the configure
900 # phase. Remove those because those directories won't be there on
901 # the end-users system.
902 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
903 'Versions', version, 'lib', 'python%s'%(version,),
Ned Deilye59e4c52011-01-29 18:56:28 +0000904 'config' + config_suffix, 'Makefile')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905 fp = open(path, 'r')
906 data = fp.read()
907 fp.close()
908
909 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
910 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
911 fp = open(path, 'w')
912 fp.write(data)
913 fp.close()
914
915 # Add symlinks in /usr/local/bin, using relative links
916 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
917 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
918 'Python.framework', 'Versions', version, 'bin')
919 if os.path.exists(usr_local_bin):
920 shutil.rmtree(usr_local_bin)
921 os.makedirs(usr_local_bin)
922 for fn in os.listdir(
923 os.path.join(frmDir, 'Versions', version, 'bin')):
924 os.symlink(os.path.join(to_framework, fn),
925 os.path.join(usr_local_bin, fn))
926
927 os.chdir(curdir)
928
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000929 if PYTHON_3:
930 # Remove the 'Current' link, that way we don't accidently mess
931 # with an already installed version of python 2
932 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
933 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000934
935def patchFile(inPath, outPath):
936 data = fileContents(inPath)
937 data = data.replace('$FULL_VERSION', getFullVersion())
938 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000939 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000940 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000941 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000942
943 # This one is not handy as a template variable
944 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily7d9cf832010-11-27 16:42:15 -0800945 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000946 fp.write(data)
947 fp.close()
948
949def patchScript(inPath, outPath):
950 data = fileContents(inPath)
951 data = data.replace('@PYVER@', getVersion())
Ned Deily7d9cf832010-11-27 16:42:15 -0800952 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000953 fp.write(data)
954 fp.close()
Ned Deily7d9cf832010-11-27 16:42:15 -0800955 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000956
957
958
959def packageFromRecipe(targetDir, recipe):
960 curdir = os.getcwd()
961 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000962 # The major version (such as 2.5) is included in the package name
963 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000964 # common.
965 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000966 srcdir = recipe.get('source')
967 pkgroot = recipe.get('topdir', srcdir)
968 postflight = recipe.get('postflight')
969 readme = textwrap.dedent(recipe['readme'])
970 isRequired = recipe.get('required', True)
971
Ned Deily7d9cf832010-11-27 16:42:15 -0800972 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000973
974 # Substitute some variables
975 textvars = dict(
976 VER=getVersion(),
977 FULLVER=getFullVersion(),
978 )
979 readme = readme % textvars
980
981 if pkgroot is not None:
982 pkgroot = pkgroot % textvars
983 else:
984 pkgroot = '/'
985
986 if srcdir is not None:
987 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
988 srcdir = srcdir % textvars
989
990 if postflight is not None:
991 postflight = os.path.abspath(postflight)
992
993 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
994 os.makedirs(packageContents)
995
996 if srcdir is not None:
997 os.chdir(srcdir)
998 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
999 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1000 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1001
1002 fn = os.path.join(packageContents, 'PkgInfo')
1003 fp = open(fn, 'w')
1004 fp.write('pmkrpkg1')
1005 fp.close()
1006
1007 rsrcDir = os.path.join(packageContents, "Resources")
1008 os.mkdir(rsrcDir)
1009 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1010 fp.write(readme)
1011 fp.close()
1012
1013 if postflight is not None:
1014 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1015
1016 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001017 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001018 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001019 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1020 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1021 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001022 CFBundleShortVersionString=vers,
1023 IFMajorVersion=major,
1024 IFMinorVersion=minor,
1025 IFPkgFormatVersion=0.10000000149011612,
1026 IFPkgFlagAllowBackRev=False,
1027 IFPkgFlagAuthorizationAction="RootAuthorization",
1028 IFPkgFlagDefaultLocation=pkgroot,
1029 IFPkgFlagFollowLinks=True,
1030 IFPkgFlagInstallFat=True,
1031 IFPkgFlagIsRequired=isRequired,
1032 IFPkgFlagOverwritePermissions=False,
1033 IFPkgFlagRelocatable=False,
1034 IFPkgFlagRestartAction="NoRestart",
1035 IFPkgFlagRootVolumeOnly=True,
1036 IFPkgFlagUpdateInstalledLangauges=False,
1037 )
1038 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1039
1040 pl = Plist(
1041 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001042 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001043 IFPkgDescriptionVersion=vers,
1044 )
1045 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1046
1047 finally:
1048 os.chdir(curdir)
1049
1050
1051def makeMpkgPlist(path):
1052
1053 vers = getFullVersion()
Ned Deily7d9cf832010-11-27 16:42:15 -08001054 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001055
1056 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001057 CFBundleGetInfoString="Python %s"%(vers,),
1058 CFBundleIdentifier='org.python.Python',
1059 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001060 CFBundleShortVersionString=vers,
1061 IFMajorVersion=major,
1062 IFMinorVersion=minor,
1063 IFPkgFlagComponentDirectory="Contents/Packages",
1064 IFPkgFlagPackageList=[
1065 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001066 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001067 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001068 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001069 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001070 ],
1071 IFPkgFormatVersion=0.10000000149011612,
1072 IFPkgFlagBackgroundScaling="proportional",
1073 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001074 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001075 )
1076
1077 writePlist(pl, path)
1078
1079
1080def buildInstaller():
1081
1082 # Zap all compiled files
1083 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1084 for fn in filenames:
1085 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1086 os.unlink(os.path.join(dirpath, fn))
1087
1088 outdir = os.path.join(WORKDIR, 'installer')
1089 if os.path.exists(outdir):
1090 shutil.rmtree(outdir)
1091 os.mkdir(outdir)
1092
Ronald Oussoren1943f862009-03-30 19:39:14 +00001093 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001094 pkgcontents = os.path.join(pkgroot, 'Packages')
1095 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001096 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001097 packageFromRecipe(pkgcontents, recipe)
1098
1099 rsrcDir = os.path.join(pkgroot, 'Resources')
1100
1101 fn = os.path.join(pkgroot, 'PkgInfo')
1102 fp = open(fn, 'w')
1103 fp.write('pmkrpkg1')
1104 fp.close()
1105
1106 os.mkdir(rsrcDir)
1107
1108 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1109 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001110 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001111 IFPkgDescriptionVersion=getVersion(),
1112 )
1113
1114 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1115 for fn in os.listdir('resources'):
1116 if fn == '.svn': continue
1117 if fn.endswith('.jpg'):
1118 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1119 else:
1120 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1121
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001122 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001123
1124
1125def installSize(clear=False, _saved=[]):
1126 if clear:
1127 del _saved[:]
1128 if not _saved:
1129 data = captureCommand("du -ks %s"%(
1130 shellQuote(os.path.join(WORKDIR, '_root'))))
1131 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1132 return _saved[0]
1133
1134
1135def buildDMG():
1136 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001137 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001138 """
1139 outdir = os.path.join(WORKDIR, 'diskimage')
1140 if os.path.exists(outdir):
1141 shutil.rmtree(outdir)
1142
1143 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001144 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001145 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001146 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001147 imagepath = imagepath + '.dmg'
1148
1149 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001150 volname='Python %s'%(getFullVersion())
1151 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1152 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001153 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001154 shellQuote(imagepath + ".tmp.dmg" )))
1155
1156
1157 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1158 os.mkdir(os.path.join(WORKDIR, "mnt"))
1159 runCommand("hdiutil attach %s -mountroot %s"%(
1160 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1161
1162 # Custom icon for the DMG, shown when the DMG is mounted.
1163 shutil.copy("../Icons/Disk Image.icns",
1164 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1165 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1166 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1167
1168 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1169
1170 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1171 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1172 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1173 setIcon(imagepath, "../Icons/Disk Image.icns")
1174
1175 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001176
1177 return imagepath
1178
1179
1180def setIcon(filePath, icnsPath):
1181 """
1182 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001183 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001184
Ronald Oussoren70050672010-04-30 15:00:26 +00001185 dirPath = os.path.normpath(os.path.dirname(__file__))
1186 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001187 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1188 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1189 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001190 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1191 if not os.path.exists(appPath):
1192 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001193 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1194 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001195
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001196 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1197 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001198
1199def main():
1200 # First parse options and check if we can perform our work
1201 parseOptions()
1202 checkEnvironment()
1203
Ronald Oussoren1943f862009-03-30 19:39:14 +00001204 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001205 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001206
1207 if os.path.exists(WORKDIR):
1208 shutil.rmtree(WORKDIR)
1209 os.mkdir(WORKDIR)
1210
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001211 os.environ['LC_ALL'] = 'C'
1212
Thomas Wouters477c8d52006-05-27 19:21:47 +00001213 # Then build third-party libraries such as sleepycat DB4.
1214 buildLibraries()
1215
1216 # Now build python itself
1217 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001218
1219 # And then build the documentation
1220 # Remove the Deployment Target from the shell
1221 # environment, it's no longer needed and
1222 # an unexpected build target can cause problems
1223 # when Sphinx and its dependencies need to
1224 # be (re-)installed.
1225 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001226 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001227
1228
1229 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001230 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001231 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001232 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001233
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001234 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001235 getVersion(),))
Ned Deily7d9cf832010-11-27 16:42:15 -08001236 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001237 setIcon(folder, "../Icons/Python Folder.icns")
1238
1239 # Create the installer
1240 buildInstaller()
1241
1242 # And copy the readme into the directory containing the installer
1243 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1244
1245 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001246 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001247
1248 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily7d9cf832010-11-27 16:42:15 -08001249 fp.write("# BUILD INFO\n")
1250 fp.write("# Date: %s\n" % time.ctime())
1251 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001252 fp.close()
1253
Thomas Wouters477c8d52006-05-27 19:21:47 +00001254 # And copy it to a DMG
1255 buildDMG()
1256
Thomas Wouters477c8d52006-05-27 19:21:47 +00001257if __name__ == "__main__":
1258 main()