blob: ba920127c42300600a0d5e3a31b4a0fc401d5d04 [file] [log] [blame]
Ned Deily53c460d2011-01-30 01:43:40 +00001#!/usr/bin/python
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00002"""
Ned Deily53c460d2011-01-30 01:43:40 +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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00007
Ned Deily53c460d2011-01-30 01:43:40 +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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000012
13Usage: see USAGE variable in the script.
14"""
15import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
Ronald Oussoren74d3eef2006-10-10 07:55:06 +000016import grp
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000017
Ronald Oussoren158ad592006-11-07 16:00:34 +000018INCLUDE_TIMESTAMP = 1
19VERBOSE = 1
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000020
21from plistlib import Plist
22
23import MacOS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +000034 Return the string value in a form that can safely be inserted into
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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]
Ned Deily53c460d2011-01-30 01:43:40 +000045 raise RuntimeError, "Cannot find variable %s" % variable[:-1]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000046
47def getVersion():
48 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
49
Ned Deily53c460d2011-01-30 01:43:40 +000050def getVersionTuple():
51 return tuple([int(n) for n in getVersion().split('.')])
52
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000058 raise RuntimeError, "Cannot find full version??"
59
Ronald Oussorenaa560962006-11-07 15:53:38 +000060# The directory we'll use to create the build (will be erased and recreated)
Ronald Oussoren158ad592006-11-07 16:00:34 +000061WORKDIR = "/tmp/_py"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000062
Ronald Oussorenaa560962006-11-07 15:53:38 +000063# The directory we'll use to store third-party sources. Set this to something
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000064# else if you don't want to re-fetch required libraries every time.
Ronald Oussoren158ad592006-11-07 16:00:34 +000065DEPSRC = os.path.join(WORKDIR, 'third-party')
66DEPSRC = os.path.expanduser('~/Universal/other-sources')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000067
68# Location of the preferred SDK
Ronald Oussoren209d4c32009-09-29 19:34:13 +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
83SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +000084
Ronald Oussoren508282e2009-03-30 19:34:51 +000085universal_opts_map = { '32-bit': ('i386', 'ppc',),
86 '64-bit': ('x86_64', 'ppc64',),
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Oussoren508282e2009-03-30 19:34:51 +000096
97UNIVERSALOPTS = tuple(universal_opts_map.keys())
98
99UNIVERSALARCHS = '32-bit'
100
101ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000102
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200103# Source directory (assume we're in Mac/BuildScript)
Ronald Oussoren158ad592006-11-07 16:00:34 +0000104SRCDIR = os.path.dirname(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000105 os.path.dirname(
106 os.path.dirname(
107 os.path.abspath(__file__
108 ))))
109
Ronald Oussoren508282e2009-03-30 19:34:51 +0000110# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
111DEPTARGET = '10.3'
112
Ronald Oussoren209d4c32009-09-29 19:34:13 +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
Ned Deily53c460d2011-01-30 01:43:40 +0000122PYTHON_3 = getVersionTuple() >= (3, 0)
123
Ronald Oussoren158ad592006-11-07 16:00:34 +0000124USAGE = textwrap.dedent("""\
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +0000134 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
135 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000136""")% globals()
137
138
139# Instructions for building libraries that are necessary for building a
140# batteries included python.
Ronald Oussoren508282e2009-03-30 19:34:51 +0000141# [The recipes are defined here for convenience but instantiated later after
142# command line options have been processed.]
143def library_recipes():
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000144 result = []
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000145
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000146 if DEPTARGET < '10.5':
147 result.extend([
148 dict(
Ned Deily53c460d2011-01-30 01:43:40 +0000149 name="Bzip2 1.0.6",
150 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
151 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000152 configure=None,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000153 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
154 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000155 shellQuote(os.path.join(WORKDIR, 'libraries')),
156 ' -arch '.join(ARCHLIST),
157 SDKPATH,
Ronald Oussoren508282e2009-03-30 19:34:51 +0000158 ),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000159 ),
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,
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000165 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
166 CC,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000167 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 Deily53c460d2011-01-30 01:43:40 +0000174 name="GNU Readline 6.1.2",
175 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
176 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000177 patchlevel='0',
178 patches=[
179 # The readline maintainers don't do actual micro releases, but
180 # just ship a set of patches.
Ned Deily53c460d2011-01-30 01:43:40 +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',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000183 ]
184 ),
185 dict(
Ned Deily53c460d2011-01-30 01:43:40 +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 '"'),
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000195 configure_pre=[
196 '--enable-threadsafe',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000197 '--enable-shared=no',
198 '--enable-static=yes',
Ned Deily53c460d2011-01-30 01:43:40 +0000199 '--disable-readline',
200 '--disable-dependency-tracking',
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deily53c460d2011-01-30 01:43:40 +0000208 "--enable-widec",
Ronald Oussorenc66ced32009-09-20 20:16:11 +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 Deily53c460d2011-01-30 01:43:40 +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 ])
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000248
249 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000250
251
252# Instructions for building packages inside the .mpkg.
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000253def pkg_recipes():
Ned Deily53c460d2011-01-30 01:43:40 +0000254 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000255 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",
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000266 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000267 ),
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).
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000276
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000277 It also installs a number of examples and demos.
278 """,
279 required=False,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000280 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000281 ),
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,
Ronald Oussoren2f4f63a2010-07-23 11:11:26 +0000292 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000293 ),
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,
Ronald Oussoren1a13cff2009-12-24 13:30:42 +0000307 selected='selected',
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000308 ),
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.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000316
Ronald Oussorenc66ced32009-09-20 20:16:11 +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,
Ned Deily53c460d2011-01-30 01:43:40 +0000325 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000326 ),
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,
Ned Deily53c460d2011-01-30 01:43:40 +0000343 selected=unselected_for_python3,
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000344 )
345 )
346 return result
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000347
348def 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 Melottic2077b02011-03-16 12:34:31 +0200365 Run a command and raise RuntimeError if it fails. Output is suppressed
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000366 unless the command fails.
367 """
368 fd = os.popen(commandline, 'r')
369 data = fd.read()
370 xit = fd.close()
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000371 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000372 sys.stdout.write(data)
373 raise RuntimeError, "command failed: %s"%(commandline,)
374
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 Peterson5b63acd2008-03-29 15:24:25 +0000382 if xit is not None:
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000383 sys.stdout.write(data)
384 raise RuntimeError, "command failed: %s"%(commandline,)
385
386 return data
387
Ronald Oussoren287128a2010-04-18 14:01:05 +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
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000405def checkEnvironment():
406 """
407 Check that we're running on a supported system.
408 """
409
Ned Deily53c460d2011-01-30 01:43:40 +0000410 if sys.version_info[0:2] < (2, 4):
411 fatal("This script must be run with Python 2.4 or later")
412
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000413 if platform.system() != 'Darwin':
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000414 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000415
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000416 if int(platform.release().split('.')[0]) < 8:
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000417 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren287128a2010-04-18 14:01:05 +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']:
Ned Deily53c460d2011-01-30 01:43:40 +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 Oussoren287128a2010-04-18 14:01:05 +0000438 libfw = os.path.join('/', fwpth)
439 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily53c460d2011-01-30 01:43:40 +0000440 #version = "%(upper)s_VERSION" % fw
441 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussoren287128a2010-04-18 14:01:05 +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]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000456
Ronald Oussorenac4b7ad2010-04-20 05:50:44 +0000457 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
458 print "Setting default PATH: %s"%(os.environ['PATH'])
459
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000460
Ronald Oussoren158ad592006-11-07 16:00:34 +0000461def parseOptions(args=None):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000462 """
463 Parse arguments and update global settings.
464 """
Ronald Oussoren508282e2009-03-30 19:34:51 +0000465 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000466 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000467
468 if args is None:
469 args = sys.argv[1:]
470
471 try:
472 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren508282e2009-03-30 19:34:51 +0000473 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
474 'dep-target=', 'universal-archs=', 'help' ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000475 except getopt.error, msg:
476 print msg
477 sys.exit(1)
478
479 if args:
480 print "Additional arguments"
481 sys.exit(1)
482
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000483 deptarget = None
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000484 for k, v in options:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000485 if k in ('-h', '-?', '--help'):
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000486 print USAGE
487 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 Oussoren508282e2009-03-30 19:34:51 +0000501 elif k in ('--dep-target', ):
502 DEPTARGET=v
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000503 deptarget=v
Ronald Oussoren508282e2009-03-30 19:34:51 +0000504
505 elif k in ('--universal-archs', ):
506 if v in UNIVERSALOPTS:
507 UNIVERSALARCHS = v
508 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Ronald Oussorenc66ced32009-09-20 20:16:11 +0000509 if deptarget is None:
510 # Select alternate default deployment
511 # target
512 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren508282e2009-03-30 19:34:51 +0000513 else:
514 raise NotImplementedError, v
515
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000516 else:
517 raise NotImplementedError, k
518
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
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000524 CC=target_cc_map[DEPTARGET]
525
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000526 print "Settings:"
527 print " * Source directory:", SRCDIR
528 print " * Build directory: ", WORKDIR
529 print " * SDK location: ", SDKPATH
Ronald Oussoren508282e2009-03-30 19:34:51 +0000530 print " * Third-party source:", DEPSRC
531 print " * Deployment target:", DEPTARGET
532 print " * Universal architectures:", ARCHLIST
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000533 print " * C compiler:", CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000534 print ""
535
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)
579 raise RuntimeError, "Cannot extract %s"%(archiveName,)
580
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:
601 print "Using existing file for", url
602 return
603 fpIn = urllib2.urlopen(url)
604 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):
640 print "Using local copy of %s"%(name,)
641
642 else:
Ronald Oussoren508282e2009-03-30 19:34:51 +0000643 print "Did not find local copy of %s"%(name,)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000644 print "Downloading %s"%(name,)
645 downloadURL(url, sourceArchive)
646 print "Archive for %s stored as %s"%(name, sourceArchive)
647
648 print "Extracting archive for %s"%(name,)
649 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
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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 ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000677
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000685
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000693 shellQuote(SDKPATH)[1:-1],
Ronald Oussoren9b8b6192006-06-27 12:53:52 +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 ])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000704
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000705 if 'configure_post' in recipe:
706 configure_args = configure_args = list(recipe['configure_post'])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000707
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000708 configure_args.insert(0, configure)
709 configure_args = [ shellQuote(a) for a in configure_args ]
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000710
Ned Deily53c460d2011-01-30 01:43:40 +0000711 if 'configure_env' in recipe:
712 configure_args.insert(0, recipe['configure_env'])
713
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000714 print "Running configure for %s"%(name,)
715 runCommand(' '.join(configure_args) + ' 2>&1')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000716
717 print "Running install for %s"%(name,)
718 runCommand('{ ' + install + ' ;} 2>&1')
719
720 print "Done %s"%(name,)
721 print ""
722
723 os.chdir(curdir)
724
725def buildLibraries():
726 """
727 Build our dependencies into $WORKDIR/libraries/usr/local
728 """
729 print ""
730 print "Building required libraries"
731 print ""
732 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 Oussoren508282e2009-03-30 19:34:51 +0000737 for recipe in library_recipes():
Ronald Oussoren9b8b6192006-06-27 12:53:52 +0000738 buildRecipe(recipe, universal, ARCHLIST)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000739
740
741
742def buildPythonDocs():
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000743 # This stores the documentation as Resources/English.lproj/Documentation
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000744 # inside the framwork. pydoc and IDLE will pick it up there.
745 print "Install python documentation"
746 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000747 buildDir = os.path.join('../../Doc')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000748 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000749 curDir = os.getcwd()
750 os.chdir(buildDir)
751 runCommand('make update')
Martin v. Löwis896c4772010-04-22 11:34:36 +0000752 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren648a4fe2009-03-30 17:15:29 +0000753 os.chdir(curDir)
754 if not os.path.exists(docdir):
755 os.mkdir(docdir)
Ronald Oussoren209d4c32009-09-29 19:34:13 +0000756 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000757
758
759def buildPython():
Ronald Oussoren508282e2009-03-30 19:34:51 +0000760 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily53c460d2011-01-30 01:43:40 +0000769 os.makedirs(buildDir)
770 os.makedirs(rootDir)
771 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren008af852009-03-30 20:02:08 +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')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000788 print "Running configure..."
Ronald Oussoren508282e2009-03-30 19:34:51 +0000789 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
790 "--with-universal-archs=%s "
Ned Deily53c460d2011-01-30 01:43:40 +0000791 "%s "
Ronald Oussoren508282e2009-03-30 19:34:51 +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,
Ned Deily53c460d2011-01-30 01:43:40 +0000796 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren508282e2009-03-30 19:34:51 +0000797 shellQuote(WORKDIR)[1:-1],
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000798 shellQuote(WORKDIR)[1:-1]))
799
800 print "Running make"
801 runCommand("make")
802
Ned Deily53c460d2011-01-30 01:43:40 +0000803 print "Running make install"
804 runCommand("make install DESTDIR=%s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000805 shellQuote(rootDir)))
806
Ronald Oussorenaa560962006-11-07 15:53:38 +0000807 print "Running make frameworkinstallextras"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000808 runCommand("make frameworkinstallextras DESTDIR=%s"%(
809 shellQuote(rootDir)))
810
Ronald Oussoren008af852009-03-30 20:02:08 +0000811 del os.environ['DYLD_LIBRARY_PATH']
Ronald Oussorenaa560962006-11-07 15:53:38 +0000812 print "Copying required shared libraries"
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
823 print "Fix file modes"
824 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000825 gid = grp.getgrnam('admin').gr_gid
826
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000827 for dirpath, dirnames, filenames in os.walk(frmDir):
828 for dn in dirnames:
829 os.chmod(os.path.join(dirpath, dn), 0775)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000830 os.chown(os.path.join(dirpath, dn), -1, gid)
Tim Petersef3f32f2006-10-18 05:09:12 +0000831
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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)
Ronald Oussoren74d3eef2006-10-10 07:55:06 +0000840 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
841 os.chown(p, -1, gid)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000842
Ned Deily53c460d2011-01-30 01:43:40 +0000843 if PYTHON_3:
844 LDVERSION=None
845 VERSION=None
846 ABIFLAGS=None
847
848 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
849 for ln in fp:
850 if ln.startswith('VERSION='):
851 VERSION=ln.split()[1]
852 if ln.startswith('ABIFLAGS='):
853 ABIFLAGS=ln.split()[1]
854 if ln.startswith('LDVERSION='):
855 LDVERSION=ln.split()[1]
856 fp.close()
857
858 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
859 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
860 config_suffix = '-' + LDVERSION
861 else:
862 config_suffix = '' # Python 2.x
863
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Deily53c460d2011-01-30 01:43:40 +0000869 'config' + config_suffix, 'Makefile')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
Ned Deily53c460d2011-01-30 01:43:40 +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'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000899
900def patchFile(inPath, outPath):
901 data = fileContents(inPath)
902 data = data.replace('$FULL_VERSION', getFullVersion())
903 data = data.replace('$VERSION', getVersion())
Ronald Oussoren508282e2009-03-30 19:34:51 +0000904 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussoren1e0a9982010-10-20 13:01:04 +0000905 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +0000906 data = data.replace('$INSTALL_SIZE', installSize())
Ronald Oussorenc5555542006-06-11 20:24:45 +0000907
908 # This one is not handy as a template variable
909 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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:
Ronald Oussorenaa560962006-11-07 15:53:38 +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
Ronald Oussorenc5555542006-06-11 20:24:45 +0000929 # common.
930 pkgname = '%s-%s'%(recipe['name'], getVersion())
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
937 print "- building package %s"%(pkgname,)
938
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 Oussoren508282e2009-03-30 19:34:51 +0000984 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
985 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
986 CFBundleName='Python.%s'%(pkgname,),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001007 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001022 CFBundleGetInfoString="Python %s"%(vers,),
1023 CFBundleIdentifier='org.python.Python',
1024 CFBundleName='Python',
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001025 CFBundleShortVersionString=vers,
1026 IFMajorVersion=major,
1027 IFMinorVersion=minor,
1028 IFPkgFlagComponentDirectory="Contents/Packages",
1029 IFPkgFlagPackageList=[
1030 dict(
Ronald Oussorenc5555542006-06-11 20:24:45 +00001031 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Ronald Oussoren1a13cff2009-12-24 13:30:42 +00001032 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001033 )
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001034 for item in pkg_recipes()
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001035 ],
1036 IFPkgFormatVersion=0.10000000149011612,
1037 IFPkgFlagBackgroundScaling="proportional",
1038 IFPkgFlagBackgroundAlignment="left",
Ronald Oussorenc5555542006-06-11 20:24:45 +00001039 IFPkgFlagAuthorizationAction="RootAuthorization",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001058 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001059 pkgcontents = os.path.join(pkgroot, 'Packages')
1060 os.makedirs(pkgcontents)
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001061 for recipe in pkg_recipes():
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 Oussoren508282e2009-03-30 19:34:51 +00001075 IFPkgDescriptionTitle="Python",
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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
Ronald Oussorenc5555542006-06-11 20:24:45 +00001087 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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 """
Ronald Oussorenaa560962006-11-07 15:53:38 +00001102 Create DMG containing the rootDir.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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,
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001109 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001110 if INCLUDE_TIMESTAMP:
Ronald Oussorenc66ced32009-09-20 20:16:11 +00001111 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001112 imagepath = imagepath + '.dmg'
1113
1114 os.mkdir(outdir)
Ronald Oussoren508282e2009-03-30 19:34:51 +00001115 volname='Python %s'%(getFullVersion())
1116 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1117 shellQuote(volname),
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001118 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren508282e2009-03-30 19:34:51 +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")
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001141
1142 return imagepath
1143
1144
1145def setIcon(filePath, icnsPath):
1146 """
1147 Set the custom icon for the specified file or directory.
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001148 """
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001149
Ronald Oussoren5d18cc62010-04-30 14:58:39 +00001150 dirPath = os.path.normpath(os.path.dirname(__file__))
1151 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren648a4fe2009-03-30 17:15:29 +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 Oussoren5d18cc62010-04-30 14:58:39 +00001155 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1156 if not os.path.exists(appPath):
1157 os.makedirs(appPath)
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001158 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1159 shellQuote(toolPath), shellQuote(dirPath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001160
Ronald Oussoren648a4fe2009-03-30 17:15:29 +00001161 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1162 shellQuote(filePath)))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001163
1164def main():
1165 # First parse options and check if we can perform our work
1166 parseOptions()
1167 checkEnvironment()
1168
Ronald Oussoren508282e2009-03-30 19:34:51 +00001169 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Ronald Oussoren209d4c32009-09-29 19:34:13 +00001170 os.environ['CC'] = CC
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001171
1172 if os.path.exists(WORKDIR):
1173 shutil.rmtree(WORKDIR)
1174 os.mkdir(WORKDIR)
1175
Ronald Oussoren287128a2010-04-18 14:01:05 +00001176 os.environ['LC_ALL'] = 'C'
1177
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001178 # Then build third-party libraries such as sleepycat DB4.
1179 buildLibraries()
1180
1181 # Now build python itself
1182 buildPython()
Ronald Oussorene392b352009-03-31 13:20:45 +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']
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001191 buildPythonDocs()
Ronald Oussorene392b352009-03-31 13:20:45 +00001192
1193
1194 # Prepare the applications folder
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001195 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonc3104762008-10-03 11:52:06 +00001196 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussoren799868e2009-03-04 21:07:19 +00001197 patchScript("scripts/postflight.patch-profile", fn)
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001198
Benjamin Petersonc3104762008-10-03 11:52:06 +00001199 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +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.
Ronald Oussorenc5555542006-06-11 20:24:45 +00001211 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001212
1213 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1214 print >> fp, "# BUILD INFO"
1215 print >> fp, "# Date:", time.ctime()
1216 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
1217 fp.close()
1218
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001219 # And copy it to a DMG
1220 buildDMG()
1221
Ronald Oussoren0e5b70d2006-06-07 18:58:42 +00001222if __name__ == "__main__":
1223 main()