# Script for building the _ssl and _hashlib modules for Windows. # Uses Perl to setup the OpenSSL environment correctly # and build OpenSSL, then invokes a simple nmake session # for the actual _ssl.pyd and _hashlib.pyd DLLs. # THEORETICALLY, you can: # * Unpack the latest SSL release one level above your main Python source # directory. It is likely you will already find the zlib library and # any other external packages there. # * Install ActivePerl and ensure it is somewhere on your path. # * Run this script from the PCBuild directory. # # it should configure and build SSL, then build the _ssl and _hashlib # Python extensions without intervention. # Modified by Christian Heimes # Now this script supports pre-generated makefiles and assembly files. # Developers don't need an installation of Perl anymore to build Python. A svn # checkout from our svn repository is enough. # # In Order to create the files in the case of an update you still need Perl. # Run build_ssl in this order: # python.exe build_ssl.py Release x64 # python.exe build_ssl.py Release Win32 from __future__ import with_statement, print_function import os import re import sys import time import subprocess from shutil import copy from distutils import log from distutils.spawn import find_executable from distutils.file_util import copy_file from distutils.sysconfig import parse_makefile, expand_makefile_vars # The mk1mf.pl output filename template # !!! This must match what is used in prepare_ssl.py MK1MF_FMT = 'ms\\nt{}.mak' # The header files output directory name template # !!! This must match what is used in prepare_ssl.py INCLUDE_FMT = 'include{}' # Fetch all the directory definitions from VC properties def get_project_properties(propfile): macro_pattern = r'' with open(propfile) as fin: items = re.findall(macro_pattern, fin.read(), re.MULTILINE) props = dict(items) for name, value in items: try: props[name] = expand_makefile_vars(value, props) except TypeError: # value contains undefined variable reference, drop it del props[name] return props _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") def fix_makefile(makefile, platform_makefile, suffix): """Fix some stuff in all makefiles """ subs = { 'PERL': 'rem', # just in case 'CP': 'copy', 'MKDIR': 'mkdir', 'OUT_D': 'out' + suffix, 'TMP_D': 'tmp' + suffix, 'INC_D': INCLUDE_FMT.format(suffix), 'INCO_D': '$(INC_D)\\openssl', } with open(platform_makefile) as fin, open(makefile, 'w') as fout: for line in fin: m = _variable_rx.match(line) if m: name = m.group(1) if name in subs: line = '%s=%s\n' % (name, subs[name]) fout.write(line) _copy_rx = re.compile(r'\t\$\(PERL\) ' r'\$\(SRC_D\)\\util\\copy-if-different.pl ' r'"([^"]+)"\s+"([^"]+)"') def copy_files(makefile, makevars): # Create the destination directories (see 'init' rule in nt.dll) for varname in ('TMP_D', 'LIB_D', 'INC_D', 'INCO_D'): dirname = makevars[varname] if not os.path.isdir(dirname): os.mkdir(dirname) # Process the just local library headers (HEADER) as installed headers # (EXHEADER) are handled by prepare_ssl.py (see 'headers' rule in nt.dll) headers = set(makevars['HEADER'].split()) with open(makefile) as fin: for line in fin: m = _copy_rx.match(line) if m: src, dst = m.groups() src = expand_makefile_vars(src, makevars) dst = expand_makefile_vars(dst, makevars) if dst in headers: copy_file(src, dst, preserve_times=False, update=True) # Update buildinf.h for the build platform. def fix_buildinf(makevars): platform_cpp_symbol = 'MK1MF_PLATFORM_' platform_cpp_symbol += makevars['PLATFORM'].replace('-', '_') fn = expand_makefile_vars('$(INCL_D)\\buildinf.h', makevars) with open(fn, 'w') as f: # sanity check f.write(('#ifndef {}\n' ' #error "Windows build (PLATFORM={PLATFORM}) only"\n' '#endif\n').format(platform_cpp_symbol, **makevars)) buildinf = ( '#define CFLAGS "compiler: cl {CFLAG}"\n' '#define PLATFORM "{PLATFORM}"\n' '#define DATE "{}"\n' ).format(time.asctime(time.gmtime()), **makevars) f.write(buildinf) print('Updating buildinf:') print(buildinf) sys.stdout.flush() def main(): if sys.argv[1] == "Debug": print("OpenSSL debug builds aren't supported.") elif sys.argv[1] != "Release": raise ValueError('Unrecognized configuration: %s' % sys.argv[1]) if sys.argv[2] == "Win32": platform = "VC-WIN32" suffix = '32' elif sys.argv[2] == "x64": platform = "VC-WIN64A" suffix = '64' else: raise ValueError('Unrecognized platform: %s' % sys.argv[2]) # Have the distutils functions display information output log.set_verbosity(1) # Use the same properties that are used in the VS projects solution_dir = os.path.dirname(__file__) propfile = os.path.join(solution_dir, 'pyproject.vsprops') props = get_project_properties(propfile) # Ensure we have the necessary external depenedencies ssl_dir = os.path.join(solution_dir, props['opensslDir']) if not os.path.isdir(ssl_dir): print("Could not find the OpenSSL sources, try running " "'build.bat -e'") sys.exit(1) # Ensure the executables used herein are available. if not find_executable('nmake.exe'): print('Could not find nmake.exe, try running env.bat') sys.exit(1) # add our copy of NASM to PATH. It will be on the same level as openssl externals_dir = os.path.join(solution_dir, props['externalsDir']) for dir in os.listdir(externals_dir): if dir.startswith('nasm'): nasm_dir = os.path.join(externals_dir, dir) nasm_dir = os.path.abspath(nasm_dir) old_path = os.environ['PATH'] os.environ['PATH'] = os.pathsep.join([nasm_dir, old_path]) break else: if not find_executable('nasm.exe'): print('Could not find nasm.exe, please add to PATH') sys.exit(1) # If the ssl makefiles do not exist, we invoke PCbuild/prepare_ssl.py # to generate them. platform_makefile = MK1MF_FMT.format(suffix) if not os.path.isfile(os.path.join(ssl_dir, platform_makefile)): pcbuild_dir = os.path.join(os.path.dirname(externals_dir), 'PCbuild') prepare_ssl = os.path.join(pcbuild_dir, 'prepare_ssl.py') rc = subprocess.call([sys.executable, prepare_ssl, ssl_dir]) if rc: print('Executing', prepare_ssl, 'failed (error %d)' % rc) sys.exit(rc) old_cd = os.getcwd() try: os.chdir(ssl_dir) # Get the variables defined in the current makefile, if it exists. makefile = MK1MF_FMT.format('') try: makevars = parse_makefile(makefile) except EnvironmentError: makevars = {'PLATFORM': None} # Rebuild the makefile when building for different a platform than # the last run. if makevars['PLATFORM'] != platform: print("Updating the makefile...") sys.stdout.flush() # Firstly, apply the changes for the platform makefile into # a temporary file to prevent any errors from this script # causing false positives on subsequent runs. new_makefile = makefile + '.new' fix_makefile(new_makefile, platform_makefile, suffix) makevars = parse_makefile(new_makefile) # Secondly, perform the make recipes that use Perl copy_files(new_makefile, makevars) # Set our build information in buildinf.h. # XXX: This isn't needed for a properly "prepared" SSL, but # it fixes the current checked-in external (as of 2017-05). fix_buildinf(makevars) # Finally, move the temporary file to its real destination. if os.path.exists(makefile): os.remove(makefile) os.rename(new_makefile, makefile) # Now run make. makeCommand = "nmake /nologo /f \"%s\" lib" % makefile print("Executing ssl makefiles:", makeCommand) sys.stdout.flush() rc = os.system(makeCommand) if rc: print("Executing", makefile, "failed (error %d)" % rc) sys.exit(rc) finally: os.chdir(old_cd) sys.exit(rc) if __name__=='__main__': main()