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