1# Script for building the _ssl and _hashlib modules for Windows. 2# Uses Perl to setup the OpenSSL environment correctly 3# and build OpenSSL, then invokes a simple nmake session 4# for the actual _ssl.pyd and _hashlib.pyd DLLs. 5 6# THEORETICALLY, you can: 7# * Unpack the latest SSL release one level above your main Python source 8# directory. It is likely you will already find the zlib library and 9# any other external packages there. 10# * Install ActivePerl and ensure it is somewhere on your path. 11# * Run this script from the PCBuild directory. 12# 13# it should configure and build SSL, then build the _ssl and _hashlib 14# Python extensions without intervention. 15 16# Modified by Christian Heimes 17# Now this script supports pre-generated makefiles and assembly files. 18# Developers don't need an installation of Perl anymore to build Python. A svn 19# checkout from our svn repository is enough. 20# 21# In Order to create the files in the case of an update you still need Perl. 22# Run build_ssl in this order: 23# python.exe build_ssl.py Release x64 24# python.exe build_ssl.py Release Win32 25 26from __future__ import with_statement, print_function 27import os 28import re 29import sys 30import time 31import subprocess 32from shutil import copy 33from distutils import log 34from distutils.spawn import find_executable 35from distutils.file_util import copy_file 36from distutils.sysconfig import parse_makefile, expand_makefile_vars 37 38# The mk1mf.pl output filename template 39# !!! This must match what is used in prepare_ssl.py 40MK1MF_FMT = 'ms\\nt{}.mak' 41 42# The header files output directory name template 43# !!! This must match what is used in prepare_ssl.py 44INCLUDE_FMT = 'include{}' 45 46# Fetch all the directory definitions from VC properties 47def get_project_properties(propfile): 48 macro_pattern = r'<UserMacro\s+Name="([^"]+?)"\s+Value="([^"]*?)"\s*/>' 49 with open(propfile) as fin: 50 items = re.findall(macro_pattern, fin.read(), re.MULTILINE) 51 props = dict(items) 52 for name, value in items: 53 try: 54 props[name] = expand_makefile_vars(value, props) 55 except TypeError: 56 # value contains undefined variable reference, drop it 57 del props[name] 58 return props 59 60 61_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") 62def fix_makefile(makefile, platform_makefile, suffix): 63 """Fix some stuff in all makefiles 64 """ 65 subs = { 66 'PERL': 'rem', # just in case 67 'CP': 'copy', 68 'MKDIR': 'mkdir', 69 'OUT_D': 'out' + suffix, 70 'TMP_D': 'tmp' + suffix, 71 'INC_D': INCLUDE_FMT.format(suffix), 72 'INCO_D': '$(INC_D)\\openssl', 73 } 74 with open(platform_makefile) as fin, open(makefile, 'w') as fout: 75 for line in fin: 76 m = _variable_rx.match(line) 77 if m: 78 name = m.group(1) 79 if name in subs: 80 line = '%s=%s\n' % (name, subs[name]) 81 fout.write(line) 82 83 84_copy_rx = re.compile(r'\t\$\(PERL\) ' 85 r'\$\(SRC_D\)\\util\\copy-if-different.pl ' 86 r'"([^"]+)"\s+"([^"]+)"') 87def copy_files(makefile, makevars): 88 # Create the destination directories (see 'init' rule in nt.dll) 89 for varname in ('TMP_D', 'LIB_D', 'INC_D', 'INCO_D'): 90 dirname = makevars[varname] 91 if not os.path.isdir(dirname): 92 os.mkdir(dirname) 93 # Process the just local library headers (HEADER) as installed headers 94 # (EXHEADER) are handled by prepare_ssl.py (see 'headers' rule in nt.dll) 95 headers = set(makevars['HEADER'].split()) 96 with open(makefile) as fin: 97 for line in fin: 98 m = _copy_rx.match(line) 99 if m: 100 src, dst = m.groups() 101 src = expand_makefile_vars(src, makevars) 102 dst = expand_makefile_vars(dst, makevars) 103 if dst in headers: 104 copy_file(src, dst, preserve_times=False, update=True) 105 106 107# Update buildinf.h for the build platform. 108def fix_buildinf(makevars): 109 platform_cpp_symbol = 'MK1MF_PLATFORM_' 110 platform_cpp_symbol += makevars['PLATFORM'].replace('-', '_') 111 fn = expand_makefile_vars('$(INCL_D)\\buildinf.h', makevars) 112 with open(fn, 'w') as f: 113 # sanity check 114 f.write(('#ifndef {}\n' 115 ' #error "Windows build (PLATFORM={PLATFORM}) only"\n' 116 '#endif\n').format(platform_cpp_symbol, **makevars)) 117 buildinf = ( 118 '#define CFLAGS "compiler: cl {CFLAG}"\n' 119 '#define PLATFORM "{PLATFORM}"\n' 120 '#define DATE "{}"\n' 121 ).format(time.asctime(time.gmtime()), 122 **makevars) 123 f.write(buildinf) 124 print('Updating buildinf:') 125 print(buildinf) 126 sys.stdout.flush() 127 128 129def main(): 130 if sys.argv[1] == "Debug": 131 print("OpenSSL debug builds aren't supported.") 132 elif sys.argv[1] != "Release": 133 raise ValueError('Unrecognized configuration: %s' % sys.argv[1]) 134 135 if sys.argv[2] == "Win32": 136 platform = "VC-WIN32" 137 suffix = '32' 138 elif sys.argv[2] == "x64": 139 platform = "VC-WIN64A" 140 suffix = '64' 141 else: 142 raise ValueError('Unrecognized platform: %s' % sys.argv[2]) 143 144 # Have the distutils functions display information output 145 log.set_verbosity(1) 146 147 # Use the same properties that are used in the VS projects 148 solution_dir = os.path.dirname(__file__) 149 propfile = os.path.join(solution_dir, 'pyproject.vsprops') 150 props = get_project_properties(propfile) 151 152 # Ensure we have the necessary external depenedencies 153 ssl_dir = os.path.join(solution_dir, props['opensslDir']) 154 if not os.path.isdir(ssl_dir): 155 print("Could not find the OpenSSL sources, try running " 156 "'build.bat -e'") 157 sys.exit(1) 158 159 # Ensure the executables used herein are available. 160 if not find_executable('nmake.exe'): 161 print('Could not find nmake.exe, try running env.bat') 162 sys.exit(1) 163 164 # add our copy of NASM to PATH. It will be on the same level as openssl 165 externals_dir = os.path.join(solution_dir, props['externalsDir']) 166 for dir in os.listdir(externals_dir): 167 if dir.startswith('nasm'): 168 nasm_dir = os.path.join(externals_dir, dir) 169 nasm_dir = os.path.abspath(nasm_dir) 170 old_path = os.environ['PATH'] 171 os.environ['PATH'] = os.pathsep.join([nasm_dir, old_path]) 172 break 173 else: 174 if not find_executable('nasm.exe'): 175 print('Could not find nasm.exe, please add to PATH') 176 sys.exit(1) 177 178 # If the ssl makefiles do not exist, we invoke PCbuild/prepare_ssl.py 179 # to generate them. 180 platform_makefile = MK1MF_FMT.format(suffix) 181 if not os.path.isfile(os.path.join(ssl_dir, platform_makefile)): 182 pcbuild_dir = os.path.join(os.path.dirname(externals_dir), 'PCbuild') 183 prepare_ssl = os.path.join(pcbuild_dir, 'prepare_ssl.py') 184 rc = subprocess.call([sys.executable, prepare_ssl, ssl_dir]) 185 if rc: 186 print('Executing', prepare_ssl, 'failed (error %d)' % rc) 187 sys.exit(rc) 188 189 old_cd = os.getcwd() 190 try: 191 os.chdir(ssl_dir) 192 193 # Get the variables defined in the current makefile, if it exists. 194 makefile = MK1MF_FMT.format('') 195 try: 196 makevars = parse_makefile(makefile) 197 except EnvironmentError: 198 makevars = {'PLATFORM': None} 199 200 # Rebuild the makefile when building for different a platform than 201 # the last run. 202 if makevars['PLATFORM'] != platform: 203 print("Updating the makefile...") 204 sys.stdout.flush() 205 # Firstly, apply the changes for the platform makefile into 206 # a temporary file to prevent any errors from this script 207 # causing false positives on subsequent runs. 208 new_makefile = makefile + '.new' 209 fix_makefile(new_makefile, platform_makefile, suffix) 210 makevars = parse_makefile(new_makefile) 211 212 # Secondly, perform the make recipes that use Perl 213 copy_files(new_makefile, makevars) 214 215 # Set our build information in buildinf.h. 216 # XXX: This isn't needed for a properly "prepared" SSL, but 217 # it fixes the current checked-in external (as of 2017-05). 218 fix_buildinf(makevars) 219 220 # Finally, move the temporary file to its real destination. 221 if os.path.exists(makefile): 222 os.remove(makefile) 223 os.rename(new_makefile, makefile) 224 225 # Now run make. 226 makeCommand = "nmake /nologo /f \"%s\" lib" % makefile 227 print("Executing ssl makefiles:", makeCommand) 228 sys.stdout.flush() 229 rc = os.system(makeCommand) 230 if rc: 231 print("Executing", makefile, "failed (error %d)" % rc) 232 sys.exit(rc) 233 finally: 234 os.chdir(old_cd) 235 sys.exit(rc) 236 237if __name__=='__main__': 238 main() 239