blob: f120adaab52326067159f3f8787954e7228578ec [file] [log] [blame]
Benjamin Peterson9cf41d02010-03-11 22:33:25 +00001#!/usr/bin/env python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00005work. 64-bit or four-way universal builds require at least OS X 10.5 and
6the 10.5 SDK.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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"""
13import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Ronald Oussoren74d3eef2006-10-10 07:55:06 +000014import grp
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000015
Ronald Oussoren158ad592006-11-07 16:00:34 +000016INCLUDE_TIMESTAMP = 1
17VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000018
19from plistlib import Plist
20
21import MacOS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000032 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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]
43
44def getVersion():
45 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
46
47def getFullVersion():
48 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
49 for ln in open(fn):
50 if 'PY_VERSION' in ln:
51 return ln.split()[-1][1:-1]
52
53 raise RuntimeError, "Cannot find full version??"
54
Ronald Oussorenaa560962006-11-07 15:53:38 +000055# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +000056WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000057
Ronald Oussorenaa560962006-11-07 15:53:38 +000058# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000059# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +000060DEPSRC = os.path.join(WORKDIR, 'third-party')
61DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000062
63# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +000064
65### There are some issues with the SDK selection below here,
66### The resulting binary doesn't work on all platforms that
67### it should. Always default to the 10.4u SDK until that
68### isue is resolved.
69###
70##if int(os.uname()[2].split('.')[0]) == 8:
71## # Explicitly use the 10.4u (universal) SDK when
72## # building on 10.4, the system headers are not
73## # useable for a universal build
74## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
75##else:
76## SDKPATH = "/"
77
78SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000079
Ronald Oussoren508282e2009-03-30 19:34:51 +000080universal_opts_map = { '32-bit': ('i386', 'ppc',),
81 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +000082 'intel': ('i386', 'x86_64'),
83 '3-way': ('ppc', 'i386', 'x86_64'),
84 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
85default_target_map = {
86 '64-bit': '10.5',
87 '3-way': '10.5',
88 'intel': '10.5',
89 'all': '10.5',
90}
Ronald Oussoren508282e2009-03-30 19:34:51 +000091
92UNIVERSALOPTS = tuple(universal_opts_map.keys())
93
94UNIVERSALARCHS = '32-bit'
95
96ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +000097
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000098# Source directory (asume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +000099SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000100 os.path.dirname(
101 os.path.dirname(
102 os.path.abspath(__file__
103 ))))
104
Ronald Oussoren508282e2009-03-30 19:34:51 +0000105# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
106DEPTARGET = '10.3'
107
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000108target_cc_map = {
109 '10.3': 'gcc-4.0',
110 '10.4': 'gcc-4.0',
111 '10.5': 'gcc-4.0',
112 '10.6': 'gcc-4.2',
113}
114
115CC = target_cc_map[DEPTARGET]
116
Ronald Oussoren158ad592006-11-07 16:00:34 +0000117USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000118 Usage: build_python [options]
119
120 Options:
121 -? or -h: Show this message
122 -b DIR
123 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
124 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
125 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
126 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren508282e2009-03-30 19:34:51 +0000127 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
128 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000129""")% globals()
130
131
132# Instructions for building libraries that are necessary for building a
133# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000134# [The recipes are defined here for convenience but instantiated later after
135# command line options have been processed.]
136def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000137 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000138
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000139 if DEPTARGET < '10.5':
140 result.extend([
141 dict(
142 name="Bzip2 1.0.5",
143 url="http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz",
144 checksum='3c15a0c8d1d3ee1c46a1634d00617b1a',
145 configure=None,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000146 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
147 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000148 shellQuote(os.path.join(WORKDIR, 'libraries')),
149 ' -arch '.join(ARCHLIST),
150 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000151 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000152 ),
153 dict(
154 name="ZLib 1.2.3",
155 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
156 checksum='debc62758716a169df9f62e6ab2bc634',
157 configure=None,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000158 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
159 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000160 shellQuote(os.path.join(WORKDIR, 'libraries')),
161 ' -arch '.join(ARCHLIST),
162 SDKPATH,
163 ),
164 ),
165 dict(
166 # Note that GNU readline is GPL'd software
167 name="GNU Readline 5.1.4",
168 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
169 checksum='7ee5a692db88b30ca48927a13fd60e46',
170 patchlevel='0',
171 patches=[
172 # The readline maintainers don't do actual micro releases, but
173 # just ship a set of patches.
174 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
175 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
176 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
177 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
178 ]
179 ),
180 dict(
181 name="SQLite 3.6.11",
182 url="http://www.sqlite.org/sqlite-3.6.11.tar.gz",
183 checksum='7ebb099696ab76cc6ff65dd496d17858',
184 configure_pre=[
185 '--enable-threadsafe',
186 '--enable-tempstore',
187 '--enable-shared=no',
188 '--enable-static=yes',
189 '--disable-tcl',
190 ]
191 ),
192 dict(
193 name="NCurses 5.5",
194 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
195 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
196 configure_pre=[
197 "--without-cxx",
198 "--without-ada",
199 "--without-progs",
200 "--without-curses-h",
201 "--enable-shared",
202 "--with-shared",
203 "--datadir=/usr/share",
204 "--sysconfdir=/etc",
205 "--sharedstatedir=/usr/com",
206 "--with-terminfo-dirs=/usr/share/terminfo",
207 "--with-default-terminfo-dir=/usr/share/terminfo",
208 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
209 "--enable-termcap",
210 ],
211 patches=[
212 "ncurses-5.5.patch",
213 ],
214 useLDFlags=False,
215 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
216 shellQuote(os.path.join(WORKDIR, 'libraries')),
217 shellQuote(os.path.join(WORKDIR, 'libraries')),
218 getVersion(),
219 ),
220 ),
221 ])
222
223 result.extend([
Ronald Oussoren508282e2009-03-30 19:34:51 +0000224 dict(
225 name="Sleepycat DB 4.7.25",
226 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
227 checksum='ec2b87e833779681a0c3a814aa71359e',
228 buildDir="build_unix",
229 configure="../dist/configure",
230 configure_pre=[
231 '--includedir=/usr/local/include/db4',
232 ]
233 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000234 ])
235
236 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000237
238
239# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000240def pkg_recipes():
241 result = [
242 dict(
243 name="PythonFramework",
244 long_name="Python Framework",
245 source="/Library/Frameworks/Python.framework",
246 readme="""\
247 This package installs Python.framework, that is the python
248 interpreter and the standard library. This also includes Python
249 wrappers for lots of Mac OS X API's.
250 """,
251 postflight="scripts/postflight.framework",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000252 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000253 ),
254 dict(
255 name="PythonApplications",
256 long_name="GUI Applications",
257 source="/Applications/Python %(VER)s",
258 readme="""\
259 This package installs IDLE (an interactive Python IDE),
260 Python Launcher and Build Applet (create application bundles
261 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000262
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000263 It also installs a number of examples and demos.
264 """,
265 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000266 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000267 ),
268 dict(
269 name="PythonUnixTools",
270 long_name="UNIX command-line tools",
271 source="/usr/local/bin",
272 readme="""\
273 This package installs the unix tools in /usr/local/bin for
274 compatibility with older releases of Python. This package
275 is not necessary to use Python.
276 """,
277 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000278 selected='unselected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000279 ),
280 dict(
281 name="PythonDocumentation",
282 long_name="Python Documentation",
283 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
284 source="/pydocs",
285 readme="""\
286 This package installs the python documentation at a location
287 that is useable for pydoc and IDLE. If you have installed Xcode
288 it will also install a link to the documentation in
289 /Developer/Documentation/Python
290 """,
291 postflight="scripts/postflight.documentation",
292 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000293 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000294 ),
295 dict(
296 name="PythonProfileChanges",
297 long_name="Shell profile updater",
298 readme="""\
299 This packages updates your shell profile to make sure that
300 the Python tools are found by your shell in preference of
301 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000302
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000303 If you don't install this package you'll have to add
304 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
305 to your PATH by hand.
306 """,
307 postflight="scripts/postflight.patch-profile",
308 topdir="/Library/Frameworks/Python.framework",
309 source="/empty-dir",
310 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000311 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000312 ),
313 ]
314
315 if DEPTARGET < '10.4':
316 result.append(
317 dict(
318 name="PythonSystemFixes",
319 long_name="Fix system Python",
320 readme="""\
321 This package updates the system python installation on
322 Mac OS X 10.3 to ensure that you can build new python extensions
323 using that copy of python after installing this version.
324 """,
325 postflight="../Tools/fixapplepython23.py",
326 topdir="/Library/Frameworks/Python.framework",
327 source="/empty-dir",
328 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000329 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000330 )
331 )
332 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000333
334def fatal(msg):
335 """
336 A fatal error, bail out.
337 """
338 sys.stderr.write('FATAL: ')
339 sys.stderr.write(msg)
340 sys.stderr.write('\n')
341 sys.exit(1)
342
343def fileContents(fn):
344 """
345 Return the contents of the named file
346 """
347 return open(fn, 'rb').read()
348
349def runCommand(commandline):
350 """
351 Run a command and raise RuntimeError if it fails. Output is surpressed
352 unless the command fails.
353 """
354 fd = os.popen(commandline, 'r')
355 data = fd.read()
356 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000357 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000358 sys.stdout.write(data)
359 raise RuntimeError, "command failed: %s"%(commandline,)
360
361 if VERBOSE:
362 sys.stdout.write(data); sys.stdout.flush()
363
364def captureCommand(commandline):
365 fd = os.popen(commandline, 'r')
366 data = fd.read()
367 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000368 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000369 sys.stdout.write(data)
370 raise RuntimeError, "command failed: %s"%(commandline,)
371
372 return data
373
Ronald Oussoren287128a2010-04-18 14:01:05 +0000374def getTclTkVersion(configfile, versionline):
375 """
376 search Tcl or Tk configuration file for version line
377 """
378 try:
379 f = open(configfile, "r")
380 except:
381 fatal("Framework configuration file not found: %s" % configfile)
382
383 for l in f:
384 if l.startswith(versionline):
385 f.close()
386 return l
387
388 fatal("Version variable %s not found in framework configuration file: %s"
389 % (versionline, configfile))
390
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000391def checkEnvironment():
392 """
393 Check that we're running on a supported system.
394 """
395
396 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000397 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000398
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000399 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000400 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000401
402 if not os.path.exists(SDKPATH):
403 fatal("Please install the latest version of Xcode and the %s SDK"%(
404 os.path.basename(SDKPATH[:-4])))
405
Ronald Oussoren287128a2010-04-18 14:01:05 +0000406 # Because we only support dynamic load of only one major/minor version of
407 # Tcl/Tk, ensure:
408 # 1. there are no user-installed frameworks of Tcl/Tk with version
409 # higher than the Apple-supplied system version
410 # 2. there is a user-installed framework in /Library/Frameworks with the
411 # same version as the system version. This allows users to choose
412 # to install a newer patch level.
413
414 for framework in ['Tcl', 'Tk']:
415 fw = dict(lower=framework.lower(),
416 upper=framework.upper(),
417 cap=framework.capitalize())
418 fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
419 sysfw = os.path.join('/System', fwpth)
420 libfw = os.path.join('/', fwpth)
421 usrfw = os.path.join(os.getenv('HOME'), fwpth)
422 version = "%(upper)s_VERSION" % fw
423 if getTclTkVersion(libfw, version) != getTclTkVersion(sysfw, version):
424 fatal("Version of %s must match %s" % (libfw, sysfw) )
425 if os.path.exists(usrfw):
426 fatal("Please rename %s to avoid possible dynamic load issues."
427 % usrfw)
428
429 # Remove inherited environment variables which might influence build
430 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
431 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
432 for ev in list(os.environ):
433 for prefix in environ_var_prefixes:
434 if ev.startswith(prefix) :
435 print "INFO: deleting environment variable %s=%s" % (
436 ev, os.environ[ev])
437 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000438
439
Ronald Oussoren158ad592006-11-07 16:00:34 +0000440def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000441 """
442 Parse arguments and update global settings.
443 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000444 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000445 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000446
447 if args is None:
448 args = sys.argv[1:]
449
450 try:
451 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000452 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
453 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000454 except getopt.error, msg:
455 print msg
456 sys.exit(1)
457
458 if args:
459 print "Additional arguments"
460 sys.exit(1)
461
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000462 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000463 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000464 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000465 print USAGE
466 sys.exit(0)
467
468 elif k in ('-d', '--build-dir'):
469 WORKDIR=v
470
471 elif k in ('--third-party',):
472 DEPSRC=v
473
474 elif k in ('--sdk-path',):
475 SDKPATH=v
476
477 elif k in ('--src-dir',):
478 SRCDIR=v
479
Ronald Oussoren508282e2009-03-30 19:34:51 +0000480 elif k in ('--dep-target', ):
481 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000482 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000483
484 elif k in ('--universal-archs', ):
485 if v in UNIVERSALOPTS:
486 UNIVERSALARCHS = v
487 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000488 if deptarget is None:
489 # Select alternate default deployment
490 # target
491 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000492 else:
493 raise NotImplementedError, v
494
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000495 else:
496 raise NotImplementedError, k
497
498 SRCDIR=os.path.abspath(SRCDIR)
499 WORKDIR=os.path.abspath(WORKDIR)
500 SDKPATH=os.path.abspath(SDKPATH)
501 DEPSRC=os.path.abspath(DEPSRC)
502
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000503 CC=target_cc_map[DEPTARGET]
504
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000505 print "Settings:"
506 print " * Source directory:", SRCDIR
507 print " * Build directory: ", WORKDIR
508 print " * SDK location: ", SDKPATH
Ronald Oussoren508282e2009-03-30 19:34:51 +0000509 print " * Third-party source:", DEPSRC
510 print " * Deployment target:", DEPTARGET
511 print " * Universal architectures:", ARCHLIST
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000512 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000513 print ""
514
515
516
517
518def extractArchive(builddir, archiveName):
519 """
520 Extract a source archive into 'builddir'. Returns the path of the
521 extracted archive.
522
523 XXX: This function assumes that archives contain a toplevel directory
524 that is has the same name as the basename of the archive. This is
525 save enough for anything we use.
526 """
527 curdir = os.getcwd()
528 try:
529 os.chdir(builddir)
530 if archiveName.endswith('.tar.gz'):
531 retval = os.path.basename(archiveName[:-7])
532 if os.path.exists(retval):
533 shutil.rmtree(retval)
534 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
535
536 elif archiveName.endswith('.tar.bz2'):
537 retval = os.path.basename(archiveName[:-8])
538 if os.path.exists(retval):
539 shutil.rmtree(retval)
540 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
541
542 elif archiveName.endswith('.tar'):
543 retval = os.path.basename(archiveName[:-4])
544 if os.path.exists(retval):
545 shutil.rmtree(retval)
546 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
547
548 elif archiveName.endswith('.zip'):
549 retval = os.path.basename(archiveName[:-4])
550 if os.path.exists(retval):
551 shutil.rmtree(retval)
552 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
553
554 data = fp.read()
555 xit = fp.close()
556 if xit is not None:
557 sys.stdout.write(data)
558 raise RuntimeError, "Cannot extract %s"%(archiveName,)
559
560 return os.path.join(builddir, retval)
561
562 finally:
563 os.chdir(curdir)
564
565KNOWNSIZES = {
566 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
567 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
568}
569
570def downloadURL(url, fname):
571 """
572 Download the contents of the url into the file.
573 """
574 try:
575 size = os.path.getsize(fname)
576 except OSError:
577 pass
578 else:
579 if KNOWNSIZES.get(url) == size:
580 print "Using existing file for", url
581 return
582 fpIn = urllib2.urlopen(url)
583 fpOut = open(fname, 'wb')
584 block = fpIn.read(10240)
585 try:
586 while block:
587 fpOut.write(block)
588 block = fpIn.read(10240)
589 fpIn.close()
590 fpOut.close()
591 except:
592 try:
593 os.unlink(fname)
594 except:
595 pass
596
597def buildRecipe(recipe, basedir, archList):
598 """
599 Build software using a recipe. This function does the
600 'configure;make;make install' dance for C software, with a possibility
601 to customize this process, basically a poor-mans DarwinPorts.
602 """
603 curdir = os.getcwd()
604
605 name = recipe['name']
606 url = recipe['url']
607 configure = recipe.get('configure', './configure')
608 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
609 shellQuote(basedir)))
610
611 archiveName = os.path.split(url)[-1]
612 sourceArchive = os.path.join(DEPSRC, archiveName)
613
614 if not os.path.exists(DEPSRC):
615 os.mkdir(DEPSRC)
616
617
618 if os.path.exists(sourceArchive):
619 print "Using local copy of %s"%(name,)
620
621 else:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000622 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000623 print "Downloading %s"%(name,)
624 downloadURL(url, sourceArchive)
625 print "Archive for %s stored as %s"%(name, sourceArchive)
626
627 print "Extracting archive for %s"%(name,)
628 buildDir=os.path.join(WORKDIR, '_bld')
629 if not os.path.exists(buildDir):
630 os.mkdir(buildDir)
631
632 workDir = extractArchive(buildDir, sourceArchive)
633 os.chdir(workDir)
634 if 'buildDir' in recipe:
635 os.chdir(recipe['buildDir'])
636
637
638 for fn in recipe.get('patches', ()):
639 if fn.startswith('http://'):
640 # Download the patch before applying it.
641 path = os.path.join(DEPSRC, os.path.basename(fn))
642 downloadURL(fn, path)
643 fn = path
644
645 fn = os.path.join(curdir, fn)
646 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
647 shellQuote(fn),))
648
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000649 if configure is not None:
650 configure_args = [
651 "--prefix=/usr/local",
652 "--enable-static",
653 "--disable-shared",
654 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
655 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000656
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000657 if 'configure_pre' in recipe:
658 args = list(recipe['configure_pre'])
659 if '--disable-static' in args:
660 configure_args.remove('--enable-static')
661 if '--enable-shared' in args:
662 configure_args.remove('--disable-shared')
663 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000664
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000665 if recipe.get('useLDFlags', 1):
666 configure_args.extend([
667 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
668 ' -arch '.join(archList),
669 shellQuote(SDKPATH)[1:-1],
670 shellQuote(basedir)[1:-1],),
671 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000672 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000673 shellQuote(basedir)[1:-1],
674 ' -arch '.join(archList)),
675 ])
676 else:
677 configure_args.extend([
678 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
679 ' -arch '.join(archList),
680 shellQuote(SDKPATH)[1:-1],
681 shellQuote(basedir)[1:-1],),
682 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000683
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000684 if 'configure_post' in recipe:
685 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000686
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000687 configure_args.insert(0, configure)
688 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000689
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000690 print "Running configure for %s"%(name,)
691 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000692
693 print "Running install for %s"%(name,)
694 runCommand('{ ' + install + ' ;} 2>&1')
695
696 print "Done %s"%(name,)
697 print ""
698
699 os.chdir(curdir)
700
701def buildLibraries():
702 """
703 Build our dependencies into $WORKDIR/libraries/usr/local
704 """
705 print ""
706 print "Building required libraries"
707 print ""
708 universal = os.path.join(WORKDIR, 'libraries')
709 os.mkdir(universal)
710 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
711 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
712
Ronald Oussoren508282e2009-03-30 19:34:51 +0000713 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000714 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000715
716
717
718def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000719 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000720 # inside the framwork. pydoc and IDLE will pick it up there.
721 print "Install python documentation"
722 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000723 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000724 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000725 curDir = os.getcwd()
726 os.chdir(buildDir)
727 runCommand('make update')
728 runCommand('make html')
729 os.chdir(curDir)
730 if not os.path.exists(docdir):
731 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000732 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000733
734
735def buildPython():
Ronald Oussoren508282e2009-03-30 19:34:51 +0000736 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000737
738 buildDir = os.path.join(WORKDIR, '_bld', 'python')
739 rootDir = os.path.join(WORKDIR, '_root')
740
741 if os.path.exists(buildDir):
742 shutil.rmtree(buildDir)
743 if os.path.exists(rootDir):
744 shutil.rmtree(rootDir)
745 os.mkdir(buildDir)
746 os.mkdir(rootDir)
747 os.mkdir(os.path.join(rootDir, 'empty-dir'))
748 curdir = os.getcwd()
749 os.chdir(buildDir)
750
751 # Not sure if this is still needed, the original build script
752 # claims that parts of the install assume python.exe exists.
753 os.symlink('python', os.path.join(buildDir, 'python.exe'))
754
755 # Extract the version from the configure file, needed to calculate
756 # several paths.
757 version = getVersion()
758
Ronald Oussoren008af852009-03-30 20:02:08 +0000759 # Since the extra libs are not in their installed framework location
760 # during the build, augment the library path so that the interpreter
761 # will find them during its extension import sanity checks.
762 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
763 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000764 print "Running configure..."
Ronald Oussoren508282e2009-03-30 19:34:51 +0000765 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
766 "--with-universal-archs=%s "
767 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
768 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
769 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
770 UNIVERSALARCHS,
771 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000772 shellQuote(WORKDIR)[1:-1]))
773
774 print "Running make"
775 runCommand("make")
776
Ronald Oussorenaa560962006-11-07 15:53:38 +0000777 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000778 runCommand("make frameworkinstall DESTDIR=%s"%(
779 shellQuote(rootDir)))
780
Ronald Oussorenaa560962006-11-07 15:53:38 +0000781 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000782 runCommand("make frameworkinstallextras DESTDIR=%s"%(
783 shellQuote(rootDir)))
784
Ronald Oussoren008af852009-03-30 20:02:08 +0000785 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000786 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000787 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
788 runCommand("mv %s/* %s"%(
789 shellQuote(os.path.join(
790 WORKDIR, 'libraries', 'Library', 'Frameworks',
791 'Python.framework', 'Versions', getVersion(),
792 'lib')),
793 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
794 'Python.framework', 'Versions', getVersion(),
795 'lib'))))
796
797 print "Fix file modes"
798 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000799 gid = grp.getgrnam('admin').gr_gid
800
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000801 for dirpath, dirnames, filenames in os.walk(frmDir):
802 for dn in dirnames:
803 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000804 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000805
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000806
807 for fn in filenames:
808 if os.path.islink(fn):
809 continue
810
811 # "chmod g+w $fn"
812 p = os.path.join(dirpath, fn)
813 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000814 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
815 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000816
817 # We added some directories to the search path during the configure
818 # phase. Remove those because those directories won't be there on
819 # the end-users system.
820 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
821 'Versions', version, 'lib', 'python%s'%(version,),
822 'config', 'Makefile')
823 fp = open(path, 'r')
824 data = fp.read()
825 fp.close()
826
827 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
828 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
829 fp = open(path, 'w')
830 fp.write(data)
831 fp.close()
832
833 # Add symlinks in /usr/local/bin, using relative links
834 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
835 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
836 'Python.framework', 'Versions', version, 'bin')
837 if os.path.exists(usr_local_bin):
838 shutil.rmtree(usr_local_bin)
839 os.makedirs(usr_local_bin)
840 for fn in os.listdir(
841 os.path.join(frmDir, 'Versions', version, 'bin')):
842 os.symlink(os.path.join(to_framework, fn),
843 os.path.join(usr_local_bin, fn))
844
845 os.chdir(curdir)
846
847
848
849def patchFile(inPath, outPath):
850 data = fileContents(inPath)
851 data = data.replace('$FULL_VERSION', getFullVersion())
852 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +0000853 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000854 data = data.replace('$ARCHITECTURES', "i386, ppc")
855 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000856
857 # This one is not handy as a template variable
858 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000859 fp = open(outPath, 'wb')
860 fp.write(data)
861 fp.close()
862
863def patchScript(inPath, outPath):
864 data = fileContents(inPath)
865 data = data.replace('@PYVER@', getVersion())
866 fp = open(outPath, 'wb')
867 fp.write(data)
868 fp.close()
869 os.chmod(outPath, 0755)
870
871
872
873def packageFromRecipe(targetDir, recipe):
874 curdir = os.getcwd()
875 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000876 # The major version (such as 2.5) is included in the package name
877 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000878 # common.
879 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000880 srcdir = recipe.get('source')
881 pkgroot = recipe.get('topdir', srcdir)
882 postflight = recipe.get('postflight')
883 readme = textwrap.dedent(recipe['readme'])
884 isRequired = recipe.get('required', True)
885
886 print "- building package %s"%(pkgname,)
887
888 # Substitute some variables
889 textvars = dict(
890 VER=getVersion(),
891 FULLVER=getFullVersion(),
892 )
893 readme = readme % textvars
894
895 if pkgroot is not None:
896 pkgroot = pkgroot % textvars
897 else:
898 pkgroot = '/'
899
900 if srcdir is not None:
901 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
902 srcdir = srcdir % textvars
903
904 if postflight is not None:
905 postflight = os.path.abspath(postflight)
906
907 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
908 os.makedirs(packageContents)
909
910 if srcdir is not None:
911 os.chdir(srcdir)
912 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
913 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
914 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
915
916 fn = os.path.join(packageContents, 'PkgInfo')
917 fp = open(fn, 'w')
918 fp.write('pmkrpkg1')
919 fp.close()
920
921 rsrcDir = os.path.join(packageContents, "Resources")
922 os.mkdir(rsrcDir)
923 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
924 fp.write(readme)
925 fp.close()
926
927 if postflight is not None:
928 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
929
930 vers = getFullVersion()
931 major, minor = map(int, getVersion().split('.', 2))
932 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000933 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
934 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
935 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000936 CFBundleShortVersionString=vers,
937 IFMajorVersion=major,
938 IFMinorVersion=minor,
939 IFPkgFormatVersion=0.10000000149011612,
940 IFPkgFlagAllowBackRev=False,
941 IFPkgFlagAuthorizationAction="RootAuthorization",
942 IFPkgFlagDefaultLocation=pkgroot,
943 IFPkgFlagFollowLinks=True,
944 IFPkgFlagInstallFat=True,
945 IFPkgFlagIsRequired=isRequired,
946 IFPkgFlagOverwritePermissions=False,
947 IFPkgFlagRelocatable=False,
948 IFPkgFlagRestartAction="NoRestart",
949 IFPkgFlagRootVolumeOnly=True,
950 IFPkgFlagUpdateInstalledLangauges=False,
951 )
952 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
953
954 pl = Plist(
955 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000956 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000957 IFPkgDescriptionVersion=vers,
958 )
959 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
960
961 finally:
962 os.chdir(curdir)
963
964
965def makeMpkgPlist(path):
966
967 vers = getFullVersion()
968 major, minor = map(int, getVersion().split('.', 2))
969
970 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000971 CFBundleGetInfoString="Python %s"%(vers,),
972 CFBundleIdentifier='org.python.Python',
973 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000974 CFBundleShortVersionString=vers,
975 IFMajorVersion=major,
976 IFMinorVersion=minor,
977 IFPkgFlagComponentDirectory="Contents/Packages",
978 IFPkgFlagPackageList=[
979 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000980 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000981 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000982 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000983 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000984 ],
985 IFPkgFormatVersion=0.10000000149011612,
986 IFPkgFlagBackgroundScaling="proportional",
987 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000988 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000989 )
990
991 writePlist(pl, path)
992
993
994def buildInstaller():
995
996 # Zap all compiled files
997 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
998 for fn in filenames:
999 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1000 os.unlink(os.path.join(dirpath, fn))
1001
1002 outdir = os.path.join(WORKDIR, 'installer')
1003 if os.path.exists(outdir):
1004 shutil.rmtree(outdir)
1005 os.mkdir(outdir)
1006
Ronald Oussoren508282e2009-03-30 19:34:51 +00001007 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001008 pkgcontents = os.path.join(pkgroot, 'Packages')
1009 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001010 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001011 packageFromRecipe(pkgcontents, recipe)
1012
1013 rsrcDir = os.path.join(pkgroot, 'Resources')
1014
1015 fn = os.path.join(pkgroot, 'PkgInfo')
1016 fp = open(fn, 'w')
1017 fp.write('pmkrpkg1')
1018 fp.close()
1019
1020 os.mkdir(rsrcDir)
1021
1022 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1023 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001024 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001025 IFPkgDescriptionVersion=getVersion(),
1026 )
1027
1028 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1029 for fn in os.listdir('resources'):
1030 if fn == '.svn': continue
1031 if fn.endswith('.jpg'):
1032 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1033 else:
1034 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1035
Ronald Oussorenc5555542006-06-11 20:24:45 +00001036 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001037
1038
1039def installSize(clear=False, _saved=[]):
1040 if clear:
1041 del _saved[:]
1042 if not _saved:
1043 data = captureCommand("du -ks %s"%(
1044 shellQuote(os.path.join(WORKDIR, '_root'))))
1045 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1046 return _saved[0]
1047
1048
1049def buildDMG():
1050 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001051 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001052 """
1053 outdir = os.path.join(WORKDIR, 'diskimage')
1054 if os.path.exists(outdir):
1055 shutil.rmtree(outdir)
1056
1057 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001058 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001059 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001060 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001061 imagepath = imagepath + '.dmg'
1062
1063 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001064 volname='Python %s'%(getFullVersion())
1065 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1066 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001067 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001068 shellQuote(imagepath + ".tmp.dmg" )))
1069
1070
1071 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1072 os.mkdir(os.path.join(WORKDIR, "mnt"))
1073 runCommand("hdiutil attach %s -mountroot %s"%(
1074 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1075
1076 # Custom icon for the DMG, shown when the DMG is mounted.
1077 shutil.copy("../Icons/Disk Image.icns",
1078 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1079 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1080 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1081
1082 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1083
1084 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1085 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1086 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1087 setIcon(imagepath, "../Icons/Disk Image.icns")
1088
1089 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001090
1091 return imagepath
1092
1093
1094def setIcon(filePath, icnsPath):
1095 """
1096 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001097 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001098
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001099 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1100 dirPath = os.path.dirname(__file__)
1101 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1102 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1103 # to connections to the window server.
1104 if not os.path.exists('seticon.app/Contents/MacOS'):
1105 os.makedirs('seticon.app/Contents/MacOS')
1106 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1107 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001108
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001109 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1110 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001111
1112def main():
1113 # First parse options and check if we can perform our work
1114 parseOptions()
1115 checkEnvironment()
1116
Ronald Oussoren508282e2009-03-30 19:34:51 +00001117 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001118 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001119
1120 if os.path.exists(WORKDIR):
1121 shutil.rmtree(WORKDIR)
1122 os.mkdir(WORKDIR)
1123
Ronald Oussoren287128a2010-04-18 14:01:05 +00001124 os.environ['LC_ALL'] = 'C'
1125
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001126 # Then build third-party libraries such as sleepycat DB4.
1127 buildLibraries()
1128
1129 # Now build python itself
1130 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001131
1132 # And then build the documentation
1133 # Remove the Deployment Target from the shell
1134 # environment, it's no longer needed and
1135 # an unexpected build target can cause problems
1136 # when Sphinx and its dependencies need to
1137 # be (re-)installed.
1138 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001139 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001140
1141
1142 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001143 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001144 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001145 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001146
Benjamin Petersonc3104762008-10-03 11:52:06 +00001147 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001148 getVersion(),))
1149 os.chmod(folder, 0755)
1150 setIcon(folder, "../Icons/Python Folder.icns")
1151
1152 # Create the installer
1153 buildInstaller()
1154
1155 # And copy the readme into the directory containing the installer
1156 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1157
1158 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001159 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001160
1161 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1162 print >> fp, "# BUILD INFO"
1163 print >> fp, "# Date:", time.ctime()
1164 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1165 fp.close()
1166
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001167 # And copy it to a DMG
1168 buildDMG()
1169
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001170if __name__ == "__main__":
1171 main()