@@ -62,11 +62,16 @@ def shellQuote(value):
6262 return "'%s'" % (value .replace ("'" , "'\" '\" '" ))
6363
6464def 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+ """
6570 variable = variable + '='
6671 for ln in open (fn , 'r' ):
6772 if ln .startswith (variable ):
6873 value = ln [len (variable ):].strip ()
69- return value [ 1 : - 1 ]
74+ return value . strip ( " \" '" )
7075 raise RuntimeError ("Cannot find variable %s" % variable [:- 1 ])
7176
7277_cache_getVersion = None
@@ -78,9 +83,6 @@ def getVersion():
7883 os .path .join (SRCDIR , 'configure' ), 'PACKAGE_VERSION' )
7984 return _cache_getVersion
8085
81- def getVersionTuple ():
82- return tuple ([int (n ) for n in getVersion ().split ('.' )])
83-
8486def getVersionMajorMinor ():
8587 return tuple ([int (n ) for n in getVersion ().split ('.' , 2 )])
8688
@@ -97,6 +99,9 @@ def getFullVersion():
9799 return _cache_getFullVersion
98100 raise RuntimeError ("Cannot find full version??" )
99101
102+ FW_PREFIX = ["Library" , "Frameworks" , "Python.framework" ]
103+ FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
104+
100105# The directory we'll use to create the build (will be erased and recreated)
101106WORKDIR = "/tmp/_py"
102107
@@ -164,7 +169,7 @@ def getTargetCompilers():
164169
165170CC , CXX = getTargetCompilers ()
166171
167- PYTHON_3 = getVersionTuple () >= (3 , 0 )
172+ PYTHON_3 = getVersionMajorMinor () >= (3 , 0 )
168173
169174USAGE = textwrap .dedent ("""\
170175 Usage: build_python [options]
@@ -188,6 +193,10 @@ def getTargetCompilers():
188193# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
189194EXPECTED_SHARED_LIBS = {}
190195
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.
198+ THIRD_PARTY_LIBS = []
199+
191200# Instructions for building libraries that are necessary for building a
192201# batteries included python.
193202# [The recipes are defined here for convenience but instantiated later after
@@ -197,6 +206,49 @@ def library_recipes():
197206
198207 LT_10_5 = bool (getDeptargetTuple () < (10 , 5 ))
199208
209+ if getDeptargetTuple () < (10 , 6 ):
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+ SDKs, continue to use the deprecated but
230+ # less out-of-date Apple 0.9.8 libs for now. While they are less
231+ # secure than using an up-to-date 1.0.1 version, doing so
232+ # avoids the big problems of forcing users to have to manage
233+ # default CAs themselves, thanks to the Apple libs using private TEA
234+ # APIs for cert validation from keychains if validation using the
235+ # standard OpenSSL locations (/System/Library/OpenSSL, normally empty)
236+ # fails.
237+
238+ result .extend ([
239+ dict (
240+ name = "OpenSSL 1.0.1j" ,
241+ url = "https://www.openssl.org/source/openssl-1.0.1j.tar.gz" ,
242+ checksum = 'f7175c9cd3c39bb1907ac8bba9df8ed3' ,
243+ patches = [
244+ "openssl_sdk_makedepend.patch" ,
245+ ],
246+ buildrecipe = build_universal_openssl ,
247+ configure = None ,
248+ install = None ,
249+ ),
250+ ])
251+
200252# Disable for now
201253 if False : # if getDeptargetTuple() > (10, 5):
202254 result .extend ([
@@ -617,6 +669,7 @@ def parseOptions(args=None):
617669 """
618670 global WORKDIR , DEPSRC , SDKPATH , SRCDIR , DEPTARGET
619671 global UNIVERSALOPTS , UNIVERSALARCHS , ARCHLIST , CC , CXX
672+ global FW_VERSION_PREFIX
620673
621674 if args is None :
622675 args = sys .argv [1 :]
@@ -676,19 +729,21 @@ def parseOptions(args=None):
676729
677730 CC , CXX = getTargetCompilers ()
678731
679- print ("Settings:" )
680- print (" * Source directory:" , SRCDIR )
681- print (" * Build directory: " , WORKDIR )
682- print (" * SDK location: " , SDKPATH )
683- print (" * Third-party source:" , DEPSRC )
684- print (" * Deployment target:" , DEPTARGET )
685- print (" * Universal architectures:" , ARCHLIST )
686- print (" * C compiler:" , CC )
687- print (" * C++ compiler:" , CXX )
732+ FW_VERSION_PREFIX = FW_PREFIX [:] + ["Versions" , getVersion ()]
733+
734+ print ("-- Settings:" )
735+ print (" * Source directory: %s" % SRCDIR )
736+ print (" * Build directory: %s" % WORKDIR )
737+ print (" * SDK location: %s" % SDKPATH )
738+ print (" * Third-party source: %s" % DEPSRC )
739+ print (" * Deployment target: %s" % DEPTARGET )
740+ print (" * Universal archs: %s" % str (ARCHLIST ))
741+ print (" * C compiler: %s" % CC )
742+ print (" * C++ compiler: %s" % CXX )
743+ print ("" )
744+ print (" -- Building a Python %s framework at patch level %s"
745+ % (getVersion (), getFullVersion ()))
688746 print ("" )
689-
690-
691-
692747
693748def extractArchive (builddir , archiveName ):
694749 """
@@ -780,6 +835,132 @@ def verifyThirdPartyFile(url, checksum, fname):
780835 % (shellQuote (fname ), checksum ) ):
781836 fatal ('MD5 checksum mismatch for file %s' % fname )
782837
838+ def build_universal_openssl (basedir , archList ):
839+ """
840+ Special case build recipe for universal build of openssl.
841+
842+ The upstream OpenSSL build system does not directly support
843+ OS X universal builds. We need to build each architecture
844+ separately then lipo them together into fat libraries.
845+ """
846+
847+ # OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
848+ # If we are building on a 10.4.x or earlier system,
849+ # unilaterally disable assembly code building to avoid the problem.
850+ no_asm = int (platform .release ().split ("." )[0 ]) < 9
851+
852+ def build_openssl_arch (archbase , arch ):
853+ "Build one architecture of openssl"
854+ arch_opts = {
855+ "i386" : ["darwin-i386-cc" ],
856+ "x86_64" : ["darwin64-x86_64-cc" , "enable-ec_nistp_64_gcc_128" ],
857+ "ppc" : ["darwin-ppc-cc" ],
858+ "ppc64" : ["darwin64-ppc-cc" ],
859+ }
860+ configure_opts = [
861+ "no-krb5" ,
862+ "no-idea" ,
863+ "no-mdc2" ,
864+ "no-rc5" ,
865+ "no-zlib" ,
866+ "enable-tlsext" ,
867+ "no-ssl2" ,
868+ "no-ssl3" ,
869+ "no-ssl3-method" ,
870+ # "enable-unit-test",
871+ "shared" ,
872+ "--install_prefix=%s" % shellQuote (archbase ),
873+ "--prefix=%s" % os .path .join ("/" , * FW_VERSION_PREFIX ),
874+ "--openssldir=/System/Library/OpenSSL" ,
875+ ]
876+ if no_asm :
877+ configure_opts .append ("no-asm" )
878+ runCommand (" " .join (["perl" , "Configure" ]
879+ + arch_opts [arch ] + configure_opts ))
880+ runCommand ("make depend OSX_SDK=%s" % SDKPATH )
881+ runCommand ("make all OSX_SDK=%s" % SDKPATH )
882+ runCommand ("make install_sw OSX_SDK=%s" % SDKPATH )
883+ # runCommand("make test")
884+ return
885+
886+ srcdir = os .getcwd ()
887+ universalbase = os .path .join (srcdir , ".." ,
888+ os .path .basename (srcdir ) + "-universal" )
889+ os .mkdir (universalbase )
890+ archbasefws = []
891+ for arch in archList :
892+ # fresh copy of the source tree
893+ archsrc = os .path .join (universalbase , arch , "src" )
894+ shutil .copytree (srcdir , archsrc , symlinks = True )
895+ # install base for this arch
896+ archbase = os .path .join (universalbase , arch , "root" )
897+ os .mkdir (archbase )
898+ # Python framework base within install_prefix:
899+ # the build will install into this framework..
900+ # This is to ensure that the resulting shared libs have
901+ # the desired real install paths built into them.
902+ archbasefw = os .path .join (archbase , * FW_VERSION_PREFIX )
903+
904+ # build one architecture
905+ os .chdir (archsrc )
906+ build_openssl_arch (archbase , arch )
907+ os .chdir (srcdir )
908+ archbasefws .append (archbasefw )
909+
910+ # copy arch-independent files from last build into the basedir framework
911+ basefw = os .path .join (basedir , * FW_VERSION_PREFIX )
912+ shutil .copytree (
913+ os .path .join (archbasefw , "include" , "openssl" ),
914+ os .path .join (basefw , "include" , "openssl" )
915+ )
916+
917+ shlib_version_number = grepValue (os .path .join (archsrc , "Makefile" ),
918+ "SHLIB_VERSION_NUMBER" )
919+ # e.g. -> "1.0.0"
920+ libcrypto = "libcrypto.dylib"
921+ libcrypto_versioned = libcrypto .replace ("." , "." + shlib_version_number + "." )
922+ # e.g. -> "libcrypto.1.0.0.dylib"
923+ libssl = "libssl.dylib"
924+ libssl_versioned = libssl .replace ("." , "." + shlib_version_number + "." )
925+ # e.g. -> "libssl.1.0.0.dylib"
926+
927+ try :
928+ os .mkdir (os .path .join (basefw , "lib" ))
929+ except OSError :
930+ pass
931+
932+ # merge the individual arch-dependent shared libs into a fat shared lib
933+ archbasefws .insert (0 , basefw )
934+ for (lib_unversioned , lib_versioned ) in [
935+ (libcrypto , libcrypto_versioned ),
936+ (libssl , libssl_versioned )
937+ ]:
938+ runCommand ("lipo -create -output " +
939+ " " .join (shellQuote (
940+ os .path .join (fw , "lib" , lib_versioned ))
941+ for fw in archbasefws ))
942+ # and create an unversioned symlink of it
943+ os .symlink (lib_versioned , os .path .join (basefw , "lib" , lib_unversioned ))
944+
945+ # Create links in the temp include and lib dirs that will be injected
946+ # into the Python build so that setup.py can find them while building
947+ # and the versioned links so that the setup.py post-build import test
948+ # does not fail.
949+ relative_path = os .path .join (".." , ".." , ".." , * FW_VERSION_PREFIX )
950+ for fn in [
951+ ["include" , "openssl" ],
952+ ["lib" , libcrypto ],
953+ ["lib" , libssl ],
954+ ["lib" , libcrypto_versioned ],
955+ ["lib" , libssl_versioned ],
956+ ]:
957+ os .symlink (
958+ os .path .join (relative_path , * fn ),
959+ os .path .join (basedir , "usr" , "local" , * fn )
960+ )
961+
962+ return
963+
783964def buildRecipe (recipe , basedir , archList ):
784965 """
785966 Build software using a recipe. This function does the
@@ -789,8 +970,10 @@ def buildRecipe(recipe, basedir, archList):
789970 curdir = os .getcwd ()
790971
791972 name = recipe ['name' ]
973+ THIRD_PARTY_LIBS .append (name )
792974 url = recipe ['url' ]
793975 configure = recipe .get ('configure' , './configure' )
976+ buildrecipe = recipe .get ('buildrecipe' , None )
794977 install = recipe .get ('install' , 'make && make install DESTDIR=%s' % (
795978 shellQuote (basedir )))
796979
@@ -888,8 +1071,13 @@ def buildRecipe(recipe, basedir, archList):
8881071 print ("Running configure for %s" % (name ,))
8891072 runCommand (' ' .join (configure_args ) + ' 2>&1' )
8901073
891- print ("Running install for %s" % (name ,))
892- runCommand ('{ ' + install + ' ;} 2>&1' )
1074+ if buildrecipe is not None :
1075+ # call special-case build recipe, e.g. for openssl
1076+ buildrecipe (basedir , archList )
1077+
1078+ if install is not None :
1079+ print ("Running install for %s" % (name ,))
1080+ runCommand ('{ ' + install + ' ;} 2>&1' )
8931081
8941082 print ("Done %s" % (name ,))
8951083 print ("" )
@@ -1145,6 +1333,7 @@ def patchFile(inPath, outPath):
11451333 data = data .replace ('$MACOSX_DEPLOYMENT_TARGET' , '' .join ((DEPTARGET , ' or later' )))
11461334 data = data .replace ('$ARCHITECTURES' , ", " .join (universal_opts_map [UNIVERSALARCHS ]))
11471335 data = data .replace ('$INSTALL_SIZE' , installSize ())
1336+ data = data .replace ('$THIRD_PARTY_LIBS' , "\\ \n " .join (THIRD_PARTY_LIBS ))
11481337
11491338 # This one is not handy as a template variable
11501339 data = data .replace ('$PYTHONFRAMEWORKINSTALLDIR' , '/Library/Frameworks/Python.framework' )
@@ -1327,8 +1516,6 @@ def buildInstaller():
13271516 else :
13281517 patchFile (os .path .join ('resources' , fn ), os .path .join (rsrcDir , fn ))
13291518
1330- shutil .copy ("../../LICENSE" , os .path .join (rsrcDir , 'License.txt' ))
1331-
13321519
13331520def installSize (clear = False , _saved = []):
13341521 if clear :
@@ -1436,23 +1623,27 @@ def main():
14361623
14371624
14381625 # Prepare the applications folder
1439- fn = os .path .join (WORKDIR , "_root" , "Applications" ,
1440- "Python %s" % (getVersion (),), "Update Shell Profile.command" )
1441- patchScript ("scripts/postflight.patch-profile" , fn )
1442-
14431626 folder = os .path .join (WORKDIR , "_root" , "Applications" , "Python %s" % (
14441627 getVersion (),))
1628+ fn = os .path .join (folder , "License.rtf" )
1629+ patchFile ("resources/License.rtf" , fn )
1630+ fn = os .path .join (folder , "ReadMe.rtf" )
1631+ patchFile ("resources/ReadMe.rtf" , fn )
1632+ fn = os .path .join (folder , "Update Shell Profile.command" )
1633+ patchScript ("scripts/postflight.patch-profile" , fn )
14451634 os .chmod (folder , STAT_0o755 )
14461635 setIcon (folder , "../Icons/Python Folder.icns" )
14471636
14481637 # Create the installer
14491638 buildInstaller ()
14501639
14511640 # And copy the readme into the directory containing the installer
1452- patchFile ('resources/ReadMe.txt' , os .path .join (WORKDIR , 'installer' , 'ReadMe.txt' ))
1641+ patchFile ('resources/ReadMe.rtf' ,
1642+ os .path .join (WORKDIR , 'installer' , 'ReadMe.rtf' ))
14531643
14541644 # Ditto for the license file.
1455- shutil .copy ('../../LICENSE' , os .path .join (WORKDIR , 'installer' , 'License.txt' ))
1645+ patchFile ('resources/License.rtf' ,
1646+ os .path .join (WORKDIR , 'installer' , 'License.rtf' ))
14561647
14571648 fp = open (os .path .join (WORKDIR , 'installer' , 'Build.txt' ), 'w' )
14581649 fp .write ("# BUILD INFO\n " )
0 commit comments