blob: ffb30796490651eeac59adbee61bd0498c7873ea [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
Ned Deily4d4c0ee2012-04-01 00:17:33 -0700146 result.extend([
147 dict(
148 name="XZ 5.0.3",
149 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
150 checksum='fefe52f9ecd521de2a8ce38c21a27574',
151 configure_pre=[
152 '--disable-dependency-tracking',
153 ]
154 )
155 ])
156
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000157 if DEPTARGET < '10.5':
158 result.extend([
159 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000160 name="Bzip2 1.0.6",
161 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
162 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000163 configure=None,
164 install='make install CC=%s PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
165 CC,
166 shellQuote(os.path.join(WORKDIR, 'libraries')),
167 ' -arch '.join(ARCHLIST),
168 SDKPATH,
169 ),
170 ),
171 dict(
172 name="ZLib 1.2.3",
173 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
174 checksum='debc62758716a169df9f62e6ab2bc634',
175 configure=None,
176 install='make install CC=%s prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
177 CC,
178 shellQuote(os.path.join(WORKDIR, 'libraries')),
179 ' -arch '.join(ARCHLIST),
180 SDKPATH,
181 ),
182 ),
183 dict(
184 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000185 name="GNU Readline 6.1.2",
186 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
187 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000188 patchlevel='0',
189 patches=[
190 # The readline maintainers don't do actual micro releases, but
191 # just ship a set of patches.
Ned Deily4f7ff782011-01-15 05:29:12 +0000192 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
193 'http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000194 ]
195 ),
196 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000197 name="SQLite 3.7.4",
198 url="http://www.sqlite.org/sqlite-autoconf-3070400.tar.gz",
199 checksum='8f0c690bfb33c3cbbc2471c3d9ba0158',
200 configure_env=('CFLAGS="-Os'
201 ' -DSQLITE_ENABLE_FTS3'
202 ' -DSQLITE_ENABLE_FTS3_PARENTHESIS'
203 ' -DSQLITE_ENABLE_RTREE'
204 ' -DSQLITE_TCL=0'
205 '"'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000206 configure_pre=[
207 '--enable-threadsafe',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000208 '--enable-shared=no',
209 '--enable-static=yes',
Ned Deily4f7ff782011-01-15 05:29:12 +0000210 '--disable-readline',
211 '--disable-dependency-tracking',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000212 ]
213 ),
214 dict(
215 name="NCurses 5.5",
216 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
217 checksum='e73c1ac10b4bfc46db43b2ddfd6244ef',
218 configure_pre=[
Ned Deily4f7ff782011-01-15 05:29:12 +0000219 "--enable-widec",
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000220 "--without-cxx",
221 "--without-ada",
222 "--without-progs",
223 "--without-curses-h",
224 "--enable-shared",
225 "--with-shared",
226 "--datadir=/usr/share",
227 "--sysconfdir=/etc",
228 "--sharedstatedir=/usr/com",
229 "--with-terminfo-dirs=/usr/share/terminfo",
230 "--with-default-terminfo-dir=/usr/share/terminfo",
231 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
232 "--enable-termcap",
233 ],
234 patches=[
235 "ncurses-5.5.patch",
236 ],
237 useLDFlags=False,
238 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
239 shellQuote(os.path.join(WORKDIR, 'libraries')),
240 shellQuote(os.path.join(WORKDIR, 'libraries')),
241 getVersion(),
242 ),
243 ),
244 ])
245
Ned Deily4f7ff782011-01-15 05:29:12 +0000246 if not PYTHON_3:
247 result.extend([
248 dict(
249 name="Sleepycat DB 4.7.25",
250 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
251 checksum='ec2b87e833779681a0c3a814aa71359e',
252 buildDir="build_unix",
253 configure="../dist/configure",
254 configure_pre=[
255 '--includedir=/usr/local/include/db4',
256 ]
257 ),
258 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000259
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000260 return result
261
Thomas Wouters477c8d52006-05-27 19:21:47 +0000262
Thomas Wouters477c8d52006-05-27 19:21:47 +0000263# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000264def pkg_recipes():
265 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
266 result = [
267 dict(
268 name="PythonFramework",
269 long_name="Python Framework",
270 source="/Library/Frameworks/Python.framework",
271 readme="""\
272 This package installs Python.framework, that is the python
273 interpreter and the standard library. This also includes Python
274 wrappers for lots of Mac OS X API's.
275 """,
276 postflight="scripts/postflight.framework",
277 selected='selected',
278 ),
279 dict(
280 name="PythonApplications",
281 long_name="GUI Applications",
282 source="/Applications/Python %(VER)s",
283 readme="""\
284 This package installs IDLE (an interactive Python IDE),
285 Python Launcher and Build Applet (create application bundles
286 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000287
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000288 It also installs a number of examples and demos.
289 """,
290 required=False,
291 selected='selected',
292 ),
293 dict(
294 name="PythonUnixTools",
295 long_name="UNIX command-line tools",
296 source="/usr/local/bin",
297 readme="""\
298 This package installs the unix tools in /usr/local/bin for
299 compatibility with older releases of Python. This package
300 is not necessary to use Python.
301 """,
302 required=False,
303 selected='selected',
304 ),
305 dict(
306 name="PythonDocumentation",
307 long_name="Python Documentation",
308 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
309 source="/pydocs",
310 readme="""\
311 This package installs the python documentation at a location
312 that is useable for pydoc and IDLE. If you have installed Xcode
313 it will also install a link to the documentation in
314 /Developer/Documentation/Python
315 """,
316 postflight="scripts/postflight.documentation",
317 required=False,
318 selected='selected',
319 ),
320 dict(
321 name="PythonProfileChanges",
322 long_name="Shell profile updater",
323 readme="""\
324 This packages updates your shell profile to make sure that
325 the Python tools are found by your shell in preference of
326 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000327
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000328 If you don't install this package you'll have to add
329 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
330 to your PATH by hand.
331 """,
332 postflight="scripts/postflight.patch-profile",
333 topdir="/Library/Frameworks/Python.framework",
334 source="/empty-dir",
335 required=False,
336 selected=unselected_for_python3,
337 ),
338 ]
339
340 if DEPTARGET < '10.4':
341 result.append(
342 dict(
343 name="PythonSystemFixes",
344 long_name="Fix system Python",
345 readme="""\
346 This package updates the system python installation on
347 Mac OS X 10.3 to ensure that you can build new python extensions
348 using that copy of python after installing this version.
349 """,
350 postflight="../Tools/fixapplepython23.py",
351 topdir="/Library/Frameworks/Python.framework",
352 source="/empty-dir",
353 required=False,
354 selected=unselected_for_python3,
355 )
356 )
357 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000358
Thomas Wouters477c8d52006-05-27 19:21:47 +0000359def fatal(msg):
360 """
361 A fatal error, bail out.
362 """
363 sys.stderr.write('FATAL: ')
364 sys.stderr.write(msg)
365 sys.stderr.write('\n')
366 sys.exit(1)
367
368def fileContents(fn):
369 """
370 Return the contents of the named file
371 """
372 return open(fn, 'rb').read()
373
374def runCommand(commandline):
375 """
Ezio Melotti13925002011-03-16 11:05:33 +0200376 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000377 unless the command fails.
378 """
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 if VERBOSE:
387 sys.stdout.write(data); sys.stdout.flush()
388
389def captureCommand(commandline):
390 fd = os.popen(commandline, 'r')
391 data = fd.read()
392 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000393 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000394 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000395 raise RuntimeError, "command failed: %s"%(commandline,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000396
397 return data
398
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000399def getTclTkVersion(configfile, versionline):
400 """
401 search Tcl or Tk configuration file for version line
402 """
403 try:
404 f = open(configfile, "r")
405 except:
406 fatal("Framework configuration file not found: %s" % configfile)
407
408 for l in f:
409 if l.startswith(versionline):
410 f.close()
411 return l
412
413 fatal("Version variable %s not found in framework configuration file: %s"
414 % (versionline, configfile))
415
Thomas Wouters477c8d52006-05-27 19:21:47 +0000416def checkEnvironment():
417 """
418 Check that we're running on a supported system.
419 """
420
Ned Deilye59e4c52011-01-29 18:56:28 +0000421 if sys.version_info[0:2] < (2, 4):
422 fatal("This script must be run with Python 2.4 or later")
423
Thomas Wouters477c8d52006-05-27 19:21:47 +0000424 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000425 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000426
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000427 if int(platform.release().split('.')[0]) < 8:
428 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000429
430 if not os.path.exists(SDKPATH):
431 fatal("Please install the latest version of Xcode and the %s SDK"%(
432 os.path.basename(SDKPATH[:-4])))
433
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000434 # Because we only support dynamic load of only one major/minor version of
435 # Tcl/Tk, ensure:
436 # 1. there are no user-installed frameworks of Tcl/Tk with version
437 # higher than the Apple-supplied system version
438 # 2. there is a user-installed framework in /Library/Frameworks with the
439 # same version as the system version. This allows users to choose
440 # to install a newer patch level.
441
442 for framework in ['Tcl', 'Tk']:
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000443 #fw = dict(lower=framework.lower(),
444 # upper=framework.upper(),
445 # cap=framework.capitalize())
446 #fwpth = "Library/Frameworks/%(cap)s.framework/%(lower)sConfig.sh" % fw
447 fwpth = 'Library/Frameworks/Tcl.framework/Versions/Current'
448 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000449 libfw = os.path.join('/', fwpth)
450 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000451 #version = "%(upper)s_VERSION" % fw
452 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000453 fatal("Version of %s must match %s" % (libfw, sysfw) )
454 if os.path.exists(usrfw):
455 fatal("Please rename %s to avoid possible dynamic load issues."
456 % usrfw)
457
458 # Remove inherited environment variables which might influence build
459 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
460 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
461 for ev in list(os.environ):
462 for prefix in environ_var_prefixes:
463 if ev.startswith(prefix) :
464 print "INFO: deleting environment variable %s=%s" % (
465 ev, os.environ[ev])
466 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000467
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000468 os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin'
469 print "Setting default PATH: %s"%(os.environ['PATH'])
470
Thomas Wouters477c8d52006-05-27 19:21:47 +0000471
Thomas Wouters89f507f2006-12-13 04:49:30 +0000472def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000473 """
474 Parse arguments and update global settings.
475 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000476 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000477 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478
479 if args is None:
480 args = sys.argv[1:]
481
482 try:
483 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000484 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
485 'dep-target=', 'universal-archs=', 'help' ])
Benjamin Peterson623918e2008-12-20 22:50:25 +0000486 except getopt.error, msg:
487 print msg
Thomas Wouters477c8d52006-05-27 19:21:47 +0000488 sys.exit(1)
489
490 if args:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000491 print "Additional arguments"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000492 sys.exit(1)
493
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000494 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000495 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000496 if k in ('-h', '-?', '--help'):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000497 print USAGE
Thomas Wouters477c8d52006-05-27 19:21:47 +0000498 sys.exit(0)
499
500 elif k in ('-d', '--build-dir'):
501 WORKDIR=v
502
503 elif k in ('--third-party',):
504 DEPSRC=v
505
506 elif k in ('--sdk-path',):
507 SDKPATH=v
508
509 elif k in ('--src-dir',):
510 SRCDIR=v
511
Ronald Oussoren1943f862009-03-30 19:39:14 +0000512 elif k in ('--dep-target', ):
513 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000514 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000515
516 elif k in ('--universal-archs', ):
517 if v in UNIVERSALOPTS:
518 UNIVERSALARCHS = v
519 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000520 if deptarget is None:
521 # Select alternate default deployment
522 # target
523 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000524 else:
525 raise NotImplementedError, v
526
Thomas Wouters477c8d52006-05-27 19:21:47 +0000527 else:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000528 raise NotImplementedError, k
Thomas Wouters477c8d52006-05-27 19:21:47 +0000529
530 SRCDIR=os.path.abspath(SRCDIR)
531 WORKDIR=os.path.abspath(WORKDIR)
532 SDKPATH=os.path.abspath(SDKPATH)
533 DEPSRC=os.path.abspath(DEPSRC)
534
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000535 CC=target_cc_map[DEPTARGET]
536
Benjamin Peterson623918e2008-12-20 22:50:25 +0000537 print "Settings:"
538 print " * Source directory:", SRCDIR
539 print " * Build directory: ", WORKDIR
540 print " * SDK location: ", SDKPATH
Ronald Oussoren1943f862009-03-30 19:39:14 +0000541 print " * Third-party source:", DEPSRC
542 print " * Deployment target:", DEPTARGET
543 print " * Universal architectures:", ARCHLIST
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000544 print " * C compiler:", CC
Benjamin Peterson623918e2008-12-20 22:50:25 +0000545 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000546
547
548
549
550def extractArchive(builddir, archiveName):
551 """
552 Extract a source archive into 'builddir'. Returns the path of the
553 extracted archive.
554
555 XXX: This function assumes that archives contain a toplevel directory
556 that is has the same name as the basename of the archive. This is
557 save enough for anything we use.
558 """
559 curdir = os.getcwd()
560 try:
561 os.chdir(builddir)
562 if archiveName.endswith('.tar.gz'):
563 retval = os.path.basename(archiveName[:-7])
564 if os.path.exists(retval):
565 shutil.rmtree(retval)
566 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
567
568 elif archiveName.endswith('.tar.bz2'):
569 retval = os.path.basename(archiveName[:-8])
570 if os.path.exists(retval):
571 shutil.rmtree(retval)
572 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
573
574 elif archiveName.endswith('.tar'):
575 retval = os.path.basename(archiveName[:-4])
576 if os.path.exists(retval):
577 shutil.rmtree(retval)
578 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
579
580 elif archiveName.endswith('.zip'):
581 retval = os.path.basename(archiveName[:-4])
582 if os.path.exists(retval):
583 shutil.rmtree(retval)
584 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
585
586 data = fp.read()
587 xit = fp.close()
588 if xit is not None:
589 sys.stdout.write(data)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000590 raise RuntimeError, "Cannot extract %s"%(archiveName,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000591
592 return os.path.join(builddir, retval)
593
594 finally:
595 os.chdir(curdir)
596
597KNOWNSIZES = {
598 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
599 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
600}
601
602def downloadURL(url, fname):
603 """
604 Download the contents of the url into the file.
605 """
606 try:
607 size = os.path.getsize(fname)
608 except OSError:
609 pass
610 else:
611 if KNOWNSIZES.get(url) == size:
Benjamin Peterson623918e2008-12-20 22:50:25 +0000612 print "Using existing file for", url
Thomas Wouters477c8d52006-05-27 19:21:47 +0000613 return
Benjamin Peterson623918e2008-12-20 22:50:25 +0000614 fpIn = urllib2.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000615 fpOut = open(fname, 'wb')
616 block = fpIn.read(10240)
617 try:
618 while block:
619 fpOut.write(block)
620 block = fpIn.read(10240)
621 fpIn.close()
622 fpOut.close()
623 except:
624 try:
625 os.unlink(fname)
626 except:
627 pass
628
629def buildRecipe(recipe, basedir, archList):
630 """
631 Build software using a recipe. This function does the
632 'configure;make;make install' dance for C software, with a possibility
633 to customize this process, basically a poor-mans DarwinPorts.
634 """
635 curdir = os.getcwd()
636
637 name = recipe['name']
638 url = recipe['url']
639 configure = recipe.get('configure', './configure')
640 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
641 shellQuote(basedir)))
642
643 archiveName = os.path.split(url)[-1]
644 sourceArchive = os.path.join(DEPSRC, archiveName)
645
646 if not os.path.exists(DEPSRC):
647 os.mkdir(DEPSRC)
648
649
650 if os.path.exists(sourceArchive):
Benjamin Peterson623918e2008-12-20 22:50:25 +0000651 print "Using local copy of %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000652
653 else:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000654 print "Did not find local copy of %s"%(name,)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000655 print "Downloading %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000656 downloadURL(url, sourceArchive)
Benjamin Peterson623918e2008-12-20 22:50:25 +0000657 print "Archive for %s stored as %s"%(name, sourceArchive)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000658
Benjamin Peterson623918e2008-12-20 22:50:25 +0000659 print "Extracting archive for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000660 buildDir=os.path.join(WORKDIR, '_bld')
661 if not os.path.exists(buildDir):
662 os.mkdir(buildDir)
663
664 workDir = extractArchive(buildDir, sourceArchive)
665 os.chdir(workDir)
666 if 'buildDir' in recipe:
667 os.chdir(recipe['buildDir'])
668
669
670 for fn in recipe.get('patches', ()):
671 if fn.startswith('http://'):
672 # Download the patch before applying it.
673 path = os.path.join(DEPSRC, os.path.basename(fn))
674 downloadURL(fn, path)
675 fn = path
676
677 fn = os.path.join(curdir, fn)
678 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
679 shellQuote(fn),))
680
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000681 if configure is not None:
682 configure_args = [
683 "--prefix=/usr/local",
684 "--enable-static",
685 "--disable-shared",
686 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
687 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000688
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 if 'configure_pre' in recipe:
690 args = list(recipe['configure_pre'])
691 if '--disable-static' in args:
692 configure_args.remove('--enable-static')
693 if '--enable-shared' in args:
694 configure_args.remove('--disable-shared')
695 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000696
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000697 if recipe.get('useLDFlags', 1):
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 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000704 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000705 shellQuote(basedir)[1:-1],
706 ' -arch '.join(archList)),
707 ])
708 else:
709 configure_args.extend([
710 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
711 ' -arch '.join(archList),
712 shellQuote(SDKPATH)[1:-1],
713 shellQuote(basedir)[1:-1],),
714 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000715
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000716 if 'configure_post' in recipe:
717 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000718
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000719 configure_args.insert(0, configure)
720 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000721
Ned Deily4f7ff782011-01-15 05:29:12 +0000722 if 'configure_env' in recipe:
723 configure_args.insert(0, recipe['configure_env'])
724
Benjamin Peterson623918e2008-12-20 22:50:25 +0000725 print "Running configure for %s"%(name,)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000726 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000727
Benjamin Peterson623918e2008-12-20 22:50:25 +0000728 print "Running install for %s"%(name,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000729 runCommand('{ ' + install + ' ;} 2>&1')
730
Benjamin Peterson623918e2008-12-20 22:50:25 +0000731 print "Done %s"%(name,)
732 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000733
734 os.chdir(curdir)
735
736def buildLibraries():
737 """
738 Build our dependencies into $WORKDIR/libraries/usr/local
739 """
Benjamin Peterson623918e2008-12-20 22:50:25 +0000740 print ""
741 print "Building required libraries"
742 print ""
Thomas Wouters477c8d52006-05-27 19:21:47 +0000743 universal = os.path.join(WORKDIR, 'libraries')
744 os.mkdir(universal)
745 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
746 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
747
Ronald Oussoren1943f862009-03-30 19:39:14 +0000748 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000749 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000750
751
752
753def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000754 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000755 # inside the framwork. pydoc and IDLE will pick it up there.
Benjamin Peterson623918e2008-12-20 22:50:25 +0000756 print "Install python documentation"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000757 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000758 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000759 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000760 curDir = os.getcwd()
761 os.chdir(buildDir)
762 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000763 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000764 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000765 if not os.path.exists(docdir):
766 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000767 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000768
769
770def buildPython():
Ronald Oussoren1943f862009-03-30 19:39:14 +0000771 print "Building a universal python for %s architectures" % UNIVERSALARCHS
Thomas Wouters477c8d52006-05-27 19:21:47 +0000772
773 buildDir = os.path.join(WORKDIR, '_bld', 'python')
774 rootDir = os.path.join(WORKDIR, '_root')
775
776 if os.path.exists(buildDir):
777 shutil.rmtree(buildDir)
778 if os.path.exists(rootDir):
779 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000780 os.makedirs(buildDir)
781 os.makedirs(rootDir)
782 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000783 curdir = os.getcwd()
784 os.chdir(buildDir)
785
786 # Not sure if this is still needed, the original build script
787 # claims that parts of the install assume python.exe exists.
788 os.symlink('python', os.path.join(buildDir, 'python.exe'))
789
790 # Extract the version from the configure file, needed to calculate
791 # several paths.
792 version = getVersion()
793
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000794 # Since the extra libs are not in their installed framework location
795 # during the build, augment the library path so that the interpreter
796 # will find them during its extension import sanity checks.
797 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
798 'libraries', 'usr', 'local', 'lib')
Benjamin Peterson623918e2008-12-20 22:50:25 +0000799 print "Running configure..."
Ronald Oussoren1943f862009-03-30 19:39:14 +0000800 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000801 "--with-universal-archs=%s "
802 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000803 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
804 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
805 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
806 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000807 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000808 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000809 shellQuote(WORKDIR)[1:-1]))
810
Benjamin Peterson623918e2008-12-20 22:50:25 +0000811 print "Running make"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000812 runCommand("make")
813
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000814 print "Running make install"
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000815 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000816 shellQuote(rootDir)))
817
Benjamin Peterson623918e2008-12-20 22:50:25 +0000818 print "Running make frameworkinstallextras"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000819 runCommand("make frameworkinstallextras DESTDIR=%s"%(
820 shellQuote(rootDir)))
821
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000822 del os.environ['DYLD_LIBRARY_PATH']
Benjamin Peterson623918e2008-12-20 22:50:25 +0000823 print "Copying required shared libraries"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000824 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
825 runCommand("mv %s/* %s"%(
826 shellQuote(os.path.join(
827 WORKDIR, 'libraries', 'Library', 'Frameworks',
828 'Python.framework', 'Versions', getVersion(),
829 'lib')),
830 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
831 'Python.framework', 'Versions', getVersion(),
832 'lib'))))
833
Benjamin Peterson623918e2008-12-20 22:50:25 +0000834 print "Fix file modes"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000835 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000836 gid = grp.getgrnam('admin').gr_gid
837
Thomas Wouters477c8d52006-05-27 19:21:47 +0000838 for dirpath, dirnames, filenames in os.walk(frmDir):
839 for dn in dirnames:
840 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000841 os.chown(os.path.join(dirpath, dn), -1, gid)
842
Thomas Wouters477c8d52006-05-27 19:21:47 +0000843
844 for fn in filenames:
845 if os.path.islink(fn):
846 continue
847
848 # "chmod g+w $fn"
849 p = os.path.join(dirpath, fn)
850 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000851 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
852 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000853
Ned Deilye59e4c52011-01-29 18:56:28 +0000854 if PYTHON_3:
855 LDVERSION=None
856 VERSION=None
857 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000858
Ned Deilye59e4c52011-01-29 18:56:28 +0000859 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000860 for ln in fp:
861 if ln.startswith('VERSION='):
862 VERSION=ln.split()[1]
863 if ln.startswith('ABIFLAGS='):
864 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000865 if ln.startswith('LDVERSION='):
866 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000867 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000868
Ned Deilye59e4c52011-01-29 18:56:28 +0000869 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
870 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
871 config_suffix = '-' + LDVERSION
872 else:
873 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000874
Thomas Wouters477c8d52006-05-27 19:21:47 +0000875 # We added some directories to the search path during the configure
876 # phase. Remove those because those directories won't be there on
877 # the end-users system.
878 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
879 'Versions', version, 'lib', 'python%s'%(version,),
Ned Deilye59e4c52011-01-29 18:56:28 +0000880 'config' + config_suffix, 'Makefile')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000881 fp = open(path, 'r')
882 data = fp.read()
883 fp.close()
884
885 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
886 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
887 fp = open(path, 'w')
888 fp.write(data)
889 fp.close()
890
891 # Add symlinks in /usr/local/bin, using relative links
892 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
893 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
894 'Python.framework', 'Versions', version, 'bin')
895 if os.path.exists(usr_local_bin):
896 shutil.rmtree(usr_local_bin)
897 os.makedirs(usr_local_bin)
898 for fn in os.listdir(
899 os.path.join(frmDir, 'Versions', version, 'bin')):
900 os.symlink(os.path.join(to_framework, fn),
901 os.path.join(usr_local_bin, fn))
902
903 os.chdir(curdir)
904
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000905 if PYTHON_3:
906 # Remove the 'Current' link, that way we don't accidently mess
907 # with an already installed version of python 2
908 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
909 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000910
911def patchFile(inPath, outPath):
912 data = fileContents(inPath)
913 data = data.replace('$FULL_VERSION', getFullVersion())
914 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +0000915 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +0000916 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000917 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000918
919 # This one is not handy as a template variable
920 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000921 fp = open(outPath, 'wb')
922 fp.write(data)
923 fp.close()
924
925def patchScript(inPath, outPath):
926 data = fileContents(inPath)
927 data = data.replace('@PYVER@', getVersion())
928 fp = open(outPath, 'wb')
929 fp.write(data)
930 fp.close()
931 os.chmod(outPath, 0755)
932
933
934
935def packageFromRecipe(targetDir, recipe):
936 curdir = os.getcwd()
937 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000938 # The major version (such as 2.5) is included in the package name
939 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000940 # common.
941 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000942 srcdir = recipe.get('source')
943 pkgroot = recipe.get('topdir', srcdir)
944 postflight = recipe.get('postflight')
945 readme = textwrap.dedent(recipe['readme'])
946 isRequired = recipe.get('required', True)
947
Benjamin Peterson623918e2008-12-20 22:50:25 +0000948 print "- building package %s"%(pkgname,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000949
950 # Substitute some variables
951 textvars = dict(
952 VER=getVersion(),
953 FULLVER=getFullVersion(),
954 )
955 readme = readme % textvars
956
957 if pkgroot is not None:
958 pkgroot = pkgroot % textvars
959 else:
960 pkgroot = '/'
961
962 if srcdir is not None:
963 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
964 srcdir = srcdir % textvars
965
966 if postflight is not None:
967 postflight = os.path.abspath(postflight)
968
969 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
970 os.makedirs(packageContents)
971
972 if srcdir is not None:
973 os.chdir(srcdir)
974 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
975 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
976 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
977
978 fn = os.path.join(packageContents, 'PkgInfo')
979 fp = open(fn, 'w')
980 fp.write('pmkrpkg1')
981 fp.close()
982
983 rsrcDir = os.path.join(packageContents, "Resources")
984 os.mkdir(rsrcDir)
985 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
986 fp.write(readme)
987 fp.close()
988
989 if postflight is not None:
990 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
991
992 vers = getFullVersion()
993 major, minor = map(int, getVersion().split('.', 2))
994 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +0000995 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
996 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
997 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000998 CFBundleShortVersionString=vers,
999 IFMajorVersion=major,
1000 IFMinorVersion=minor,
1001 IFPkgFormatVersion=0.10000000149011612,
1002 IFPkgFlagAllowBackRev=False,
1003 IFPkgFlagAuthorizationAction="RootAuthorization",
1004 IFPkgFlagDefaultLocation=pkgroot,
1005 IFPkgFlagFollowLinks=True,
1006 IFPkgFlagInstallFat=True,
1007 IFPkgFlagIsRequired=isRequired,
1008 IFPkgFlagOverwritePermissions=False,
1009 IFPkgFlagRelocatable=False,
1010 IFPkgFlagRestartAction="NoRestart",
1011 IFPkgFlagRootVolumeOnly=True,
1012 IFPkgFlagUpdateInstalledLangauges=False,
1013 )
1014 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1015
1016 pl = Plist(
1017 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001018 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001019 IFPkgDescriptionVersion=vers,
1020 )
1021 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1022
1023 finally:
1024 os.chdir(curdir)
1025
1026
1027def makeMpkgPlist(path):
1028
1029 vers = getFullVersion()
1030 major, minor = map(int, getVersion().split('.', 2))
1031
1032 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001033 CFBundleGetInfoString="Python %s"%(vers,),
1034 CFBundleIdentifier='org.python.Python',
1035 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001036 CFBundleShortVersionString=vers,
1037 IFMajorVersion=major,
1038 IFMinorVersion=minor,
1039 IFPkgFlagComponentDirectory="Contents/Packages",
1040 IFPkgFlagPackageList=[
1041 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001042 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001043 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001044 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001045 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001046 ],
1047 IFPkgFormatVersion=0.10000000149011612,
1048 IFPkgFlagBackgroundScaling="proportional",
1049 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001050 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001051 )
1052
1053 writePlist(pl, path)
1054
1055
1056def buildInstaller():
1057
1058 # Zap all compiled files
1059 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1060 for fn in filenames:
1061 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1062 os.unlink(os.path.join(dirpath, fn))
1063
1064 outdir = os.path.join(WORKDIR, 'installer')
1065 if os.path.exists(outdir):
1066 shutil.rmtree(outdir)
1067 os.mkdir(outdir)
1068
Ronald Oussoren1943f862009-03-30 19:39:14 +00001069 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001070 pkgcontents = os.path.join(pkgroot, 'Packages')
1071 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001072 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001073 packageFromRecipe(pkgcontents, recipe)
1074
1075 rsrcDir = os.path.join(pkgroot, 'Resources')
1076
1077 fn = os.path.join(pkgroot, 'PkgInfo')
1078 fp = open(fn, 'w')
1079 fp.write('pmkrpkg1')
1080 fp.close()
1081
1082 os.mkdir(rsrcDir)
1083
1084 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1085 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001086 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001087 IFPkgDescriptionVersion=getVersion(),
1088 )
1089
1090 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1091 for fn in os.listdir('resources'):
1092 if fn == '.svn': continue
1093 if fn.endswith('.jpg'):
1094 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1095 else:
1096 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1097
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001098 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001099
1100
1101def installSize(clear=False, _saved=[]):
1102 if clear:
1103 del _saved[:]
1104 if not _saved:
1105 data = captureCommand("du -ks %s"%(
1106 shellQuote(os.path.join(WORKDIR, '_root'))))
1107 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1108 return _saved[0]
1109
1110
1111def buildDMG():
1112 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001113 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001114 """
1115 outdir = os.path.join(WORKDIR, 'diskimage')
1116 if os.path.exists(outdir):
1117 shutil.rmtree(outdir)
1118
1119 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001120 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001121 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001122 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001123 imagepath = imagepath + '.dmg'
1124
1125 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001126 volname='Python %s'%(getFullVersion())
1127 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1128 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001129 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001130 shellQuote(imagepath + ".tmp.dmg" )))
1131
1132
1133 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1134 os.mkdir(os.path.join(WORKDIR, "mnt"))
1135 runCommand("hdiutil attach %s -mountroot %s"%(
1136 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1137
1138 # Custom icon for the DMG, shown when the DMG is mounted.
1139 shutil.copy("../Icons/Disk Image.icns",
1140 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1141 runCommand("/Developer/Tools/SetFile -a C %s/"%(
1142 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1143
1144 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1145
1146 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1147 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1148 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1149 setIcon(imagepath, "../Icons/Disk Image.icns")
1150
1151 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001152
1153 return imagepath
1154
1155
1156def setIcon(filePath, icnsPath):
1157 """
1158 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001160
Ronald Oussoren70050672010-04-30 15:00:26 +00001161 dirPath = os.path.normpath(os.path.dirname(__file__))
1162 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001163 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1164 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1165 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001166 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1167 if not os.path.exists(appPath):
1168 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001169 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1170 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001171
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001172 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1173 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001174
1175def main():
1176 # First parse options and check if we can perform our work
1177 parseOptions()
1178 checkEnvironment()
1179
Ronald Oussoren1943f862009-03-30 19:39:14 +00001180 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001181 os.environ['CC'] = CC
Thomas Wouters477c8d52006-05-27 19:21:47 +00001182
1183 if os.path.exists(WORKDIR):
1184 shutil.rmtree(WORKDIR)
1185 os.mkdir(WORKDIR)
1186
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001187 os.environ['LC_ALL'] = 'C'
1188
Thomas Wouters477c8d52006-05-27 19:21:47 +00001189 # Then build third-party libraries such as sleepycat DB4.
1190 buildLibraries()
1191
1192 # Now build python itself
1193 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001194
1195 # And then build the documentation
1196 # Remove the Deployment Target from the shell
1197 # environment, it's no longer needed and
1198 # an unexpected build target can cause problems
1199 # when Sphinx and its dependencies need to
1200 # be (re-)installed.
1201 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001202 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001203
1204
1205 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001206 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001207 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001208 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001209
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001210 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001211 getVersion(),))
1212 os.chmod(folder, 0755)
1213 setIcon(folder, "../Icons/Python Folder.icns")
1214
1215 # Create the installer
1216 buildInstaller()
1217
1218 # And copy the readme into the directory containing the installer
1219 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1220
1221 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001222 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001223
1224 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Benjamin Peterson623918e2008-12-20 22:50:25 +00001225 print >> fp, "# BUILD INFO"
1226 print >> fp, "# Date:", time.ctime()
1227 print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
Thomas Wouters477c8d52006-05-27 19:21:47 +00001228 fp.close()
1229
Thomas Wouters477c8d52006-05-27 19:21:47 +00001230 # And copy it to a DMG
1231 buildDMG()
1232
Thomas Wouters477c8d52006-05-27 19:21:47 +00001233if __name__ == "__main__":
1234 main()