blob: 18b6803691d5ff8e3da7d4a59248aaec7e391a1c [file] [log] [blame]
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001#!/usr/bin/python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
3This script is used to build the "official unofficial" universal build on
4Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its
Ronald Oussoren1943f862009-03-30 19:39:14 +00005work. 64-bit or four-way universal builds require at least OS X 10.5 and
6the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
8Please ensure that this script keeps working with Python 2.3, to avoid
9bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
10
11Usage: see USAGE variable in the script.
12"""
Benjamin Peterson623918e2008-12-20 22:50:25 +000013import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Thomas Wouters89f507f2006-12-13 04:49:30 +000014import grp
Thomas Wouters477c8d52006-05-27 19:21:47 +000015
Thomas Wouters89f507f2006-12-13 04:49:30 +000016INCLUDE_TIMESTAMP = 1
17VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000018
19from plistlib import Plist
20
21import MacOS
Thomas Wouters477c8d52006-05-27 19:21:47 +000022
23try:
24 from plistlib import writePlist
25except ImportError:
26 # We're run using python2.3
27 def writePlist(plist, path):
28 plist.write(path)
29
30def shellQuote(value):
31 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000032 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000033 a shell command.
34 """
35 return "'%s'"%(value.replace("'", "'\"'\"'"))
36
37def grepValue(fn, variable):
38 variable = variable + '='
39 for ln in open(fn, 'r'):
40 if ln.startswith(variable):
41 value = ln[len(variable):].strip()
42 return value[1:-1]
Benjamin Petersond9b7d482010-03-19 21:42:45 +000043 raise RuntimeError, "Cannot find variable %s" % variable[:-1]
Thomas Wouters477c8d52006-05-27 19:21:47 +000044
45def getVersion():
46 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
47
Benjamin Petersond9b7d482010-03-19 21:42:45 +000048def getVersionTuple():
49 return tuple([int(n) for n in getVersion().split('.')])
50
Thomas Wouters477c8d52006-05-27 19:21:47 +000051def getFullVersion():
52 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
53 for ln in open(fn):
54 if 'PY_VERSION' in ln:
55 return ln.split()[-1][1:-1]
Benjamin Peterson623918e2008-12-20 22:50:25 +000056 raise RuntimeError, "Cannot find full version??"
Thomas Wouters477c8d52006-05-27 19:21:47 +000057
Thomas Wouters89f507f2006-12-13 04:49:30 +000058# The directory we'll use to create the build (will be erased and recreated)
59WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000060
Thomas Wouters89f507f2006-12-13 04:49:30 +000061# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000062# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000063DEPSRC = os.path.join(WORKDIR, 'third-party')
64DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000065
66# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +000067
68### There are some issues with the SDK selection below here,
69### The resulting binary doesn't work on all platforms that
70### it should. Always default to the 10.4u SDK until that
71### isue is resolved.
72###
73##if int(os.uname()[2].split('.')[0]) == 8:
74## # Explicitly use the 10.4u (universal) SDK when
75## # building on 10.4, the system headers are not
76## # useable for a universal build
77## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
78##else:
79## SDKPATH = "/"
80
Thomas Wouters89f507f2006-12-13 04:49:30 +000081SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +000082
Ronald Oussoren1943f862009-03-30 19:39:14 +000083universal_opts_map = { '32-bit': ('i386', 'ppc',),
84 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +000085 'intel': ('i386', 'x86_64'),
86 '3-way': ('ppc', 'i386', 'x86_64'),
87 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
88default_target_map = {
89 '64-bit': '10.5',
90 '3-way': '10.5',
91 'intel': '10.5',
92 'all': '10.5',
93}
Ronald Oussoren1943f862009-03-30 19:39:14 +000094
95UNIVERSALOPTS = tuple(universal_opts_map.keys())
96
97UNIVERSALARCHS = '32-bit'
98
99ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000101# Source directory (asume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000102SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000103 os.path.dirname(
104 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000105 os.path.abspath(__file__
106 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000107
Ronald Oussoren1943f862009-03-30 19:39:14 +0000108# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
109DEPTARGET = '10.3'
110
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000111target_cc_map = {
112 '10.3': 'gcc-4.0',
113 '10.4': 'gcc-4.0',
114 '10.5': 'gcc-4.0',
115 '10.6': 'gcc-4.2',
116}
117
118CC = target_cc_map[DEPTARGET]
119
120PYTHON_3 = getVersionTuple() >= (3, 0)
121
Thomas Wouters89f507f2006-12-13 04:49:30 +0000122USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000123 Usage: build_python [options]
124
125 Options:
126 -? or -h: Show this message
127 -b DIR
128 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
129 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
130 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
131 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000132 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
133 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000134""")% globals()
135
136
137# Instructions for building libraries that are necessary for building a
138# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000139# [The recipes are defined here for convenience but instantiated later after
140# command line options have been processed.]
141def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000142 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000144 if DEPTARGET < '10.5':
145 result.extend([
146 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000147 name="Bzip2 1.0.6",
148 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
149 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000150 configure=None,
151 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
152 CC,
153 shellQuote(os.path.join(WORKDIR, 'libraries')),
154 ' -arch '.join(ARCHLIST),
155 SDKPATH,
156 ),
157 ),
158 dict(
159 name="ZLib 1.2.3",
160 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
161 checksum='debc62758716a169df9f62e6ab2bc634',
162 configure=None,
163 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
164 CC,
165 shellQuote(os.path.join(WORKDIR, 'libraries')),
166 ' -arch '.join(ARCHLIST),
167 SDKPATH,
168 ),
169 ),
170 dict(
171 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000172 name="GNU Readline 6.1.2",
173 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
174 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000175 patchlevel='0',
176 patches=[
177 # The readline maintainers don't do actual micro releases, but
178 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000179 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
180 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000181 ]
182 ),
183 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000184 name="SQLite 3.7.4",
185 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
186 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
187 configure_env=('CFLAGS="-Os'
188 ' -DSQLITE_ENABLE_FTS3'
189 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
190 ' -DSQLITE_ENABLE_RTREE'
191 ' -DSQLITE_TCL=0'
192 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000193 configure_pre=[
194 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000195 '--enable-shared=no',
196 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000197 '--disable-readline',
198 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000199 ]
200 ),
201 dict(
202 name="NCurses 5.5",
203 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
204 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
205 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000206 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000207 "--without-cxx",
208 "--without-ada",
209 "--without-progs",
210 "--without-curses-h",
211 "--enable-shared",
212 "--with-shared",
213 "--datadir=/usr/share",
214 "--sysconfdir=/etc",
215 "--sharedstatedir=/usr/com",
216 "--with-terminfo-dirs=/usr/share/terminfo",
217 "--with-default-terminfo-dir=/usr/share/terminfo",
218 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
219 "--enable-termcap",
220 ],
221 patches=[
222 "ncurses-5.5.patch",
223 ],
224 useLDFlags=False,
225 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
226 shellQuote(os.path.join(WORKDIR, 'libraries')),
227 shellQuote(os.path.join(WORKDIR, 'libraries')),
228 getVersion(),
229 ),
230 ),
231 ])
232
Ned Deily4f7ff782011-01-15 05:29:12 +0000233 if not PYTHON_3:
234 result.extend([
235 dict(
236 name="Sleepycat DB 4.7.25",
237 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
238 checksum='ec2b87e833779681a0c3a814aa71359e',
239 buildDir="build_unix",
240 configure="../dist/configure",
241 configure_pre=[
242 '--includedir=/usr/local/include/db4',
243 ]
244 ),
245 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000246
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000247 return result
248
Thomas Wouters477c8d52006-05-27 19:21:47 +0000249
Thomas Wouters477c8d52006-05-27 19:21:47 +0000250# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000251def pkg_recipes():
252 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
253 result = [
254 dict(
255 name="PythonFramework",
256 long_name="Python Framework",
257 source="/Library/Frameworks/Python.framework",
258 readme="""\
259 This package installs Python.framework, that is the python
260 interpreter and the standard library. This also includes Python
261 wrappers for lots of Mac OS X API's.
262 """,
263 postflight="scripts/postflight.framework",
264 selected='selected',
265 ),
266 dict(
267 name="PythonApplications",
268 long_name="GUI Applications",
269 source="/Applications/Python %(VER)s",
270 readme="""\
271 This package installs IDLE (an interactive Python IDE),
272 Python Launcher and Build Applet (create application bundles
273 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000274
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000275 It also installs a number of examples and demos.
276 """,
277 required=False,
278 selected='selected',
279 ),
280 dict(
281 name="PythonUnixTools",
282 long_name="UNIX command-line tools",
283 source="/usr/local/bin",
284 readme="""\
285 This package installs the unix tools in /usr/local/bin for
286 compatibility with older releases of Python. This package
287 is not necessary to use Python.
288 """,
289 required=False,
290 selected='selected',
291 ),
292 dict(
293 name="PythonDocumentation",
294 long_name="Python Documentation",
295 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
296 source="/pydocs",
297 readme="""\
298 This package installs the python documentation at a location
299 that is useable for pydoc and IDLE. If you have installed Xcode
300 it will also install a link to the documentation in
301 /Developer/Documentation/Python
302 """,
303 postflight="scripts/postflight.documentation",
304 required=False,
305 selected='selected',
306 ),
307 dict(
308 name="PythonProfileChanges",
309 long_name="Shell profile updater",
310 readme="""\
311 This packages updates your shell profile to make sure that
312 the Python tools are found by your shell in preference of
313 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000314
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000315 If you don't install this package you'll have to add
316 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
317 to your PATH by hand.
318 """,
319 postflight="scripts/postflight.patch-profile",
320 topdir="/Library/Frameworks/Python.framework",
321 source="/empty-dir",
322 required=False,
323 selected=unselected_for_python3,
324 ),
325 ]
326
327 if DEPTARGET < '10.4':
328 result.append(
329 dict(
330 name="PythonSystemFixes",
331 long_name="Fix system Python",
332 readme="""\
333 This package updates the system python installation on
334 Mac OS X 10.3 to ensure that you can build new python extensions
335 using that copy of python after installing this version.
336 """,
337 postflight="../Tools/fixapplepython23.py",
338 topdir="/Library/Frameworks/Python.framework",
339 source="/empty-dir",
340 required=False,
341 selected=unselected_for_python3,
342 )
343 )
344 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000345
Thomas Wouters477c8d52006-05-27 19:21:47 +0000346def fatal(msg):
347 """
348 A fatal error, bail out.
349 """
350 sys.stderr.write('FATAL: ')
351 sys.stderr.write(msg)
352 sys.stderr.write('\n')
353 sys.exit(1)
354
355def fileContents(fn):
356 """
357 Return the contents of the named file
358 """
359 return open(fn, 'rb').read()
360
361def runCommand(commandline):
362 """
363 Run a command and raise RuntimeError if it fails. Output is surpressed
364 unless the command fails.
365 """
366 fd = os.popen(commandline, 'r')
367 data = fd.read()
368 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000369 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000370 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000371 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000372
373 if VERBOSE:
374 sys.stdout.write(data); sys.stdout.flush()
375
376def captureCommand(commandline):
377 fd = os.popen(commandline, 'r')
378 data = fd.read()
379 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000380 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000381 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000382 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000383
384 return data
385
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000386def getTclTkVersion(configfile, versionline):
387 """
388 search Tcl or Tk configuration file for version line
389 """
390 try:
391 f = open(configfile, "r")
392 except:
393 fatal("Framework configuration file not found: %s" % configfile)
394
395 for l in f:
396 if l.startswith(versionline):
397 f.close()
398 return l
399
400 fatal("Version variable %s not found in framework configuration file: %s"
401 % (versionline, configfile))
402
Thomas Wouters477c8d52006-05-27 19:21:47 +0000403def checkEnvironment():
404 """
405 Check that we're running on a supported system.
406 """
407
408 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000409 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000410
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000411 if int(platform.release().split('.')[0]) < 8:
412 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000413
414 if not os.path.exists(SDKPATH):
415 fatal("Please install the latest version of Xcode and the %s SDK"%(
416 os.path.basename(SDKPATH[:-4])))
417
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000418 # Because we only support dynamic load of only one major/minor version of
419 # Tcl/Tk, ensure:
420 # 1. there are no user-installed frameworks of Tcl/Tk with version
421 # higher than the Apple-supplied system version
422 # 2. there is a user-installed framework in /Library/Frameworks with the
423 # same version as the system version. This allows users to choose
424 # to install a newer patch level.
425
426 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000427 #fw = dict(lower=framework.lower(),
428 # upper=framework.upper(),
429 # cap=framework.capitalize())
430 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
431 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
432 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000433 libfw = os.path.join('/', fwpth)
434 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000435 #version = "%(upper)s_VERSION" % fw
436 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000437 fatal("Version of %s must match %s" % (libfw, sysfw) )
438 if os.path.exists(usrfw):
439 fatal("Please rename %s to avoid possible dynamic load issues."
440 % usrfw)
441
442 # Remove inherited environment variables which might influence build
443 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
444 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
445 for ev in list(os.environ):
446 for prefix in environ_var_prefixes:
447 if ev.startswith(prefix) :
448 print "INFO: deleting environment variable %s=%s" % (
449 ev, os.environ[ev])
450 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000451
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000452 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
453 print "Setting default PATH: %s"%(os.environ['PATH'])
454
Thomas Wouters477c8d52006-05-27 19:21:47 +0000455
Thomas Wouters89f507f2006-12-13 04:49:30 +0000456def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000457 """
458 Parse arguments and update global settings.
459 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000460 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000461 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000462
463 if args is None:
464 args = sys.argv[1:]
465
466 try:
467 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000468 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
469 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000470 except getopt.error, msg:
471 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000472 sys.exit(1)
473
474 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000475 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000476 sys.exit(1)
477
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000478 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000479 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000480 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000481 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482 sys.exit(0)
483
484 elif k in ('-d', '--build-dir'):
485 WORKDIR=v
486
487 elif k in ('--third-party',):
488 DEPSRC=v
489
490 elif k in ('--sdk-path',):
491 SDKPATH=v
492
493 elif k in ('--src-dir',):
494 SRCDIR=v
495
Ronald Oussoren1943f862009-03-30 19:39:14 +0000496 elif k in ('--dep-target', ):
497 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000498 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000499
500 elif k in ('--universal-archs', ):
501 if v in UNIVERSALOPTS:
502 UNIVERSALARCHS = v
503 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000504 if deptarget is None:
505 # Select alternate default deployment
506 # target
507 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000508 else:
509 raise NotImplementedError, v
510
Thomas Wouters477c8d52006-05-27 19:21:47 +0000511 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000512 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000513
514 SRCDIR=os.path.abspath(SRCDIR)
515 WORKDIR=os.path.abspath(WORKDIR)
516 SDKPATH=os.path.abspath(SDKPATH)
517 DEPSRC=os.path.abspath(DEPSRC)
518
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000519 CC=target_cc_map[DEPTARGET]
520
Benjamin Peterson623918e2008-12-20 22:50:25 +0000521 print "Settings:"
522 print " * Source directory:", SRCDIR
523 print " * Build directory: ", WORKDIR
524 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000525 print " * Third-party source:", DEPSRC
526 print " * Deployment target:", DEPTARGET
527 print " * Universal architectures:", ARCHLIST
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000528 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000529 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000530
531
532
533
534def extractArchive(builddir, archiveName):
535 """
536 Extract a source archive into 'builddir'. Returns the path of the
537 extracted archive.
538
539 XXX: This function assumes that archives contain a toplevel directory
540 that is has the same name as the basename of the archive. This is
541 save enough for anything we use.
542 """
543 curdir = os.getcwd()
544 try:
545 os.chdir(builddir)
546 if archiveName.endswith('.tar.gz'):
547 retval = os.path.basename(archiveName[:-7])
548 if os.path.exists(retval):
549 shutil.rmtree(retval)
550 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
551
552 elif archiveName.endswith('.tar.bz2'):
553 retval = os.path.basename(archiveName[:-8])
554 if os.path.exists(retval):
555 shutil.rmtree(retval)
556 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
557
558 elif archiveName.endswith('.tar'):
559 retval = os.path.basename(archiveName[:-4])
560 if os.path.exists(retval):
561 shutil.rmtree(retval)
562 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
563
564 elif archiveName.endswith('.zip'):
565 retval = os.path.basename(archiveName[:-4])
566 if os.path.exists(retval):
567 shutil.rmtree(retval)
568 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
569
570 data = fp.read()
571 xit = fp.close()
572 if xit is not None:
573 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000574 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000575
576 return os.path.join(builddir, retval)
577
578 finally:
579 os.chdir(curdir)
580
581KNOWNSIZES = {
582 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
583 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
584}
585
586def downloadURL(url, fname):
587 """
588 Download the contents of the url into the file.
589 """
590 try:
591 size = os.path.getsize(fname)
592 except OSError:
593 pass
594 else:
595 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000596 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000597 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000598 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000599 fpOut = open(fname, 'wb')
600 block = fpIn.read(10240)
601 try:
602 while block:
603 fpOut.write(block)
604 block = fpIn.read(10240)
605 fpIn.close()
606 fpOut.close()
607 except:
608 try:
609 os.unlink(fname)
610 except:
611 pass
612
613def buildRecipe(recipe, basedir, archList):
614 """
615 Build software using a recipe. This function does the
616 'configure;make;make install' dance for C software, with a possibility
617 to customize this process, basically a poor-mans DarwinPorts.
618 """
619 curdir = os.getcwd()
620
621 name = recipe['name']
622 url = recipe['url']
623 configure = recipe.get('configure', './configure')
624 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
625 shellQuote(basedir)))
626
627 archiveName = os.path.split(url)[-1]
628 sourceArchive = os.path.join(DEPSRC, archiveName)
629
630 if not os.path.exists(DEPSRC):
631 os.mkdir(DEPSRC)
632
633
634 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000635 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000636
637 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000638 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000639 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000640 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000641 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000642
Benjamin Peterson623918e2008-12-20 22:50:25 +0000643 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000644 buildDir=os.path.join(WORKDIR, '_bld')
645 if not os.path.exists(buildDir):
646 os.mkdir(buildDir)
647
648 workDir = extractArchive(buildDir, sourceArchive)
649 os.chdir(workDir)
650 if 'buildDir' in recipe:
651 os.chdir(recipe['buildDir'])
652
653
654 for fn in recipe.get('patches', ()):
655 if fn.startswith('http://'):
656 # Download the patch before applying it.
657 path = os.path.join(DEPSRC, os.path.basename(fn))
658 downloadURL(fn, path)
659 fn = path
660
661 fn = os.path.join(curdir, fn)
662 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
663 shellQuote(fn),))
664
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000665 if configure is not None:
666 configure_args = [
667 "--prefix=/usr/local",
668 "--enable-static",
669 "--disable-shared",
670 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
671 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000672
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000673 if 'configure_pre' in recipe:
674 args = list(recipe['configure_pre'])
675 if '--disable-static' in args:
676 configure_args.remove('--enable-static')
677 if '--enable-shared' in args:
678 configure_args.remove('--disable-shared')
679 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000680
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000681 if recipe.get('useLDFlags', 1):
682 configure_args.extend([
683 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
684 ' -arch '.join(archList),
685 shellQuote(SDKPATH)[1:-1],
686 shellQuote(basedir)[1:-1],),
687 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000688 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 shellQuote(basedir)[1:-1],
690 ' -arch '.join(archList)),
691 ])
692 else:
693 configure_args.extend([
694 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
695 ' -arch '.join(archList),
696 shellQuote(SDKPATH)[1:-1],
697 shellQuote(basedir)[1:-1],),
698 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000699
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000700 if 'configure_post' in recipe:
701 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000702
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000703 configure_args.insert(0, configure)
704 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000705
Ned Deily4f7ff782011-01-15 05:29:12 +0000706 if 'configure_env' in recipe:
707 configure_args.insert(0, recipe['configure_env'])
708
Benjamin Peterson623918e2008-12-20 22:50:25 +0000709 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000710 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000711
Benjamin Peterson623918e2008-12-20 22:50:25 +0000712 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000713 runCommand('{ ' + install + ' ;} 2>&1')
714
Benjamin Peterson623918e2008-12-20 22:50:25 +0000715 print "Done %s"%(name,)
716 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000717
718 os.chdir(curdir)
719
720def buildLibraries():
721 """
722 Build our dependencies into $WORKDIR/libraries/usr/local
723 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000724 print ""
725 print "Building required libraries"
726 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000727 universal = os.path.join(WORKDIR, 'libraries')
728 os.mkdir(universal)
729 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
730 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
731
Ronald Oussoren1943f862009-03-30 19:39:14 +0000732 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000733 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734
735
736
737def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000738 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000740 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000741 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000742 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000744 curDir = os.getcwd()
745 os.chdir(buildDir)
746 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000747 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000748 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000749 if not os.path.exists(docdir):
750 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000751 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000752
753
754def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000755 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000756
757 buildDir = os.path.join(WORKDIR, '_bld', 'python')
758 rootDir = os.path.join(WORKDIR, '_root')
759
760 if os.path.exists(buildDir):
761 shutil.rmtree(buildDir)
762 if os.path.exists(rootDir):
763 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000764 os.makedirs(buildDir)
765 os.makedirs(rootDir)
766 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000767 curdir = os.getcwd()
768 os.chdir(buildDir)
769
770 # Not sure if this is still needed, the original build script
771 # claims that parts of the install assume python.exe exists.
772 os.symlink('python', os.path.join(buildDir, 'python.exe'))
773
774 # Extract the version from the configure file, needed to calculate
775 # several paths.
776 version = getVersion()
777
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000778 # Since the extra libs are not in their installed framework location
779 # during the build, augment the library path so that the interpreter
780 # will find them during its extension import sanity checks.
781 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
782 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000783 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000784 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000785 "--with-universal-archs=%s "
786 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000787 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
788 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
789 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
790 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000791 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000792 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000793 shellQuote(WORKDIR)[1:-1]))
794
Benjamin Peterson623918e2008-12-20 22:50:25 +0000795 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000796 runCommand("make")
797
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000798 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000799 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000800 shellQuote(rootDir)))
801
Benjamin Peterson623918e2008-12-20 22:50:25 +0000802 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000803 runCommand("make frameworkinstallextras DESTDIR=%s"%(
804 shellQuote(rootDir)))
805
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000806 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000807 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000808 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
809 runCommand("mv %s/* %s"%(
810 shellQuote(os.path.join(
811 WORKDIR, 'libraries', 'Library', 'Frameworks',
812 'Python.framework', 'Versions', getVersion(),
813 'lib')),
814 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
815 'Python.framework', 'Versions', getVersion(),
816 'lib'))))
817
Benjamin Peterson623918e2008-12-20 22:50:25 +0000818 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000819 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000820 gid = grp.getgrnam('admin').gr_gid
821
Thomas Wouters477c8d52006-05-27 19:21:47 +0000822 for dirpath, dirnames, filenames in os.walk(frmDir):
823 for dn in dirnames:
824 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000825 os.chown(os.path.join(dirpath, dn), -1, gid)
826
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827
828 for fn in filenames:
829 if os.path.islink(fn):
830 continue
831
832 # "chmod g+w $fn"
833 p = os.path.join(dirpath, fn)
834 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000835 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
836 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000837
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000838 LDVERSION=None
839 VERSION=None
840 ABIFLAGS=None
841
842 with open(os.path.join(buildDir, 'Makefile')) as fp:
843 for ln in fp:
844 if ln.startswith('VERSION='):
845 VERSION=ln.split()[1]
846 if ln.startswith('ABIFLAGS='):
847 ABIFLAGS=ln.split()[1]
848
849 if ln.startswith('LDVERSION='):
850 LDVERSION=ln.split()[1]
851
852 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
853 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
854
Thomas Wouters477c8d52006-05-27 19:21:47 +0000855 # We added some directories to the search path during the configure
856 # phase. Remove those because those directories won't be there on
857 # the end-users system.
858 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
859 'Versions', version, 'lib', 'python%s'%(version,),
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000860 'config-' + LDVERSION, 'Makefile')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000861 fp = open(path, 'r')
862 data = fp.read()
863 fp.close()
864
865 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
866 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
867 fp = open(path, 'w')
868 fp.write(data)
869 fp.close()
870
871 # Add symlinks in /usr/local/bin, using relative links
872 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
873 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
874 'Python.framework', 'Versions', version, 'bin')
875 if os.path.exists(usr_local_bin):
876 shutil.rmtree(usr_local_bin)
877 os.makedirs(usr_local_bin)
878 for fn in os.listdir(
879 os.path.join(frmDir, 'Versions', version, 'bin')):
880 os.symlink(os.path.join(to_framework, fn),
881 os.path.join(usr_local_bin, fn))
882
883 os.chdir(curdir)
884
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000885 if PYTHON_3:
886 # Remove the 'Current' link, that way we don't accidently mess
887 # with an already installed version of python 2
888 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
889 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000890
891def patchFile(inPath, outPath):
892 data = fileContents(inPath)
893 data = data.replace('$FULL_VERSION', getFullVersion())
894 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000895 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000896 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000897 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000898
899 # This one is not handy as a template variable
900 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000901 fp = open(outPath, 'wb')
902 fp.write(data)
903 fp.close()
904
905def patchScript(inPath, outPath):
906 data = fileContents(inPath)
907 data = data.replace('@PYVER@', getVersion())
908 fp = open(outPath, 'wb')
909 fp.write(data)
910 fp.close()
911 os.chmod(outPath, 0755)
912
913
914
915def packageFromRecipe(targetDir, recipe):
916 curdir = os.getcwd()
917 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000918 # The major version (such as 2.5) is included in the package name
919 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000920 # common.
921 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000922 srcdir = recipe.get('source')
923 pkgroot = recipe.get('topdir', srcdir)
924 postflight = recipe.get('postflight')
925 readme = textwrap.dedent(recipe['readme'])
926 isRequired = recipe.get('required', True)
927
Benjamin Peterson623918e2008-12-20 22:50:25 +0000928 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929
930 # Substitute some variables
931 textvars = dict(
932 VER=getVersion(),
933 FULLVER=getFullVersion(),
934 )
935 readme = readme % textvars
936
937 if pkgroot is not None:
938 pkgroot = pkgroot % textvars
939 else:
940 pkgroot = '/'
941
942 if srcdir is not None:
943 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
944 srcdir = srcdir % textvars
945
946 if postflight is not None:
947 postflight = os.path.abspath(postflight)
948
949 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
950 os.makedirs(packageContents)
951
952 if srcdir is not None:
953 os.chdir(srcdir)
954 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
955 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
956 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
957
958 fn = os.path.join(packageContents, 'PkgInfo')
959 fp = open(fn, 'w')
960 fp.write('pmkrpkg1')
961 fp.close()
962
963 rsrcDir = os.path.join(packageContents, "Resources")
964 os.mkdir(rsrcDir)
965 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
966 fp.write(readme)
967 fp.close()
968
969 if postflight is not None:
970 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
971
972 vers = getFullVersion()
973 major, minor = map(int, getVersion().split('.', 2))
974 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000975 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
976 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
977 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000978 CFBundleShortVersionString=vers,
979 IFMajorVersion=major,
980 IFMinorVersion=minor,
981 IFPkgFormatVersion=0.10000000149011612,
982 IFPkgFlagAllowBackRev=False,
983 IFPkgFlagAuthorizationAction="RootAuthorization",
984 IFPkgFlagDefaultLocation=pkgroot,
985 IFPkgFlagFollowLinks=True,
986 IFPkgFlagInstallFat=True,
987 IFPkgFlagIsRequired=isRequired,
988 IFPkgFlagOverwritePermissions=False,
989 IFPkgFlagRelocatable=False,
990 IFPkgFlagRestartAction="NoRestart",
991 IFPkgFlagRootVolumeOnly=True,
992 IFPkgFlagUpdateInstalledLangauges=False,
993 )
994 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
995
996 pl = Plist(
997 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +0000998 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000999 IFPkgDescriptionVersion=vers,
1000 )
1001 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1002
1003 finally:
1004 os.chdir(curdir)
1005
1006
1007def makeMpkgPlist(path):
1008
1009 vers = getFullVersion()
1010 major, minor = map(int, getVersion().split('.', 2))
1011
1012 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001013 CFBundleGetInfoString="Python %s"%(vers,),
1014 CFBundleIdentifier='org.python.Python',
1015 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001016 CFBundleShortVersionString=vers,
1017 IFMajorVersion=major,
1018 IFMinorVersion=minor,
1019 IFPkgFlagComponentDirectory="Contents/Packages",
1020 IFPkgFlagPackageList=[
1021 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001022 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001023 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001024 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001025 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001026 ],
1027 IFPkgFormatVersion=0.10000000149011612,
1028 IFPkgFlagBackgroundScaling="proportional",
1029 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001030 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001031 )
1032
1033 writePlist(pl, path)
1034
1035
1036def buildInstaller():
1037
1038 # Zap all compiled files
1039 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1040 for fn in filenames:
1041 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1042 os.unlink(os.path.join(dirpath, fn))
1043
1044 outdir = os.path.join(WORKDIR, 'installer')
1045 if os.path.exists(outdir):
1046 shutil.rmtree(outdir)
1047 os.mkdir(outdir)
1048
Ronald Oussoren1943f862009-03-30 19:39:14 +00001049 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001050 pkgcontents = os.path.join(pkgroot, 'Packages')
1051 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001052 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001053 packageFromRecipe(pkgcontents, recipe)
1054
1055 rsrcDir = os.path.join(pkgroot, 'Resources')
1056
1057 fn = os.path.join(pkgroot, 'PkgInfo')
1058 fp = open(fn, 'w')
1059 fp.write('pmkrpkg1')
1060 fp.close()
1061
1062 os.mkdir(rsrcDir)
1063
1064 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1065 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001066 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001067 IFPkgDescriptionVersion=getVersion(),
1068 )
1069
1070 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1071 for fn in os.listdir('resources'):
1072 if fn == '.svn': continue
1073 if fn.endswith('.jpg'):
1074 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1075 else:
1076 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1077
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001078 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001079
1080
1081def installSize(clear=False, _saved=[]):
1082 if clear:
1083 del _saved[:]
1084 if not _saved:
1085 data = captureCommand("du -ks %s"%(
1086 shellQuote(os.path.join(WORKDIR, '_root'))))
1087 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1088 return _saved[0]
1089
1090
1091def buildDMG():
1092 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001093 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001094 """
1095 outdir = os.path.join(WORKDIR, 'diskimage')
1096 if os.path.exists(outdir):
1097 shutil.rmtree(outdir)
1098
1099 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001100 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001101 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001102 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001103 imagepath = imagepath + '.dmg'
1104
1105 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001106 volname='Python %s'%(getFullVersion())
1107 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1108 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001109 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001110 shellQuote(imagepath + ".tmp.dmg" )))
1111
1112
1113 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1114 os.mkdir(os.path.join(WORKDIR, "mnt"))
1115 runCommand("hdiutil attach %s -mountroot %s"%(
1116 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1117
1118 # Custom icon for the DMG, shown when the DMG is mounted.
1119 shutil.copy("../Icons/Disk Image.icns",
1120 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1121 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1122 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1123
1124 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1125
1126 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1127 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1128 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1129 setIcon(imagepath, "../Icons/Disk Image.icns")
1130
1131 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001132
1133 return imagepath
1134
1135
1136def setIcon(filePath, icnsPath):
1137 """
1138 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001139 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001140
Ronald Oussoren70050672010-04-30 15:00:26 +00001141 dirPath = os.path.normpath(os.path.dirname(__file__))
1142 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001143 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1144 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1145 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001146 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1147 if not os.path.exists(appPath):
1148 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001149 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1150 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001151
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001152 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1153 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001154
1155def main():
1156 # First parse options and check if we can perform our work
1157 parseOptions()
1158 checkEnvironment()
1159
Ronald Oussoren1943f862009-03-30 19:39:14 +00001160 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001161 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001162
1163 if os.path.exists(WORKDIR):
1164 shutil.rmtree(WORKDIR)
1165 os.mkdir(WORKDIR)
1166
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001167 os.environ['LC_ALL'] = 'C'
1168
Thomas Wouters477c8d52006-05-27 19:21:47 +00001169 # Then build third-party libraries such as sleepycat DB4.
1170 buildLibraries()
1171
1172 # Now build python itself
1173 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001174
1175 # And then build the documentation
1176 # Remove the Deployment Target from the shell
1177 # environment, it's no longer needed and
1178 # an unexpected build target can cause problems
1179 # when Sphinx and its dependencies need to
1180 # be (re-)installed.
1181 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001182 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001183
1184
1185 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001186 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001187 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001188 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001189
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001190 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001191 getVersion(),))
1192 os.chmod(folder, 0755)
1193 setIcon(folder, "../Icons/Python Folder.icns")
1194
1195 # Create the installer
1196 buildInstaller()
1197
1198 # And copy the readme into the directory containing the installer
1199 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1200
1201 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001202 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001203
1204 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001205 print >> fp, "# BUILD INFO"
1206 print >> fp, "# Date:", time.ctime()
1207 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001208 fp.close()
1209
Thomas Wouters477c8d52006-05-27 19:21:47 +00001210 # And copy it to a DMG
1211 buildDMG()
1212
Thomas Wouters477c8d52006-05-27 19:21:47 +00001213if __name__ == "__main__":
1214 main()