blob: ba920127c42300600a0d5e3a31b4a0fc401d5d04 [file] [log] [blame]
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001#!/usr/bin/python
Thomas Wouters477c8d52006-05-27 19:21:47 +00002"""
Ned Deilye59e4c52011-01-29 18:56:28 +00003This script is used to build "official" universal installers on Mac OS X.
4It requires at least Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK for
532-bit builds. 64-bit or four-way universal builds require at least
6OS X 10.5 and the 10.5 SDK.
Thomas Wouters477c8d52006-05-27 19:21:47 +00007
Ned Deilye59e4c52011-01-29 18:56:28 +00008Please ensure that this script keeps working with Python 2.5, to avoid
9bootstrap issues (/usr/bin/python is Python 2.5 on OSX 10.5). Sphinx,
10which is used to build the documentation, currently requires at least
11Python 2.4.
Thomas Wouters477c8d52006-05-27 19:21:47 +000012
13Usage: see USAGE variable in the script.
14"""
Benjamin Peterson623918e2008-12-20 22:50:25 +000015import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Thomas Wouters89f507f2006-12-13 04:49:30 +000016import grp
Thomas Wouters477c8d52006-05-27 19:21:47 +000017
Thomas Wouters89f507f2006-12-13 04:49:30 +000018INCLUDE_TIMESTAMP = 1
19VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000020
21from plistlib import Plist
22
23import MacOS
Thomas Wouters477c8d52006-05-27 19:21:47 +000024
25try:
26 from plistlib import writePlist
27except ImportError:
28 # We're run using python2.3
29 def writePlist(plist, path):
30 plist.write(path)
31
32def shellQuote(value):
33 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000034 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000035 a shell command.
36 """
37 return "'%s'"%(value.replace("'", "'\"'\"'"))
38
39def grepValue(fn, variable):
40 variable = variable + '='
41 for ln in open(fn, 'r'):
42 if ln.startswith(variable):
43 value = ln[len(variable):].strip()
44 return value[1:-1]
Benjamin Petersond9b7d482010-03-19 21:42:45 +000045 raise RuntimeError, "Cannot find variable %s" % variable[:-1]
Thomas Wouters477c8d52006-05-27 19:21:47 +000046
47def getVersion():
48 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
49
Benjamin Petersond9b7d482010-03-19 21:42:45 +000050def getVersionTuple():
51 return tuple([int(n) for n in getVersion().split('.')])
52
Thomas Wouters477c8d52006-05-27 19:21:47 +000053def getFullVersion():
54 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
55 for ln in open(fn):
56 if 'PY_VERSION' in ln:
57 return ln.split()[-1][1:-1]
Benjamin Peterson623918e2008-12-20 22:50:25 +000058 raise RuntimeError, "Cannot find full version??"
Thomas Wouters477c8d52006-05-27 19:21:47 +000059
Thomas Wouters89f507f2006-12-13 04:49:30 +000060# The directory we'll use to create the build (will be erased and recreated)
61WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000062
Thomas Wouters89f507f2006-12-13 04:49:30 +000063# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000064# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000065DEPSRC = os.path.join(WORKDIR, 'third-party')
66DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000067
68# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +000069
70### There are some issues with the SDK selection below here,
71### The resulting binary doesn't work on all platforms that
72### it should. Always default to the 10.4u SDK until that
73### isue is resolved.
74###
75##if int(os.uname()[2].split('.')[0]) == 8:
76## # Explicitly use the 10.4u (universal) SDK when
77## # building on 10.4, the system headers are not
78## # useable for a universal build
79## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
80##else:
81## SDKPATH = "/"
82
Thomas Wouters89f507f2006-12-13 04:49:30 +000083SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +000084
Ronald Oussoren1943f862009-03-30 19:39:14 +000085universal_opts_map = { '32-bit': ('i386', 'ppc',),
86 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +000087 'intel': ('i386', 'x86_64'),
88 '3-way': ('ppc', 'i386', 'x86_64'),
89 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
90default_target_map = {
91 '64-bit': '10.5',
92 '3-way': '10.5',
93 'intel': '10.5',
94 'all': '10.5',
95}
Ronald Oussoren1943f862009-03-30 19:39:14 +000096
97UNIVERSALOPTS = tuple(universal_opts_map.keys())
98
99UNIVERSALARCHS = '32-bit'
100
101ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000102
Ezio Melotti42da6632011-03-15 05:18:48 +0200103# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000104SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000105 os.path.dirname(
106 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000107 os.path.abspath(__file__
108 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000109
Ronald Oussoren1943f862009-03-30 19:39:14 +0000110# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
111DEPTARGET = '10.3'
112
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000113target_cc_map = {
114 '10.3': 'gcc-4.0',
115 '10.4': 'gcc-4.0',
116 '10.5': 'gcc-4.0',
117 '10.6': 'gcc-4.2',
118}
119
120CC = target_cc_map[DEPTARGET]
121
122PYTHON_3 = getVersionTuple() >= (3, 0)
123
Thomas Wouters89f507f2006-12-13 04:49:30 +0000124USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000125 Usage: build_python [options]
126
127 Options:
128 -? or -h: Show this message
129 -b DIR
130 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
131 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
132 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
133 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
135 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000136""")% globals()
137
138
139# Instructions for building libraries that are necessary for building a
140# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000141# [The recipes are defined here for convenience but instantiated later after
142# command line options have been processed.]
143def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000144 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000145
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000146 if DEPTARGET < '10.5':
147 result.extend([
148 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000149 name="Bzip2 1.0.6",
150 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
151 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000152 configure=None,
153 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
154 CC,
155 shellQuote(os.path.join(WORKDIR, 'libraries')),
156 ' -arch '.join(ARCHLIST),
157 SDKPATH,
158 ),
159 ),
160 dict(
161 name="ZLib 1.2.3",
162 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
163 checksum='debc62758716a169df9f62e6ab2bc634',
164 configure=None,
165 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
166 CC,
167 shellQuote(os.path.join(WORKDIR, 'libraries')),
168 ' -arch '.join(ARCHLIST),
169 SDKPATH,
170 ),
171 ),
172 dict(
173 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000174 name="GNU Readline 6.1.2",
175 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
176 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000177 patchlevel='0',
178 patches=[
179 # The readline maintainers don't do actual micro releases, but
180 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000181 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
182 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000183 ]
184 ),
185 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000186 name="SQLite 3.7.4",
187 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
188 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
189 configure_env=('CFLAGS="-Os'
190 ' -DSQLITE_ENABLE_FTS3'
191 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
192 ' -DSQLITE_ENABLE_RTREE'
193 ' -DSQLITE_TCL=0'
194 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000195 configure_pre=[
196 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000197 '--enable-shared=no',
198 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000199 '--disable-readline',
200 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000201 ]
202 ),
203 dict(
204 name="NCurses 5.5",
205 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
206 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
207 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000208 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000209 "--without-cxx",
210 "--without-ada",
211 "--without-progs",
212 "--without-curses-h",
213 "--enable-shared",
214 "--with-shared",
215 "--datadir=/usr/share",
216 "--sysconfdir=/etc",
217 "--sharedstatedir=/usr/com",
218 "--with-terminfo-dirs=/usr/share/terminfo",
219 "--with-default-terminfo-dir=/usr/share/terminfo",
220 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
221 "--enable-termcap",
222 ],
223 patches=[
224 "ncurses-5.5.patch",
225 ],
226 useLDFlags=False,
227 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
228 shellQuote(os.path.join(WORKDIR, 'libraries')),
229 shellQuote(os.path.join(WORKDIR, 'libraries')),
230 getVersion(),
231 ),
232 ),
233 ])
234
Ned Deily4f7ff782011-01-15 05:29:12 +0000235 if not PYTHON_3:
236 result.extend([
237 dict(
238 name="Sleepycat DB 4.7.25",
239 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
240 checksum='ec2b87e833779681a0c3a814aa71359e',
241 buildDir="build_unix",
242 configure="../dist/configure",
243 configure_pre=[
244 '--includedir=/usr/local/include/db4',
245 ]
246 ),
247 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000248
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000249 return result
250
Thomas Wouters477c8d52006-05-27 19:21:47 +0000251
Thomas Wouters477c8d52006-05-27 19:21:47 +0000252# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000253def pkg_recipes():
254 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
255 result = [
256 dict(
257 name="PythonFramework",
258 long_name="Python Framework",
259 source="/Library/Frameworks/Python.framework",
260 readme="""\
261 This package installs Python.framework, that is the python
262 interpreter and the standard library. This also includes Python
263 wrappers for lots of Mac OS X API's.
264 """,
265 postflight="scripts/postflight.framework",
266 selected='selected',
267 ),
268 dict(
269 name="PythonApplications",
270 long_name="GUI Applications",
271 source="/Applications/Python %(VER)s",
272 readme="""\
273 This package installs IDLE (an interactive Python IDE),
274 Python Launcher and Build Applet (create application bundles
275 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000276
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000277 It also installs a number of examples and demos.
278 """,
279 required=False,
280 selected='selected',
281 ),
282 dict(
283 name="PythonUnixTools",
284 long_name="UNIX command-line tools",
285 source="/usr/local/bin",
286 readme="""\
287 This package installs the unix tools in /usr/local/bin for
288 compatibility with older releases of Python. This package
289 is not necessary to use Python.
290 """,
291 required=False,
292 selected='selected',
293 ),
294 dict(
295 name="PythonDocumentation",
296 long_name="Python Documentation",
297 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
298 source="/pydocs",
299 readme="""\
300 This package installs the python documentation at a location
301 that is useable for pydoc and IDLE. If you have installed Xcode
302 it will also install a link to the documentation in
303 /Developer/Documentation/Python
304 """,
305 postflight="scripts/postflight.documentation",
306 required=False,
307 selected='selected',
308 ),
309 dict(
310 name="PythonProfileChanges",
311 long_name="Shell profile updater",
312 readme="""\
313 This packages updates your shell profile to make sure that
314 the Python tools are found by your shell in preference of
315 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000316
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000317 If you don't install this package you'll have to add
318 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
319 to your PATH by hand.
320 """,
321 postflight="scripts/postflight.patch-profile",
322 topdir="/Library/Frameworks/Python.framework",
323 source="/empty-dir",
324 required=False,
325 selected=unselected_for_python3,
326 ),
327 ]
328
329 if DEPTARGET < '10.4':
330 result.append(
331 dict(
332 name="PythonSystemFixes",
333 long_name="Fix system Python",
334 readme="""\
335 This package updates the system python installation on
336 Mac OS X 10.3 to ensure that you can build new python extensions
337 using that copy of python after installing this version.
338 """,
339 postflight="../Tools/fixapplepython23.py",
340 topdir="/Library/Frameworks/Python.framework",
341 source="/empty-dir",
342 required=False,
343 selected=unselected_for_python3,
344 )
345 )
346 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000347
Thomas Wouters477c8d52006-05-27 19:21:47 +0000348def fatal(msg):
349 """
350 A fatal error, bail out.
351 """
352 sys.stderr.write('FATAL: ')
353 sys.stderr.write(msg)
354 sys.stderr.write('\n')
355 sys.exit(1)
356
357def fileContents(fn):
358 """
359 Return the contents of the named file
360 """
361 return open(fn, 'rb').read()
362
363def runCommand(commandline):
364 """
Ezio Melotti13925002011-03-16 11:05:33 +0200365 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000366 unless the command fails.
367 """
368 fd = os.popen(commandline, 'r')
369 data = fd.read()
370 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000371 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000372 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000373 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000374
375 if VERBOSE:
376 sys.stdout.write(data); sys.stdout.flush()
377
378def captureCommand(commandline):
379 fd = os.popen(commandline, 'r')
380 data = fd.read()
381 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000382 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000383 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000384 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000385
386 return data
387
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000388def getTclTkVersion(configfile, versionline):
389 """
390 search Tcl or Tk configuration file for version line
391 """
392 try:
393 f = open(configfile, "r")
394 except:
395 fatal("Framework configuration file not found: %s" % configfile)
396
397 for l in f:
398 if l.startswith(versionline):
399 f.close()
400 return l
401
402 fatal("Version variable %s not found in framework configuration file: %s"
403 % (versionline, configfile))
404
Thomas Wouters477c8d52006-05-27 19:21:47 +0000405def checkEnvironment():
406 """
407 Check that we're running on a supported system.
408 """
409
Ned Deilye59e4c52011-01-29 18:56:28 +0000410 if sys.version_info[0:2] < (2, 4):
411 fatal("This script must be run with Python 2.4 or later")
412
Thomas Wouters477c8d52006-05-27 19:21:47 +0000413 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000414 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000415
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000416 if int(platform.release().split('.')[0]) < 8:
417 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000418
419 if not os.path.exists(SDKPATH):
420 fatal("Please install the latest version of Xcode and the %s SDK"%(
421 os.path.basename(SDKPATH[:-4])))
422
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000423 # Because we only support dynamic load of only one major/minor version of
424 # Tcl/Tk, ensure:
425 # 1. there are no user-installed frameworks of Tcl/Tk with version
426 # higher than the Apple-supplied system version
427 # 2. there is a user-installed framework in /Library/Frameworks with the
428 # same version as the system version. This allows users to choose
429 # to install a newer patch level.
430
431 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000432 #fw = dict(lower=framework.lower(),
433 # upper=framework.upper(),
434 # cap=framework.capitalize())
435 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
436 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
437 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000438 libfw = os.path.join('/', fwpth)
439 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000440 #version = "%(upper)s_VERSION" % fw
441 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000442 fatal("Version of %s must match %s" % (libfw, sysfw) )
443 if os.path.exists(usrfw):
444 fatal("Please rename %s to avoid possible dynamic load issues."
445 % usrfw)
446
447 # Remove inherited environment variables which might influence build
448 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
449 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
450 for ev in list(os.environ):
451 for prefix in environ_var_prefixes:
452 if ev.startswith(prefix) :
453 print "INFO: deleting environment variable %s=%s" % (
454 ev, os.environ[ev])
455 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000456
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000457 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
458 print "Setting default PATH: %s"%(os.environ['PATH'])
459
Thomas Wouters477c8d52006-05-27 19:21:47 +0000460
Thomas Wouters89f507f2006-12-13 04:49:30 +0000461def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000462 """
463 Parse arguments and update global settings.
464 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000465 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000466 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000467
468 if args is None:
469 args = sys.argv[1:]
470
471 try:
472 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000473 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
474 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000475 except getopt.error, msg:
476 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000477 sys.exit(1)
478
479 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000480 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000481 sys.exit(1)
482
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000483 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000485 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000486 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000487 sys.exit(0)
488
489 elif k in ('-d', '--build-dir'):
490 WORKDIR=v
491
492 elif k in ('--third-party',):
493 DEPSRC=v
494
495 elif k in ('--sdk-path',):
496 SDKPATH=v
497
498 elif k in ('--src-dir',):
499 SRCDIR=v
500
Ronald Oussoren1943f862009-03-30 19:39:14 +0000501 elif k in ('--dep-target', ):
502 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000503 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000504
505 elif k in ('--universal-archs', ):
506 if v in UNIVERSALOPTS:
507 UNIVERSALARCHS = v
508 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000509 if deptarget is None:
510 # Select alternate default deployment
511 # target
512 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000513 else:
514 raise NotImplementedError, v
515
Thomas Wouters477c8d52006-05-27 19:21:47 +0000516 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000517 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000518
519 SRCDIR=os.path.abspath(SRCDIR)
520 WORKDIR=os.path.abspath(WORKDIR)
521 SDKPATH=os.path.abspath(SDKPATH)
522 DEPSRC=os.path.abspath(DEPSRC)
523
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000524 CC=target_cc_map[DEPTARGET]
525
Benjamin Peterson623918e2008-12-20 22:50:25 +0000526 print "Settings:"
527 print " * Source directory:", SRCDIR
528 print " * Build directory: ", WORKDIR
529 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000530 print " * Third-party source:", DEPSRC
531 print " * Deployment target:", DEPTARGET
532 print " * Universal architectures:", ARCHLIST
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000533 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000534 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535
536
537
538
539def extractArchive(builddir, archiveName):
540 """
541 Extract a source archive into 'builddir'. Returns the path of the
542 extracted archive.
543
544 XXX: This function assumes that archives contain a toplevel directory
545 that is has the same name as the basename of the archive. This is
546 save enough for anything we use.
547 """
548 curdir = os.getcwd()
549 try:
550 os.chdir(builddir)
551 if archiveName.endswith('.tar.gz'):
552 retval = os.path.basename(archiveName[:-7])
553 if os.path.exists(retval):
554 shutil.rmtree(retval)
555 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
556
557 elif archiveName.endswith('.tar.bz2'):
558 retval = os.path.basename(archiveName[:-8])
559 if os.path.exists(retval):
560 shutil.rmtree(retval)
561 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
562
563 elif archiveName.endswith('.tar'):
564 retval = os.path.basename(archiveName[:-4])
565 if os.path.exists(retval):
566 shutil.rmtree(retval)
567 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
568
569 elif archiveName.endswith('.zip'):
570 retval = os.path.basename(archiveName[:-4])
571 if os.path.exists(retval):
572 shutil.rmtree(retval)
573 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
574
575 data = fp.read()
576 xit = fp.close()
577 if xit is not None:
578 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000579 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000580
581 return os.path.join(builddir, retval)
582
583 finally:
584 os.chdir(curdir)
585
586KNOWNSIZES = {
587 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
588 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
589}
590
591def downloadURL(url, fname):
592 """
593 Download the contents of the url into the file.
594 """
595 try:
596 size = os.path.getsize(fname)
597 except OSError:
598 pass
599 else:
600 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000601 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000602 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000603 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000604 fpOut = open(fname, 'wb')
605 block = fpIn.read(10240)
606 try:
607 while block:
608 fpOut.write(block)
609 block = fpIn.read(10240)
610 fpIn.close()
611 fpOut.close()
612 except:
613 try:
614 os.unlink(fname)
615 except:
616 pass
617
618def buildRecipe(recipe, basedir, archList):
619 """
620 Build software using a recipe. This function does the
621 'configure;make;make install' dance for C software, with a possibility
622 to customize this process, basically a poor-mans DarwinPorts.
623 """
624 curdir = os.getcwd()
625
626 name = recipe['name']
627 url = recipe['url']
628 configure = recipe.get('configure', './configure')
629 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
630 shellQuote(basedir)))
631
632 archiveName = os.path.split(url)[-1]
633 sourceArchive = os.path.join(DEPSRC, archiveName)
634
635 if not os.path.exists(DEPSRC):
636 os.mkdir(DEPSRC)
637
638
639 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000640 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000641
642 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000643 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000644 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000645 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000646 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000647
Benjamin Peterson623918e2008-12-20 22:50:25 +0000648 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000649 buildDir=os.path.join(WORKDIR, '_bld')
650 if not os.path.exists(buildDir):
651 os.mkdir(buildDir)
652
653 workDir = extractArchive(buildDir, sourceArchive)
654 os.chdir(workDir)
655 if 'buildDir' in recipe:
656 os.chdir(recipe['buildDir'])
657
658
659 for fn in recipe.get('patches', ()):
660 if fn.startswith('http://'):
661 # Download the patch before applying it.
662 path = os.path.join(DEPSRC, os.path.basename(fn))
663 downloadURL(fn, path)
664 fn = path
665
666 fn = os.path.join(curdir, fn)
667 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
668 shellQuote(fn),))
669
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000670 if configure is not None:
671 configure_args = [
672 "--prefix=/usr/local",
673 "--enable-static",
674 "--disable-shared",
675 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
676 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000678 if 'configure_pre' in recipe:
679 args = list(recipe['configure_pre'])
680 if '--disable-static' in args:
681 configure_args.remove('--enable-static')
682 if '--enable-shared' in args:
683 configure_args.remove('--disable-shared')
684 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000685
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000686 if recipe.get('useLDFlags', 1):
687 configure_args.extend([
688 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
689 ' -arch '.join(archList),
690 shellQuote(SDKPATH)[1:-1],
691 shellQuote(basedir)[1:-1],),
692 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000693 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000694 shellQuote(basedir)[1:-1],
695 ' -arch '.join(archList)),
696 ])
697 else:
698 configure_args.extend([
699 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
700 ' -arch '.join(archList),
701 shellQuote(SDKPATH)[1:-1],
702 shellQuote(basedir)[1:-1],),
703 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000705 if 'configure_post' in recipe:
706 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000707
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000708 configure_args.insert(0, configure)
709 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000710
Ned Deily4f7ff782011-01-15 05:29:12 +0000711 if 'configure_env' in recipe:
712 configure_args.insert(0, recipe['configure_env'])
713
Benjamin Peterson623918e2008-12-20 22:50:25 +0000714 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000715 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000716
Benjamin Peterson623918e2008-12-20 22:50:25 +0000717 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000718 runCommand('{ ' + install + ' ;} 2>&1')
719
Benjamin Peterson623918e2008-12-20 22:50:25 +0000720 print "Done %s"%(name,)
721 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000722
723 os.chdir(curdir)
724
725def buildLibraries():
726 """
727 Build our dependencies into $WORKDIR/libraries/usr/local
728 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000729 print ""
730 print "Building required libraries"
731 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000732 universal = os.path.join(WORKDIR, 'libraries')
733 os.mkdir(universal)
734 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
735 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
736
Ronald Oussoren1943f862009-03-30 19:39:14 +0000737 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000738 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000739
740
741
742def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000743 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000744 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000745 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000746 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000747 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000748 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000749 curDir = os.getcwd()
750 os.chdir(buildDir)
751 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000752 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000753 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000754 if not os.path.exists(docdir):
755 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000756 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000757
758
759def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000760 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000761
762 buildDir = os.path.join(WORKDIR, '_bld', 'python')
763 rootDir = os.path.join(WORKDIR, '_root')
764
765 if os.path.exists(buildDir):
766 shutil.rmtree(buildDir)
767 if os.path.exists(rootDir):
768 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000769 os.makedirs(buildDir)
770 os.makedirs(rootDir)
771 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000772 curdir = os.getcwd()
773 os.chdir(buildDir)
774
775 # Not sure if this is still needed, the original build script
776 # claims that parts of the install assume python.exe exists.
777 os.symlink('python', os.path.join(buildDir, 'python.exe'))
778
779 # Extract the version from the configure file, needed to calculate
780 # several paths.
781 version = getVersion()
782
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000783 # Since the extra libs are not in their installed framework location
784 # during the build, augment the library path so that the interpreter
785 # will find them during its extension import sanity checks.
786 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
787 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000788 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000789 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000790 "--with-universal-archs=%s "
791 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000792 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
793 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
794 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
795 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000796 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000797 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000798 shellQuote(WORKDIR)[1:-1]))
799
Benjamin Peterson623918e2008-12-20 22:50:25 +0000800 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000801 runCommand("make")
802
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000803 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000804 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000805 shellQuote(rootDir)))
806
Benjamin Peterson623918e2008-12-20 22:50:25 +0000807 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000808 runCommand("make frameworkinstallextras DESTDIR=%s"%(
809 shellQuote(rootDir)))
810
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000811 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000812 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000813 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
814 runCommand("mv %s/* %s"%(
815 shellQuote(os.path.join(
816 WORKDIR, 'libraries', 'Library', 'Frameworks',
817 'Python.framework', 'Versions', getVersion(),
818 'lib')),
819 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
820 'Python.framework', 'Versions', getVersion(),
821 'lib'))))
822
Benjamin Peterson623918e2008-12-20 22:50:25 +0000823 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000824 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000825 gid = grp.getgrnam('admin').gr_gid
826
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827 for dirpath, dirnames, filenames in os.walk(frmDir):
828 for dn in dirnames:
829 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000830 os.chown(os.path.join(dirpath, dn), -1, gid)
831
Thomas Wouters477c8d52006-05-27 19:21:47 +0000832
833 for fn in filenames:
834 if os.path.islink(fn):
835 continue
836
837 # "chmod g+w $fn"
838 p = os.path.join(dirpath, fn)
839 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000840 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
841 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000842
Ned Deilye59e4c52011-01-29 18:56:28 +0000843 if PYTHON_3:
844 LDVERSION=None
845 VERSION=None
846 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000847
Ned Deilye59e4c52011-01-29 18:56:28 +0000848 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000849 for ln in fp:
850 if ln.startswith('VERSION='):
851 VERSION=ln.split()[1]
852 if ln.startswith('ABIFLAGS='):
853 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000854 if ln.startswith('LDVERSION='):
855 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000856 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000857
Ned Deilye59e4c52011-01-29 18:56:28 +0000858 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
859 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
860 config_suffix = '-' + LDVERSION
861 else:
862 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000863
Thomas Wouters477c8d52006-05-27 19:21:47 +0000864 # We added some directories to the search path during the configure
865 # phase. Remove those because those directories won't be there on
866 # the end-users system.
867 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
868 'Versions', version, 'lib', 'python%s'%(version,),
Ned Deilye59e4c52011-01-29 18:56:28 +0000869 'config' + config_suffix, 'Makefile')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000870 fp = open(path, 'r')
871 data = fp.read()
872 fp.close()
873
874 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
875 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
876 fp = open(path, 'w')
877 fp.write(data)
878 fp.close()
879
880 # Add symlinks in /usr/local/bin, using relative links
881 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
882 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
883 'Python.framework', 'Versions', version, 'bin')
884 if os.path.exists(usr_local_bin):
885 shutil.rmtree(usr_local_bin)
886 os.makedirs(usr_local_bin)
887 for fn in os.listdir(
888 os.path.join(frmDir, 'Versions', version, 'bin')):
889 os.symlink(os.path.join(to_framework, fn),
890 os.path.join(usr_local_bin, fn))
891
892 os.chdir(curdir)
893
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000894 if PYTHON_3:
895 # Remove the 'Current' link, that way we don't accidently mess
896 # with an already installed version of python 2
897 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
898 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000899
900def patchFile(inPath, outPath):
901 data = fileContents(inPath)
902 data = data.replace('$FULL_VERSION', getFullVersion())
903 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000904 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000905 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000906 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000907
908 # This one is not handy as a template variable
909 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000910 fp = open(outPath, 'wb')
911 fp.write(data)
912 fp.close()
913
914def patchScript(inPath, outPath):
915 data = fileContents(inPath)
916 data = data.replace('@PYVER@', getVersion())
917 fp = open(outPath, 'wb')
918 fp.write(data)
919 fp.close()
920 os.chmod(outPath, 0755)
921
922
923
924def packageFromRecipe(targetDir, recipe):
925 curdir = os.getcwd()
926 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000927 # The major version (such as 2.5) is included in the package name
928 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000929 # common.
930 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000931 srcdir = recipe.get('source')
932 pkgroot = recipe.get('topdir', srcdir)
933 postflight = recipe.get('postflight')
934 readme = textwrap.dedent(recipe['readme'])
935 isRequired = recipe.get('required', True)
936
Benjamin Peterson623918e2008-12-20 22:50:25 +0000937 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000938
939 # Substitute some variables
940 textvars = dict(
941 VER=getVersion(),
942 FULLVER=getFullVersion(),
943 )
944 readme = readme % textvars
945
946 if pkgroot is not None:
947 pkgroot = pkgroot % textvars
948 else:
949 pkgroot = '/'
950
951 if srcdir is not None:
952 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
953 srcdir = srcdir % textvars
954
955 if postflight is not None:
956 postflight = os.path.abspath(postflight)
957
958 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
959 os.makedirs(packageContents)
960
961 if srcdir is not None:
962 os.chdir(srcdir)
963 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
964 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
965 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
966
967 fn = os.path.join(packageContents, 'PkgInfo')
968 fp = open(fn, 'w')
969 fp.write('pmkrpkg1')
970 fp.close()
971
972 rsrcDir = os.path.join(packageContents, "Resources")
973 os.mkdir(rsrcDir)
974 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
975 fp.write(readme)
976 fp.close()
977
978 if postflight is not None:
979 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
980
981 vers = getFullVersion()
982 major, minor = map(int, getVersion().split('.', 2))
983 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000984 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
985 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
986 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000987 CFBundleShortVersionString=vers,
988 IFMajorVersion=major,
989 IFMinorVersion=minor,
990 IFPkgFormatVersion=0.10000000149011612,
991 IFPkgFlagAllowBackRev=False,
992 IFPkgFlagAuthorizationAction="RootAuthorization",
993 IFPkgFlagDefaultLocation=pkgroot,
994 IFPkgFlagFollowLinks=True,
995 IFPkgFlagInstallFat=True,
996 IFPkgFlagIsRequired=isRequired,
997 IFPkgFlagOverwritePermissions=False,
998 IFPkgFlagRelocatable=False,
999 IFPkgFlagRestartAction="NoRestart",
1000 IFPkgFlagRootVolumeOnly=True,
1001 IFPkgFlagUpdateInstalledLangauges=False,
1002 )
1003 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1004
1005 pl = Plist(
1006 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001007 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001008 IFPkgDescriptionVersion=vers,
1009 )
1010 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1011
1012 finally:
1013 os.chdir(curdir)
1014
1015
1016def makeMpkgPlist(path):
1017
1018 vers = getFullVersion()
1019 major, minor = map(int, getVersion().split('.', 2))
1020
1021 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001022 CFBundleGetInfoString="Python %s"%(vers,),
1023 CFBundleIdentifier='org.python.Python',
1024 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001025 CFBundleShortVersionString=vers,
1026 IFMajorVersion=major,
1027 IFMinorVersion=minor,
1028 IFPkgFlagComponentDirectory="Contents/Packages",
1029 IFPkgFlagPackageList=[
1030 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001031 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001032 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001033 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001034 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001035 ],
1036 IFPkgFormatVersion=0.10000000149011612,
1037 IFPkgFlagBackgroundScaling="proportional",
1038 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001039 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001040 )
1041
1042 writePlist(pl, path)
1043
1044
1045def buildInstaller():
1046
1047 # Zap all compiled files
1048 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1049 for fn in filenames:
1050 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1051 os.unlink(os.path.join(dirpath, fn))
1052
1053 outdir = os.path.join(WORKDIR, 'installer')
1054 if os.path.exists(outdir):
1055 shutil.rmtree(outdir)
1056 os.mkdir(outdir)
1057
Ronald Oussoren1943f862009-03-30 19:39:14 +00001058 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001059 pkgcontents = os.path.join(pkgroot, 'Packages')
1060 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001061 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001062 packageFromRecipe(pkgcontents, recipe)
1063
1064 rsrcDir = os.path.join(pkgroot, 'Resources')
1065
1066 fn = os.path.join(pkgroot, 'PkgInfo')
1067 fp = open(fn, 'w')
1068 fp.write('pmkrpkg1')
1069 fp.close()
1070
1071 os.mkdir(rsrcDir)
1072
1073 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1074 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001075 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001076 IFPkgDescriptionVersion=getVersion(),
1077 )
1078
1079 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1080 for fn in os.listdir('resources'):
1081 if fn == '.svn': continue
1082 if fn.endswith('.jpg'):
1083 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1084 else:
1085 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1086
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001087 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001088
1089
1090def installSize(clear=False, _saved=[]):
1091 if clear:
1092 del _saved[:]
1093 if not _saved:
1094 data = captureCommand("du -ks %s"%(
1095 shellQuote(os.path.join(WORKDIR, '_root'))))
1096 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1097 return _saved[0]
1098
1099
1100def buildDMG():
1101 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001102 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001103 """
1104 outdir = os.path.join(WORKDIR, 'diskimage')
1105 if os.path.exists(outdir):
1106 shutil.rmtree(outdir)
1107
1108 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001109 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001110 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001111 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001112 imagepath = imagepath + '.dmg'
1113
1114 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001115 volname='Python %s'%(getFullVersion())
1116 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1117 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001118 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001119 shellQuote(imagepath + ".tmp.dmg" )))
1120
1121
1122 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1123 os.mkdir(os.path.join(WORKDIR, "mnt"))
1124 runCommand("hdiutil attach %s -mountroot %s"%(
1125 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1126
1127 # Custom icon for the DMG, shown when the DMG is mounted.
1128 shutil.copy("../Icons/Disk Image.icns",
1129 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1130 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1131 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1132
1133 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1134
1135 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1136 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1137 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1138 setIcon(imagepath, "../Icons/Disk Image.icns")
1139
1140 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001141
1142 return imagepath
1143
1144
1145def setIcon(filePath, icnsPath):
1146 """
1147 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001148 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001149
Ronald Oussoren70050672010-04-30 15:00:26 +00001150 dirPath = os.path.normpath(os.path.dirname(__file__))
1151 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001152 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1153 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1154 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001155 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1156 if not os.path.exists(appPath):
1157 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001158 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1159 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001160
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001161 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1162 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001163
1164def main():
1165 # First parse options and check if we can perform our work
1166 parseOptions()
1167 checkEnvironment()
1168
Ronald Oussoren1943f862009-03-30 19:39:14 +00001169 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001170 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001171
1172 if os.path.exists(WORKDIR):
1173 shutil.rmtree(WORKDIR)
1174 os.mkdir(WORKDIR)
1175
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001176 os.environ['LC_ALL'] = 'C'
1177
Thomas Wouters477c8d52006-05-27 19:21:47 +00001178 # Then build third-party libraries such as sleepycat DB4.
1179 buildLibraries()
1180
1181 # Now build python itself
1182 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001183
1184 # And then build the documentation
1185 # Remove the Deployment Target from the shell
1186 # environment, it's no longer needed and
1187 # an unexpected build target can cause problems
1188 # when Sphinx and its dependencies need to
1189 # be (re-)installed.
1190 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001191 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001192
1193
1194 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001195 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001196 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001197 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001198
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001199 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001200 getVersion(),))
1201 os.chmod(folder, 0755)
1202 setIcon(folder, "../Icons/Python Folder.icns")
1203
1204 # Create the installer
1205 buildInstaller()
1206
1207 # And copy the readme into the directory containing the installer
1208 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1209
1210 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001211 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001212
1213 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001214 print >> fp, "# BUILD INFO"
1215 print >> fp, "# Date:", time.ctime()
1216 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001217 fp.close()
1218
Thomas Wouters477c8d52006-05-27 19:21:47 +00001219 # And copy it to a DMG
1220 buildDMG()
1221
Thomas Wouters477c8d52006-05-27 19:21:47 +00001222if __name__ == "__main__":
1223 main()