blob: b07711481933c449e21e4e1759a9425ba86b0752 [file] [log] [blame]
Ronald Oussorend14b6002009-03-30 17:17:52 +00001#!/usr/bin/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 Oussoren7f2e6e62009-03-30 19:37:10 +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 Oussorenc9d113b2009-09-29 19:35:03 +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 Oussoren7f2e6e62009-03-30 19:37:10 +000080universal_opts_map = { '32-bit': ('i386', 'ppc',),
81 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussoren3aeda282009-09-20 20:17:15 +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 Oussoren7f2e6e62009-03-30 19:37:10 +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 Oussoren7f2e6e62009-03-30 19:37:10 +0000105# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
106DEPTARGET = '10.3'
107
Ronald Oussorenc9d113b2009-09-29 19:35:03 +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 Oussoren7f2e6e62009-03-30 19:37:10 +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 Oussoren7f2e6e62009-03-30 19:37:10 +0000134# [The recipes are defined here for convenience but instantiated later after
135# command line options have been processed.]
136def library_recipes():
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000137 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000138
Ronald Oussoren3aeda282009-09-20 20:17:15 +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 Oussorenc9d113b2009-09-29 19:35:03 +0000146 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
147 CC,
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000148 shellQuote(os.path.join(WORKDIR, 'libraries')),
149 ' -arch '.join(ARCHLIST),
150 SDKPATH,
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000151 ),
Ronald Oussoren3aeda282009-09-20 20:17:15 +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 Oussorenc9d113b2009-09-29 19:35:03 +0000158 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
159 CC,
Ronald Oussoren3aeda282009-09-20 20:17:15 +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 Oussoren7f2e6e62009-03-30 19:37:10 +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 Oussoren3aeda282009-09-20 20:17:15 +0000234 ])
235
236 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000237
238
239# Instructions for building packages inside the .mpkg.
Ronald Oussoren3aeda282009-09-20 20:17:15 +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",
252 ),
253 dict(
254 name="PythonApplications",
255 long_name="GUI Applications",
256 source="/Applications/Python %(VER)s",
257 readme="""\
258 This package installs IDLE (an interactive Python IDE),
259 Python Launcher and Build Applet (create application bundles
260 from python scripts).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000261
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000262 It also installs a number of examples and demos.
263 """,
264 required=False,
265 ),
266 dict(
267 name="PythonUnixTools",
268 long_name="UNIX command-line tools",
269 source="/usr/local/bin",
270 readme="""\
271 This package installs the unix tools in /usr/local/bin for
272 compatibility with older releases of Python. This package
273 is not necessary to use Python.
274 """,
275 required=False,
276 ),
277 dict(
278 name="PythonDocumentation",
279 long_name="Python Documentation",
280 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
281 source="/pydocs",
282 readme="""\
283 This package installs the python documentation at a location
284 that is useable for pydoc and IDLE. If you have installed Xcode
285 it will also install a link to the documentation in
286 /Developer/Documentation/Python
287 """,
288 postflight="scripts/postflight.documentation",
289 required=False,
290 ),
291 dict(
292 name="PythonProfileChanges",
293 long_name="Shell profile updater",
294 readme="""\
295 This packages updates your shell profile to make sure that
296 the Python tools are found by your shell in preference of
297 the system provided Python tools.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000298
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000299 If you don't install this package you'll have to add
300 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
301 to your PATH by hand.
302 """,
303 postflight="scripts/postflight.patch-profile",
304 topdir="/Library/Frameworks/Python.framework",
305 source="/empty-dir",
306 required=False,
307 ),
308 ]
309
310 if DEPTARGET < '10.4':
311 result.append(
312 dict(
313 name="PythonSystemFixes",
314 long_name="Fix system Python",
315 readme="""\
316 This package updates the system python installation on
317 Mac OS X 10.3 to ensure that you can build new python extensions
318 using that copy of python after installing this version.
319 """,
320 postflight="../Tools/fixapplepython23.py",
321 topdir="/Library/Frameworks/Python.framework",
322 source="/empty-dir",
323 required=False,
324 )
325 )
326 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000327
328def fatal(msg):
329 """
330 A fatal error, bail out.
331 """
332 sys.stderr.write('FATAL: ')
333 sys.stderr.write(msg)
334 sys.stderr.write('\n')
335 sys.exit(1)
336
337def fileContents(fn):
338 """
339 Return the contents of the named file
340 """
341 return open(fn, 'rb').read()
342
343def runCommand(commandline):
344 """
345 Run a command and raise RuntimeError if it fails. Output is surpressed
346 unless the command fails.
347 """
348 fd = os.popen(commandline, 'r')
349 data = fd.read()
350 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000351 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000352 sys.stdout.write(data)
353 raise RuntimeError, "command failed: %s"%(commandline,)
354
355 if VERBOSE:
356 sys.stdout.write(data); sys.stdout.flush()
357
358def captureCommand(commandline):
359 fd = os.popen(commandline, 'r')
360 data = fd.read()
361 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000362 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000363 sys.stdout.write(data)
364 raise RuntimeError, "command failed: %s"%(commandline,)
365
366 return data
367
Ronald Oussoren7fd72822010-04-18 15:23:23 +0000368def getTclTkVersion(configfile, versionline):
369 """
370 search Tcl or Tk configuration file for version line
371 """
372 try:
373 f = open(configfile, "r")
374 except:
375 fatal("Framework configuration file not found: %s" % configfile)
376
377 for l in f:
378 if l.startswith(versionline):
379 f.close()
380 return l
381
382 fatal("Version variable %s not found in framework configuration file: %s"
383 % (versionline, configfile))
384
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000385def checkEnvironment():
386 """
387 Check that we're running on a supported system.
388 """
389
390 if platform.system() != 'Darwin':
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000391 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000392
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000393 if int(platform.release().split('.')[0]) < 8:
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000394 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000395
396 if not os.path.exists(SDKPATH):
397 fatal("Please install the latest version of Xcode and the %s SDK"%(
398 os.path.basename(SDKPATH[:-4])))
399
Ronald Oussoren7fd72822010-04-18 15:23:23 +0000400 # Because we only support dynamic load of only one major/minor version of
401 # Tcl/Tk, ensure:
402 # 1. there are no user-installed frameworks of Tcl/Tk with version
403 # higher than the Apple-supplied system version
404 # 2. there is a user-installed framework in /Library/Frameworks with the
405 # same version as the system version. This allows users to choose
406 # to install a newer patch level.
407
408 for framework in ['Tcl', 'Tk']:
409 fw = dict(lower=framework.lower(),
410 upper=framework.upper(),
411 cap=framework.capitalize())
412 fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
413 sysfw = os.path.join('/System', fwpth)
414 libfw = os.path.join('/', fwpth)
415 usrfw = os.path.join(os.getenv('HOME'), fwpth)
416 version = "%(upper)s_VERSION" % fw
417 if getTclTkVersion(libfw, version) != getTclTkVersion(sysfw, version):
418 fatal("Version of %s must match %s" % (libfw, sysfw) )
419 if os.path.exists(usrfw):
420 fatal("Please rename %s to avoid possible dynamic load issues."
421 % usrfw)
422
423 # Remove inherited environment variables which might influence build
424 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
425 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
426 for ev in list(os.environ):
427 for prefix in environ_var_prefixes:
428 if ev.startswith(prefix) :
429 print "INFO: deleting environment variable %s=%s" % (
430 ev, os.environ[ev])
431 del os.environ[ev]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000432
433
Ronald Oussoren158ad592006-11-07 16:00:34 +0000434def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000435 """
436 Parse arguments and update global settings.
437 """
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000438 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000439 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000440
441 if args is None:
442 args = sys.argv[1:]
443
444 try:
445 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000446 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
447 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000448 except getopt.error, msg:
449 print msg
450 sys.exit(1)
451
452 if args:
453 print "Additional arguments"
454 sys.exit(1)
455
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000456 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000457 for k, v in options:
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000458 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000459 print USAGE
460 sys.exit(0)
461
462 elif k in ('-d', '--build-dir'):
463 WORKDIR=v
464
465 elif k in ('--third-party',):
466 DEPSRC=v
467
468 elif k in ('--sdk-path',):
469 SDKPATH=v
470
471 elif k in ('--src-dir',):
472 SRCDIR=v
473
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000474 elif k in ('--dep-target', ):
475 DEPTARGET=v
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000476 deptarget=v
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000477
478 elif k in ('--universal-archs', ):
479 if v in UNIVERSALOPTS:
480 UNIVERSALARCHS = v
481 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000482 if deptarget is None:
483 # Select alternate default deployment
484 # target
485 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000486 else:
487 raise NotImplementedError, v
488
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000489 else:
490 raise NotImplementedError, k
491
492 SRCDIR=os.path.abspath(SRCDIR)
493 WORKDIR=os.path.abspath(WORKDIR)
494 SDKPATH=os.path.abspath(SDKPATH)
495 DEPSRC=os.path.abspath(DEPSRC)
496
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000497 CC=target_cc_map[DEPTARGET]
498
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000499 print "Settings:"
500 print " * Source directory:", SRCDIR
501 print " * Build directory: ", WORKDIR
502 print " * SDK location: ", SDKPATH
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000503 print " * Third-party source:", DEPSRC
504 print " * Deployment target:", DEPTARGET
505 print " * Universal architectures:", ARCHLIST
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000506 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000507 print ""
508
509
510
511
512def extractArchive(builddir, archiveName):
513 """
514 Extract a source archive into 'builddir'. Returns the path of the
515 extracted archive.
516
517 XXX: This function assumes that archives contain a toplevel directory
518 that is has the same name as the basename of the archive. This is
519 save enough for anything we use.
520 """
521 curdir = os.getcwd()
522 try:
523 os.chdir(builddir)
524 if archiveName.endswith('.tar.gz'):
525 retval = os.path.basename(archiveName[:-7])
526 if os.path.exists(retval):
527 shutil.rmtree(retval)
528 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
529
530 elif archiveName.endswith('.tar.bz2'):
531 retval = os.path.basename(archiveName[:-8])
532 if os.path.exists(retval):
533 shutil.rmtree(retval)
534 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
535
536 elif archiveName.endswith('.tar'):
537 retval = os.path.basename(archiveName[:-4])
538 if os.path.exists(retval):
539 shutil.rmtree(retval)
540 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
541
542 elif archiveName.endswith('.zip'):
543 retval = os.path.basename(archiveName[:-4])
544 if os.path.exists(retval):
545 shutil.rmtree(retval)
546 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
547
548 data = fp.read()
549 xit = fp.close()
550 if xit is not None:
551 sys.stdout.write(data)
552 raise RuntimeError, "Cannot extract %s"%(archiveName,)
553
554 return os.path.join(builddir, retval)
555
556 finally:
557 os.chdir(curdir)
558
559KNOWNSIZES = {
560 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
561 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
562}
563
564def downloadURL(url, fname):
565 """
566 Download the contents of the url into the file.
567 """
568 try:
569 size = os.path.getsize(fname)
570 except OSError:
571 pass
572 else:
573 if KNOWNSIZES.get(url) == size:
574 print "Using existing file for", url
575 return
576 fpIn = urllib2.urlopen(url)
577 fpOut = open(fname, 'wb')
578 block = fpIn.read(10240)
579 try:
580 while block:
581 fpOut.write(block)
582 block = fpIn.read(10240)
583 fpIn.close()
584 fpOut.close()
585 except:
586 try:
587 os.unlink(fname)
588 except:
589 pass
590
591def buildRecipe(recipe, basedir, archList):
592 """
593 Build software using a recipe. This function does the
594 'configure;make;make install' dance for C software, with a possibility
595 to customize this process, basically a poor-mans DarwinPorts.
596 """
597 curdir = os.getcwd()
598
599 name = recipe['name']
600 url = recipe['url']
601 configure = recipe.get('configure', './configure')
602 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
603 shellQuote(basedir)))
604
605 archiveName = os.path.split(url)[-1]
606 sourceArchive = os.path.join(DEPSRC, archiveName)
607
608 if not os.path.exists(DEPSRC):
609 os.mkdir(DEPSRC)
610
611
612 if os.path.exists(sourceArchive):
613 print "Using local copy of %s"%(name,)
614
615 else:
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000616 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000617 print "Downloading %s"%(name,)
618 downloadURL(url, sourceArchive)
619 print "Archive for %s stored as %s"%(name, sourceArchive)
620
621 print "Extracting archive for %s"%(name,)
622 buildDir=os.path.join(WORKDIR, '_bld')
623 if not os.path.exists(buildDir):
624 os.mkdir(buildDir)
625
626 workDir = extractArchive(buildDir, sourceArchive)
627 os.chdir(workDir)
628 if 'buildDir' in recipe:
629 os.chdir(recipe['buildDir'])
630
631
632 for fn in recipe.get('patches', ()):
633 if fn.startswith('http://'):
634 # Download the patch before applying it.
635 path = os.path.join(DEPSRC, os.path.basename(fn))
636 downloadURL(fn, path)
637 fn = path
638
639 fn = os.path.join(curdir, fn)
640 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
641 shellQuote(fn),))
642
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000643 if configure is not None:
644 configure_args = [
645 "--prefix=/usr/local",
646 "--enable-static",
647 "--disable-shared",
648 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
649 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000650
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000651 if 'configure_pre' in recipe:
652 args = list(recipe['configure_pre'])
653 if '--disable-static' in args:
654 configure_args.remove('--enable-static')
655 if '--enable-shared' in args:
656 configure_args.remove('--disable-shared')
657 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000658
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000659 if recipe.get('useLDFlags', 1):
660 configure_args.extend([
661 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
662 ' -arch '.join(archList),
663 shellQuote(SDKPATH)[1:-1],
664 shellQuote(basedir)[1:-1],),
665 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000666 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000667 shellQuote(basedir)[1:-1],
668 ' -arch '.join(archList)),
669 ])
670 else:
671 configure_args.extend([
672 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
673 ' -arch '.join(archList),
674 shellQuote(SDKPATH)[1:-1],
675 shellQuote(basedir)[1:-1],),
676 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000677
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000678 if 'configure_post' in recipe:
679 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000680
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000681 configure_args.insert(0, configure)
682 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000683
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000684 print "Running configure for %s"%(name,)
685 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000686
687 print "Running install for %s"%(name,)
688 runCommand('{ ' + install + ' ;} 2>&1')
689
690 print "Done %s"%(name,)
691 print ""
692
693 os.chdir(curdir)
694
695def buildLibraries():
696 """
697 Build our dependencies into $WORKDIR/libraries/usr/local
698 """
699 print ""
700 print "Building required libraries"
701 print ""
702 universal = os.path.join(WORKDIR, 'libraries')
703 os.mkdir(universal)
704 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
705 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
706
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000707 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000708 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000709
710
711
712def buildPythonDocs():
Ronald Oussorend14b6002009-03-30 17:17:52 +0000713 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000714 # inside the framwork. pydoc and IDLE will pick it up there.
715 print "Install python documentation"
716 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussorend14b6002009-03-30 17:17:52 +0000717 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000718 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussorend14b6002009-03-30 17:17:52 +0000719 curDir = os.getcwd()
720 os.chdir(buildDir)
721 runCommand('make update')
722 runCommand('make html')
723 os.chdir(curDir)
724 if not os.path.exists(docdir):
725 os.mkdir(docdir)
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000726 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000727
728
729def buildPython():
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000730 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000731
732 buildDir = os.path.join(WORKDIR, '_bld', 'python')
733 rootDir = os.path.join(WORKDIR, '_root')
734
735 if os.path.exists(buildDir):
736 shutil.rmtree(buildDir)
737 if os.path.exists(rootDir):
738 shutil.rmtree(rootDir)
739 os.mkdir(buildDir)
740 os.mkdir(rootDir)
741 os.mkdir(os.path.join(rootDir, 'empty-dir'))
742 curdir = os.getcwd()
743 os.chdir(buildDir)
744
745 # Not sure if this is still needed, the original build script
746 # claims that parts of the install assume python.exe exists.
747 os.symlink('python', os.path.join(buildDir, 'python.exe'))
748
749 # Extract the version from the configure file, needed to calculate
750 # several paths.
751 version = getVersion()
752
Ronald Oussoren524e36a2009-03-30 20:04:12 +0000753 # Since the extra libs are not in their installed framework location
754 # during the build, augment the library path so that the interpreter
755 # will find them during its extension import sanity checks.
756 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
757 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000758 print "Running configure..."
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000759 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
760 "--with-universal-archs=%s "
761 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
762 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
763 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
764 UNIVERSALARCHS,
765 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000766 shellQuote(WORKDIR)[1:-1]))
767
768 print "Running make"
769 runCommand("make")
770
Ronald Oussorenaa560962006-11-07 15:53:38 +0000771 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000772 runCommand("make frameworkinstall DESTDIR=%s"%(
773 shellQuote(rootDir)))
774
Ronald Oussorenaa560962006-11-07 15:53:38 +0000775 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000776 runCommand("make frameworkinstallextras DESTDIR=%s"%(
777 shellQuote(rootDir)))
778
Ronald Oussoren524e36a2009-03-30 20:04:12 +0000779 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000780 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000781 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
782 runCommand("mv %s/* %s"%(
783 shellQuote(os.path.join(
784 WORKDIR, 'libraries', 'Library', 'Frameworks',
785 'Python.framework', 'Versions', getVersion(),
786 'lib')),
787 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
788 'Python.framework', 'Versions', getVersion(),
789 'lib'))))
790
791 print "Fix file modes"
792 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000793 gid = grp.getgrnam('admin').gr_gid
794
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000795 for dirpath, dirnames, filenames in os.walk(frmDir):
796 for dn in dirnames:
797 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000798 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000799
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000800
801 for fn in filenames:
802 if os.path.islink(fn):
803 continue
804
805 # "chmod g+w $fn"
806 p = os.path.join(dirpath, fn)
807 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000808 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
809 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000810
811 # We added some directories to the search path during the configure
812 # phase. Remove those because those directories won't be there on
813 # the end-users system.
814 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
815 'Versions', version, 'lib', 'python%s'%(version,),
816 'config', 'Makefile')
817 fp = open(path, 'r')
818 data = fp.read()
819 fp.close()
820
821 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
822 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
823 fp = open(path, 'w')
824 fp.write(data)
825 fp.close()
826
827 # Add symlinks in /usr/local/bin, using relative links
828 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
829 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
830 'Python.framework', 'Versions', version, 'bin')
831 if os.path.exists(usr_local_bin):
832 shutil.rmtree(usr_local_bin)
833 os.makedirs(usr_local_bin)
834 for fn in os.listdir(
835 os.path.join(frmDir, 'Versions', version, 'bin')):
836 os.symlink(os.path.join(to_framework, fn),
837 os.path.join(usr_local_bin, fn))
838
839 os.chdir(curdir)
840
841
842
843def patchFile(inPath, outPath):
844 data = fileContents(inPath)
845 data = data.replace('$FULL_VERSION', getFullVersion())
846 data = data.replace('$VERSION', getVersion())
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000847 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000848 data = data.replace('$ARCHITECTURES', "i386, ppc")
849 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000850
851 # This one is not handy as a template variable
852 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000853 fp = open(outPath, 'wb')
854 fp.write(data)
855 fp.close()
856
857def patchScript(inPath, outPath):
858 data = fileContents(inPath)
859 data = data.replace('@PYVER@', getVersion())
860 fp = open(outPath, 'wb')
861 fp.write(data)
862 fp.close()
863 os.chmod(outPath, 0755)
864
865
866
867def packageFromRecipe(targetDir, recipe):
868 curdir = os.getcwd()
869 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000870 # The major version (such as 2.5) is included in the package name
871 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000872 # common.
873 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000874 srcdir = recipe.get('source')
875 pkgroot = recipe.get('topdir', srcdir)
876 postflight = recipe.get('postflight')
877 readme = textwrap.dedent(recipe['readme'])
878 isRequired = recipe.get('required', True)
879
880 print "- building package %s"%(pkgname,)
881
882 # Substitute some variables
883 textvars = dict(
884 VER=getVersion(),
885 FULLVER=getFullVersion(),
886 )
887 readme = readme % textvars
888
889 if pkgroot is not None:
890 pkgroot = pkgroot % textvars
891 else:
892 pkgroot = '/'
893
894 if srcdir is not None:
895 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
896 srcdir = srcdir % textvars
897
898 if postflight is not None:
899 postflight = os.path.abspath(postflight)
900
901 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
902 os.makedirs(packageContents)
903
904 if srcdir is not None:
905 os.chdir(srcdir)
906 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
907 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
908 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
909
910 fn = os.path.join(packageContents, 'PkgInfo')
911 fp = open(fn, 'w')
912 fp.write('pmkrpkg1')
913 fp.close()
914
915 rsrcDir = os.path.join(packageContents, "Resources")
916 os.mkdir(rsrcDir)
917 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
918 fp.write(readme)
919 fp.close()
920
921 if postflight is not None:
922 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
923
924 vers = getFullVersion()
925 major, minor = map(int, getVersion().split('.', 2))
926 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000927 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
928 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
929 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000930 CFBundleShortVersionString=vers,
931 IFMajorVersion=major,
932 IFMinorVersion=minor,
933 IFPkgFormatVersion=0.10000000149011612,
934 IFPkgFlagAllowBackRev=False,
935 IFPkgFlagAuthorizationAction="RootAuthorization",
936 IFPkgFlagDefaultLocation=pkgroot,
937 IFPkgFlagFollowLinks=True,
938 IFPkgFlagInstallFat=True,
939 IFPkgFlagIsRequired=isRequired,
940 IFPkgFlagOverwritePermissions=False,
941 IFPkgFlagRelocatable=False,
942 IFPkgFlagRestartAction="NoRestart",
943 IFPkgFlagRootVolumeOnly=True,
944 IFPkgFlagUpdateInstalledLangauges=False,
945 )
946 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
947
948 pl = Plist(
949 IFPkgDescriptionDescription=readme,
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000950 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000951 IFPkgDescriptionVersion=vers,
952 )
953 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
954
955 finally:
956 os.chdir(curdir)
957
958
959def makeMpkgPlist(path):
960
961 vers = getFullVersion()
962 major, minor = map(int, getVersion().split('.', 2))
963
964 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000965 CFBundleGetInfoString="Python %s"%(vers,),
966 CFBundleIdentifier='org.python.Python',
967 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000968 CFBundleShortVersionString=vers,
969 IFMajorVersion=major,
970 IFMinorVersion=minor,
971 IFPkgFlagComponentDirectory="Contents/Packages",
972 IFPkgFlagPackageList=[
973 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000974 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000975 IFPkgFlagPackageSelection='selected'
976 )
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000977 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000978 ],
979 IFPkgFormatVersion=0.10000000149011612,
980 IFPkgFlagBackgroundScaling="proportional",
981 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000982 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000983 )
984
985 writePlist(pl, path)
986
987
988def buildInstaller():
989
990 # Zap all compiled files
991 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
992 for fn in filenames:
993 if fn.endswith('.pyc') or fn.endswith('.pyo'):
994 os.unlink(os.path.join(dirpath, fn))
995
996 outdir = os.path.join(WORKDIR, 'installer')
997 if os.path.exists(outdir):
998 shutil.rmtree(outdir)
999 os.mkdir(outdir)
1000
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001001 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001002 pkgcontents = os.path.join(pkgroot, 'Packages')
1003 os.makedirs(pkgcontents)
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001004 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001005 packageFromRecipe(pkgcontents, recipe)
1006
1007 rsrcDir = os.path.join(pkgroot, 'Resources')
1008
1009 fn = os.path.join(pkgroot, 'PkgInfo')
1010 fp = open(fn, 'w')
1011 fp.write('pmkrpkg1')
1012 fp.close()
1013
1014 os.mkdir(rsrcDir)
1015
1016 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1017 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001018 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001019 IFPkgDescriptionVersion=getVersion(),
1020 )
1021
1022 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1023 for fn in os.listdir('resources'):
1024 if fn == '.svn': continue
1025 if fn.endswith('.jpg'):
1026 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1027 else:
1028 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1029
Ronald Oussorenc5555542006-06-11 20:24:45 +00001030 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001031
1032
1033def installSize(clear=False, _saved=[]):
1034 if clear:
1035 del _saved[:]
1036 if not _saved:
1037 data = captureCommand("du -ks %s"%(
1038 shellQuote(os.path.join(WORKDIR, '_root'))))
1039 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1040 return _saved[0]
1041
1042
1043def buildDMG():
1044 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001045 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001046 """
1047 outdir = os.path.join(WORKDIR, 'diskimage')
1048 if os.path.exists(outdir):
1049 shutil.rmtree(outdir)
1050
1051 imagepath = os.path.join(outdir,
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001052 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001053 if INCLUDE_TIMESTAMP:
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001054 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001055 imagepath = imagepath + '.dmg'
1056
1057 os.mkdir(outdir)
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001058 volname='Python %s'%(getFullVersion())
1059 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1060 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001061 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001062 shellQuote(imagepath + ".tmp.dmg" )))
1063
1064
1065 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1066 os.mkdir(os.path.join(WORKDIR, "mnt"))
1067 runCommand("hdiutil attach %s -mountroot %s"%(
1068 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1069
1070 # Custom icon for the DMG, shown when the DMG is mounted.
1071 shutil.copy("../Icons/Disk Image.icns",
1072 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1073 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1074 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1075
1076 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1077
1078 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1079 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1080 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1081 setIcon(imagepath, "../Icons/Disk Image.icns")
1082
1083 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001084
1085 return imagepath
1086
1087
1088def setIcon(filePath, icnsPath):
1089 """
1090 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001091 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001092
Ronald Oussorend14b6002009-03-30 17:17:52 +00001093 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1094 dirPath = os.path.dirname(__file__)
1095 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1096 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1097 # to connections to the window server.
1098 if not os.path.exists('seticon.app/Contents/MacOS'):
1099 os.makedirs('seticon.app/Contents/MacOS')
1100 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1101 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001102
Ronald Oussorend14b6002009-03-30 17:17:52 +00001103 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1104 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001105
1106def main():
1107 # First parse options and check if we can perform our work
1108 parseOptions()
1109 checkEnvironment()
1110
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001111 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussorenc9d113b2009-09-29 19:35:03 +00001112 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001113
1114 if os.path.exists(WORKDIR):
1115 shutil.rmtree(WORKDIR)
1116 os.mkdir(WORKDIR)
1117
Ronald Oussoren7fd72822010-04-18 15:23:23 +00001118 os.environ['LC_ALL'] = 'C'
1119
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001120 # Then build third-party libraries such as sleepycat DB4.
1121 buildLibraries()
1122
1123 # Now build python itself
1124 buildPython()
Ronald Oussoren85da5e82009-03-31 13:23:26 +00001125
1126 # And then build the documentation
1127 # Remove the Deployment Target from the shell
1128 # environment, it's no longer needed and
1129 # an unexpected build target can cause problems
1130 # when Sphinx and its dependencies need to
1131 # be (re-)installed.
1132 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001133 buildPythonDocs()
Ronald Oussoren85da5e82009-03-31 13:23:26 +00001134
1135
1136 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001137 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonbe2c0a92008-10-04 21:33:08 +00001138 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren03674a32009-03-04 21:10:17 +00001139 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001140
Benjamin Petersonbe2c0a92008-10-04 21:33:08 +00001141 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001142 getVersion(),))
1143 os.chmod(folder, 0755)
1144 setIcon(folder, "../Icons/Python Folder.icns")
1145
1146 # Create the installer
1147 buildInstaller()
1148
1149 # And copy the readme into the directory containing the installer
1150 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1151
1152 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001153 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001154
1155 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1156 print >> fp, "# BUILD INFO"
1157 print >> fp, "# Date:", time.ctime()
1158 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1159 fp.close()
1160
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001161 # And copy it to a DMG
1162 buildDMG()
1163
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001164if __name__ == "__main__":
1165 main()