blob: 0d0a9b7278f543cba3ab6fae31e3d4758c1e40b1 [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
Ronald Oussoren6f761872010-04-20 06:37:38 +0000433 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
434 print "Setting default PATH: %s"%(os.environ['PATH'])
435
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000436
Ronald Oussoren158ad592006-11-07 16:00:34 +0000437def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000438 """
439 Parse arguments and update global settings.
440 """
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000441 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000442 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000443
444 if args is None:
445 args = sys.argv[1:]
446
447 try:
448 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000449 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
450 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000451 except getopt.error, msg:
452 print msg
453 sys.exit(1)
454
455 if args:
456 print "Additional arguments"
457 sys.exit(1)
458
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000459 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000460 for k, v in options:
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000461 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000462 print USAGE
463 sys.exit(0)
464
465 elif k in ('-d', '--build-dir'):
466 WORKDIR=v
467
468 elif k in ('--third-party',):
469 DEPSRC=v
470
471 elif k in ('--sdk-path',):
472 SDKPATH=v
473
474 elif k in ('--src-dir',):
475 SRCDIR=v
476
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000477 elif k in ('--dep-target', ):
478 DEPTARGET=v
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000479 deptarget=v
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000480
481 elif k in ('--universal-archs', ):
482 if v in UNIVERSALOPTS:
483 UNIVERSALARCHS = v
484 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000485 if deptarget is None:
486 # Select alternate default deployment
487 # target
488 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000489 else:
490 raise NotImplementedError, v
491
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000492 else:
493 raise NotImplementedError, k
494
495 SRCDIR=os.path.abspath(SRCDIR)
496 WORKDIR=os.path.abspath(WORKDIR)
497 SDKPATH=os.path.abspath(SDKPATH)
498 DEPSRC=os.path.abspath(DEPSRC)
499
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000500 CC=target_cc_map[DEPTARGET]
501
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000502 print "Settings:"
503 print " * Source directory:", SRCDIR
504 print " * Build directory: ", WORKDIR
505 print " * SDK location: ", SDKPATH
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000506 print " * Third-party source:", DEPSRC
507 print " * Deployment target:", DEPTARGET
508 print " * Universal architectures:", ARCHLIST
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000509 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000510 print ""
511
512
513
514
515def extractArchive(builddir, archiveName):
516 """
517 Extract a source archive into 'builddir'. Returns the path of the
518 extracted archive.
519
520 XXX: This function assumes that archives contain a toplevel directory
521 that is has the same name as the basename of the archive. This is
522 save enough for anything we use.
523 """
524 curdir = os.getcwd()
525 try:
526 os.chdir(builddir)
527 if archiveName.endswith('.tar.gz'):
528 retval = os.path.basename(archiveName[:-7])
529 if os.path.exists(retval):
530 shutil.rmtree(retval)
531 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
532
533 elif archiveName.endswith('.tar.bz2'):
534 retval = os.path.basename(archiveName[:-8])
535 if os.path.exists(retval):
536 shutil.rmtree(retval)
537 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
538
539 elif archiveName.endswith('.tar'):
540 retval = os.path.basename(archiveName[:-4])
541 if os.path.exists(retval):
542 shutil.rmtree(retval)
543 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
544
545 elif archiveName.endswith('.zip'):
546 retval = os.path.basename(archiveName[:-4])
547 if os.path.exists(retval):
548 shutil.rmtree(retval)
549 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
550
551 data = fp.read()
552 xit = fp.close()
553 if xit is not None:
554 sys.stdout.write(data)
555 raise RuntimeError, "Cannot extract %s"%(archiveName,)
556
557 return os.path.join(builddir, retval)
558
559 finally:
560 os.chdir(curdir)
561
562KNOWNSIZES = {
563 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
564 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
565}
566
567def downloadURL(url, fname):
568 """
569 Download the contents of the url into the file.
570 """
571 try:
572 size = os.path.getsize(fname)
573 except OSError:
574 pass
575 else:
576 if KNOWNSIZES.get(url) == size:
577 print "Using existing file for", url
578 return
579 fpIn = urllib2.urlopen(url)
580 fpOut = open(fname, 'wb')
581 block = fpIn.read(10240)
582 try:
583 while block:
584 fpOut.write(block)
585 block = fpIn.read(10240)
586 fpIn.close()
587 fpOut.close()
588 except:
589 try:
590 os.unlink(fname)
591 except:
592 pass
593
594def buildRecipe(recipe, basedir, archList):
595 """
596 Build software using a recipe. This function does the
597 'configure;make;make install' dance for C software, with a possibility
598 to customize this process, basically a poor-mans DarwinPorts.
599 """
600 curdir = os.getcwd()
601
602 name = recipe['name']
603 url = recipe['url']
604 configure = recipe.get('configure', './configure')
605 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
606 shellQuote(basedir)))
607
608 archiveName = os.path.split(url)[-1]
609 sourceArchive = os.path.join(DEPSRC, archiveName)
610
611 if not os.path.exists(DEPSRC):
612 os.mkdir(DEPSRC)
613
614
615 if os.path.exists(sourceArchive):
616 print "Using local copy of %s"%(name,)
617
618 else:
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000619 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000620 print "Downloading %s"%(name,)
621 downloadURL(url, sourceArchive)
622 print "Archive for %s stored as %s"%(name, sourceArchive)
623
624 print "Extracting archive for %s"%(name,)
625 buildDir=os.path.join(WORKDIR, '_bld')
626 if not os.path.exists(buildDir):
627 os.mkdir(buildDir)
628
629 workDir = extractArchive(buildDir, sourceArchive)
630 os.chdir(workDir)
631 if 'buildDir' in recipe:
632 os.chdir(recipe['buildDir'])
633
634
635 for fn in recipe.get('patches', ()):
636 if fn.startswith('http://'):
637 # Download the patch before applying it.
638 path = os.path.join(DEPSRC, os.path.basename(fn))
639 downloadURL(fn, path)
640 fn = path
641
642 fn = os.path.join(curdir, fn)
643 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
644 shellQuote(fn),))
645
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000646 if configure is not None:
647 configure_args = [
648 "--prefix=/usr/local",
649 "--enable-static",
650 "--disable-shared",
651 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
652 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000653
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000654 if 'configure_pre' in recipe:
655 args = list(recipe['configure_pre'])
656 if '--disable-static' in args:
657 configure_args.remove('--enable-static')
658 if '--enable-shared' in args:
659 configure_args.remove('--disable-shared')
660 configure_args.extend(args)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000661
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000662 if recipe.get('useLDFlags', 1):
663 configure_args.extend([
664 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
665 ' -arch '.join(archList),
666 shellQuote(SDKPATH)[1:-1],
667 shellQuote(basedir)[1:-1],),
668 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000669 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000670 shellQuote(basedir)[1:-1],
671 ' -arch '.join(archList)),
672 ])
673 else:
674 configure_args.extend([
675 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
676 ' -arch '.join(archList),
677 shellQuote(SDKPATH)[1:-1],
678 shellQuote(basedir)[1:-1],),
679 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000680
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000681 if 'configure_post' in recipe:
682 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000683
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000684 configure_args.insert(0, configure)
685 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000686
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000687 print "Running configure for %s"%(name,)
688 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000689
690 print "Running install for %s"%(name,)
691 runCommand('{ ' + install + ' ;} 2>&1')
692
693 print "Done %s"%(name,)
694 print ""
695
696 os.chdir(curdir)
697
698def buildLibraries():
699 """
700 Build our dependencies into $WORKDIR/libraries/usr/local
701 """
702 print ""
703 print "Building required libraries"
704 print ""
705 universal = os.path.join(WORKDIR, 'libraries')
706 os.mkdir(universal)
707 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
708 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
709
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000710 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000711 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000712
713
714
715def buildPythonDocs():
Ronald Oussorend14b6002009-03-30 17:17:52 +0000716 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000717 # inside the framwork. pydoc and IDLE will pick it up there.
718 print "Install python documentation"
719 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussorend14b6002009-03-30 17:17:52 +0000720 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000721 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussorend14b6002009-03-30 17:17:52 +0000722 curDir = os.getcwd()
723 os.chdir(buildDir)
724 runCommand('make update')
Martin v. Löwis78ca4662010-04-22 13:15:47 +0000725 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussorend14b6002009-03-30 17:17:52 +0000726 os.chdir(curDir)
727 if not os.path.exists(docdir):
728 os.mkdir(docdir)
Ronald Oussorenc9d113b2009-09-29 19:35:03 +0000729 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000730
731
732def buildPython():
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000733 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000734
735 buildDir = os.path.join(WORKDIR, '_bld', 'python')
736 rootDir = os.path.join(WORKDIR, '_root')
737
738 if os.path.exists(buildDir):
739 shutil.rmtree(buildDir)
740 if os.path.exists(rootDir):
741 shutil.rmtree(rootDir)
742 os.mkdir(buildDir)
743 os.mkdir(rootDir)
744 os.mkdir(os.path.join(rootDir, 'empty-dir'))
745 curdir = os.getcwd()
746 os.chdir(buildDir)
747
748 # Not sure if this is still needed, the original build script
749 # claims that parts of the install assume python.exe exists.
750 os.symlink('python', os.path.join(buildDir, 'python.exe'))
751
752 # Extract the version from the configure file, needed to calculate
753 # several paths.
754 version = getVersion()
755
Ronald Oussoren524e36a2009-03-30 20:04:12 +0000756 # Since the extra libs are not in their installed framework location
757 # during the build, augment the library path so that the interpreter
758 # will find them during its extension import sanity checks.
759 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
760 'libraries', 'usr', 'local', 'lib')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000761 print "Running configure..."
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000762 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
763 "--with-universal-archs=%s "
764 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
765 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
766 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
767 UNIVERSALARCHS,
768 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000769 shellQuote(WORKDIR)[1:-1]))
770
771 print "Running make"
772 runCommand("make")
773
Ronald Oussorenaa560962006-11-07 15:53:38 +0000774 print "Running make frameworkinstall"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000775 runCommand("make frameworkinstall DESTDIR=%s"%(
776 shellQuote(rootDir)))
777
Ronald Oussorenaa560962006-11-07 15:53:38 +0000778 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000779 runCommand("make frameworkinstallextras DESTDIR=%s"%(
780 shellQuote(rootDir)))
781
Ronald Oussoren524e36a2009-03-30 20:04:12 +0000782 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000783 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000784 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
785 runCommand("mv %s/* %s"%(
786 shellQuote(os.path.join(
787 WORKDIR, 'libraries', 'Library', 'Frameworks',
788 'Python.framework', 'Versions', getVersion(),
789 'lib')),
790 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
791 'Python.framework', 'Versions', getVersion(),
792 'lib'))))
793
794 print "Fix file modes"
795 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000796 gid = grp.getgrnam('admin').gr_gid
797
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000798 for dirpath, dirnames, filenames in os.walk(frmDir):
799 for dn in dirnames:
800 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000801 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000802
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000803
804 for fn in filenames:
805 if os.path.islink(fn):
806 continue
807
808 # "chmod g+w $fn"
809 p = os.path.join(dirpath, fn)
810 st = os.stat(p)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000811 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
812 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000813
814 # We added some directories to the search path during the configure
815 # phase. Remove those because those directories won't be there on
816 # the end-users system.
817 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
818 'Versions', version, 'lib', 'python%s'%(version,),
819 'config', 'Makefile')
820 fp = open(path, 'r')
821 data = fp.read()
822 fp.close()
823
824 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
825 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
826 fp = open(path, 'w')
827 fp.write(data)
828 fp.close()
829
830 # Add symlinks in /usr/local/bin, using relative links
831 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
832 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
833 'Python.framework', 'Versions', version, 'bin')
834 if os.path.exists(usr_local_bin):
835 shutil.rmtree(usr_local_bin)
836 os.makedirs(usr_local_bin)
837 for fn in os.listdir(
838 os.path.join(frmDir, 'Versions', version, 'bin')):
839 os.symlink(os.path.join(to_framework, fn),
840 os.path.join(usr_local_bin, fn))
841
842 os.chdir(curdir)
843
844
845
846def patchFile(inPath, outPath):
847 data = fileContents(inPath)
848 data = data.replace('$FULL_VERSION', getFullVersion())
849 data = data.replace('$VERSION', getVersion())
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000850 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000851 data = data.replace('$ARCHITECTURES', "i386, ppc")
852 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000853
854 # This one is not handy as a template variable
855 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000856 fp = open(outPath, 'wb')
857 fp.write(data)
858 fp.close()
859
860def patchScript(inPath, outPath):
861 data = fileContents(inPath)
862 data = data.replace('@PYVER@', getVersion())
863 fp = open(outPath, 'wb')
864 fp.write(data)
865 fp.close()
866 os.chmod(outPath, 0755)
867
868
869
870def packageFromRecipe(targetDir, recipe):
871 curdir = os.getcwd()
872 try:
Ronald Oussorenaa560962006-11-07 15:53:38 +0000873 # The major version (such as 2.5) is included in the package name
874 # because having two version of python installed at the same time is
Ronald Oussorenc5555542006-06-11 20:24:45 +0000875 # common.
876 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000877 srcdir = recipe.get('source')
878 pkgroot = recipe.get('topdir', srcdir)
879 postflight = recipe.get('postflight')
880 readme = textwrap.dedent(recipe['readme'])
881 isRequired = recipe.get('required', True)
882
883 print "- building package %s"%(pkgname,)
884
885 # Substitute some variables
886 textvars = dict(
887 VER=getVersion(),
888 FULLVER=getFullVersion(),
889 )
890 readme = readme % textvars
891
892 if pkgroot is not None:
893 pkgroot = pkgroot % textvars
894 else:
895 pkgroot = '/'
896
897 if srcdir is not None:
898 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
899 srcdir = srcdir % textvars
900
901 if postflight is not None:
902 postflight = os.path.abspath(postflight)
903
904 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
905 os.makedirs(packageContents)
906
907 if srcdir is not None:
908 os.chdir(srcdir)
909 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
910 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
911 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
912
913 fn = os.path.join(packageContents, 'PkgInfo')
914 fp = open(fn, 'w')
915 fp.write('pmkrpkg1')
916 fp.close()
917
918 rsrcDir = os.path.join(packageContents, "Resources")
919 os.mkdir(rsrcDir)
920 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
921 fp.write(readme)
922 fp.close()
923
924 if postflight is not None:
925 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
926
927 vers = getFullVersion()
928 major, minor = map(int, getVersion().split('.', 2))
929 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000930 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
931 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
932 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000933 CFBundleShortVersionString=vers,
934 IFMajorVersion=major,
935 IFMinorVersion=minor,
936 IFPkgFormatVersion=0.10000000149011612,
937 IFPkgFlagAllowBackRev=False,
938 IFPkgFlagAuthorizationAction="RootAuthorization",
939 IFPkgFlagDefaultLocation=pkgroot,
940 IFPkgFlagFollowLinks=True,
941 IFPkgFlagInstallFat=True,
942 IFPkgFlagIsRequired=isRequired,
943 IFPkgFlagOverwritePermissions=False,
944 IFPkgFlagRelocatable=False,
945 IFPkgFlagRestartAction="NoRestart",
946 IFPkgFlagRootVolumeOnly=True,
947 IFPkgFlagUpdateInstalledLangauges=False,
948 )
949 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
950
951 pl = Plist(
952 IFPkgDescriptionDescription=readme,
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000953 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000954 IFPkgDescriptionVersion=vers,
955 )
956 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
957
958 finally:
959 os.chdir(curdir)
960
961
962def makeMpkgPlist(path):
963
964 vers = getFullVersion()
965 major, minor = map(int, getVersion().split('.', 2))
966
967 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +0000968 CFBundleGetInfoString="Python %s"%(vers,),
969 CFBundleIdentifier='org.python.Python',
970 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000971 CFBundleShortVersionString=vers,
972 IFMajorVersion=major,
973 IFMinorVersion=minor,
974 IFPkgFlagComponentDirectory="Contents/Packages",
975 IFPkgFlagPackageList=[
976 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +0000977 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000978 IFPkgFlagPackageSelection='selected'
979 )
Ronald Oussoren3aeda282009-09-20 20:17:15 +0000980 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000981 ],
982 IFPkgFormatVersion=0.10000000149011612,
983 IFPkgFlagBackgroundScaling="proportional",
984 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +0000985 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000986 )
987
988 writePlist(pl, path)
989
990
991def buildInstaller():
992
993 # Zap all compiled files
994 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
995 for fn in filenames:
996 if fn.endswith('.pyc') or fn.endswith('.pyo'):
997 os.unlink(os.path.join(dirpath, fn))
998
999 outdir = os.path.join(WORKDIR, 'installer')
1000 if os.path.exists(outdir):
1001 shutil.rmtree(outdir)
1002 os.mkdir(outdir)
1003
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001004 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001005 pkgcontents = os.path.join(pkgroot, 'Packages')
1006 os.makedirs(pkgcontents)
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001007 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001008 packageFromRecipe(pkgcontents, recipe)
1009
1010 rsrcDir = os.path.join(pkgroot, 'Resources')
1011
1012 fn = os.path.join(pkgroot, 'PkgInfo')
1013 fp = open(fn, 'w')
1014 fp.write('pmkrpkg1')
1015 fp.close()
1016
1017 os.mkdir(rsrcDir)
1018
1019 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1020 pl = Plist(
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001021 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001022 IFPkgDescriptionVersion=getVersion(),
1023 )
1024
1025 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1026 for fn in os.listdir('resources'):
1027 if fn == '.svn': continue
1028 if fn.endswith('.jpg'):
1029 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1030 else:
1031 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1032
Ronald Oussorenc5555542006-06-11 20:24:45 +00001033 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001034
1035
1036def installSize(clear=False, _saved=[]):
1037 if clear:
1038 del _saved[:]
1039 if not _saved:
1040 data = captureCommand("du -ks %s"%(
1041 shellQuote(os.path.join(WORKDIR, '_root'))))
1042 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1043 return _saved[0]
1044
1045
1046def buildDMG():
1047 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001048 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001049 """
1050 outdir = os.path.join(WORKDIR, 'diskimage')
1051 if os.path.exists(outdir):
1052 shutil.rmtree(outdir)
1053
1054 imagepath = os.path.join(outdir,
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001055 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001056 if INCLUDE_TIMESTAMP:
Ronald Oussoren3aeda282009-09-20 20:17:15 +00001057 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001058 imagepath = imagepath + '.dmg'
1059
1060 os.mkdir(outdir)
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001061 volname='Python %s'%(getFullVersion())
1062 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1063 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001064 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001065 shellQuote(imagepath + ".tmp.dmg" )))
1066
1067
1068 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1069 os.mkdir(os.path.join(WORKDIR, "mnt"))
1070 runCommand("hdiutil attach %s -mountroot %s"%(
1071 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1072
1073 # Custom icon for the DMG, shown when the DMG is mounted.
1074 shutil.copy("../Icons/Disk Image.icns",
1075 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1076 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1077 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1078
1079 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1080
1081 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1082 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1083 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1084 setIcon(imagepath, "../Icons/Disk Image.icns")
1085
1086 os.unlink(imagepath + ".tmp.dmg")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001087
1088 return imagepath
1089
1090
1091def setIcon(filePath, icnsPath):
1092 """
1093 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001094 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001095
Ronald Oussorend14b6002009-03-30 17:17:52 +00001096 toolPath = os.path.join(os.path.dirname(__file__), "seticon.app/Contents/MacOS/seticon")
1097 dirPath = os.path.dirname(__file__)
1098 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1099 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1100 # to connections to the window server.
1101 if not os.path.exists('seticon.app/Contents/MacOS'):
1102 os.makedirs('seticon.app/Contents/MacOS')
1103 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1104 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001105
Ronald Oussorend14b6002009-03-30 17:17:52 +00001106 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1107 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001108
1109def main():
1110 # First parse options and check if we can perform our work
1111 parseOptions()
1112 checkEnvironment()
1113
Ronald Oussoren7f2e6e62009-03-30 19:37:10 +00001114 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussorenc9d113b2009-09-29 19:35:03 +00001115 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001116
1117 if os.path.exists(WORKDIR):
1118 shutil.rmtree(WORKDIR)
1119 os.mkdir(WORKDIR)
1120
Ronald Oussoren7fd72822010-04-18 15:23:23 +00001121 os.environ['LC_ALL'] = 'C'
1122
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001123 # Then build third-party libraries such as sleepycat DB4.
1124 buildLibraries()
1125
1126 # Now build python itself
1127 buildPython()
Ronald Oussoren85da5e82009-03-31 13:23:26 +00001128
1129 # And then build the documentation
1130 # Remove the Deployment Target from the shell
1131 # environment, it's no longer needed and
1132 # an unexpected build target can cause problems
1133 # when Sphinx and its dependencies need to
1134 # be (re-)installed.
1135 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001136 buildPythonDocs()
Ronald Oussoren85da5e82009-03-31 13:23:26 +00001137
1138
1139 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001140 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonbe2c0a92008-10-04 21:33:08 +00001141 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren03674a32009-03-04 21:10:17 +00001142 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001143
Benjamin Petersonbe2c0a92008-10-04 21:33:08 +00001144 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001145 getVersion(),))
1146 os.chmod(folder, 0755)
1147 setIcon(folder, "../Icons/Python Folder.icns")
1148
1149 # Create the installer
1150 buildInstaller()
1151
1152 # And copy the readme into the directory containing the installer
1153 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1154
1155 # Ditto for the license file.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001156 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001157
1158 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1159 print >> fp, "# BUILD INFO"
1160 print >> fp, "# Date:", time.ctime()
1161 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1162 fp.close()
1163
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001164 # And copy it to a DMG
1165 buildDMG()
1166
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001167if __name__ == "__main__":
1168 main()