blob: fbbb484fd7e1f1cf44c6c14c28f7b528847e7386 [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
Ronald Oussorenac4b7ad2010-04-20 05:50:44 +0000439 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
440 print "Setting default PATH: %s"%(os.environ['PATH'])
441
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000442
Ronald Oussoren158ad592006-11-07 16:00:34 +0000443def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000444 """
445 Parse arguments and update global settings.
446 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000447 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000448 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000449
450 if args is None:
451 args = sys.argv[1:]
452
453 try:
454 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000455 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
456 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000457 except getopt.error, msg:
458 print msg
459 sys.exit(1)
460
461 if args:
462 print "Additional arguments"
463 sys.exit(1)
464
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000465 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000466 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000467 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000468 print USAGE
469 sys.exit(0)
470
471 elif k in ('-d', '--build-dir'):
472 WORKDIR=v
473
474 elif k in ('--third-party',):
475 DEPSRC=v
476
477 elif k in ('--sdk-path',):
478 SDKPATH=v
479
480 elif k in ('--src-dir',):
481 SRCDIR=v
482
Ronald Oussoren508282e2009-03-30 19:34:51 +0000483 elif k in ('--dep-target', ):
484 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000485 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000486
487 elif k in ('--universal-archs', ):
488 if v in UNIVERSALOPTS:
489 UNIVERSALARCHS = v
490 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000491 if deptarget is None:
492 # Select alternate default deployment
493 # target
494 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000495 else:
496 raise NotImplementedError, v
497
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000498 else:
499 raise NotImplementedError, k
500
501 SRCDIR=os.path.abspath(SRCDIR)
502 WORKDIR=os.path.abspath(WORKDIR)
503 SDKPATH=os.path.abspath(SDKPATH)
504 DEPSRC=os.path.abspath(DEPSRC)
505
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000506 CC=target_cc_map[DEPTARGET]
507
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000508 print "Settings:"
509 print " * Source directory:", SRCDIR
510 print " * Build directory: ", WORKDIR
511 print " * SDK location: ", SDKPATH
Ronald Oussoren508282e2009-03-30 19:34:51 +0000512 print " * Third-party source:", DEPSRC
513 print " * Deployment target:", DEPTARGET
514 print " * Universal architectures:", ARCHLIST
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000515 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000516 print ""
517
518
519
520
521def extractArchive(builddir, archiveName):
522 """
523 Extract a source archive into 'builddir'. Returns the path of the
524 extracted archive.
525
526 XXX: This function assumes that archives contain a toplevel directory
527 that is has the same name as the basename of the archive. This is
528 save enough for anything we use.
529 """
530 curdir = os.getcwd()
531 try:
532 os.chdir(builddir)
533 if archiveName.endswith('.tar.gz'):
534 retval = os.path.basename(archiveName[:-7])
535 if os.path.exists(retval):
536 shutil.rmtree(retval)
537 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
538
539 elif archiveName.endswith('.tar.bz2'):
540 retval = os.path.basename(archiveName[:-8])
541 if os.path.exists(retval):
542 shutil.rmtree(retval)
543 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
544
545 elif archiveName.endswith('.tar'):
546 retval = os.path.basename(archiveName[:-4])
547 if os.path.exists(retval):
548 shutil.rmtree(retval)
549 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
550
551 elif archiveName.endswith('.zip'):
552 retval = os.path.basename(archiveName[:-4])
553 if os.path.exists(retval):
554 shutil.rmtree(retval)
555 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
556
557 data = fp.read()
558 xit = fp.close()
559 if xit is not None:
560 sys.stdout.write(data)
561 raise RuntimeError, "Cannot extract %s"%(archiveName,)
562
563 return os.path.join(builddir, retval)
564
565 finally:
566 os.chdir(curdir)
567
568KNOWNSIZES = {
569 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
570 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
571}
572
573def downloadURL(url, fname):
574 """
575 Download the contents of the url into the file.
576 """
577 try:
578 size = os.path.getsize(fname)
579 except OSError:
580 pass
581 else:
582 if KNOWNSIZES.get(url) == size:
583 print "Using existing file for", url
584 return
585 fpIn = urllib2.urlopen(url)
586 fpOut = open(fname, 'wb')
587 block = fpIn.read(10240)
588 try:
589 while block:
590 fpOut.write(block)
591 block = fpIn.read(10240)
592 fpIn.close()
593 fpOut.close()
594 except:
595 try:
596 os.unlink(fname)
597 except:
598 pass
599
600def buildRecipe(recipe, basedir, archList):
601 """
602 Build software using a recipe. This function does the
603 'configure;make;make install' dance for C software, with a possibility
604 to customize this process, basically a poor-mans DarwinPorts.
605 """
606 curdir = os.getcwd()
607
608 name = recipe['name']
609 url = recipe['url']
610 configure = recipe.get('configure', './configure')
611 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
612 shellQuote(basedir)))
613
614 archiveName = os.path.split(url)[-1]
615 sourceArchive = os.path.join(DEPSRC, archiveName)
616
617 if not os.path.exists(DEPSRC):
618 os.mkdir(DEPSRC)
619
620
621 if os.path.exists(sourceArchive):
622 print "Using local copy of %s"%(name,)
623
624 else:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000625 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000626 print "Downloading %s"%(name,)
627 downloadURL(url, sourceArchive)
628 print "Archive for %s stored as %s"%(name, sourceArchive)
629
630 print "Extracting archive for %s"%(name,)
631 buildDir=os.path.join(WORKDIR, '_bld')
632 if not os.path.exists(buildDir):
633 os.mkdir(buildDir)
634
635 workDir = extractArchive(buildDir, sourceArchive)
636 os.chdir(workDir)
637 if 'buildDir' in recipe:
638 os.chdir(recipe['buildDir'])
639
640
641 for fn in recipe.get('patches', ()):
642 if fn.startswith('http://'):
643 # Download the patch before applying it.
644 path = os.path.join(DEPSRC, os.path.basename(fn))
645 downloadURL(fn, path)
646 fn = path
647
648 fn = os.path.join(curdir, fn)
649 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
650 shellQuote(fn),))
651
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000652 if configure is not None:
653 configure_args = [
654 "--prefix=/usr/local",
655 "--enable-static",
656 "--disable-shared",
657 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
658 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000659
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000660 if 'configure_pre' in recipe:
661 args = list(recipe['configure_pre'])
662 if '--disable-static' in args:
663 configure_args.remove('--enable-static')
664 if '--enable-shared' in args:
665 configure_args.remove('--disable-shared')
666 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000667
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000668 if recipe.get('useLDFlags', 1):
669 configure_args.extend([
670 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
671 ' -arch '.join(archList),
672 shellQuote(SDKPATH)[1:-1],
673 shellQuote(basedir)[1:-1],),
674 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000675 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000676 shellQuote(basedir)[1:-1],
677 ' -arch '.join(archList)),
678 ])
679 else:
680 configure_args.extend([
681 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
682 ' -arch '.join(archList),
683 shellQuote(SDKPATH)[1:-1],
684 shellQuote(basedir)[1:-1],),
685 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000686
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000687 if 'configure_post' in recipe:
688 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000689
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000690 configure_args.insert(0, configure)
691 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000692
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000693 print "Running configure for %s"%(name,)
694 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000695
696 print "Running install for %s"%(name,)
697 runCommand('{ ' + install + ' ;} 2>&1')
698
699 print "Done %s"%(name,)
700 print ""
701
702 os.chdir(curdir)
703
704def buildLibraries():
705 """
706 Build our dependencies into $WORKDIR/libraries/usr/local
707 """
708 print ""
709 print "Building required libraries"
710 print ""
711 universal = os.path.join(WORKDIR, 'libraries')
712 os.mkdir(universal)
713 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
714 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
715
Ronald Oussoren508282e2009-03-30 19:34:51 +0000716 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000717 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000718
719
720
721def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000722 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000723 # inside the framwork. pydoc and IDLE will pick it up there.
724 print "Install python documentation"
725 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000726 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000727 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000728 curDir = os.getcwd()
729 os.chdir(buildDir)
730 runCommand('make update')
731 runCommand('make html')
732 os.chdir(curDir)
733 if not os.path.exists(docdir):
734 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000735 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000736
737
738def buildPython():
Ronald Oussoren508282e2009-03-30 19:34:51 +0000739 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000740
741 buildDir = os.path.join(WORKDIR, '_bld', 'python')
742 rootDir = os.path.join(WORKDIR, '_root')
743
744 if os.path.exists(buildDir):
745 shutil.rmtree(buildDir)
746 if os.path.exists(rootDir):
747 shutil.rmtree(rootDir)
748 os.mkdir(buildDir)
749 os.mkdir(rootDir)
750 os.mkdir(os.path.join(rootDir, 'empty-dir'))
751 curdir = os.getcwd()
752 os.chdir(buildDir)
753
754 # Not sure if this is still needed, the original build script
755 # claims that parts of the install assume python.exe exists.
756 os.symlink('python', os.path.join(buildDir, 'python.exe'))
757
758 # Extract the version from the configure file, needed to calculate
759 # several paths.
760 version = getVersion()
761
Ronald Oussoren008af852009-03-30 20:02:08 +0000762 # Since the extra libs are not in their installed framework location
763 # during the build, augment the library path so that the interpreter
764 # will find them during its extension import sanity checks.
765 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
766 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000767 print "Running configure..."
Ronald Oussoren508282e2009-03-30 19:34:51 +0000768 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
769 "--with-universal-archs=%s "
770 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
771 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
772 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
773 UNIVERSALARCHS,
774 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000775 shellQuote(WORKDIR)[1:-1]))
776
777 print "Running make"
778 runCommand("make")
779
Ronald Oussorenaa560962006-11-07 15:53:38 +0000780 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000781 runCommand("make frameworkinstall DESTDIR=%s"%(
782 shellQuote(rootDir)))
783
Ronald Oussorenaa560962006-11-07 15:53:38 +0000784 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000785 runCommand("make frameworkinstallextras DESTDIR=%s"%(
786 shellQuote(rootDir)))
787
Ronald Oussoren008af852009-03-30 20:02:08 +0000788 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000789 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000790 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
791 runCommand("mv %s/* %s"%(
792 shellQuote(os.path.join(
793 WORKDIR, 'libraries', 'Library', 'Frameworks',
794 'Python.framework', 'Versions', getVersion(),
795 'lib')),
796 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
797 'Python.framework', 'Versions', getVersion(),
798 'lib'))))
799
800 print "Fix file modes"
801 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000802 gid = grp.getgrnam('admin').gr_gid
803
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000804 for dirpath, dirnames, filenames in os.walk(frmDir):
805 for dn in dirnames:
806 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000807 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000808
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000809
810 for fn in filenames:
811 if os.path.islink(fn):
812 continue
813
814 # "chmod g+w $fn"
815 p = os.path.join(dirpath, fn)
816 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000817 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
818 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000819
820 # We added some directories to the search path during the configure
821 # phase. Remove those because those directories won't be there on
822 # the end-users system.
823 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
824 'Versions', version, 'lib', 'python%s'%(version,),
825 'config', 'Makefile')
826 fp = open(path, 'r')
827 data = fp.read()
828 fp.close()
829
830 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
831 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
832 fp = open(path, 'w')
833 fp.write(data)
834 fp.close()
835
836 # Add symlinks in /usr/local/bin, using relative links
837 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
838 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
839 'Python.framework', 'Versions', version, 'bin')
840 if os.path.exists(usr_local_bin):
841 shutil.rmtree(usr_local_bin)
842 os.makedirs(usr_local_bin)
843 for fn in os.listdir(
844 os.path.join(frmDir, 'Versions', version, 'bin')):
845 os.symlink(os.path.join(to_framework, fn),
846 os.path.join(usr_local_bin, fn))
847
848 os.chdir(curdir)
849
850
851
852def patchFile(inPath, outPath):
853 data = fileContents(inPath)
854 data = data.replace('$FULL_VERSION', getFullVersion())
855 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +0000856 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000857 data = data.replace('$ARCHITECTURES', "i386, ppc")
858 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000859
860 # This one is not handy as a template variable
861 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000862 fp = open(outPath, 'wb')
863 fp.write(data)
864 fp.close()
865
866def patchScript(inPath, outPath):
867 data = fileContents(inPath)
868 data = data.replace('@PYVER@', getVersion())
869 fp = open(outPath, 'wb')
870 fp.write(data)
871 fp.close()
872 os.chmod(outPath, 0755)
873
874
875
876def packageFromRecipe(targetDir, recipe):
877 curdir = os.getcwd()
878 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000879 # The major version (such as 2.5) is included in the package name
880 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000881 # common.
882 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000883 srcdir = recipe.get('source')
884 pkgroot = recipe.get('topdir', srcdir)
885 postflight = recipe.get('postflight')
886 readme = textwrap.dedent(recipe['readme'])
887 isRequired = recipe.get('required', True)
888
889 print "- building package %s"%(pkgname,)
890
891 # Substitute some variables
892 textvars = dict(
893 VER=getVersion(),
894 FULLVER=getFullVersion(),
895 )
896 readme = readme % textvars
897
898 if pkgroot is not None:
899 pkgroot = pkgroot % textvars
900 else:
901 pkgroot = '/'
902
903 if srcdir is not None:
904 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
905 srcdir = srcdir % textvars
906
907 if postflight is not None:
908 postflight = os.path.abspath(postflight)
909
910 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
911 os.makedirs(packageContents)
912
913 if srcdir is not None:
914 os.chdir(srcdir)
915 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
916 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
917 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
918
919 fn = os.path.join(packageContents, 'PkgInfo')
920 fp = open(fn, 'w')
921 fp.write('pmkrpkg1')
922 fp.close()
923
924 rsrcDir = os.path.join(packageContents, "Resources")
925 os.mkdir(rsrcDir)
926 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
927 fp.write(readme)
928 fp.close()
929
930 if postflight is not None:
931 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
932
933 vers = getFullVersion()
934 major, minor = map(int, getVersion().split('.', 2))
935 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000936 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
937 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
938 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000939 CFBundleShortVersionString=vers,
940 IFMajorVersion=major,
941 IFMinorVersion=minor,
942 IFPkgFormatVersion=0.10000000149011612,
943 IFPkgFlagAllowBackRev=False,
944 IFPkgFlagAuthorizationAction="RootAuthorization",
945 IFPkgFlagDefaultLocation=pkgroot,
946 IFPkgFlagFollowLinks=True,
947 IFPkgFlagInstallFat=True,
948 IFPkgFlagIsRequired=isRequired,
949 IFPkgFlagOverwritePermissions=False,
950 IFPkgFlagRelocatable=False,
951 IFPkgFlagRestartAction="NoRestart",
952 IFPkgFlagRootVolumeOnly=True,
953 IFPkgFlagUpdateInstalledLangauges=False,
954 )
955 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
956
957 pl = Plist(
958 IFPkgDescriptionDescription=readme,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000959 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000960 IFPkgDescriptionVersion=vers,
961 )
962 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
963
964 finally:
965 os.chdir(curdir)
966
967
968def makeMpkgPlist(path):
969
970 vers = getFullVersion()
971 major, minor = map(int, getVersion().split('.', 2))
972
973 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +0000974 CFBundleGetInfoString="Python %s"%(vers,),
975 CFBundleIdentifier='org.python.Python',
976 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000977 CFBundleShortVersionString=vers,
978 IFMajorVersion=major,
979 IFMinorVersion=minor,
980 IFPkgFlagComponentDirectory="Contents/Packages",
981 IFPkgFlagPackageList=[
982 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000983 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000984 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000985 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000986 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000987 ],
988 IFPkgFormatVersion=0.10000000149011612,
989 IFPkgFlagBackgroundScaling="proportional",
990 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000991 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000992 )
993
994 writePlist(pl, path)
995
996
997def buildInstaller():
998
999 # Zap all compiled files
1000 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1001 for fn in filenames:
1002 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1003 os.unlink(os.path.join(dirpath, fn))
1004
1005 outdir = os.path.join(WORKDIR, 'installer')
1006 if os.path.exists(outdir):
1007 shutil.rmtree(outdir)
1008 os.mkdir(outdir)
1009
Ronald Oussoren508282e2009-03-30 19:34:51 +00001010 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001011 pkgcontents = os.path.join(pkgroot, 'Packages')
1012 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001013 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001014 packageFromRecipe(pkgcontents, recipe)
1015
1016 rsrcDir = os.path.join(pkgroot, 'Resources')
1017
1018 fn = os.path.join(pkgroot, 'PkgInfo')
1019 fp = open(fn, 'w')
1020 fp.write('pmkrpkg1')
1021 fp.close()
1022
1023 os.mkdir(rsrcDir)
1024
1025 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1026 pl = Plist(
Ronald Oussoren508282e2009-03-30 19:34:51 +00001027 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001028 IFPkgDescriptionVersion=getVersion(),
1029 )
1030
1031 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1032 for fn in os.listdir('resources'):
1033 if fn == '.svn': continue
1034 if fn.endswith('.jpg'):
1035 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1036 else:
1037 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1038
Ronald Oussorenc5555542006-06-11 20:24:45 +00001039 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001040
1041
1042def installSize(clear=False, _saved=[]):
1043 if clear:
1044 del _saved[:]
1045 if not _saved:
1046 data = captureCommand("du -ks %s"%(
1047 shellQuote(os.path.join(WORKDIR, '_root'))))
1048 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1049 return _saved[0]
1050
1051
1052def buildDMG():
1053 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001054 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001055 """
1056 outdir = os.path.join(WORKDIR, 'diskimage')
1057 if os.path.exists(outdir):
1058 shutil.rmtree(outdir)
1059
1060 imagepath = os.path.join(outdir,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001061 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001062 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001063 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001064 imagepath = imagepath + '.dmg'
1065
1066 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001067 volname='Python %s'%(getFullVersion())
1068 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1069 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001070 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +00001071 shellQuote(imagepath + ".tmp.dmg" )))
1072
1073
1074 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1075 os.mkdir(os.path.join(WORKDIR, "mnt"))
1076 runCommand("hdiutil attach %s -mountroot %s"%(
1077 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1078
1079 # Custom icon for the DMG, shown when the DMG is mounted.
1080 shutil.copy("../Icons/Disk Image.icns",
1081 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1082 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1083 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1084
1085 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1086
1087 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1088 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1089 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1090 setIcon(imagepath, "../Icons/Disk Image.icns")
1091
1092 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001093
1094 return imagepath
1095
1096
1097def setIcon(filePath, icnsPath):
1098 """
1099 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001100 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001101
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001102 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1103 dirPath = os.path.dirname(__file__)
1104 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1105 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1106 # to connections to the window server.
1107 if not os.path.exists('seticon.app/Contents/MacOS'):
1108 os.makedirs('seticon.app/Contents/MacOS')
1109 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1110 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001111
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001112 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1113 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001114
1115def main():
1116 # First parse options and check if we can perform our work
1117 parseOptions()
1118 checkEnvironment()
1119
Ronald Oussoren508282e2009-03-30 19:34:51 +00001120 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001121 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001122
1123 if os.path.exists(WORKDIR):
1124 shutil.rmtree(WORKDIR)
1125 os.mkdir(WORKDIR)
1126
Ronald Oussoren287128a2010-04-18 14:01:05 +00001127 os.environ['LC_ALL'] = 'C'
1128
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001129 # Then build third-party libraries such as sleepycat DB4.
1130 buildLibraries()
1131
1132 # Now build python itself
1133 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +00001134
1135 # And then build the documentation
1136 # Remove the Deployment Target from the shell
1137 # environment, it's no longer needed and
1138 # an unexpected build target can cause problems
1139 # when Sphinx and its dependencies need to
1140 # be (re-)installed.
1141 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001142 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001143
1144
1145 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001146 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001147 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001148 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001149
Benjamin Petersonc3104762008-10-03 11:52:06 +00001150 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001151 getVersion(),))
1152 os.chmod(folder, 0755)
1153 setIcon(folder, "../Icons/Python Folder.icns")
1154
1155 # Create the installer
1156 buildInstaller()
1157
1158 # And copy the readme into the directory containing the installer
1159 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1160
1161 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001162 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001163
1164 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1165 print >> fp, "# BUILD INFO"
1166 print >> fp, "# Date:", time.ctime()
1167 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1168 fp.close()
1169
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001170 # And copy it to a DMG
1171 buildDMG()
1172
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001173if __name__ == "__main__":
1174 main()