• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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