source: python/trunk/Mac/BuildScript/build-installer.py@ 560

Last change on this file since 560 was 391, checked in by dmik, 11 years ago

python: Merge vendor 2.7.6 to trunk.

  • Property svn:eol-style set to native
File size: 54.0 KB
Line 
1#!/usr/bin/env python
2"""
3This script is used to build "official" universal installers on Mac OS X.
4It requires at least Mac OS X 10.5, Xcode 3, 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.
7
8Please 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.
12
13In 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
27Usage: see USAGE variable in the script.
28"""
29import 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 )
42
43INCLUDE_TIMESTAMP = 1
44VERBOSE = 1
45
46from plistlib import Plist
47
48try:
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 """
57 Return the string value in a form that can safely be inserted into
58 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]
68 raise RuntimeError("Cannot find variable %s" % variable[:-1])
69
70_cache_getVersion = None
71
72def getVersion():
73 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
78
79def getVersionTuple():
80 return tuple([int(n) for n in getVersion().split('.')])
81
82def getVersionMajorMinor():
83 return tuple([int(n) for n in getVersion().split('.', 2)])
84
85_cache_getFullVersion = None
86
87def getFullVersion():
88 global _cache_getFullVersion
89 if _cache_getFullVersion is not None:
90 return _cache_getFullVersion
91 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
92 for ln in open(fn):
93 if 'PY_VERSION' in ln:
94 _cache_getFullVersion = ln.split()[-1][1:-1]
95 return _cache_getFullVersion
96 raise RuntimeError("Cannot find full version??")
97
98# The directory we'll use to create the build (will be erased and recreated)
99WORKDIR = "/tmp/_py"
100
101# The directory we'll use to store third-party sources. Set this to something
102# else if you don't want to re-fetch required libraries every time.
103DEPSRC = os.path.join(WORKDIR, 'third-party')
104DEPSRC = os.path.expanduser('~/Universal/other-sources')
105
106# Location of the preferred SDK
107
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
111### issue is resolved.
112###
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
121SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
122
123universal_opts_map = { '32-bit': ('i386', 'ppc',),
124 '64-bit': ('x86_64', 'ppc64',),
125 '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}
134
135UNIVERSALOPTS = tuple(universal_opts_map.keys())
136
137UNIVERSALARCHS = '32-bit'
138
139ARCHLIST = universal_opts_map[UNIVERSALARCHS]
140
141# Source directory (assume we're in Mac/BuildScript)
142SRCDIR = os.path.dirname(
143 os.path.dirname(
144 os.path.dirname(
145 os.path.abspath(__file__
146 ))))
147
148# $MACOSX_DEPLOYMENT_TARGET -> minimum OS X level
149DEPTARGET = '10.3'
150
151target_cc_map = {
152 '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++'),
158 '10.9': ('clang', 'clang++'),
159}
160
161CC, CXX = target_cc_map[DEPTARGET]
162
163PYTHON_3 = getVersionTuple() >= (3, 0)
164
165USAGE = textwrap.dedent("""\
166 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)
175 --dep-target=10.n OS X deployment target (default: %(DEPTARGET)r)
176 --universal-archs=x universal architectures (options: %(UNIVERSALOPTS)r, default: %(UNIVERSALARCHS)r)
177""")% globals()
178
179# 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 = {}
186
187# Instructions for building libraries that are necessary for building a
188# batteries included python.
189# [The recipes are defined here for convenience but instantiated later after
190# command line options have been processed.]
191def library_recipes():
192 result = []
193
194 LT_10_5 = bool(DEPTARGET < '10.5')
195
196 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 4)):
197 result.extend([
198 dict(
199 name="Tcl 8.5.15",
200 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tcl8.5.15-src.tar.gz",
201 checksum='f3df162f92c69b254079c4d0af7a690f',
202 buildDir="unix",
203 configure_pre=[
204 '--enable-shared',
205 '--enable-threads',
206 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
207 ],
208 useLDFlags=False,
209 install='make TCL_LIBRARY=%(TCL_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
210 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
211 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
212 },
213 ),
214 dict(
215 name="Tk 8.5.15",
216 url="ftp://ftp.tcl.tk/pub/tcl//tcl8_5/tk8.5.15-src.tar.gz",
217 checksum='55b8e33f903210a4e1c8bce0f820657f',
218 patches=[
219 "issue19373_tk_8_5_15_source.patch",
220 ],
221 buildDir="unix",
222 configure_pre=[
223 '--enable-aqua',
224 '--enable-shared',
225 '--enable-threads',
226 '--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib'%(getVersion(),),
227 ],
228 useLDFlags=False,
229 install='make TCL_LIBRARY=%(TCL_LIBRARY)s TK_LIBRARY=%(TK_LIBRARY)s && make install TCL_LIBRARY=%(TCL_LIBRARY)s TK_LIBRARY=%(TK_LIBRARY)s DESTDIR=%(DESTDIR)s'%{
230 "DESTDIR": shellQuote(os.path.join(WORKDIR, 'libraries')),
231 "TCL_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tcl8.5'%(getVersion())),
232 "TK_LIBRARY": shellQuote('/Library/Frameworks/Python.framework/Versions/%s/lib/tk8.5'%(getVersion())),
233 },
234 ),
235 ])
236
237 if getVersionTuple() >= (3, 3):
238 result.extend([
239 dict(
240 name="XZ 5.0.3",
241 url="http://tukaani.org/xz/xz-5.0.3.tar.gz",
242 checksum='fefe52f9ecd521de2a8ce38c21a27574',
243 configure_pre=[
244 '--disable-dependency-tracking',
245 ]
246 ),
247 ])
248
249 result.extend([
250 dict(
251 name="NCurses 5.9",
252 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz",
253 checksum='8cb9c412e5f2d96bc6f459aa8c6282a1',
254 configure_pre=[
255 "--enable-widec",
256 "--without-cxx",
257 "--without-cxx-binding",
258 "--without-ada",
259 "--without-curses-h",
260 "--enable-shared",
261 "--with-shared",
262 "--without-debug",
263 "--without-normal",
264 "--without-tests",
265 "--without-manpages",
266 "--datadir=/usr/share",
267 "--sysconfdir=/etc",
268 "--sharedstatedir=/usr/com",
269 "--with-terminfo-dirs=/usr/share/terminfo",
270 "--with-default-terminfo-dir=/usr/share/terminfo",
271 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
272 ],
273 patchscripts=[
274 ("ftp://invisible-island.net/ncurses//5.9/ncurses-5.9-20120616-patch.sh.bz2",
275 "f54bf02a349f96a7c4f0d00922f3a0d4"),
276 ],
277 useLDFlags=False,
278 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
279 shellQuote(os.path.join(WORKDIR, 'libraries')),
280 shellQuote(os.path.join(WORKDIR, 'libraries')),
281 getVersion(),
282 ),
283 ),
284 dict(
285 name="SQLite 3.7.13",
286 url="http://www.sqlite.org/sqlite-autoconf-3071300.tar.gz",
287 checksum='c97df403e8a3d5b67bb408fcd6aabd8e',
288 extra_cflags=('-Os '
289 '-DSQLITE_ENABLE_FTS4 '
290 '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
291 '-DSQLITE_ENABLE_RTREE '
292 '-DSQLITE_TCL=0 '
293 '%s' % ('','-DSQLITE_WITHOUT_ZONEMALLOC ')[LT_10_5]),
294 configure_pre=[
295 '--enable-threadsafe',
296 '--enable-shared=no',
297 '--enable-static=yes',
298 '--disable-readline',
299 '--disable-dependency-tracking',
300 ]
301 ),
302 ])
303
304 if DEPTARGET < '10.5':
305 result.extend([
306 dict(
307 name="Bzip2 1.0.6",
308 url="http://bzip.org/1.0.6/bzip2-1.0.6.tar.gz",
309 checksum='00b516f4704d4a7cb50a1d97e6e8e15b',
310 configure=None,
311 install='make install CC=%s CXX=%s, PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
312 CC, CXX,
313 shellQuote(os.path.join(WORKDIR, 'libraries')),
314 ' -arch '.join(ARCHLIST),
315 SDKPATH,
316 ),
317 ),
318 dict(
319 name="ZLib 1.2.3",
320 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
321 checksum='debc62758716a169df9f62e6ab2bc634',
322 configure=None,
323 install='make install CC=%s CXX=%s, prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
324 CC, CXX,
325 shellQuote(os.path.join(WORKDIR, 'libraries')),
326 ' -arch '.join(ARCHLIST),
327 SDKPATH,
328 ),
329 ),
330 dict(
331 # Note that GNU readline is GPL'd software
332 name="GNU Readline 6.1.2",
333 url="http://ftp.gnu.org/pub/gnu/readline/readline-6.1.tar.gz" ,
334 checksum='fc2f7e714fe792db1ce6ddc4c9fb4ef3',
335 patchlevel='0',
336 patches=[
337 # The readline maintainers don't do actual micro releases, but
338 # just ship a set of patches.
339 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-001',
340 'c642f2e84d820884b0bf9fd176bc6c3f'),
341 ('http://ftp.gnu.org/pub/gnu/readline/readline-6.1-patches/readline61-002',
342 '1a76781a1ea734e831588285db7ec9b1'),
343 ]
344 ),
345 ])
346
347 if not PYTHON_3:
348 result.extend([
349 dict(
350 name="Sleepycat DB 4.7.25",
351 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
352 checksum='ec2b87e833779681a0c3a814aa71359e',
353 buildDir="build_unix",
354 configure="../dist/configure",
355 configure_pre=[
356 '--includedir=/usr/local/include/db4',
357 ]
358 ),
359 ])
360
361 return result
362
363
364# Instructions for building packages inside the .mpkg.
365def pkg_recipes():
366 unselected_for_python3 = ('selected', 'unselected')[PYTHON_3]
367 result = [
368 dict(
369 name="PythonFramework",
370 long_name="Python Framework",
371 source="/Library/Frameworks/Python.framework",
372 readme="""\
373 This package installs Python.framework, that is the python
374 interpreter and the standard library. This also includes Python
375 wrappers for lots of Mac OS X API's.
376 """,
377 postflight="scripts/postflight.framework",
378 selected='selected',
379 ),
380 dict(
381 name="PythonApplications",
382 long_name="GUI Applications",
383 source="/Applications/Python %(VER)s",
384 readme="""\
385 This package installs IDLE (an interactive Python IDE),
386 Python Launcher and Build Applet (create application bundles
387 from python scripts).
388
389 It also installs a number of examples and demos.
390 """,
391 required=False,
392 selected='selected',
393 ),
394 dict(
395 name="PythonUnixTools",
396 long_name="UNIX command-line tools",
397 source="/usr/local/bin",
398 readme="""\
399 This package installs the unix tools in /usr/local/bin for
400 compatibility with older releases of Python. This package
401 is not necessary to use Python.
402 """,
403 required=False,
404 selected='selected',
405 ),
406 dict(
407 name="PythonDocumentation",
408 long_name="Python Documentation",
409 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
410 source="/pydocs",
411 readme="""\
412 This package installs the python documentation at a location
413 that is useable for pydoc and IDLE.
414 """,
415 postflight="scripts/postflight.documentation",
416 required=False,
417 selected='selected',
418 ),
419 dict(
420 name="PythonProfileChanges",
421 long_name="Shell profile updater",
422 readme="""\
423 This packages updates your shell profile to make sure that
424 the Python tools are found by your shell in preference of
425 the system provided Python tools.
426
427 If you don't install this package you'll have to add
428 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
429 to your PATH by hand.
430 """,
431 postflight="scripts/postflight.patch-profile",
432 topdir="/Library/Frameworks/Python.framework",
433 source="/empty-dir",
434 required=False,
435 selected=unselected_for_python3,
436 ),
437 ]
438
439 if DEPTARGET < '10.4' and not PYTHON_3:
440 result.append(
441 dict(
442 name="PythonSystemFixes",
443 long_name="Fix system Python",
444 readme="""\
445 This package updates the system python installation on
446 Mac OS X 10.3 to ensure that you can build new python extensions
447 using that copy of python after installing this version.
448 """,
449 postflight="../Tools/fixapplepython23.py",
450 topdir="/Library/Frameworks/Python.framework",
451 source="/empty-dir",
452 required=False,
453 selected=unselected_for_python3,
454 )
455 )
456 return result
457
458def fatal(msg):
459 """
460 A fatal error, bail out.
461 """
462 sys.stderr.write('FATAL: ')
463 sys.stderr.write(msg)
464 sys.stderr.write('\n')
465 sys.exit(1)
466
467def fileContents(fn):
468 """
469 Return the contents of the named file
470 """
471 return open(fn, 'r').read()
472
473def runCommand(commandline):
474 """
475 Run a command and raise RuntimeError if it fails. Output is suppressed
476 unless the command fails.
477 """
478 fd = os.popen(commandline, 'r')
479 data = fd.read()
480 xit = fd.close()
481 if xit is not None:
482 sys.stdout.write(data)
483 raise RuntimeError("command failed: %s"%(commandline,))
484
485 if VERBOSE:
486 sys.stdout.write(data); sys.stdout.flush()
487
488def captureCommand(commandline):
489 fd = os.popen(commandline, 'r')
490 data = fd.read()
491 xit = fd.close()
492 if xit is not None:
493 sys.stdout.write(data)
494 raise RuntimeError("command failed: %s"%(commandline,))
495
496 return data
497
498def getTclTkVersion(configfile, versionline):
499 """
500 search Tcl or Tk configuration file for version line
501 """
502 try:
503 f = open(configfile, "r")
504 except:
505 fatal("Framework configuration file not found: %s" % configfile)
506
507 for l in f:
508 if l.startswith(versionline):
509 f.close()
510 return l
511
512 fatal("Version variable %s not found in framework configuration file: %s"
513 % (versionline, configfile))
514
515def checkEnvironment():
516 """
517 Check that we're running on a supported system.
518 """
519
520 if sys.version_info[0:2] < (2, 4):
521 fatal("This script must be run with Python 2.4 or later")
522
523 if platform.system() != 'Darwin':
524 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
525
526 if int(platform.release().split('.')[0]) < 8:
527 fatal("This script should be run on a Mac OS X 10.4 (or later) system")
528
529 if not os.path.exists(SDKPATH):
530 fatal("Please install the latest version of Xcode and the %s SDK"%(
531 os.path.basename(SDKPATH[:-4])))
532
533 # Because we only support dynamic load of only one major/minor version of
534 # Tcl/Tk, ensure:
535 # 1. there are no user-installed frameworks of Tcl/Tk with version
536 # higher than the Apple-supplied system version in
537 # SDKROOT/System/Library/Frameworks
538 # 2. there is a user-installed framework (usually ActiveTcl) in (or linked
539 # in) SDKROOT/Library/Frameworks with the same version as the system
540 # version. This allows users to choose to install a newer patch level.
541
542 frameworks = {}
543 for framework in ['Tcl', 'Tk']:
544 fwpth = 'Library/Frameworks/%s.framework/Versions/Current' % framework
545 sysfw = os.path.join(SDKPATH, 'System', fwpth)
546 libfw = os.path.join(SDKPATH, fwpth)
547 usrfw = os.path.join(os.getenv('HOME'), fwpth)
548 frameworks[framework] = os.readlink(sysfw)
549 if not os.path.exists(libfw):
550 fatal("Please install a link to a current %s %s as %s so "
551 "the user can override the system framework."
552 % (framework, frameworks[framework], libfw))
553 if os.readlink(libfw) != os.readlink(sysfw):
554 fatal("Version of %s must match %s" % (libfw, sysfw) )
555 if os.path.exists(usrfw):
556 fatal("Please rename %s to avoid possible dynamic load issues."
557 % usrfw)
558
559 if frameworks['Tcl'] != frameworks['Tk']:
560 fatal("The Tcl and Tk frameworks are not the same version.")
561
562 # add files to check after build
563 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
564 "/Library/Frameworks/Tcl.framework/Versions/%s/Tcl"
565 % frameworks['Tcl'],
566 "/Library/Frameworks/Tk.framework/Versions/%s/Tk"
567 % frameworks['Tk'],
568 ]
569
570 # For 10.6+ builds, we build two versions of _tkinter:
571 # - the traditional version (renamed to _tkinter_library.so) linked
572 # with /Library/Frameworks/{Tcl,Tk}.framework
573 # - the default version linked with our builtin copies of Tcl and Tk
574 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 4)):
575 EXPECTED_SHARED_LIBS['_tkinter_library.so'] = \
576 EXPECTED_SHARED_LIBS['_tkinter.so']
577 EXPECTED_SHARED_LIBS['_tkinter.so'] = [
578 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtcl%s.dylib"
579 % (getVersion(), frameworks['Tcl']),
580 "/Library/Frameworks/Python.framework/Versions/%s/lib/libtk%s.dylib"
581 % (getVersion(), frameworks['Tk']),
582 ]
583
584 # Remove inherited environment variables which might influence build
585 environ_var_prefixes = ['CPATH', 'C_INCLUDE_', 'DYLD_', 'LANG', 'LC_',
586 'LD_', 'LIBRARY_', 'PATH', 'PYTHON']
587 for ev in list(os.environ):
588 for prefix in environ_var_prefixes:
589 if ev.startswith(prefix) :
590 print("INFO: deleting environment variable %s=%s" % (
591 ev, os.environ[ev]))
592 del os.environ[ev]
593
594 base_path = '/bin:/sbin:/usr/bin:/usr/sbin'
595 if 'SDK_TOOLS_BIN' in os.environ:
596 base_path = os.environ['SDK_TOOLS_BIN'] + ':' + base_path
597 # Xcode 2.5 on OS X 10.4 does not include SetFile in its usr/bin;
598 # add its fixed location here if it exists
599 OLD_DEVELOPER_TOOLS = '/Developer/Tools'
600 if os.path.isdir(OLD_DEVELOPER_TOOLS):
601 base_path = base_path + ':' + OLD_DEVELOPER_TOOLS
602 os.environ['PATH'] = base_path
603 print("Setting default PATH: %s"%(os.environ['PATH']))
604
605
606def parseOptions(args=None):
607 """
608 Parse arguments and update global settings.
609 """
610 global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
611 global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
612
613 if args is None:
614 args = sys.argv[1:]
615
616 try:
617 options, args = getopt.getopt(args, '?hb',
618 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir=',
619 'dep-target=', 'universal-archs=', 'help' ])
620 except getopt.GetoptError:
621 print(sys.exc_info()[1])
622 sys.exit(1)
623
624 if args:
625 print("Additional arguments")
626 sys.exit(1)
627
628 deptarget = None
629 for k, v in options:
630 if k in ('-h', '-?', '--help'):
631 print(USAGE)
632 sys.exit(0)
633
634 elif k in ('-d', '--build-dir'):
635 WORKDIR=v
636
637 elif k in ('--third-party',):
638 DEPSRC=v
639
640 elif k in ('--sdk-path',):
641 SDKPATH=v
642
643 elif k in ('--src-dir',):
644 SRCDIR=v
645
646 elif k in ('--dep-target', ):
647 DEPTARGET=v
648 deptarget=v
649
650 elif k in ('--universal-archs', ):
651 if v in UNIVERSALOPTS:
652 UNIVERSALARCHS = v
653 ARCHLIST = universal_opts_map[UNIVERSALARCHS]
654 if deptarget is None:
655 # Select alternate default deployment
656 # target
657 DEPTARGET = default_target_map.get(v, '10.3')
658 else:
659 raise NotImplementedError(v)
660
661 else:
662 raise NotImplementedError(k)
663
664 SRCDIR=os.path.abspath(SRCDIR)
665 WORKDIR=os.path.abspath(WORKDIR)
666 SDKPATH=os.path.abspath(SDKPATH)
667 DEPSRC=os.path.abspath(DEPSRC)
668
669 CC, CXX=target_cc_map[DEPTARGET]
670
671 print("Settings:")
672 print(" * Source directory:", SRCDIR)
673 print(" * Build directory: ", WORKDIR)
674 print(" * SDK location: ", SDKPATH)
675 print(" * Third-party source:", DEPSRC)
676 print(" * Deployment target:", DEPTARGET)
677 print(" * Universal architectures:", ARCHLIST)
678 print(" * C compiler:", CC)
679 print(" * C++ compiler:", CXX)
680 print("")
681
682
683
684
685def extractArchive(builddir, archiveName):
686 """
687 Extract a source archive into 'builddir'. Returns the path of the
688 extracted archive.
689
690 XXX: This function assumes that archives contain a toplevel directory
691 that is has the same name as the basename of the archive. This is
692 safe enough for almost anything we use. Unfortunately, it does not
693 work for current Tcl and Tk source releases where the basename of
694 the archive ends with "-src" but the uncompressed directory does not.
695 For now, just special case Tcl and Tk tar.gz downloads.
696 """
697 curdir = os.getcwd()
698 try:
699 os.chdir(builddir)
700 if archiveName.endswith('.tar.gz'):
701 retval = os.path.basename(archiveName[:-7])
702 if ((retval.startswith('tcl') or retval.startswith('tk'))
703 and retval.endswith('-src')):
704 retval = retval[:-4]
705 if os.path.exists(retval):
706 shutil.rmtree(retval)
707 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
708
709 elif archiveName.endswith('.tar.bz2'):
710 retval = os.path.basename(archiveName[:-8])
711 if os.path.exists(retval):
712 shutil.rmtree(retval)
713 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
714
715 elif archiveName.endswith('.tar'):
716 retval = os.path.basename(archiveName[:-4])
717 if os.path.exists(retval):
718 shutil.rmtree(retval)
719 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
720
721 elif archiveName.endswith('.zip'):
722 retval = os.path.basename(archiveName[:-4])
723 if os.path.exists(retval):
724 shutil.rmtree(retval)
725 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
726
727 data = fp.read()
728 xit = fp.close()
729 if xit is not None:
730 sys.stdout.write(data)
731 raise RuntimeError("Cannot extract %s"%(archiveName,))
732
733 return os.path.join(builddir, retval)
734
735 finally:
736 os.chdir(curdir)
737
738def downloadURL(url, fname):
739 """
740 Download the contents of the url into the file.
741 """
742 fpIn = urllib_request.urlopen(url)
743 fpOut = open(fname, 'wb')
744 block = fpIn.read(10240)
745 try:
746 while block:
747 fpOut.write(block)
748 block = fpIn.read(10240)
749 fpIn.close()
750 fpOut.close()
751 except:
752 try:
753 os.unlink(fname)
754 except:
755 pass
756
757def verifyThirdPartyFile(url, checksum, fname):
758 """
759 Download file from url to filename fname if it does not already exist.
760 Abort if file contents does not match supplied md5 checksum.
761 """
762 name = os.path.basename(fname)
763 if os.path.exists(fname):
764 print("Using local copy of %s"%(name,))
765 else:
766 print("Did not find local copy of %s"%(name,))
767 print("Downloading %s"%(name,))
768 downloadURL(url, fname)
769 print("Archive for %s stored as %s"%(name, fname))
770 if os.system(
771 'MD5=$(openssl md5 %s) ; test "${MD5##*= }" = "%s"'
772 % (shellQuote(fname), checksum) ):
773 fatal('MD5 checksum mismatch for file %s' % fname)
774
775def buildRecipe(recipe, basedir, archList):
776 """
777 Build software using a recipe. This function does the
778 'configure;make;make install' dance for C software, with a possibility
779 to customize this process, basically a poor-mans DarwinPorts.
780 """
781 curdir = os.getcwd()
782
783 name = recipe['name']
784 url = recipe['url']
785 configure = recipe.get('configure', './configure')
786 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
787 shellQuote(basedir)))
788
789 archiveName = os.path.split(url)[-1]
790 sourceArchive = os.path.join(DEPSRC, archiveName)
791
792 if not os.path.exists(DEPSRC):
793 os.mkdir(DEPSRC)
794
795 verifyThirdPartyFile(url, recipe['checksum'], sourceArchive)
796 print("Extracting archive for %s"%(name,))
797 buildDir=os.path.join(WORKDIR, '_bld')
798 if not os.path.exists(buildDir):
799 os.mkdir(buildDir)
800
801 workDir = extractArchive(buildDir, sourceArchive)
802 os.chdir(workDir)
803
804 for patch in recipe.get('patches', ()):
805 if isinstance(patch, tuple):
806 url, checksum = patch
807 fn = os.path.join(DEPSRC, os.path.basename(url))
808 verifyThirdPartyFile(url, checksum, fn)
809 else:
810 # patch is a file in the source directory
811 fn = os.path.join(curdir, patch)
812 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
813 shellQuote(fn),))
814
815 for patchscript in recipe.get('patchscripts', ()):
816 if isinstance(patchscript, tuple):
817 url, checksum = patchscript
818 fn = os.path.join(DEPSRC, os.path.basename(url))
819 verifyThirdPartyFile(url, checksum, fn)
820 else:
821 # patch is a file in the source directory
822 fn = os.path.join(curdir, patchscript)
823 if fn.endswith('.bz2'):
824 runCommand('bunzip2 -fk %s' % shellQuote(fn))
825 fn = fn[:-4]
826 runCommand('sh %s' % shellQuote(fn))
827 os.unlink(fn)
828
829 if 'buildDir' in recipe:
830 os.chdir(recipe['buildDir'])
831
832 if configure is not None:
833 configure_args = [
834 "--prefix=/usr/local",
835 "--enable-static",
836 "--disable-shared",
837 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
838 ]
839
840 if 'configure_pre' in recipe:
841 args = list(recipe['configure_pre'])
842 if '--disable-static' in args:
843 configure_args.remove('--enable-static')
844 if '--enable-shared' in args:
845 configure_args.remove('--disable-shared')
846 configure_args.extend(args)
847
848 if recipe.get('useLDFlags', 1):
849 configure_args.extend([
850 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
851 "-I%s/usr/local/include"%(
852 recipe.get('extra_cflags', ''),
853 DEPTARGET,
854 ' -arch '.join(archList),
855 shellQuote(SDKPATH)[1:-1],
856 shellQuote(basedir)[1:-1],),
857 "LDFLAGS=-mmacosx-version-min=%s -syslibroot,%s -L%s/usr/local/lib -arch %s"%(
858 DEPTARGET,
859 shellQuote(SDKPATH)[1:-1],
860 shellQuote(basedir)[1:-1],
861 ' -arch '.join(archList)),
862 ])
863 else:
864 configure_args.extend([
865 "CFLAGS=%s-mmacosx-version-min=%s -arch %s -isysroot %s "
866 "-I%s/usr/local/include"%(
867 recipe.get('extra_cflags', ''),
868 DEPTARGET,
869 ' -arch '.join(archList),
870 shellQuote(SDKPATH)[1:-1],
871 shellQuote(basedir)[1:-1],),
872 ])
873
874 if 'configure_post' in recipe:
875 configure_args = configure_args + list(recipe['configure_post'])
876
877 configure_args.insert(0, configure)
878 configure_args = [ shellQuote(a) for a in configure_args ]
879
880 print("Running configure for %s"%(name,))
881 runCommand(' '.join(configure_args) + ' 2>&1')
882
883 print("Running install for %s"%(name,))
884 runCommand('{ ' + install + ' ;} 2>&1')
885
886 print("Done %s"%(name,))
887 print("")
888
889 os.chdir(curdir)
890
891def buildLibraries():
892 """
893 Build our dependencies into $WORKDIR/libraries/usr/local
894 """
895 print("")
896 print("Building required libraries")
897 print("")
898 universal = os.path.join(WORKDIR, 'libraries')
899 os.mkdir(universal)
900 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
901 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
902
903 for recipe in library_recipes():
904 buildRecipe(recipe, universal, ARCHLIST)
905
906
907
908def buildPythonDocs():
909 # This stores the documentation as Resources/English.lproj/Documentation
910 # inside the framwork. pydoc and IDLE will pick it up there.
911 print("Install python documentation")
912 rootDir = os.path.join(WORKDIR, '_root')
913 buildDir = os.path.join('../../Doc')
914 docdir = os.path.join(rootDir, 'pydocs')
915 curDir = os.getcwd()
916 os.chdir(buildDir)
917 runCommand('make update')
918 runCommand("make html PYTHON='%s'" % os.path.abspath(sys.executable))
919 os.chdir(curDir)
920 if not os.path.exists(docdir):
921 os.mkdir(docdir)
922 os.rename(os.path.join(buildDir, 'build', 'html'), docdir)
923
924
925def buildPython():
926 print("Building a universal python for %s architectures" % UNIVERSALARCHS)
927
928 buildDir = os.path.join(WORKDIR, '_bld', 'python')
929 rootDir = os.path.join(WORKDIR, '_root')
930
931 if os.path.exists(buildDir):
932 shutil.rmtree(buildDir)
933 if os.path.exists(rootDir):
934 shutil.rmtree(rootDir)
935 os.makedirs(buildDir)
936 os.makedirs(rootDir)
937 os.makedirs(os.path.join(rootDir, 'empty-dir'))
938 curdir = os.getcwd()
939 os.chdir(buildDir)
940
941 # Not sure if this is still needed, the original build script
942 # claims that parts of the install assume python.exe exists.
943 os.symlink('python', os.path.join(buildDir, 'python.exe'))
944
945 # Extract the version from the configure file, needed to calculate
946 # several paths.
947 version = getVersion()
948
949 # Since the extra libs are not in their installed framework location
950 # during the build, augment the library path so that the interpreter
951 # will find them during its extension import sanity checks.
952 os.environ['DYLD_LIBRARY_PATH'] = os.path.join(WORKDIR,
953 'libraries', 'usr', 'local', 'lib')
954 print("Running configure...")
955 runCommand("%s -C --enable-framework --enable-universalsdk=%s "
956 "--with-universal-archs=%s "
957 "%s "
958 "LDFLAGS='-g -L%s/libraries/usr/local/lib' "
959 "CFLAGS='-g -I%s/libraries/usr/local/include' 2>&1"%(
960 shellQuote(os.path.join(SRCDIR, 'configure')), shellQuote(SDKPATH),
961 UNIVERSALARCHS,
962 (' ', '--with-computed-gotos ')[PYTHON_3],
963 shellQuote(WORKDIR)[1:-1],
964 shellQuote(WORKDIR)[1:-1]))
965
966 print("Running make")
967 runCommand("make")
968
969 # For deployment targets of 10.6 and higher, we build our own version
970 # of Tcl and Cocoa Aqua Tk libs because the Apple-supplied Tk 8.5 is
971 # out-of-date and has critical bugs. Save the _tkinter.so that was
972 # linked with /Library/Frameworks/{Tck,Tk}.framework and build
973 # another _tkinter.so linked with our builtin Tcl and Tk libs.
974 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 4)):
975 runCommand("find build -name '_tkinter.so' "
976 " -execdir mv '{}' _tkinter_library.so \;")
977 print("Running make to build builtin _tkinter")
978 runCommand("make TCLTK_INCLUDES='-I%s/libraries/usr/local/include' "
979 "TCLTK_LIBS='-L%s/libraries/usr/local/lib -ltcl8.5 -ltk8.5'"%(
980 shellQuote(WORKDIR)[1:-1],
981 shellQuote(WORKDIR)[1:-1]))
982 # make a copy which will be moved to lib-tkinter later
983 runCommand("find build -name '_tkinter.so' "
984 " -execdir cp -p '{}' _tkinter_builtin.so \;")
985
986 print("Running make install")
987 runCommand("make install DESTDIR=%s"%(
988 shellQuote(rootDir)))
989
990 print("Running make frameworkinstallextras")
991 runCommand("make frameworkinstallextras DESTDIR=%s"%(
992 shellQuote(rootDir)))
993
994 del os.environ['DYLD_LIBRARY_PATH']
995 print("Copying required shared libraries")
996 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
997 runCommand("mv %s/* %s"%(
998 shellQuote(os.path.join(
999 WORKDIR, 'libraries', 'Library', 'Frameworks',
1000 'Python.framework', 'Versions', getVersion(),
1001 'lib')),
1002 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
1003 'Python.framework', 'Versions', getVersion(),
1004 'lib'))))
1005
1006 path_to_lib = os.path.join(rootDir, 'Library', 'Frameworks',
1007 'Python.framework', 'Versions',
1008 version, 'lib', 'python%s'%(version,))
1009
1010 # If we made multiple versions of _tkinter, move them to
1011 # their own directories under python lib. This allows
1012 # users to select which to import by manipulating sys.path
1013 # directly or with PYTHONPATH.
1014
1015 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 4)):
1016 TKINTERS = ['builtin', 'library']
1017 tkinter_moves = [('_tkinter_' + tkn + '.so',
1018 os.path.join(path_to_lib, 'lib-tkinter', tkn))
1019 for tkn in TKINTERS]
1020 # Create the destination directories under lib-tkinter.
1021 # The permissions and uid/gid will be fixed up next.
1022 for tkm in tkinter_moves:
1023 os.makedirs(tkm[1])
1024
1025 print("Fix file modes")
1026 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
1027 gid = grp.getgrnam('admin').gr_gid
1028
1029 shared_lib_error = False
1030 moves_list = []
1031 for dirpath, dirnames, filenames in os.walk(frmDir):
1032 for dn in dirnames:
1033 os.chmod(os.path.join(dirpath, dn), STAT_0o775)
1034 os.chown(os.path.join(dirpath, dn), -1, gid)
1035
1036 for fn in filenames:
1037 if os.path.islink(fn):
1038 continue
1039
1040 # "chmod g+w $fn"
1041 p = os.path.join(dirpath, fn)
1042 st = os.stat(p)
1043 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
1044 os.chown(p, -1, gid)
1045
1046 if fn in EXPECTED_SHARED_LIBS:
1047 # check to see that this file was linked with the
1048 # expected library path and version
1049 data = captureCommand("otool -L %s" % shellQuote(p))
1050 for sl in EXPECTED_SHARED_LIBS[fn]:
1051 if ("\t%s " % sl) not in data:
1052 print("Expected shared lib %s was not linked with %s"
1053 % (sl, p))
1054 shared_lib_error = True
1055
1056 # If this is a _tkinter variant, move it to its own directory
1057 # now that we have fixed its permissions and checked that it
1058 # was linked properly. The directory was created earlier.
1059 # The files are moved after the entire tree has been walked
1060 # since the shared library checking depends on the files
1061 # having unique names.
1062 if (DEPTARGET > '10.5') and (getVersionTuple() >= (3, 4)):
1063 for tkm in tkinter_moves:
1064 if fn == tkm[0]:
1065 moves_list.append(
1066 (p, os.path.join(tkm[1], '_tkinter.so')))
1067
1068 if shared_lib_error:
1069 fatal("Unexpected shared library errors.")
1070
1071 # Now do the moves.
1072 for ml in moves_list:
1073 shutil.move(ml[0], ml[1])
1074
1075 if PYTHON_3:
1076 LDVERSION=None
1077 VERSION=None
1078 ABIFLAGS=None
1079
1080 fp = open(os.path.join(buildDir, 'Makefile'), 'r')
1081 for ln in fp:
1082 if ln.startswith('VERSION='):
1083 VERSION=ln.split()[1]
1084 if ln.startswith('ABIFLAGS='):
1085 ABIFLAGS=ln.split()[1]
1086 if ln.startswith('LDVERSION='):
1087 LDVERSION=ln.split()[1]
1088 fp.close()
1089
1090 LDVERSION = LDVERSION.replace('$(VERSION)', VERSION)
1091 LDVERSION = LDVERSION.replace('$(ABIFLAGS)', ABIFLAGS)
1092 config_suffix = '-' + LDVERSION
1093 else:
1094 config_suffix = '' # Python 2.x
1095
1096 # We added some directories to the search path during the configure
1097 # phase. Remove those because those directories won't be there on
1098 # the end-users system. Also remove the directories from _sysconfigdata.py
1099 # (added in 3.3) if it exists.
1100
1101 include_path = '-I%s/libraries/usr/local/include' % (WORKDIR,)
1102 lib_path = '-L%s/libraries/usr/local/lib' % (WORKDIR,)
1103
1104 # fix Makefile
1105 path = os.path.join(path_to_lib, 'config' + config_suffix, 'Makefile')
1106 fp = open(path, 'r')
1107 data = fp.read()
1108 fp.close()
1109
1110 for p in (include_path, lib_path):
1111 data = data.replace(" " + p, '')
1112 data = data.replace(p + " ", '')
1113
1114 fp = open(path, 'w')
1115 fp.write(data)
1116 fp.close()
1117
1118 # fix _sysconfigdata if it exists
1119 #
1120 # TODO: make this more robust! test_sysconfig_module of
1121 # distutils.tests.test_sysconfig.SysconfigTestCase tests that
1122 # the output from get_config_var in both sysconfig and
1123 # distutils.sysconfig is exactly the same for both CFLAGS and
1124 # LDFLAGS. The fixing up is now complicated by the pretty
1125 # printing in _sysconfigdata.py. Also, we are using the
1126 # pprint from the Python running the installer build which
1127 # may not cosmetically format the same as the pprint in the Python
1128 # being built (and which is used to originally generate
1129 # _sysconfigdata.py).
1130
1131 import pprint
1132 path = os.path.join(path_to_lib, '_sysconfigdata.py')
1133 if os.path.exists(path):
1134 fp = open(path, 'r')
1135 data = fp.read()
1136 fp.close()
1137 # create build_time_vars dict
1138 exec(data)
1139 vars = {}
1140 for k, v in build_time_vars.items():
1141 if type(v) == type(''):
1142 for p in (include_path, lib_path):
1143 v = v.replace(' ' + p, '')
1144 v = v.replace(p + ' ', '')
1145 vars[k] = v
1146
1147 fp = open(path, 'w')
1148 # duplicated from sysconfig._generate_posix_vars()
1149 fp.write('# system configuration generated and used by'
1150 ' the sysconfig module\n')
1151 fp.write('build_time_vars = ')
1152 pprint.pprint(vars, stream=fp)
1153 fp.close()
1154
1155 # Add symlinks in /usr/local/bin, using relative links
1156 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
1157 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
1158 'Python.framework', 'Versions', version, 'bin')
1159 if os.path.exists(usr_local_bin):
1160 shutil.rmtree(usr_local_bin)
1161 os.makedirs(usr_local_bin)
1162 for fn in os.listdir(
1163 os.path.join(frmDir, 'Versions', version, 'bin')):
1164 os.symlink(os.path.join(to_framework, fn),
1165 os.path.join(usr_local_bin, fn))
1166
1167 os.chdir(curdir)
1168
1169 if PYTHON_3:
1170 # Remove the 'Current' link, that way we don't accidentally mess
1171 # with an already installed version of python 2
1172 os.unlink(os.path.join(rootDir, 'Library', 'Frameworks',
1173 'Python.framework', 'Versions', 'Current'))
1174
1175def patchFile(inPath, outPath):
1176 data = fileContents(inPath)
1177 data = data.replace('$FULL_VERSION', getFullVersion())
1178 data = data.replace('$VERSION', getVersion())
1179 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
1180 data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
1181 data = data.replace('$INSTALL_SIZE', installSize())
1182
1183 # This one is not handy as a template variable
1184 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
1185 fp = open(outPath, 'w')
1186 fp.write(data)
1187 fp.close()
1188
1189def patchScript(inPath, outPath):
1190 data = fileContents(inPath)
1191 data = data.replace('@PYVER@', getVersion())
1192 fp = open(outPath, 'w')
1193 fp.write(data)
1194 fp.close()
1195 os.chmod(outPath, STAT_0o755)
1196
1197
1198
1199def packageFromRecipe(targetDir, recipe):
1200 curdir = os.getcwd()
1201 try:
1202 # The major version (such as 2.5) is included in the package name
1203 # because having two version of python installed at the same time is
1204 # common.
1205 pkgname = '%s-%s'%(recipe['name'], getVersion())
1206 srcdir = recipe.get('source')
1207 pkgroot = recipe.get('topdir', srcdir)
1208 postflight = recipe.get('postflight')
1209 readme = textwrap.dedent(recipe['readme'])
1210 isRequired = recipe.get('required', True)
1211
1212 print("- building package %s"%(pkgname,))
1213
1214 # Substitute some variables
1215 textvars = dict(
1216 VER=getVersion(),
1217 FULLVER=getFullVersion(),
1218 )
1219 readme = readme % textvars
1220
1221 if pkgroot is not None:
1222 pkgroot = pkgroot % textvars
1223 else:
1224 pkgroot = '/'
1225
1226 if srcdir is not None:
1227 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
1228 srcdir = srcdir % textvars
1229
1230 if postflight is not None:
1231 postflight = os.path.abspath(postflight)
1232
1233 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
1234 os.makedirs(packageContents)
1235
1236 if srcdir is not None:
1237 os.chdir(srcdir)
1238 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1239 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
1240 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
1241
1242 fn = os.path.join(packageContents, 'PkgInfo')
1243 fp = open(fn, 'w')
1244 fp.write('pmkrpkg1')
1245 fp.close()
1246
1247 rsrcDir = os.path.join(packageContents, "Resources")
1248 os.mkdir(rsrcDir)
1249 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
1250 fp.write(readme)
1251 fp.close()
1252
1253 if postflight is not None:
1254 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
1255
1256 vers = getFullVersion()
1257 major, minor = getVersionMajorMinor()
1258 pl = Plist(
1259 CFBundleGetInfoString="Python.%s %s"%(pkgname, vers,),
1260 CFBundleIdentifier='org.python.Python.%s'%(pkgname,),
1261 CFBundleName='Python.%s'%(pkgname,),
1262 CFBundleShortVersionString=vers,
1263 IFMajorVersion=major,
1264 IFMinorVersion=minor,
1265 IFPkgFormatVersion=0.10000000149011612,
1266 IFPkgFlagAllowBackRev=False,
1267 IFPkgFlagAuthorizationAction="RootAuthorization",
1268 IFPkgFlagDefaultLocation=pkgroot,
1269 IFPkgFlagFollowLinks=True,
1270 IFPkgFlagInstallFat=True,
1271 IFPkgFlagIsRequired=isRequired,
1272 IFPkgFlagOverwritePermissions=False,
1273 IFPkgFlagRelocatable=False,
1274 IFPkgFlagRestartAction="NoRestart",
1275 IFPkgFlagRootVolumeOnly=True,
1276 IFPkgFlagUpdateInstalledLangauges=False,
1277 )
1278 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
1279
1280 pl = Plist(
1281 IFPkgDescriptionDescription=readme,
1282 IFPkgDescriptionTitle=recipe.get('long_name', "Python.%s"%(pkgname,)),
1283 IFPkgDescriptionVersion=vers,
1284 )
1285 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
1286
1287 finally:
1288 os.chdir(curdir)
1289
1290
1291def makeMpkgPlist(path):
1292
1293 vers = getFullVersion()
1294 major, minor = getVersionMajorMinor()
1295
1296 pl = Plist(
1297 CFBundleGetInfoString="Python %s"%(vers,),
1298 CFBundleIdentifier='org.python.Python',
1299 CFBundleName='Python',
1300 CFBundleShortVersionString=vers,
1301 IFMajorVersion=major,
1302 IFMinorVersion=minor,
1303 IFPkgFlagComponentDirectory="Contents/Packages",
1304 IFPkgFlagPackageList=[
1305 dict(
1306 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
1307 IFPkgFlagPackageSelection=item.get('selected', 'selected'),
1308 )
1309 for item in pkg_recipes()
1310 ],
1311 IFPkgFormatVersion=0.10000000149011612,
1312 IFPkgFlagBackgroundScaling="proportional",
1313 IFPkgFlagBackgroundAlignment="left",
1314 IFPkgFlagAuthorizationAction="RootAuthorization",
1315 )
1316
1317 writePlist(pl, path)
1318
1319
1320def buildInstaller():
1321
1322 # Zap all compiled files
1323 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
1324 for fn in filenames:
1325 if fn.endswith('.pyc') or fn.endswith('.pyo'):
1326 os.unlink(os.path.join(dirpath, fn))
1327
1328 outdir = os.path.join(WORKDIR, 'installer')
1329 if os.path.exists(outdir):
1330 shutil.rmtree(outdir)
1331 os.mkdir(outdir)
1332
1333 pkgroot = os.path.join(outdir, 'Python.mpkg', 'Contents')
1334 pkgcontents = os.path.join(pkgroot, 'Packages')
1335 os.makedirs(pkgcontents)
1336 for recipe in pkg_recipes():
1337 packageFromRecipe(pkgcontents, recipe)
1338
1339 rsrcDir = os.path.join(pkgroot, 'Resources')
1340
1341 fn = os.path.join(pkgroot, 'PkgInfo')
1342 fp = open(fn, 'w')
1343 fp.write('pmkrpkg1')
1344 fp.close()
1345
1346 os.mkdir(rsrcDir)
1347
1348 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
1349 pl = Plist(
1350 IFPkgDescriptionTitle="Python",
1351 IFPkgDescriptionVersion=getVersion(),
1352 )
1353
1354 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
1355 for fn in os.listdir('resources'):
1356 if fn == '.svn': continue
1357 if fn.endswith('.jpg'):
1358 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1359 else:
1360 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
1361
1362 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
1363
1364
1365def installSize(clear=False, _saved=[]):
1366 if clear:
1367 del _saved[:]
1368 if not _saved:
1369 data = captureCommand("du -ks %s"%(
1370 shellQuote(os.path.join(WORKDIR, '_root'))))
1371 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
1372 return _saved[0]
1373
1374
1375def buildDMG():
1376 """
1377 Create DMG containing the rootDir.
1378 """
1379 outdir = os.path.join(WORKDIR, 'diskimage')
1380 if os.path.exists(outdir):
1381 shutil.rmtree(outdir)
1382
1383 imagepath = os.path.join(outdir,
1384 'python-%s-macosx%s'%(getFullVersion(),DEPTARGET))
1385 if INCLUDE_TIMESTAMP:
1386 imagepath = imagepath + '-%04d-%02d-%02d'%(time.localtime()[:3])
1387 imagepath = imagepath + '.dmg'
1388
1389 os.mkdir(outdir)
1390 volname='Python %s'%(getFullVersion())
1391 runCommand("hdiutil create -format UDRW -volname %s -srcfolder %s %s"%(
1392 shellQuote(volname),
1393 shellQuote(os.path.join(WORKDIR, 'installer')),
1394 shellQuote(imagepath + ".tmp.dmg" )))
1395
1396
1397 if not os.path.exists(os.path.join(WORKDIR, "mnt")):
1398 os.mkdir(os.path.join(WORKDIR, "mnt"))
1399 runCommand("hdiutil attach %s -mountroot %s"%(
1400 shellQuote(imagepath + ".tmp.dmg"), shellQuote(os.path.join(WORKDIR, "mnt"))))
1401
1402 # Custom icon for the DMG, shown when the DMG is mounted.
1403 shutil.copy("../Icons/Disk Image.icns",
1404 os.path.join(WORKDIR, "mnt", volname, ".VolumeIcon.icns"))
1405 runCommand("SetFile -a C %s/"%(
1406 shellQuote(os.path.join(WORKDIR, "mnt", volname)),))
1407
1408 runCommand("hdiutil detach %s"%(shellQuote(os.path.join(WORKDIR, "mnt", volname))))
1409
1410 setIcon(imagepath + ".tmp.dmg", "../Icons/Disk Image.icns")
1411 runCommand("hdiutil convert %s -format UDZO -o %s"%(
1412 shellQuote(imagepath + ".tmp.dmg"), shellQuote(imagepath)))
1413 setIcon(imagepath, "../Icons/Disk Image.icns")
1414
1415 os.unlink(imagepath + ".tmp.dmg")
1416
1417 return imagepath
1418
1419
1420def setIcon(filePath, icnsPath):
1421 """
1422 Set the custom icon for the specified file or directory.
1423 """
1424
1425 dirPath = os.path.normpath(os.path.dirname(__file__))
1426 toolPath = os.path.join(dirPath, "seticon.app/Contents/MacOS/seticon")
1427 if not os.path.exists(toolPath) or os.stat(toolPath).st_mtime < os.stat(dirPath + '/seticon.m').st_mtime:
1428 # NOTE: The tool is created inside an .app bundle, otherwise it won't work due
1429 # to connections to the window server.
1430 appPath = os.path.join(dirPath, "seticon.app/Contents/MacOS")
1431 if not os.path.exists(appPath):
1432 os.makedirs(appPath)
1433 runCommand("cc -o %s %s/seticon.m -framework Cocoa"%(
1434 shellQuote(toolPath), shellQuote(dirPath)))
1435
1436 runCommand("%s %s %s"%(shellQuote(os.path.abspath(toolPath)), shellQuote(icnsPath),
1437 shellQuote(filePath)))
1438
1439def main():
1440 # First parse options and check if we can perform our work
1441 parseOptions()
1442 checkEnvironment()
1443
1444 os.environ['MACOSX_DEPLOYMENT_TARGET'] = DEPTARGET
1445 os.environ['CC'] = CC
1446 os.environ['CXX'] = CXX
1447
1448 if os.path.exists(WORKDIR):
1449 shutil.rmtree(WORKDIR)
1450 os.mkdir(WORKDIR)
1451
1452 os.environ['LC_ALL'] = 'C'
1453
1454 # Then build third-party libraries such as sleepycat DB4.
1455 buildLibraries()
1456
1457 # Now build python itself
1458 buildPython()
1459
1460 # And then build the documentation
1461 # Remove the Deployment Target from the shell
1462 # environment, it's no longer needed and
1463 # an unexpected build target can cause problems
1464 # when Sphinx and its dependencies need to
1465 # be (re-)installed.
1466 del os.environ['MACOSX_DEPLOYMENT_TARGET']
1467 buildPythonDocs()
1468
1469
1470 # Prepare the applications folder
1471 fn = os.path.join(WORKDIR, "_root", "Applications",
1472 "Python %s"%(getVersion(),), "Update Shell Profile.command")
1473 patchScript("scripts/postflight.patch-profile", fn)
1474
1475 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
1476 getVersion(),))
1477 os.chmod(folder, STAT_0o755)
1478 setIcon(folder, "../Icons/Python Folder.icns")
1479
1480 # Create the installer
1481 buildInstaller()
1482
1483 # And copy the readme into the directory containing the installer
1484 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1485
1486 # Ditto for the license file.
1487 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
1488
1489 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
1490 fp.write("# BUILD INFO\n")
1491 fp.write("# Date: %s\n" % time.ctime())
1492 fp.write("# By: %s\n" % pwd.getpwuid(os.getuid()).pw_gecos)
1493 fp.close()
1494
1495 # And copy it to a DMG
1496 buildDMG()
1497
1498if __name__ == "__main__":
1499 main()
Note: See TracBrowser for help on using the repository browser.