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