blob: a9d40912b09d505a25999a6e3fd99f0f590cab0e [file] [log] [blame]
Ned Deily4a96a372013-01-29 00:08:32 -08001#!/usr/bin/env 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.
Ned Deily4a96a372013-01-29 00:08:32 -08004It requires at least Mac OS X 10.5, Xcode 3, and the 10.4u SDK for
Ned Deilye59e4c52011-01-29 18:56:28 +0000532-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
Ned Deily4a96a372013-01-29 00:08:32 -080013In addition to what is supplied with OS X 10.5+ and Xcode 3+, the script
14requires an installed version of hg and a third-party version of
15Tcl/Tk 8.4 (for OS X 10.4 and 10.5 deployment targets) or Tcl/TK 8.5
16(for 10.6 or later) installed in /Library/Frameworks. When installed,
17the Python built by this script will attempt to dynamically link first to
18Tcl and Tk frameworks in /Library/Frameworks if available otherwise fall
19back to the ones in /System/Library/Framework. For the build, we recommend
20installing the most recent ActiveTcl 8.4 or 8.5 version.
21
2232-bit-only installer builds are still possible on OS X 10.4 with Xcode 2.5
23and the installation of additional components, such as a newer Python
24(2.5 is needed for Python parser updates), hg, and svn (for the documentation
25build).
26
Thomas Wouters477c8d52006-05-27 19:21:47 +000027Usage: see USAGE variable in the script.
28"""
Ned Deily4a96a372013-01-29 00:08:32 -080029import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd, grp
30try:
31 import urllib2 as urllib_request
32except ImportError:
33 import urllib.request as urllib_request
34
35STAT_0o755 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
36 | stat.S_IRGRP | stat.S_IXGRP
37 | stat.S_IROTH | stat.S_IXOTH )
38
39STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
40 | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
41 | stat.S_IROTH | stat.S_IXOTH )
Thomas Wouters477c8d52006-05-27 19:21:47 +000042
Thomas Wouters89f507f2006-12-13 04:49:30 +000043INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000045
46from plistlib import Plist
47
Thomas Wouters477c8d52006-05-27 19:21:47 +000048try:
49 from plistlib import writePlist
50except ImportError:
51 # We're run using python2.3
52 def writePlist(plist, path):
53 plist.write(path)
54
55def shellQuote(value):
56 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000057 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000058 a shell command.
59 """
60 return "'%s'"%(value.replace("'", "'\"'\"'"))
61
62def grepValue(fn, variable):
63 variable = variable + '='
64 for ln in open(fn, 'r'):
65 if ln.startswith(variable):
66 value = ln[len(variable):].strip()
67 return value[1:-1]
Ned Deily4a96a372013-01-29 00:08:32 -080068 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
Thomas Wouters477c8d52006-05-27 19:21:47 +000071
72def getVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080073 global _cache_getVersion
74 if _cache_getVersion is None:
75 _cache_getVersion = grepValue(
76 os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
77 return _cache_getVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000078
Benjamin Petersond9b7d482010-03-19 21:42:45 +000079def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
Ned Deily4a96a372013-01-29 00:08:32 -080082def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
Thomas Wouters477c8d52006-05-27 19:21:47 +000087def getFullVersion():
Ned Deily4a96a372013-01-29 00:08:32 -080088 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
Thomas Wouters477c8d52006-05-27 19:21:47 +000091 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
Ned Deily4a96a372013-01-29 00:08:32 -080094 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000097
Thomas Wouters89f507f2006-12-13 04:49:30 +000098# The directory we'll use to create the build (will be erased and recreated)
99WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000100
Thomas Wouters89f507f2006-12-13 04:49:30 +0000101# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +0000102# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000105
106# Location of the preferred SDK
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000107
108### There are some issues with the SDK selection below here,
109### The resulting binary doesn't work on all platforms that
110### it should. Always default to the 10.4u SDK until that
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300111### issue is resolved.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000112###
113##if int(os.uname()[2].split('.')[0]) == 8:
114## # Explicitly use the 10.4u (universal) SDK when
115## # building on 10.4, the system headers are not
116## # useable for a universal build
117## SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
118##else:
119## SDKPATH = "/"
120
Thomas Wouters89f507f2006-12-13 04:49:30 +0000121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000122
Ronald Oussoren1943f862009-03-30 19:39:14 +0000123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000125 'intel': ('i386', 'x86_64'),
126 '3-way': ('ppc', 'i386', 'x86_64'),
127 'all': ('i386', 'ppc', 'x86_64', 'ppc64',) }
128default_target_map = {
129 '64-bit': '10.5',
130 '3-way': '10.5',
131 'intel': '10.5',
132 'all': '10.5',
133}
Ronald Oussoren1943f862009-03-30 19:39:14 +0000134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140
Ezio Melotti42da6632011-03-15 05:18:48 +0200141# Source directory (assume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000142SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143 os.path.dirname(
144 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000145 os.path.abspath(__file__
146 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000147
Ronald Oussoren1943f862009-03-30 19:39:14 +0000148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000151target_cc_map = {
Ned Deily4a96a372013-01-29 00:08:32 -0800152 '10.3': ('gcc-4.0', 'g++-4.0'),
153 '10.4': ('gcc-4.0', 'g++-4.0'),
154 '10.5': ('gcc-4.2', 'g++-4.2'),
155 '10.6': ('gcc-4.2', 'g++-4.2'),
156 '10.7': ('clang', 'clang++'),
157 '10.8': ('clang', 'clang++'),
Ned Deilyac25ca12013-10-18 20:41:16 -0700158 '10.9': ('clang', 'clang++'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000159}
160
Ned Deily4a96a372013-01-29 00:08:32 -0800161CC, CXX = target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000162
163PYTHON_3 = getVersionTuple() >= (3, 0)
164
Thomas Wouters89f507f2006-12-13 04:49:30 +0000165USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +0000166 Usage: build_python [options]
167
168 Options:
169 -? or -h: Show this message
170 -b DIR
171 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
172 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
173 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
174 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000175 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
176 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000177""")% globals()
178
Ned Deily4a96a372013-01-29 00:08:32 -0800179# Dict of object file names with shared library names to check after building.
180# This is to ensure that we ended up dynamically linking with the shared
181# library paths and versions we expected. For example:
182# EXPECTED_SHARED_LIBS['_tkinter.so'] = [
183# '/Library/Frameworks/Tcl.framework/Versions/8.5/Tcl',
184# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
185EXPECTED_SHARED_LIBS = {}
Thomas Wouters477c8d52006-05-27 19:21:47 +0000186
187# Instructions for building libraries that are necessary for building a
188# batteries included python.
Ronald Oussoren1943f862009-03-30 19:39:14 +0000189# [The recipes are defined here for convenience but instantiated later after
190# command line options have been processed.]
191def library_recipes():
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000192 result = []
Thomas Wouters477c8d52006-05-27 19:21:47 +0000193
Ned Deily4a96a372013-01-29 00:08:32 -0800194 LT_10_5 = bool(DEPTARGET < '10.5')
195
196 if getVersionTuple() >= (3, 3):
197 result.extend([
198 dict(
199 name="XZ 5.0.3",
200 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
201 checksum='fefe52f9ecd521de2a8ce38c21a27574',
202 configure_pre=[
203 '--disable-dependency-tracking',
204 ]
205 ),
206 ])
207
208 result.extend([
209 dict(
210 name="NCurses 5.9",
211 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
212 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
213 configure_pre=[
214 "--enable-widec",
215 "--without-cxx",
216 "--without-cxx-binding",
217 "--without-ada",
218 "--without-curses-h",
219 "--enable-shared",
220 "--with-shared",
221 "--without-debug",
222 "--without-normal",
223 "--without-tests",
224 "--without-manpages",
225 "--datadir=/usr/share",
226 "--sysconfdir=/etc",
227 "--sharedstatedir=/usr/com",
228 "--with-terminfo-dirs=/usr/share/terminfo",
229 "--with-default-terminfo-dir=/usr/share/terminfo",
230 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
231 ],
232 patchscripts=[
233 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
234 "f54bf02a349f96a7c4f0d00922f3a0d4"),
235 ],
236 useLDFlags=False,
237 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
238 shellQuote(os.path.join(WORKDIR, 'libraries')),
239 shellQuote(os.path.join(WORKDIR, 'libraries')),
240 getVersion(),
241 ),
242 ),
243 dict(
244 name="SQLite 3.7.13",
245 url="http://www.sqlite.org/sqlite-autoconf-3071300.tar.gz",
246 checksum='c97df403e8a3d5b67bb408fcd6aabd8e',
247 extra_cflags=('-Os '
248 '-DSQLITE_ENABLE_FTS4 '
249 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
250 '-DSQLITE_ENABLE_RTREE '
251 '-DSQLITE_TCL=0 '
252 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
253 configure_pre=[
254 '--enable-threadsafe',
255 '--enable-shared=no',
256 '--enable-static=yes',
257 '--disable-readline',
258 '--disable-dependency-tracking',
259 ]
260 ),
261 ])
262
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000263 if DEPTARGET < '10.5':
264 result.extend([
265 dict(
Ned Deily4f7ff782011-01-15 05:29:12 +0000266 name="Bzip2 1.0.6",
267 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
268 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000269 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800270 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
271 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000272 shellQuote(os.path.join(WORKDIR, 'libraries')),
273 ' -arch '.join(ARCHLIST),
274 SDKPATH,
275 ),
276 ),
277 dict(
278 name="ZLib 1.2.3",
279 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
280 checksum='debc62758716a169df9f62e6ab2bc634',
281 configure=None,
Ned Deily4a96a372013-01-29 00:08:32 -0800282 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
283 CC, CXX,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000284 shellQuote(os.path.join(WORKDIR, 'libraries')),
285 ' -arch '.join(ARCHLIST),
286 SDKPATH,
287 ),
288 ),
289 dict(
290 # Note that GNU readline is GPL'd software
Ned Deily4f7ff782011-01-15 05:29:12 +0000291 name="GNU Readline 6.1.2",
292 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
293 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000294 patchlevel='0',
295 patches=[
296 # The readline maintainers don't do actual micro releases, but
297 # just ship a set of patches.
Ned Deily4a96a372013-01-29 00:08:32 -0800298 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
299 'c642f2e84d820884b0bf9fd176bc6c3f'),
300 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
301 '1a76781a1ea734e831588285db7ec9b1'),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000302 ]
303 ),
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000304 ])
305
Ned Deily4f7ff782011-01-15 05:29:12 +0000306 if not PYTHON_3:
307 result.extend([
308 dict(
309 name="Sleepycat DB 4.7.25",
310 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
311 checksum='ec2b87e833779681a0c3a814aa71359e',
312 buildDir="build_unix",
313 configure="../dist/configure",
314 configure_pre=[
315 '--includedir=/usr/local/include/db4',
316 ]
317 ),
318 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000319
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000320 return result
321
Thomas Wouters477c8d52006-05-27 19:21:47 +0000322
Thomas Wouters477c8d52006-05-27 19:21:47 +0000323# Instructions for building packages inside the .mpkg.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000324def pkg_recipes():
325 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
326 result = [
327 dict(
328 name="PythonFramework",
329 long_name="Python Framework",
330 source="/Library/Frameworks/Python.framework",
331 readme="""\
332 This package installs Python.framework, that is the python
333 interpreter and the standard library. This also includes Python
334 wrappers for lots of Mac OS X API's.
335 """,
336 postflight="scripts/postflight.framework",
337 selected='selected',
338 ),
339 dict(
340 name="PythonApplications",
341 long_name="GUI Applications",
342 source="/Applications/Python %(VER)s",
343 readme="""\
344 This package installs IDLE (an interactive Python IDE),
345 Python Launcher and Build Applet (create application bundles
346 from python scripts).
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000347
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000348 It also installs a number of examples and demos.
349 """,
350 required=False,
351 selected='selected',
352 ),
353 dict(
354 name="PythonUnixTools",
355 long_name="UNIX command-line tools",
356 source="/usr/local/bin",
357 readme="""\
358 This package installs the unix tools in /usr/local/bin for
359 compatibility with older releases of Python. This package
360 is not necessary to use Python.
361 """,
362 required=False,
363 selected='selected',
364 ),
365 dict(
366 name="PythonDocumentation",
367 long_name="Python Documentation",
368 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
369 source="/pydocs",
370 readme="""\
371 This package installs the python documentation at a location
Ned Deily4a96a372013-01-29 00:08:32 -0800372 that is useable for pydoc and IDLE.
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000373 """,
374 postflight="scripts/postflight.documentation",
375 required=False,
376 selected='selected',
377 ),
378 dict(
379 name="PythonProfileChanges",
380 long_name="Shell profile updater",
381 readme="""\
382 This packages updates your shell profile to make sure that
383 the Python tools are found by your shell in preference of
384 the system provided Python tools.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000385
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000386 If you don't install this package you'll have to add
387 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
388 to your PATH by hand.
389 """,
390 postflight="scripts/postflight.patch-profile",
391 topdir="/Library/Frameworks/Python.framework",
392 source="/empty-dir",
393 required=False,
394 selected=unselected_for_python3,
395 ),
396 ]
397
Ned Deily4a96a372013-01-29 00:08:32 -0800398 if DEPTARGET < '10.4' and not PYTHON_3:
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000399 result.append(
400 dict(
401 name="PythonSystemFixes",
402 long_name="Fix system Python",
403 readme="""\
404 This package updates the system python installation on
405 Mac OS X 10.3 to ensure that you can build new python extensions
406 using that copy of python after installing this version.
407 """,
408 postflight="../Tools/fixapplepython23.py",
409 topdir="/Library/Frameworks/Python.framework",
410 source="/empty-dir",
411 required=False,
412 selected=unselected_for_python3,
413 )
414 )
415 return result
Thomas Wouters477c8d52006-05-27 19:21:47 +0000416
Thomas Wouters477c8d52006-05-27 19:21:47 +0000417def fatal(msg):
418 """
419 A fatal error, bail out.
420 """
421 sys.stderr.write('FATAL: ')
422 sys.stderr.write(msg)
423 sys.stderr.write('\n')
424 sys.exit(1)
425
426def fileContents(fn):
427 """
428 Return the contents of the named file
429 """
Ned Deily4a96a372013-01-29 00:08:32 -0800430 return open(fn, 'r').read()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000431
432def runCommand(commandline):
433 """
Ezio Melotti13925002011-03-16 11:05:33 +0200434 Run a command and raise RuntimeError if it fails. Output is suppressed
Thomas Wouters477c8d52006-05-27 19:21:47 +0000435 unless the command fails.
436 """
437 fd = os.popen(commandline, 'r')
438 data = fd.read()
439 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000440 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000441 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800442 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000443
444 if VERBOSE:
445 sys.stdout.write(data); sys.stdout.flush()
446
447def captureCommand(commandline):
448 fd = os.popen(commandline, 'r')
449 data = fd.read()
450 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000451 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000452 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800453 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000454
455 return data
456
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000457def getTclTkVersion(configfile, versionline):
458 """
459 search Tcl or Tk configuration file for version line
460 """
461 try:
462 f = open(configfile, "r")
463 except:
464 fatal("Framework configuration file not found: %s" % configfile)
465
466 for l in f:
467 if l.startswith(versionline):
468 f.close()
469 return l
470
471 fatal("Version variable %s not found in framework configuration file: %s"
472 % (versionline, configfile))
473
Thomas Wouters477c8d52006-05-27 19:21:47 +0000474def checkEnvironment():
475 """
476 Check that we're running on a supported system.
477 """
478
Ned Deilye59e4c52011-01-29 18:56:28 +0000479 if sys.version_info[0:2] < (2, 4):
480 fatal("This script must be run with Python 2.4 or later")
481
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482 if platform.system() != 'Darwin':
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000483 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000485 if int(platform.release().split('.')[0]) < 8:
486 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000487
488 if not os.path.exists(SDKPATH):
489 fatal("Please install the latest version of Xcode and the %s SDK"%(
490 os.path.basename(SDKPATH[:-4])))
491
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000492 # Because we only support dynamic load of only one major/minor version of
493 # Tcl/Tk, ensure:
494 # 1. there are no user-installed frameworks of Tcl/Tk with version
Ned Deily4a96a372013-01-29 00:08:32 -0800495 # higher than the Apple-supplied system version in
496 # SDKROOT/System/Library/Frameworks
497 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
498 # in) SDKROOT/Library/Frameworks with the same version as the system
499 # version. This allows users to choose to install a newer patch level.
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000500
Ned Deily4a96a372013-01-29 00:08:32 -0800501 frameworks = {}
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000502 for framework in ['Tcl', 'Tk']:
Ned Deily4a96a372013-01-29 00:08:32 -0800503 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000504 sysfw = os.path.join(SDKPATH, 'System', fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800505 libfw = os.path.join(SDKPATH, fwpth)
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000506 usrfw = os.path.join(os.getenv('HOME'), fwpth)
Ned Deily4a96a372013-01-29 00:08:32 -0800507 frameworks[framework] = os.readlink(sysfw)
508 if not os.path.exists(libfw):
509 fatal("Please install a link to a current %s %s as %s so "
510 "the user can override the system framework."
511 % (framework, frameworks[framework], libfw))
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000512 if os.readlink(libfw) != os.readlink(sysfw):
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000513 fatal("Version of %s must match %s" % (libfw, sysfw) )
514 if os.path.exists(usrfw):
515 fatal("Please rename %s to avoid possible dynamic load issues."
516 % usrfw)
517
Ned Deily4a96a372013-01-29 00:08:32 -0800518 if frameworks['Tcl'] != frameworks['Tk']:
519 fatal("The Tcl and Tk frameworks are not the same version.")
520
521 # add files to check after build
522 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
523 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
524 % frameworks['Tcl'],
525 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
526 % frameworks['Tk'],
527 ]
528
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000529 # Remove inherited environment variables which might influence build
530 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
531 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
532 for ev in list(os.environ):
533 for prefix in environ_var_prefixes:
534 if ev.startswith(prefix) :
Ned Deily4a96a372013-01-29 00:08:32 -0800535 print("INFO: deleting environment variable %s=%s" % (
536 ev, os.environ[ev]))
Ronald Oussorenc45c3d92010-04-18 15:24:17 +0000537 del os.environ[ev]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000538
Ned Deily4a96a372013-01-29 00:08:32 -0800539 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
540 if 'SDK_TOOLS_BIN' in os.environ:
541 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
542 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
543 # add its fixed location here if it exists
544 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
545 if os.path.isdir(OLD_DEVELOPER_TOOLS):
546 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
547 os.environ['PATH'] = base_path
548 print("Setting default PATH: %s"%(os.environ['PATH']))
Ronald Oussoren1e99be72010-04-20 06:36:47 +0000549
Thomas Wouters477c8d52006-05-27 19:21:47 +0000550
Thomas Wouters89f507f2006-12-13 04:49:30 +0000551def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000552 """
553 Parse arguments and update global settings.
554 """
Ronald Oussoren1943f862009-03-30 19:39:14 +0000555 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
Ned Deily4a96a372013-01-29 00:08:32 -0800556 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +0000557
558 if args is None:
559 args = sys.argv[1:]
560
561 try:
562 options, args = getopt.getopt(args, '?hb',
Ronald Oussoren1943f862009-03-30 19:39:14 +0000563 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
564 'dep-target=', 'universal-archs=', 'help' ])
Ned Deily4a96a372013-01-29 00:08:32 -0800565 except getopt.GetoptError:
566 print(sys.exc_info()[1])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000567 sys.exit(1)
568
569 if args:
Ned Deily4a96a372013-01-29 00:08:32 -0800570 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000571 sys.exit(1)
572
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000573 deptarget = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000574 for k, v in options:
Ronald Oussoren1943f862009-03-30 19:39:14 +0000575 if k in ('-h', '-?', '--help'):
Ned Deily4a96a372013-01-29 00:08:32 -0800576 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000577 sys.exit(0)
578
579 elif k in ('-d', '--build-dir'):
580 WORKDIR=v
581
582 elif k in ('--third-party',):
583 DEPSRC=v
584
585 elif k in ('--sdk-path',):
586 SDKPATH=v
587
588 elif k in ('--src-dir',):
589 SRCDIR=v
590
Ronald Oussoren1943f862009-03-30 19:39:14 +0000591 elif k in ('--dep-target', ):
592 DEPTARGET=v
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000593 deptarget=v
Ronald Oussoren1943f862009-03-30 19:39:14 +0000594
595 elif k in ('--universal-archs', ):
596 if v in UNIVERSALOPTS:
597 UNIVERSALARCHS = v
598 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000599 if deptarget is None:
600 # Select alternate default deployment
601 # target
602 DEPTARGET = default_target_map.get(v, '10.3')
Ronald Oussoren1943f862009-03-30 19:39:14 +0000603 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800604 raise NotImplementedError(v)
Ronald Oussoren1943f862009-03-30 19:39:14 +0000605
Thomas Wouters477c8d52006-05-27 19:21:47 +0000606 else:
Ned Deily4a96a372013-01-29 00:08:32 -0800607 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000608
609 SRCDIR=os.path.abspath(SRCDIR)
610 WORKDIR=os.path.abspath(WORKDIR)
611 SDKPATH=os.path.abspath(SDKPATH)
612 DEPSRC=os.path.abspath(DEPSRC)
613
Ned Deily4a96a372013-01-29 00:08:32 -0800614 CC, CXX=target_cc_map[DEPTARGET]
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000615
Ned Deily4a96a372013-01-29 00:08:32 -0800616 print("Settings:")
617 print(" * Source directory:", SRCDIR)
618 print(" * Build directory: ", WORKDIR)
619 print(" * SDK location: ", SDKPATH)
620 print(" * Third-party source:", DEPSRC)
621 print(" * Deployment target:", DEPTARGET)
622 print(" * Universal architectures:", ARCHLIST)
623 print(" * C compiler:", CC)
624 print(" * C++ compiler:", CXX)
625 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000626
627
628
629
630def extractArchive(builddir, archiveName):
631 """
632 Extract a source archive into 'builddir'. Returns the path of the
633 extracted archive.
634
635 XXX: This function assumes that archives contain a toplevel directory
636 that is has the same name as the basename of the archive. This is
637 save enough for anything we use.
638 """
639 curdir = os.getcwd()
640 try:
641 os.chdir(builddir)
642 if archiveName.endswith('.tar.gz'):
643 retval = os.path.basename(archiveName[:-7])
644 if os.path.exists(retval):
645 shutil.rmtree(retval)
646 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
647
648 elif archiveName.endswith('.tar.bz2'):
649 retval = os.path.basename(archiveName[:-8])
650 if os.path.exists(retval):
651 shutil.rmtree(retval)
652 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
653
654 elif archiveName.endswith('.tar'):
655 retval = os.path.basename(archiveName[:-4])
656 if os.path.exists(retval):
657 shutil.rmtree(retval)
658 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
659
660 elif archiveName.endswith('.zip'):
661 retval = os.path.basename(archiveName[:-4])
662 if os.path.exists(retval):
663 shutil.rmtree(retval)
664 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
665
666 data = fp.read()
667 xit = fp.close()
668 if xit is not None:
669 sys.stdout.write(data)
Ned Deily4a96a372013-01-29 00:08:32 -0800670 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000671
672 return os.path.join(builddir, retval)
673
674 finally:
675 os.chdir(curdir)
676
Thomas Wouters477c8d52006-05-27 19:21:47 +0000677def downloadURL(url, fname):
678 """
679 Download the contents of the url into the file.
680 """
Ned Deily4a96a372013-01-29 00:08:32 -0800681 fpIn = urllib_request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000682 fpOut = open(fname, 'wb')
683 block = fpIn.read(10240)
684 try:
685 while block:
686 fpOut.write(block)
687 block = fpIn.read(10240)
688 fpIn.close()
689 fpOut.close()
690 except:
691 try:
692 os.unlink(fname)
693 except:
694 pass
695
Ned Deily4a96a372013-01-29 00:08:32 -0800696def verifyThirdPartyFile(url, checksum, fname):
697 """
698 Download file from url to filename fname if it does not already exist.
699 Abort if file contents does not match supplied md5 checksum.
700 """
701 name = os.path.basename(fname)
702 if os.path.exists(fname):
703 print("Using local copy of %s"%(name,))
704 else:
705 print("Did not find local copy of %s"%(name,))
706 print("Downloading %s"%(name,))
707 downloadURL(url, fname)
708 print("Archive for %s stored as %s"%(name, fname))
709 if os.system(
710 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
711 % (shellQuote(fname), checksum) ):
712 fatal('MD5 checksum mismatch for file %s' % fname)
713
Thomas Wouters477c8d52006-05-27 19:21:47 +0000714def buildRecipe(recipe, basedir, archList):
715 """
716 Build software using a recipe. This function does the
717 'configure;make;make install' dance for C software, with a possibility
718 to customize this process, basically a poor-mans DarwinPorts.
719 """
720 curdir = os.getcwd()
721
722 name = recipe['name']
723 url = recipe['url']
724 configure = recipe.get('configure', './configure')
725 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
726 shellQuote(basedir)))
727
728 archiveName = os.path.split(url)[-1]
729 sourceArchive = os.path.join(DEPSRC, archiveName)
730
731 if not os.path.exists(DEPSRC):
732 os.mkdir(DEPSRC)
733
Ned Deily4a96a372013-01-29 00:08:32 -0800734 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
735 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000736 buildDir=os.path.join(WORKDIR, '_bld')
737 if not os.path.exists(buildDir):
738 os.mkdir(buildDir)
739
740 workDir = extractArchive(buildDir, sourceArchive)
741 os.chdir(workDir)
742 if 'buildDir' in recipe:
743 os.chdir(recipe['buildDir'])
744
Ned Deily4a96a372013-01-29 00:08:32 -0800745 for patch in recipe.get('patches', ()):
746 if isinstance(patch, tuple):
747 url, checksum = patch
748 fn = os.path.join(DEPSRC, os.path.basename(url))
749 verifyThirdPartyFile(url, checksum, fn)
750 else:
751 # patch is a file in the source directory
752 fn = os.path.join(curdir, patch)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000753 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
754 shellQuote(fn),))
755
Ned Deily4a96a372013-01-29 00:08:32 -0800756 for patchscript in recipe.get('patchscripts', ()):
757 if isinstance(patchscript, tuple):
758 url, checksum = patchscript
759 fn = os.path.join(DEPSRC, os.path.basename(url))
760 verifyThirdPartyFile(url, checksum, fn)
761 else:
762 # patch is a file in the source directory
763 fn = os.path.join(curdir, patchscript)
764 if fn.endswith('.bz2'):
765 runCommand('bunzip2 -fk %s' % shellQuote(fn))
766 fn = fn[:-4]
767 runCommand('sh %s' % shellQuote(fn))
768 os.unlink(fn)
769
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000770 if configure is not None:
771 configure_args = [
772 "--prefix=/usr/local",
773 "--enable-static",
774 "--disable-shared",
775 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
776 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000777
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000778 if 'configure_pre' in recipe:
779 args = list(recipe['configure_pre'])
780 if '--disable-static' in args:
781 configure_args.remove('--enable-static')
782 if '--enable-shared' in args:
783 configure_args.remove('--disable-shared')
784 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000785
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000786 if recipe.get('useLDFlags', 1):
787 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800788 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
789 "-I%s/usr/local/include"%(
790 recipe.get('extra_cflags', ''),
791 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000792 ' -arch '.join(archList),
793 shellQuote(SDKPATH)[1:-1],
794 shellQuote(basedir)[1:-1],),
Ned Deily4a96a372013-01-29 00:08:32 -0800795 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
796 DEPTARGET,
Thomas Wouters477c8d52006-05-27 19:21:47 +0000797 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000798 shellQuote(basedir)[1:-1],
799 ' -arch '.join(archList)),
800 ])
801 else:
802 configure_args.extend([
Ned Deily4a96a372013-01-29 00:08:32 -0800803 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
804 "-I%s/usr/local/include"%(
805 recipe.get('extra_cflags', ''),
806 DEPTARGET,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000807 ' -arch '.join(archList),
808 shellQuote(SDKPATH)[1:-1],
809 shellQuote(basedir)[1:-1],),
810 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000811
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000812 if 'configure_post' in recipe:
Ned Deily4a96a372013-01-29 00:08:32 -0800813 configure_args = configure_args + list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000814
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000815 configure_args.insert(0, configure)
816 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000817
Ned Deily4a96a372013-01-29 00:08:32 -0800818 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000819 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000820
Ned Deily4a96a372013-01-29 00:08:32 -0800821 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000822 runCommand('{ ' + install + ' ;} 2>&1')
823
Ned Deily4a96a372013-01-29 00:08:32 -0800824 print("Done %s"%(name,))
825 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000826
827 os.chdir(curdir)
828
829def buildLibraries():
830 """
831 Build our dependencies into $WORKDIR/libraries/usr/local
832 """
Ned Deily4a96a372013-01-29 00:08:32 -0800833 print("")
834 print("Building required libraries")
835 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000836 universal = os.path.join(WORKDIR, 'libraries')
837 os.mkdir(universal)
838 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
839 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
840
Ronald Oussoren1943f862009-03-30 19:39:14 +0000841 for recipe in library_recipes():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000842 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000843
844
845
846def buildPythonDocs():
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000847 # This stores the documentation as Resources/English.lproj/Documentation
Thomas Wouters477c8d52006-05-27 19:21:47 +0000848 # inside the framwork. pydoc and IDLE will pick it up there.
Ned Deily4a96a372013-01-29 00:08:32 -0800849 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000850 rootDir = os.path.join(WORKDIR, '_root')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000851 buildDir = os.path.join('../../Doc')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000852 docdir = os.path.join(rootDir, 'pydocs')
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000853 curDir = os.getcwd()
854 os.chdir(buildDir)
855 runCommand('make update')
Martin v. Löwis6120ddb2010-04-22 13:16:44 +0000856 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
Ronald Oussoren207b4c22009-03-30 17:20:30 +0000857 os.chdir(curDir)
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000858 if not os.path.exists(docdir):
859 os.mkdir(docdir)
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000860 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000861
862
863def buildPython():
Ned Deily4a96a372013-01-29 00:08:32 -0800864 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000865
866 buildDir = os.path.join(WORKDIR, '_bld', 'python')
867 rootDir = os.path.join(WORKDIR, '_root')
868
869 if os.path.exists(buildDir):
870 shutil.rmtree(buildDir)
871 if os.path.exists(rootDir):
872 shutil.rmtree(rootDir)
Ned Deily4f7ff782011-01-15 05:29:12 +0000873 os.makedirs(buildDir)
874 os.makedirs(rootDir)
875 os.makedirs(os.path.join(rootDir, 'empty-dir'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000876 curdir = os.getcwd()
877 os.chdir(buildDir)
878
879 # Not sure if this is still needed, the original build script
880 # claims that parts of the install assume python.exe exists.
881 os.symlink('python', os.path.join(buildDir, 'python.exe'))
882
883 # Extract the version from the configure file, needed to calculate
884 # several paths.
885 version = getVersion()
886
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000887 # Since the extra libs are not in their installed framework location
888 # during the build, augment the library path so that the interpreter
889 # will find them during its extension import sanity checks.
890 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
891 'libraries', 'usr', 'local', 'lib')
Ned Deily4a96a372013-01-29 00:08:32 -0800892 print("Running configure...")
Ronald Oussoren1943f862009-03-30 19:39:14 +0000893 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000894 "--with-universal-archs=%s "
895 "%s "
Ronald Oussoren1943f862009-03-30 19:39:14 +0000896 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
897 "OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
898 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
899 UNIVERSALARCHS,
Benjamin Petersond9b7d482010-03-19 21:42:45 +0000900 (' ', '--with-computed-gotos ')[PYTHON_3],
Ronald Oussoren1943f862009-03-30 19:39:14 +0000901 shellQuote(WORKDIR)[1:-1],
Thomas Wouters477c8d52006-05-27 19:21:47 +0000902 shellQuote(WORKDIR)[1:-1]))
903
Ned Deily4a96a372013-01-29 00:08:32 -0800904 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905 runCommand("make")
906
Ned Deily4a96a372013-01-29 00:08:32 -0800907 print("Running make install")
Ronald Oussorenf84d7e92009-05-19 11:27:25 +0000908 runCommand("make install DESTDIR=%s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000909 shellQuote(rootDir)))
910
Ned Deily4a96a372013-01-29 00:08:32 -0800911 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000912 runCommand("make frameworkinstallextras DESTDIR=%s"%(
913 shellQuote(rootDir)))
914
Ronald Oussorenac4b39f2009-03-30 20:05:35 +0000915 del os.environ['DYLD_LIBRARY_PATH']
Ned Deily4a96a372013-01-29 00:08:32 -0800916 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000917 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
918 runCommand("mv %s/* %s"%(
919 shellQuote(os.path.join(
920 WORKDIR, 'libraries', 'Library', 'Frameworks',
921 'Python.framework', 'Versions', getVersion(),
922 'lib')),
923 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
924 'Python.framework', 'Versions', getVersion(),
925 'lib'))))
926
Ned Deily4a96a372013-01-29 00:08:32 -0800927 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000928 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000929 gid = grp.getgrnam('admin').gr_gid
930
Ned Deily4a96a372013-01-29 00:08:32 -0800931 shared_lib_error = False
Thomas Wouters477c8d52006-05-27 19:21:47 +0000932 for dirpath, dirnames, filenames in os.walk(frmDir):
933 for dn in dirnames:
Ned Deily4a96a372013-01-29 00:08:32 -0800934 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000935 os.chown(os.path.join(dirpath, dn), -1, gid)
936
Thomas Wouters477c8d52006-05-27 19:21:47 +0000937 for fn in filenames:
938 if os.path.islink(fn):
939 continue
940
941 # "chmod g+w $fn"
942 p = os.path.join(dirpath, fn)
943 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000944 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
945 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000946
Ned Deily4a96a372013-01-29 00:08:32 -0800947 if fn in EXPECTED_SHARED_LIBS:
948 # check to see that this file was linked with the
949 # expected library path and version
950 data = captureCommand("otool -L %s" % shellQuote(p))
951 for sl in EXPECTED_SHARED_LIBS[fn]:
952 if ("\t%s " % sl) not in data:
953 print("Expected shared lib %s was not linked with %s"
954 % (sl, p))
955 shared_lib_error = True
956
957 if shared_lib_error:
958 fatal("Unexpected shared library errors.")
959
Ned Deilye59e4c52011-01-29 18:56:28 +0000960 if PYTHON_3:
961 LDVERSION=None
962 VERSION=None
963 ABIFLAGS=None
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000964
Ned Deilye59e4c52011-01-29 18:56:28 +0000965 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000966 for ln in fp:
967 if ln.startswith('VERSION='):
968 VERSION=ln.split()[1]
969 if ln.startswith('ABIFLAGS='):
970 ABIFLAGS=ln.split()[1]
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000971 if ln.startswith('LDVERSION='):
972 LDVERSION=ln.split()[1]
Ned Deilye59e4c52011-01-29 18:56:28 +0000973 fp.close()
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000974
Ned Deilye59e4c52011-01-29 18:56:28 +0000975 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
976 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
977 config_suffix = '-' + LDVERSION
978 else:
979 config_suffix = '' # Python 2.x
Ronald Oussoren0499d0b2010-12-07 14:41:05 +0000980
Thomas Wouters477c8d52006-05-27 19:21:47 +0000981 # We added some directories to the search path during the configure
982 # phase. Remove those because those directories won't be there on
Ned Deily4a96a372013-01-29 00:08:32 -0800983 # the end-users system. Also remove the directories from _sysconfigdata.py
984 # (added in 3.3) if it exists.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000985
Ned Deily4a96a372013-01-29 00:08:32 -0800986 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
987 'Python.framework', 'Versions',
988 version, 'lib', 'python%s'%(version,))
989 paths = [os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile'),
990 os.path.join(path_to_lib, '_sysconfigdata.py')]
991 for path in paths:
992 if not os.path.exists(path):
993 continue
994 fp = open(path, 'r')
995 data = fp.read()
996 fp.close()
997
998 data = data.replace(' -L%s/libraries/usr/local/lib'%(WORKDIR,), '')
999 data = data.replace(' -I%s/libraries/usr/local/include'%(WORKDIR,), '')
1000 fp = open(path, 'w')
1001 fp.write(data)
1002 fp.close()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001003
1004 # Add symlinks in /usr/local/bin, using relative links
1005 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1006 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1007 'Python.framework', 'Versions', version, 'bin')
1008 if os.path.exists(usr_local_bin):
1009 shutil.rmtree(usr_local_bin)
1010 os.makedirs(usr_local_bin)
1011 for fn in os.listdir(
1012 os.path.join(frmDir, 'Versions', version, 'bin')):
1013 os.symlink(os.path.join(to_framework, fn),
1014 os.path.join(usr_local_bin, fn))
1015
1016 os.chdir(curdir)
1017
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001018 if PYTHON_3:
Ezio Melotti7c4a7e62013-08-26 01:32:56 +03001019 # Remove the 'Current' link, that way we don't accidentally mess
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001020 # with an already installed version of python 2
1021 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1022 'Python.framework', 'Versions', 'Current'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001023
1024def patchFile(inPath, outPath):
1025 data = fileContents(inPath)
1026 data = data.replace('$FULL_VERSION', getFullVersion())
1027 data = data.replace('$VERSION', getVersion())
Ronald Oussoren1943f862009-03-30 19:39:14 +00001028 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
Ronald Oussorend0103292010-10-20 12:56:56 +00001029 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001030 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001031
1032 # This one is not handy as a template variable
1033 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Ned Deily4a96a372013-01-29 00:08:32 -08001034 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001035 fp.write(data)
1036 fp.close()
1037
1038def patchScript(inPath, outPath):
1039 data = fileContents(inPath)
1040 data = data.replace('@PYVER@', getVersion())
Ned Deily4a96a372013-01-29 00:08:32 -08001041 fp = open(outPath, 'w')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001042 fp.write(data)
1043 fp.close()
Ned Deily4a96a372013-01-29 00:08:32 -08001044 os.chmod(outPath, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001045
1046
1047
1048def packageFromRecipe(targetDir, recipe):
1049 curdir = os.getcwd()
1050 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +00001051 # The major version (such as 2.5) is included in the package name
1052 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001053 # common.
1054 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001055 srcdir = recipe.get('source')
1056 pkgroot = recipe.get('topdir', srcdir)
1057 postflight = recipe.get('postflight')
1058 readme = textwrap.dedent(recipe['readme'])
1059 isRequired = recipe.get('required', True)
1060
Ned Deily4a96a372013-01-29 00:08:32 -08001061 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001062
1063 # Substitute some variables
1064 textvars = dict(
1065 VER=getVersion(),
1066 FULLVER=getFullVersion(),
1067 )
1068 readme = readme % textvars
1069
1070 if pkgroot is not None:
1071 pkgroot = pkgroot % textvars
1072 else:
1073 pkgroot = '/'
1074
1075 if srcdir is not None:
1076 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1077 srcdir = srcdir % textvars
1078
1079 if postflight is not None:
1080 postflight = os.path.abspath(postflight)
1081
1082 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1083 os.makedirs(packageContents)
1084
1085 if srcdir is not None:
1086 os.chdir(srcdir)
1087 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1088 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1089 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1090
1091 fn = os.path.join(packageContents, 'PkgInfo')
1092 fp = open(fn, 'w')
1093 fp.write('pmkrpkg1')
1094 fp.close()
1095
1096 rsrcDir = os.path.join(packageContents, "Resources")
1097 os.mkdir(rsrcDir)
1098 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1099 fp.write(readme)
1100 fp.close()
1101
1102 if postflight is not None:
1103 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1104
1105 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001106 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001107 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001108 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1109 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1110 CFBundleName='Python.%s'%(pkgname,),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001111 CFBundleShortVersionString=vers,
1112 IFMajorVersion=major,
1113 IFMinorVersion=minor,
1114 IFPkgFormatVersion=0.10000000149011612,
1115 IFPkgFlagAllowBackRev=False,
1116 IFPkgFlagAuthorizationAction="RootAuthorization",
1117 IFPkgFlagDefaultLocation=pkgroot,
1118 IFPkgFlagFollowLinks=True,
1119 IFPkgFlagInstallFat=True,
1120 IFPkgFlagIsRequired=isRequired,
1121 IFPkgFlagOverwritePermissions=False,
1122 IFPkgFlagRelocatable=False,
1123 IFPkgFlagRestartAction="NoRestart",
1124 IFPkgFlagRootVolumeOnly=True,
1125 IFPkgFlagUpdateInstalledLangauges=False,
1126 )
1127 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1128
1129 pl = Plist(
1130 IFPkgDescriptionDescription=readme,
Ronald Oussoren1943f862009-03-30 19:39:14 +00001131 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001132 IFPkgDescriptionVersion=vers,
1133 )
1134 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1135
1136 finally:
1137 os.chdir(curdir)
1138
1139
1140def makeMpkgPlist(path):
1141
1142 vers = getFullVersion()
Ned Deily4a96a372013-01-29 00:08:32 -08001143 major, minor = getVersionMajorMinor()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001144
1145 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001146 CFBundleGetInfoString="Python %s"%(vers,),
1147 CFBundleIdentifier='org.python.Python',
1148 CFBundleName='Python',
Thomas Wouters477c8d52006-05-27 19:21:47 +00001149 CFBundleShortVersionString=vers,
1150 IFMajorVersion=major,
1151 IFMinorVersion=minor,
1152 IFPkgFlagComponentDirectory="Contents/Packages",
1153 IFPkgFlagPackageList=[
1154 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001155 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001156 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001157 )
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001158 for item in pkg_recipes()
Thomas Wouters477c8d52006-05-27 19:21:47 +00001159 ],
1160 IFPkgFormatVersion=0.10000000149011612,
1161 IFPkgFlagBackgroundScaling="proportional",
1162 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001163 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001164 )
1165
1166 writePlist(pl, path)
1167
1168
1169def buildInstaller():
1170
1171 # Zap all compiled files
1172 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1173 for fn in filenames:
1174 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1175 os.unlink(os.path.join(dirpath, fn))
1176
1177 outdir = os.path.join(WORKDIR, 'installer')
1178 if os.path.exists(outdir):
1179 shutil.rmtree(outdir)
1180 os.mkdir(outdir)
1181
Ronald Oussoren1943f862009-03-30 19:39:14 +00001182 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
Thomas Wouters477c8d52006-05-27 19:21:47 +00001183 pkgcontents = os.path.join(pkgroot, 'Packages')
1184 os.makedirs(pkgcontents)
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001185 for recipe in pkg_recipes():
Thomas Wouters477c8d52006-05-27 19:21:47 +00001186 packageFromRecipe(pkgcontents, recipe)
1187
1188 rsrcDir = os.path.join(pkgroot, 'Resources')
1189
1190 fn = os.path.join(pkgroot, 'PkgInfo')
1191 fp = open(fn, 'w')
1192 fp.write('pmkrpkg1')
1193 fp.close()
1194
1195 os.mkdir(rsrcDir)
1196
1197 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1198 pl = Plist(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001199 IFPkgDescriptionTitle="Python",
Thomas Wouters477c8d52006-05-27 19:21:47 +00001200 IFPkgDescriptionVersion=getVersion(),
1201 )
1202
1203 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1204 for fn in os.listdir('resources'):
1205 if fn == '.svn': continue
1206 if fn.endswith('.jpg'):
1207 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1208 else:
1209 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1210
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001211 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001212
1213
1214def installSize(clear=False, _saved=[]):
1215 if clear:
1216 del _saved[:]
1217 if not _saved:
1218 data = captureCommand("du -ks %s"%(
1219 shellQuote(os.path.join(WORKDIR, '_root'))))
1220 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1221 return _saved[0]
1222
1223
1224def buildDMG():
1225 """
Thomas Wouters89f507f2006-12-13 04:49:30 +00001226 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001227 """
1228 outdir = os.path.join(WORKDIR, 'diskimage')
1229 if os.path.exists(outdir):
1230 shutil.rmtree(outdir)
1231
1232 imagepath = os.path.join(outdir,
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001233 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001234 if INCLUDE_TIMESTAMP:
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001235 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
Thomas Wouters477c8d52006-05-27 19:21:47 +00001236 imagepath = imagepath + '.dmg'
1237
1238 os.mkdir(outdir)
Ronald Oussoren1943f862009-03-30 19:39:14 +00001239 volname='Python %s'%(getFullVersion())
1240 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1241 shellQuote(volname),
Thomas Wouters477c8d52006-05-27 19:21:47 +00001242 shellQuote(os.path.join(WORKDIR, 'installer')),
Ronald Oussoren1943f862009-03-30 19:39:14 +00001243 shellQuote(imagepath + ".tmp.dmg" )))
1244
1245
1246 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1247 os.mkdir(os.path.join(WORKDIR, "mnt"))
1248 runCommand("hdiutil attach %s -mountroot %s"%(
1249 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1250
1251 # Custom icon for the DMG, shown when the DMG is mounted.
1252 shutil.copy("../Icons/Disk Image.icns",
1253 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
Ned Deily4a96a372013-01-29 00:08:32 -08001254 runCommand("SetFile -a C %s/"%(
Ronald Oussoren1943f862009-03-30 19:39:14 +00001255 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1256
1257 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1258
1259 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1260 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1261 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1262 setIcon(imagepath, "../Icons/Disk Image.icns")
1263
1264 os.unlink(imagepath + ".tmp.dmg")
Thomas Wouters477c8d52006-05-27 19:21:47 +00001265
1266 return imagepath
1267
1268
1269def setIcon(filePath, icnsPath):
1270 """
1271 Set the custom icon for the specified file or directory.
Thomas Wouters477c8d52006-05-27 19:21:47 +00001272 """
Thomas Wouters477c8d52006-05-27 19:21:47 +00001273
Ronald Oussoren70050672010-04-30 15:00:26 +00001274 dirPath = os.path.normpath(os.path.dirname(__file__))
1275 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001276 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1277 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1278 # to connections to the window server.
Ronald Oussoren70050672010-04-30 15:00:26 +00001279 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1280 if not os.path.exists(appPath):
1281 os.makedirs(appPath)
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001282 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1283 shellQuote(toolPath), shellQuote(dirPath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001284
Ronald Oussoren207b4c22009-03-30 17:20:30 +00001285 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1286 shellQuote(filePath)))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001287
1288def main():
1289 # First parse options and check if we can perform our work
1290 parseOptions()
1291 checkEnvironment()
1292
Ronald Oussoren1943f862009-03-30 19:39:14 +00001293 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
Benjamin Petersond9b7d482010-03-19 21:42:45 +00001294 os.environ['CC'] = CC
Ned Deily4a96a372013-01-29 00:08:32 -08001295 os.environ['CXX'] = CXX
Thomas Wouters477c8d52006-05-27 19:21:47 +00001296
1297 if os.path.exists(WORKDIR):
1298 shutil.rmtree(WORKDIR)
1299 os.mkdir(WORKDIR)
1300
Ronald Oussorenc45c3d92010-04-18 15:24:17 +00001301 os.environ['LC_ALL'] = 'C'
1302
Thomas Wouters477c8d52006-05-27 19:21:47 +00001303 # Then build third-party libraries such as sleepycat DB4.
1304 buildLibraries()
1305
1306 # Now build python itself
1307 buildPython()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001308
1309 # And then build the documentation
1310 # Remove the Deployment Target from the shell
1311 # environment, it's no longer needed and
1312 # an unexpected build target can cause problems
1313 # when Sphinx and its dependencies need to
1314 # be (re-)installed.
1315 del os.environ['MACOSX_DEPLOYMENT_TARGET']
Thomas Wouters477c8d52006-05-27 19:21:47 +00001316 buildPythonDocs()
Ronald Oussoren6bf63672009-03-31 13:25:17 +00001317
1318
1319 # Prepare the applications folder
Thomas Wouters477c8d52006-05-27 19:21:47 +00001320 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001321 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Ronald Oussorenbc448662009-02-12 16:08:14 +00001322 patchScript("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001323
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001324 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001325 getVersion(),))
Ned Deily4a96a372013-01-29 00:08:32 -08001326 os.chmod(folder, STAT_0o755)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001327 setIcon(folder, "../Icons/Python Folder.icns")
1328
1329 # Create the installer
1330 buildInstaller()
1331
1332 # And copy the readme into the directory containing the installer
1333 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1334
1335 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001336 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001337
1338 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Ned Deily4a96a372013-01-29 00:08:32 -08001339 fp.write("# BUILD INFO\n")
1340 fp.write("# Date: %s\n" % time.ctime())
1341 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001342 fp.close()
1343
Thomas Wouters477c8d52006-05-27 19:21:47 +00001344 # And copy it to a DMG
1345 buildDMG()
1346
Thomas Wouters477c8d52006-05-27 19:21:47 +00001347if __name__ == "__main__":
1348 main()