blob: db083b693dca40d05883040650b1b862debdec05 [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(
147 name="Bzip2 1.0.5",
148 url="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
149 checksum='3c15a0c8d1d3ee1c46a1634d00617b1a',
150 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
172 name="GNU Readline 5.1.4",
173 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
174 checksum='7ee5a692db88b30ca48927a13fd60e46',
175 patchlevel='0',
176 patches=[
177 # The readline maintainers don't do actual micro releases, but
178 # just ship a set of patches.
179 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
180 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
181 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
182 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
183 ]
184 ),
185 dict(
186 name="SQLite 3.6.11",
187 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
188 checksum='7ebb099696ab76cc6ff65dd496d17858',
189 configure_pre=[
190 '--enable-threadsafe',
191 '--enable-tempstore',
192 '--enable-shared=no',
193 '--enable-static=yes',
194 '--disable-tcl',
195 ]
196 ),
197 dict(
198 name="NCurses 5.5",
199 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
200 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
201 configure_pre=[
202 "--without-cxx",
203 "--without-ada",
204 "--without-progs",
205 "--without-curses-h",
206 "--enable-shared",
207 "--with-shared",
208 "--datadir=/usr/share",
209 "--sysconfdir=/etc",
210 "--sharedstatedir=/usr/com",
211 "--with-terminfo-dirs=/usr/share/terminfo",
212 "--with-default-terminfo-dir=/usr/share/terminfo",
213 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
214 "--enable-termcap",
215 ],
216 patches=[
217 "ncurses-5.5.patch",
218 ],
219 useLDFlags=False,
220 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
221 shellQuote(os.path.join(WORKDIR, 'libraries')),
222 shellQuote(os.path.join(WORKDIR, 'libraries')),
223 getVersion(),
224 ),
225 ),
226 ])
227
228 result.extend([
Ronald Oussoren1943f862009-03-30 19:39:14 +0000229 dict(
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000230 name="Sleepycat DB 4.7.25",
231 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
232 checksum='ec2b87e833779681a0c3a814aa71359e',
233 buildDir="build_unix",
234 configure="../dist/configure",
Ronald Oussoren1943f862009-03-30 19:39:14 +0000235 configure_pre=[
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000236 '--includedir=/usr/local/include/db4',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000237 ]
238 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000239 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000240
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000241 return result
242
Thomas Wouters477c8d52006-05-27 19:21:47 +0000243
Thomas Wouters477c8d52006-05-27 19:21:47 +0000244# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000245def pkg_recipes():
246 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
247 result = [
248 dict(
249 name="PythonFramework",
250 long_name="Python Framework",
251 source="/Library/Frameworks/Python.framework",
252 readme="""\
253 This package installs Python.framework, that is the python
254 interpreter and the standard library. This also includes Python
255 wrappers for lots of Mac OS X API's.
256 """,
257 postflight="scripts/postflight.framework",
258 selected='selected',
259 ),
260 dict(
261 name="PythonApplications",
262 long_name="GUI Applications",
263 source="/Applications/Python %(VER)s",
264 readme="""\
265 This package installs IDLE (an interactive Python IDE),
266 Python Launcher and Build Applet (create application bundles
267 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000268
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000269 It also installs a number of examples and demos.
270 """,
271 required=False,
272 selected='selected',
273 ),
274 dict(
275 name="PythonUnixTools",
276 long_name="UNIX command-line tools",
277 source="/usr/local/bin",
278 readme="""\
279 This package installs the unix tools in /usr/local/bin for
280 compatibility with older releases of Python. This package
281 is not necessary to use Python.
282 """,
283 required=False,
284 selected='selected',
285 ),
286 dict(
287 name="PythonDocumentation",
288 long_name="Python Documentation",
289 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
290 source="/pydocs",
291 readme="""\
292 This package installs the python documentation at a location
293 that is useable for pydoc and IDLE. If you have installed Xcode
294 it will also install a link to the documentation in
295 /Developer/Documentation/Python
296 """,
297 postflight="scripts/postflight.documentation",
298 required=False,
299 selected='selected',
300 ),
301 dict(
302 name="PythonProfileChanges",
303 long_name="Shell profile updater",
304 readme="""\
305 This packages updates your shell profile to make sure that
306 the Python tools are found by your shell in preference of
307 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000308
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000309 If you don't install this package you'll have to add
310 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
311 to your PATH by hand.
312 """,
313 postflight="scripts/postflight.patch-profile",
314 topdir="/Library/Frameworks/Python.framework",
315 source="/empty-dir",
316 required=False,
317 selected=unselected_for_python3,
318 ),
319 ]
320
321 if DEPTARGET < '10.4':
322 result.append(
323 dict(
324 name="PythonSystemFixes",
325 long_name="Fix system Python",
326 readme="""\
327 This package updates the system python installation on
328 Mac OS X 10.3 to ensure that you can build new python extensions
329 using that copy of python after installing this version.
330 """,
331 postflight="../Tools/fixapplepython23.py",
332 topdir="/Library/Frameworks/Python.framework",
333 source="/empty-dir",
334 required=False,
335 selected=unselected_for_python3,
336 )
337 )
338 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000339
Thomas Wouters477c8d52006-05-27 19:21:47 +0000340def fatal(msg):
341 """
342 A fatal error, bail out.
343 """
344 sys.stderr.write('FATAL: ')
345 sys.stderr.write(msg)
346 sys.stderr.write('\n')
347 sys.exit(1)
348
349def fileContents(fn):
350 """
351 Return the contents of the named file
352 """
353 return open(fn, 'rb').read()
354
355def runCommand(commandline):
356 """
357 Run a command and raise RuntimeError if it fails. Output is surpressed
358 unless the command fails.
359 """
360 fd = os.popen(commandline, 'r')
361 data = fd.read()
362 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000363 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000364 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000365 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000366
367 if VERBOSE:
368 sys.stdout.write(data); sys.stdout.flush()
369
370def captureCommand(commandline):
371 fd = os.popen(commandline, 'r')
372 data = fd.read()
373 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000374 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000375 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000376 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000377
378 return data
379
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000380def getTclTkVersion(configfile, versionline):
381 """
382 search Tcl or Tk configuration file for version line
383 """
384 try:
385 f = open(configfile, "r")
386 except:
387 fatal("Framework configuration file not found: %s" % configfile)
388
389 for l in f:
390 if l.startswith(versionline):
391 f.close()
392 return l
393
394 fatal("Version variable %s not found in framework configuration file: %s"
395 % (versionline, configfile))
396
Thomas Wouters477c8d52006-05-27 19:21:47 +0000397def checkEnvironment():
398 """
399 Check that we're running on a supported system.
400 """
401
402 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000403 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000404
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000405 if int(platform.release().split('.')[0]) < 8:
406 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000407
408 if not os.path.exists(SDKPATH):
409 fatal("Please install the latest version of Xcode and the %s SDK"%(
410 os.path.basename(SDKPATH[:-4])))
411
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000412 # Because we only support dynamic load of only one major/minor version of
413 # Tcl/Tk, ensure:
414 # 1. there are no user-installed frameworks of Tcl/Tk with version
415 # higher than the Apple-supplied system version
416 # 2. there is a user-installed framework in /Library/Frameworks with the
417 # same version as the system version. This allows users to choose
418 # to install a newer patch level.
419
420 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000421 #fw = dict(lower=framework.lower(),
422 # upper=framework.upper(),
423 # cap=framework.capitalize())
424 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
425 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
426 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000427 libfw = os.path.join('/', fwpth)
428 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000429 #version = "%(upper)s_VERSION" % fw
430 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000431 fatal("Version of %s must match %s" % (libfw, sysfw) )
432 if os.path.exists(usrfw):
433 fatal("Please rename %s to avoid possible dynamic load issues."
434 % usrfw)
435
436 # Remove inherited environment variables which might influence build
437 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
438 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
439 for ev in list(os.environ):
440 for prefix in environ_var_prefixes:
441 if ev.startswith(prefix) :
442 print "INFO: deleting environment variable %s=%s" % (
443 ev, os.environ[ev])
444 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000445
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000446 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
447 print "Setting default PATH: %s"%(os.environ['PATH'])
448
Thomas Wouters477c8d52006-05-27 19:21:47 +0000449
Thomas Wouters89f507f2006-12-13 04:49:30 +0000450def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000451 """
452 Parse arguments and update global settings.
453 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000454 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000455 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000456
457 if args is None:
458 args = sys.argv[1:]
459
460 try:
461 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000462 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
463 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000464 except getopt.error, msg:
465 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000466 sys.exit(1)
467
468 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000469 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000470 sys.exit(1)
471
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000472 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000473 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000474 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000475 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000476 sys.exit(0)
477
478 elif k in ('-d', '--build-dir'):
479 WORKDIR=v
480
481 elif k in ('--third-party',):
482 DEPSRC=v
483
484 elif k in ('--sdk-path',):
485 SDKPATH=v
486
487 elif k in ('--src-dir',):
488 SRCDIR=v
489
Ronald Oussoren1943f862009-03-30 19:39:14 +0000490 elif k in ('--dep-target', ):
491 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000492 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000493
494 elif k in ('--universal-archs', ):
495 if v in UNIVERSALOPTS:
496 UNIVERSALARCHS = v
497 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000498 if deptarget is None:
499 # Select alternate default deployment
500 # target
501 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000502 else:
503 raise NotImplementedError, v
504
Thomas Wouters477c8d52006-05-27 19:21:47 +0000505 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000506 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000507
508 SRCDIR=os.path.abspath(SRCDIR)
509 WORKDIR=os.path.abspath(WORKDIR)
510 SDKPATH=os.path.abspath(SDKPATH)
511 DEPSRC=os.path.abspath(DEPSRC)
512
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000513 CC=target_cc_map[DEPTARGET]
514
Benjamin Peterson623918e2008-12-20 22:50:25 +0000515 print "Settings:"
516 print " * Source directory:", SRCDIR
517 print " * Build directory: ", WORKDIR
518 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000519 print " * Third-party source:", DEPSRC
520 print " * Deployment target:", DEPTARGET
521 print " * Universal architectures:", ARCHLIST
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000522 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000523 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000524
525
526
527
528def extractArchive(builddir, archiveName):
529 """
530 Extract a source archive into 'builddir'. Returns the path of the
531 extracted archive.
532
533 XXX: This function assumes that archives contain a toplevel directory
534 that is has the same name as the basename of the archive. This is
535 save enough for anything we use.
536 """
537 curdir = os.getcwd()
538 try:
539 os.chdir(builddir)
540 if archiveName.endswith('.tar.gz'):
541 retval = os.path.basename(archiveName[:-7])
542 if os.path.exists(retval):
543 shutil.rmtree(retval)
544 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
545
546 elif archiveName.endswith('.tar.bz2'):
547 retval = os.path.basename(archiveName[:-8])
548 if os.path.exists(retval):
549 shutil.rmtree(retval)
550 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
551
552 elif archiveName.endswith('.tar'):
553 retval = os.path.basename(archiveName[:-4])
554 if os.path.exists(retval):
555 shutil.rmtree(retval)
556 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
557
558 elif archiveName.endswith('.zip'):
559 retval = os.path.basename(archiveName[:-4])
560 if os.path.exists(retval):
561 shutil.rmtree(retval)
562 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
563
564 data = fp.read()
565 xit = fp.close()
566 if xit is not None:
567 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000568 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000569
570 return os.path.join(builddir, retval)
571
572 finally:
573 os.chdir(curdir)
574
575KNOWNSIZES = {
576 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
577 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
578}
579
580def downloadURL(url, fname):
581 """
582 Download the contents of the url into the file.
583 """
584 try:
585 size = os.path.getsize(fname)
586 except OSError:
587 pass
588 else:
589 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000590 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000591 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000592 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000593 fpOut = open(fname, 'wb')
594 block = fpIn.read(10240)
595 try:
596 while block:
597 fpOut.write(block)
598 block = fpIn.read(10240)
599 fpIn.close()
600 fpOut.close()
601 except:
602 try:
603 os.unlink(fname)
604 except:
605 pass
606
607def buildRecipe(recipe, basedir, archList):
608 """
609 Build software using a recipe. This function does the
610 'configure;make;make install' dance for C software, with a possibility
611 to customize this process, basically a poor-mans DarwinPorts.
612 """
613 curdir = os.getcwd()
614
615 name = recipe['name']
616 url = recipe['url']
617 configure = recipe.get('configure', './configure')
618 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
619 shellQuote(basedir)))
620
621 archiveName = os.path.split(url)[-1]
622 sourceArchive = os.path.join(DEPSRC, archiveName)
623
624 if not os.path.exists(DEPSRC):
625 os.mkdir(DEPSRC)
626
627
628 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000629 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000630
631 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000632 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000633 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000635 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000636
Benjamin Peterson623918e2008-12-20 22:50:25 +0000637 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000638 buildDir=os.path.join(WORKDIR, '_bld')
639 if not os.path.exists(buildDir):
640 os.mkdir(buildDir)
641
642 workDir = extractArchive(buildDir, sourceArchive)
643 os.chdir(workDir)
644 if 'buildDir' in recipe:
645 os.chdir(recipe['buildDir'])
646
647
648 for fn in recipe.get('patches', ()):
649 if fn.startswith('http://'):
650 # Download the patch before applying it.
651 path = os.path.join(DEPSRC, os.path.basename(fn))
652 downloadURL(fn, path)
653 fn = path
654
655 fn = os.path.join(curdir, fn)
656 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
657 shellQuote(fn),))
658
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000659 if configure is not None:
660 configure_args = [
661 "--prefix=/usr/local",
662 "--enable-static",
663 "--disable-shared",
664 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
665 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000666
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000667 if 'configure_pre' in recipe:
668 args = list(recipe['configure_pre'])
669 if '--disable-static' in args:
670 configure_args.remove('--enable-static')
671 if '--enable-shared' in args:
672 configure_args.remove('--disable-shared')
673 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000674
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000675 if recipe.get('useLDFlags', 1):
676 configure_args.extend([
677 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
678 ' -arch '.join(archList),
679 shellQuote(SDKPATH)[1:-1],
680 shellQuote(basedir)[1:-1],),
681 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000682 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000683 shellQuote(basedir)[1:-1],
684 ' -arch '.join(archList)),
685 ])
686 else:
687 configure_args.extend([
688 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
689 ' -arch '.join(archList),
690 shellQuote(SDKPATH)[1:-1],
691 shellQuote(basedir)[1:-1],),
692 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000693
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000694 if 'configure_post' in recipe:
695 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000696
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000697 configure_args.insert(0, configure)
698 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000699
Benjamin Peterson623918e2008-12-20 22:50:25 +0000700 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000701 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000702
Benjamin Peterson623918e2008-12-20 22:50:25 +0000703 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704 runCommand('{ ' + install + ' ;} 2>&1')
705
Benjamin Peterson623918e2008-12-20 22:50:25 +0000706 print "Done %s"%(name,)
707 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000708
709 os.chdir(curdir)
710
711def buildLibraries():
712 """
713 Build our dependencies into $WORKDIR/libraries/usr/local
714 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000715 print ""
716 print "Building required libraries"
717 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000718 universal = os.path.join(WORKDIR, 'libraries')
719 os.mkdir(universal)
720 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
721 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
722
Ronald Oussoren1943f862009-03-30 19:39:14 +0000723 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000724 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000725
726
727
728def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000729 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000730 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000731 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000732 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000733 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000734 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000735 curDir = os.getcwd()
736 os.chdir(buildDir)
737 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000738 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000739 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000740 if not os.path.exists(docdir):
741 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000742 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743
744
745def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000746 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000747
748 buildDir = os.path.join(WORKDIR, '_bld', 'python')
749 rootDir = os.path.join(WORKDIR, '_root')
750
751 if os.path.exists(buildDir):
752 shutil.rmtree(buildDir)
753 if os.path.exists(rootDir):
754 shutil.rmtree(rootDir)
755 os.mkdir(buildDir)
756 os.mkdir(rootDir)
757 os.mkdir(os.path.join(rootDir, 'empty-dir'))
758 curdir = os.getcwd()
759 os.chdir(buildDir)
760
761 # Not sure if this is still needed, the original build script
762 # claims that parts of the install assume python.exe exists.
763 os.symlink('python', os.path.join(buildDir, 'python.exe'))
764
765 # Extract the version from the configure file, needed to calculate
766 # several paths.
767 version = getVersion()
768
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000769 # Since the extra libs are not in their installed framework location
770 # during the build, augment the library path so that the interpreter
771 # will find them during its extension import sanity checks.
772 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
773 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000774 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000775 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000776 "--with-universal-archs=%s "
777 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000778 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
779 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
780 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
781 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000782 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000783 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000784 shellQuote(WORKDIR)[1:-1]))
785
Benjamin Peterson623918e2008-12-20 22:50:25 +0000786 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000787 runCommand("make")
788
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000789 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000790 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000791 shellQuote(rootDir)))
792
Benjamin Peterson623918e2008-12-20 22:50:25 +0000793 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000794 runCommand("make frameworkinstallextras DESTDIR=%s"%(
795 shellQuote(rootDir)))
796
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000797 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000798 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000799 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
800 runCommand("mv %s/* %s"%(
801 shellQuote(os.path.join(
802 WORKDIR, 'libraries', 'Library', 'Frameworks',
803 'Python.framework', 'Versions', getVersion(),
804 'lib')),
805 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
806 'Python.framework', 'Versions', getVersion(),
807 'lib'))))
808
Benjamin Peterson623918e2008-12-20 22:50:25 +0000809 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000810 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000811 gid = grp.getgrnam('admin').gr_gid
812
Thomas Wouters477c8d52006-05-27 19:21:47 +0000813 for dirpath, dirnames, filenames in os.walk(frmDir):
814 for dn in dirnames:
815 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000816 os.chown(os.path.join(dirpath, dn), -1, gid)
817
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818
819 for fn in filenames:
820 if os.path.islink(fn):
821 continue
822
823 # "chmod g+w $fn"
824 p = os.path.join(dirpath, fn)
825 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000826 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
827 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000828
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000829 LDVERSION=None
830 VERSION=None
831 ABIFLAGS=None
832
833 with open(os.path.join(buildDir, 'Makefile')) as fp:
834 for ln in fp:
835 if ln.startswith('VERSION='):
836 VERSION=ln.split()[1]
837 if ln.startswith('ABIFLAGS='):
838 ABIFLAGS=ln.split()[1]
839
840 if ln.startswith('LDVERSION='):
841 LDVERSION=ln.split()[1]
842
843 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
844 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
845
Thomas Wouters477c8d52006-05-27 19:21:47 +0000846 # We added some directories to the search path during the configure
847 # phase. Remove those because those directories won't be there on
848 # the end-users system.
849 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
850 'Versions', version, 'lib', 'python%s'%(version,),
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000851 'config-' + LDVERSION, 'Makefile')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000852 fp = open(path, 'r')
853 data = fp.read()
854 fp.close()
855
856 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
857 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
858 fp = open(path, 'w')
859 fp.write(data)
860 fp.close()
861
862 # Add symlinks in /usr/local/bin, using relative links
863 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
864 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
865 'Python.framework', 'Versions', version, 'bin')
866 if os.path.exists(usr_local_bin):
867 shutil.rmtree(usr_local_bin)
868 os.makedirs(usr_local_bin)
869 for fn in os.listdir(
870 os.path.join(frmDir, 'Versions', version, 'bin')):
871 os.symlink(os.path.join(to_framework, fn),
872 os.path.join(usr_local_bin, fn))
873
874 os.chdir(curdir)
875
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000876 if PYTHON_3:
877 # Remove the 'Current' link, that way we don't accidently mess
878 # with an already installed version of python 2
879 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
880 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000881
882def patchFile(inPath, outPath):
883 data = fileContents(inPath)
884 data = data.replace('$FULL_VERSION', getFullVersion())
885 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000886 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000887 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000888 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000889
890 # This one is not handy as a template variable
891 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000892 fp = open(outPath, 'wb')
893 fp.write(data)
894 fp.close()
895
896def patchScript(inPath, outPath):
897 data = fileContents(inPath)
898 data = data.replace('@PYVER@', getVersion())
899 fp = open(outPath, 'wb')
900 fp.write(data)
901 fp.close()
902 os.chmod(outPath, 0755)
903
904
905
906def packageFromRecipe(targetDir, recipe):
907 curdir = os.getcwd()
908 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000909 # The major version (such as 2.5) is included in the package name
910 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000911 # common.
912 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000913 srcdir = recipe.get('source')
914 pkgroot = recipe.get('topdir', srcdir)
915 postflight = recipe.get('postflight')
916 readme = textwrap.dedent(recipe['readme'])
917 isRequired = recipe.get('required', True)
918
Benjamin Peterson623918e2008-12-20 22:50:25 +0000919 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920
921 # Substitute some variables
922 textvars = dict(
923 VER=getVersion(),
924 FULLVER=getFullVersion(),
925 )
926 readme = readme % textvars
927
928 if pkgroot is not None:
929 pkgroot = pkgroot % textvars
930 else:
931 pkgroot = '/'
932
933 if srcdir is not None:
934 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
935 srcdir = srcdir % textvars
936
937 if postflight is not None:
938 postflight = os.path.abspath(postflight)
939
940 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
941 os.makedirs(packageContents)
942
943 if srcdir is not None:
944 os.chdir(srcdir)
945 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
946 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
947 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
948
949 fn = os.path.join(packageContents, 'PkgInfo')
950 fp = open(fn, 'w')
951 fp.write('pmkrpkg1')
952 fp.close()
953
954 rsrcDir = os.path.join(packageContents, "Resources")
955 os.mkdir(rsrcDir)
956 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
957 fp.write(readme)
958 fp.close()
959
960 if postflight is not None:
961 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
962
963 vers = getFullVersion()
964 major, minor = map(int, getVersion().split('.', 2))
965 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000966 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
967 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
968 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000969 CFBundleShortVersionString=vers,
970 IFMajorVersion=major,
971 IFMinorVersion=minor,
972 IFPkgFormatVersion=0.10000000149011612,
973 IFPkgFlagAllowBackRev=False,
974 IFPkgFlagAuthorizationAction="RootAuthorization",
975 IFPkgFlagDefaultLocation=pkgroot,
976 IFPkgFlagFollowLinks=True,
977 IFPkgFlagInstallFat=True,
978 IFPkgFlagIsRequired=isRequired,
979 IFPkgFlagOverwritePermissions=False,
980 IFPkgFlagRelocatable=False,
981 IFPkgFlagRestartAction="NoRestart",
982 IFPkgFlagRootVolumeOnly=True,
983 IFPkgFlagUpdateInstalledLangauges=False,
984 )
985 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
986
987 pl = Plist(
988 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +0000989 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000990 IFPkgDescriptionVersion=vers,
991 )
992 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
993
994 finally:
995 os.chdir(curdir)
996
997
998def makeMpkgPlist(path):
999
1000 vers = getFullVersion()
1001 major, minor = map(int, getVersion().split('.', 2))
1002
1003 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001004 CFBundleGetInfoString="Python %s"%(vers,),
1005 CFBundleIdentifier='org.python.Python',
1006 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001007 CFBundleShortVersionString=vers,
1008 IFMajorVersion=major,
1009 IFMinorVersion=minor,
1010 IFPkgFlagComponentDirectory="Contents/Packages",
1011 IFPkgFlagPackageList=[
1012 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001013 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001014 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001015 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001016 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001017 ],
1018 IFPkgFormatVersion=0.10000000149011612,
1019 IFPkgFlagBackgroundScaling="proportional",
1020 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001021 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001022 )
1023
1024 writePlist(pl, path)
1025
1026
1027def buildInstaller():
1028
1029 # Zap all compiled files
1030 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1031 for fn in filenames:
1032 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1033 os.unlink(os.path.join(dirpath, fn))
1034
1035 outdir = os.path.join(WORKDIR, 'installer')
1036 if os.path.exists(outdir):
1037 shutil.rmtree(outdir)
1038 os.mkdir(outdir)
1039
Ronald Oussoren1943f862009-03-30 19:39:14 +00001040 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001041 pkgcontents = os.path.join(pkgroot, 'Packages')
1042 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001043 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001044 packageFromRecipe(pkgcontents, recipe)
1045
1046 rsrcDir = os.path.join(pkgroot, 'Resources')
1047
1048 fn = os.path.join(pkgroot, 'PkgInfo')
1049 fp = open(fn, 'w')
1050 fp.write('pmkrpkg1')
1051 fp.close()
1052
1053 os.mkdir(rsrcDir)
1054
1055 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1056 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001057 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001058 IFPkgDescriptionVersion=getVersion(),
1059 )
1060
1061 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1062 for fn in os.listdir('resources'):
1063 if fn == '.svn': continue
1064 if fn.endswith('.jpg'):
1065 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1066 else:
1067 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1068
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001069 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001070
1071
1072def installSize(clear=False, _saved=[]):
1073 if clear:
1074 del _saved[:]
1075 if not _saved:
1076 data = captureCommand("du -ks %s"%(
1077 shellQuote(os.path.join(WORKDIR, '_root'))))
1078 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1079 return _saved[0]
1080
1081
1082def buildDMG():
1083 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001084 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001085 """
1086 outdir = os.path.join(WORKDIR, 'diskimage')
1087 if os.path.exists(outdir):
1088 shutil.rmtree(outdir)
1089
1090 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001091 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001092 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001093 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001094 imagepath = imagepath + '.dmg'
1095
1096 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001097 volname='Python %s'%(getFullVersion())
1098 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1099 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001100 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001101 shellQuote(imagepath + ".tmp.dmg" )))
1102
1103
1104 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1105 os.mkdir(os.path.join(WORKDIR, "mnt"))
1106 runCommand("hdiutil attach %s -mountroot %s"%(
1107 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1108
1109 # Custom icon for the DMG, shown when the DMG is mounted.
1110 shutil.copy("../Icons/Disk Image.icns",
1111 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1112 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1113 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1114
1115 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1116
1117 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1118 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1119 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1120 setIcon(imagepath, "../Icons/Disk Image.icns")
1121
1122 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001123
1124 return imagepath
1125
1126
1127def setIcon(filePath, icnsPath):
1128 """
1129 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001130 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001131
Ronald Oussoren70050672010-04-30 15:00:26 +00001132 dirPath = os.path.normpath(os.path.dirname(__file__))
1133 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001134 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1135 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1136 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001137 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1138 if not os.path.exists(appPath):
1139 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001140 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1141 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001142
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001143 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1144 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001145
1146def main():
1147 # First parse options and check if we can perform our work
1148 parseOptions()
1149 checkEnvironment()
1150
Ronald Oussoren1943f862009-03-30 19:39:14 +00001151 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001152 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001153
1154 if os.path.exists(WORKDIR):
1155 shutil.rmtree(WORKDIR)
1156 os.mkdir(WORKDIR)
1157
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001158 os.environ['LC_ALL'] = 'C'
1159
Thomas Wouters477c8d52006-05-27 19:21:47 +00001160 # Then build third-party libraries such as sleepycat DB4.
1161 buildLibraries()
1162
1163 # Now build python itself
1164 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001165
1166 # And then build the documentation
1167 # Remove the Deployment Target from the shell
1168 # environment, it's no longer needed and
1169 # an unexpected build target can cause problems
1170 # when Sphinx and its dependencies need to
1171 # be (re-)installed.
1172 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001173 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001174
1175
1176 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001177 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001178 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001179 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001180
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001181 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001182 getVersion(),))
1183 os.chmod(folder, 0755)
1184 setIcon(folder, "../Icons/Python Folder.icns")
1185
1186 # Create the installer
1187 buildInstaller()
1188
1189 # And copy the readme into the directory containing the installer
1190 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1191
1192 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001193 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001194
1195 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001196 print >> fp, "# BUILD INFO"
1197 print >> fp, "# Date:", time.ctime()
1198 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001199 fp.close()
1200
Thomas Wouters477c8d52006-05-27 19:21:47 +00001201 # And copy it to a DMG
1202 buildDMG()
1203
Thomas Wouters477c8d52006-05-27 19:21:47 +00001204if __name__ == "__main__":
1205 main()