17db96d56Sopenharmony_ci#!./python
27db96d56Sopenharmony_ci"""Run Python tests against multiple installations of OpenSSL and LibreSSL
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciThe script
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ci  (1) downloads OpenSSL / LibreSSL tar bundle
77db96d56Sopenharmony_ci  (2) extracts it to ./src
87db96d56Sopenharmony_ci  (3) compiles OpenSSL / LibreSSL
97db96d56Sopenharmony_ci  (4) installs OpenSSL / LibreSSL into ../multissl/$LIB/$VERSION/
107db96d56Sopenharmony_ci  (5) forces a recompilation of Python modules using the
117db96d56Sopenharmony_ci      header and library files from ../multissl/$LIB/$VERSION/
127db96d56Sopenharmony_ci  (6) runs Python's test suite
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ciThe script must be run with Python's build directory as current working
157db96d56Sopenharmony_cidirectory.
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ciThe script uses LD_RUN_PATH, LD_LIBRARY_PATH, CPPFLAGS and LDFLAGS to bend
187db96d56Sopenharmony_cisearch paths for header files and shared libraries. It's known to work on
197db96d56Sopenharmony_ciLinux with GCC and clang.
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ciPlease keep this script compatible with Python 2.7, and 3.4 to 3.7.
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_ci(c) 2013-2017 Christian Heimes <christian@python.org>
247db96d56Sopenharmony_ci"""
257db96d56Sopenharmony_cifrom __future__ import print_function
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ciimport argparse
287db96d56Sopenharmony_cifrom datetime import datetime
297db96d56Sopenharmony_ciimport logging
307db96d56Sopenharmony_ciimport os
317db96d56Sopenharmony_citry:
327db96d56Sopenharmony_ci    from urllib.request import urlopen
337db96d56Sopenharmony_ci    from urllib.error import HTTPError
347db96d56Sopenharmony_ciexcept ImportError:
357db96d56Sopenharmony_ci    from urllib2 import urlopen, HTTPError
367db96d56Sopenharmony_ciimport re
377db96d56Sopenharmony_ciimport shutil
387db96d56Sopenharmony_ciimport string
397db96d56Sopenharmony_ciimport subprocess
407db96d56Sopenharmony_ciimport sys
417db96d56Sopenharmony_ciimport tarfile
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_cilog = logging.getLogger("multissl")
457db96d56Sopenharmony_ci
467db96d56Sopenharmony_ciOPENSSL_OLD_VERSIONS = [
477db96d56Sopenharmony_ci]
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_ciOPENSSL_RECENT_VERSIONS = [
507db96d56Sopenharmony_ci    "1.1.1u",
517db96d56Sopenharmony_ci    "3.0.9",
527db96d56Sopenharmony_ci    "3.1.1",
537db96d56Sopenharmony_ci]
547db96d56Sopenharmony_ci
557db96d56Sopenharmony_ciLIBRESSL_OLD_VERSIONS = [
567db96d56Sopenharmony_ci]
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ciLIBRESSL_RECENT_VERSIONS = [
597db96d56Sopenharmony_ci]
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci# store files in ../multissl
627db96d56Sopenharmony_ciHERE = os.path.dirname(os.path.abspath(__file__))
637db96d56Sopenharmony_ciPYTHONROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
647db96d56Sopenharmony_ciMULTISSL_DIR = os.path.abspath(os.path.join(PYTHONROOT, '..', 'multissl'))
657db96d56Sopenharmony_ci
667db96d56Sopenharmony_ci
677db96d56Sopenharmony_ciparser = argparse.ArgumentParser(
687db96d56Sopenharmony_ci    prog='multissl',
697db96d56Sopenharmony_ci    description=(
707db96d56Sopenharmony_ci        "Run CPython tests with multiple OpenSSL and LibreSSL "
717db96d56Sopenharmony_ci        "versions."
727db96d56Sopenharmony_ci    )
737db96d56Sopenharmony_ci)
747db96d56Sopenharmony_ciparser.add_argument(
757db96d56Sopenharmony_ci    '--debug',
767db96d56Sopenharmony_ci    action='store_true',
777db96d56Sopenharmony_ci    help="Enable debug logging",
787db96d56Sopenharmony_ci)
797db96d56Sopenharmony_ciparser.add_argument(
807db96d56Sopenharmony_ci    '--disable-ancient',
817db96d56Sopenharmony_ci    action='store_true',
827db96d56Sopenharmony_ci    help="Don't test OpenSSL and LibreSSL versions without upstream support",
837db96d56Sopenharmony_ci)
847db96d56Sopenharmony_ciparser.add_argument(
857db96d56Sopenharmony_ci    '--openssl',
867db96d56Sopenharmony_ci    nargs='+',
877db96d56Sopenharmony_ci    default=(),
887db96d56Sopenharmony_ci    help=(
897db96d56Sopenharmony_ci        "OpenSSL versions, defaults to '{}' (ancient: '{}') if no "
907db96d56Sopenharmony_ci        "OpenSSL and LibreSSL versions are given."
917db96d56Sopenharmony_ci    ).format(OPENSSL_RECENT_VERSIONS, OPENSSL_OLD_VERSIONS)
927db96d56Sopenharmony_ci)
937db96d56Sopenharmony_ciparser.add_argument(
947db96d56Sopenharmony_ci    '--libressl',
957db96d56Sopenharmony_ci    nargs='+',
967db96d56Sopenharmony_ci    default=(),
977db96d56Sopenharmony_ci    help=(
987db96d56Sopenharmony_ci        "LibreSSL versions, defaults to '{}' (ancient: '{}') if no "
997db96d56Sopenharmony_ci        "OpenSSL and LibreSSL versions are given."
1007db96d56Sopenharmony_ci    ).format(LIBRESSL_RECENT_VERSIONS, LIBRESSL_OLD_VERSIONS)
1017db96d56Sopenharmony_ci)
1027db96d56Sopenharmony_ciparser.add_argument(
1037db96d56Sopenharmony_ci    '--tests',
1047db96d56Sopenharmony_ci    nargs='*',
1057db96d56Sopenharmony_ci    default=(),
1067db96d56Sopenharmony_ci    help="Python tests to run, defaults to all SSL related tests.",
1077db96d56Sopenharmony_ci)
1087db96d56Sopenharmony_ciparser.add_argument(
1097db96d56Sopenharmony_ci    '--base-directory',
1107db96d56Sopenharmony_ci    default=MULTISSL_DIR,
1117db96d56Sopenharmony_ci    help="Base directory for OpenSSL / LibreSSL sources and builds."
1127db96d56Sopenharmony_ci)
1137db96d56Sopenharmony_ciparser.add_argument(
1147db96d56Sopenharmony_ci    '--no-network',
1157db96d56Sopenharmony_ci    action='store_false',
1167db96d56Sopenharmony_ci    dest='network',
1177db96d56Sopenharmony_ci    help="Disable network tests."
1187db96d56Sopenharmony_ci)
1197db96d56Sopenharmony_ciparser.add_argument(
1207db96d56Sopenharmony_ci    '--steps',
1217db96d56Sopenharmony_ci    choices=['library', 'modules', 'tests'],
1227db96d56Sopenharmony_ci    default='tests',
1237db96d56Sopenharmony_ci    help=(
1247db96d56Sopenharmony_ci        "Which steps to perform. 'library' downloads and compiles OpenSSL "
1257db96d56Sopenharmony_ci        "or LibreSSL. 'module' also compiles Python modules. 'tests' builds "
1267db96d56Sopenharmony_ci        "all and runs the test suite."
1277db96d56Sopenharmony_ci    )
1287db96d56Sopenharmony_ci)
1297db96d56Sopenharmony_ciparser.add_argument(
1307db96d56Sopenharmony_ci    '--system',
1317db96d56Sopenharmony_ci    default='',
1327db96d56Sopenharmony_ci    help="Override the automatic system type detection."
1337db96d56Sopenharmony_ci)
1347db96d56Sopenharmony_ciparser.add_argument(
1357db96d56Sopenharmony_ci    '--force',
1367db96d56Sopenharmony_ci    action='store_true',
1377db96d56Sopenharmony_ci    dest='force',
1387db96d56Sopenharmony_ci    help="Force build and installation."
1397db96d56Sopenharmony_ci)
1407db96d56Sopenharmony_ciparser.add_argument(
1417db96d56Sopenharmony_ci    '--keep-sources',
1427db96d56Sopenharmony_ci    action='store_true',
1437db96d56Sopenharmony_ci    dest='keep_sources',
1447db96d56Sopenharmony_ci    help="Keep original sources for debugging."
1457db96d56Sopenharmony_ci)
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_ci
1487db96d56Sopenharmony_ciclass AbstractBuilder(object):
1497db96d56Sopenharmony_ci    library = None
1507db96d56Sopenharmony_ci    url_templates = None
1517db96d56Sopenharmony_ci    src_template = None
1527db96d56Sopenharmony_ci    build_template = None
1537db96d56Sopenharmony_ci    depend_target = None
1547db96d56Sopenharmony_ci    install_target = 'install'
1557db96d56Sopenharmony_ci    jobs = os.cpu_count()
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_ci    module_files = (
1587db96d56Sopenharmony_ci        os.path.join(PYTHONROOT, "Modules/_ssl.c"),
1597db96d56Sopenharmony_ci        os.path.join(PYTHONROOT, "Modules/_hashopenssl.c"),
1607db96d56Sopenharmony_ci    )
1617db96d56Sopenharmony_ci    module_libs = ("_ssl", "_hashlib")
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci    def __init__(self, version, args):
1647db96d56Sopenharmony_ci        self.version = version
1657db96d56Sopenharmony_ci        self.args = args
1667db96d56Sopenharmony_ci        # installation directory
1677db96d56Sopenharmony_ci        self.install_dir = os.path.join(
1687db96d56Sopenharmony_ci            os.path.join(args.base_directory, self.library.lower()), version
1697db96d56Sopenharmony_ci        )
1707db96d56Sopenharmony_ci        # source file
1717db96d56Sopenharmony_ci        self.src_dir = os.path.join(args.base_directory, 'src')
1727db96d56Sopenharmony_ci        self.src_file = os.path.join(
1737db96d56Sopenharmony_ci            self.src_dir, self.src_template.format(version))
1747db96d56Sopenharmony_ci        # build directory (removed after install)
1757db96d56Sopenharmony_ci        self.build_dir = os.path.join(
1767db96d56Sopenharmony_ci            self.src_dir, self.build_template.format(version))
1777db96d56Sopenharmony_ci        self.system = args.system
1787db96d56Sopenharmony_ci
1797db96d56Sopenharmony_ci    def __str__(self):
1807db96d56Sopenharmony_ci        return "<{0.__class__.__name__} for {0.version}>".format(self)
1817db96d56Sopenharmony_ci
1827db96d56Sopenharmony_ci    def __eq__(self, other):
1837db96d56Sopenharmony_ci        if not isinstance(other, AbstractBuilder):
1847db96d56Sopenharmony_ci            return NotImplemented
1857db96d56Sopenharmony_ci        return (
1867db96d56Sopenharmony_ci            self.library == other.library
1877db96d56Sopenharmony_ci            and self.version == other.version
1887db96d56Sopenharmony_ci        )
1897db96d56Sopenharmony_ci
1907db96d56Sopenharmony_ci    def __hash__(self):
1917db96d56Sopenharmony_ci        return hash((self.library, self.version))
1927db96d56Sopenharmony_ci
1937db96d56Sopenharmony_ci    @property
1947db96d56Sopenharmony_ci    def short_version(self):
1957db96d56Sopenharmony_ci        """Short version for OpenSSL download URL"""
1967db96d56Sopenharmony_ci        return None
1977db96d56Sopenharmony_ci
1987db96d56Sopenharmony_ci    @property
1997db96d56Sopenharmony_ci    def openssl_cli(self):
2007db96d56Sopenharmony_ci        """openssl CLI binary"""
2017db96d56Sopenharmony_ci        return os.path.join(self.install_dir, "bin", "openssl")
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ci    @property
2047db96d56Sopenharmony_ci    def openssl_version(self):
2057db96d56Sopenharmony_ci        """output of 'bin/openssl version'"""
2067db96d56Sopenharmony_ci        cmd = [self.openssl_cli, "version"]
2077db96d56Sopenharmony_ci        return self._subprocess_output(cmd)
2087db96d56Sopenharmony_ci
2097db96d56Sopenharmony_ci    @property
2107db96d56Sopenharmony_ci    def pyssl_version(self):
2117db96d56Sopenharmony_ci        """Value of ssl.OPENSSL_VERSION"""
2127db96d56Sopenharmony_ci        cmd = [
2137db96d56Sopenharmony_ci            sys.executable,
2147db96d56Sopenharmony_ci            '-c', 'import ssl; print(ssl.OPENSSL_VERSION)'
2157db96d56Sopenharmony_ci        ]
2167db96d56Sopenharmony_ci        return self._subprocess_output(cmd)
2177db96d56Sopenharmony_ci
2187db96d56Sopenharmony_ci    @property
2197db96d56Sopenharmony_ci    def include_dir(self):
2207db96d56Sopenharmony_ci        return os.path.join(self.install_dir, "include")
2217db96d56Sopenharmony_ci
2227db96d56Sopenharmony_ci    @property
2237db96d56Sopenharmony_ci    def lib_dir(self):
2247db96d56Sopenharmony_ci        return os.path.join(self.install_dir, "lib")
2257db96d56Sopenharmony_ci
2267db96d56Sopenharmony_ci    @property
2277db96d56Sopenharmony_ci    def has_openssl(self):
2287db96d56Sopenharmony_ci        return os.path.isfile(self.openssl_cli)
2297db96d56Sopenharmony_ci
2307db96d56Sopenharmony_ci    @property
2317db96d56Sopenharmony_ci    def has_src(self):
2327db96d56Sopenharmony_ci        return os.path.isfile(self.src_file)
2337db96d56Sopenharmony_ci
2347db96d56Sopenharmony_ci    def _subprocess_call(self, cmd, env=None, **kwargs):
2357db96d56Sopenharmony_ci        log.debug("Call '{}'".format(" ".join(cmd)))
2367db96d56Sopenharmony_ci        return subprocess.check_call(cmd, env=env, **kwargs)
2377db96d56Sopenharmony_ci
2387db96d56Sopenharmony_ci    def _subprocess_output(self, cmd, env=None, **kwargs):
2397db96d56Sopenharmony_ci        log.debug("Call '{}'".format(" ".join(cmd)))
2407db96d56Sopenharmony_ci        if env is None:
2417db96d56Sopenharmony_ci            env = os.environ.copy()
2427db96d56Sopenharmony_ci            env["LD_LIBRARY_PATH"] = self.lib_dir
2437db96d56Sopenharmony_ci        out = subprocess.check_output(cmd, env=env, **kwargs)
2447db96d56Sopenharmony_ci        return out.strip().decode("utf-8")
2457db96d56Sopenharmony_ci
2467db96d56Sopenharmony_ci    def _download_src(self):
2477db96d56Sopenharmony_ci        """Download sources"""
2487db96d56Sopenharmony_ci        src_dir = os.path.dirname(self.src_file)
2497db96d56Sopenharmony_ci        if not os.path.isdir(src_dir):
2507db96d56Sopenharmony_ci            os.makedirs(src_dir)
2517db96d56Sopenharmony_ci        data = None
2527db96d56Sopenharmony_ci        for url_template in self.url_templates:
2537db96d56Sopenharmony_ci            url = url_template.format(v=self.version, s=self.short_version)
2547db96d56Sopenharmony_ci            log.info("Downloading from {}".format(url))
2557db96d56Sopenharmony_ci            try:
2567db96d56Sopenharmony_ci                req = urlopen(url)
2577db96d56Sopenharmony_ci                # KISS, read all, write all
2587db96d56Sopenharmony_ci                data = req.read()
2597db96d56Sopenharmony_ci            except HTTPError as e:
2607db96d56Sopenharmony_ci                log.error(
2617db96d56Sopenharmony_ci                    "Download from {} has from failed: {}".format(url, e)
2627db96d56Sopenharmony_ci                )
2637db96d56Sopenharmony_ci            else:
2647db96d56Sopenharmony_ci                log.info("Successfully downloaded from {}".format(url))
2657db96d56Sopenharmony_ci                break
2667db96d56Sopenharmony_ci        if data is None:
2677db96d56Sopenharmony_ci            raise ValueError("All download URLs have failed")
2687db96d56Sopenharmony_ci        log.info("Storing {}".format(self.src_file))
2697db96d56Sopenharmony_ci        with open(self.src_file, "wb") as f:
2707db96d56Sopenharmony_ci            f.write(data)
2717db96d56Sopenharmony_ci
2727db96d56Sopenharmony_ci    def _unpack_src(self):
2737db96d56Sopenharmony_ci        """Unpack tar.gz bundle"""
2747db96d56Sopenharmony_ci        # cleanup
2757db96d56Sopenharmony_ci        if os.path.isdir(self.build_dir):
2767db96d56Sopenharmony_ci            shutil.rmtree(self.build_dir)
2777db96d56Sopenharmony_ci        os.makedirs(self.build_dir)
2787db96d56Sopenharmony_ci
2797db96d56Sopenharmony_ci        tf = tarfile.open(self.src_file)
2807db96d56Sopenharmony_ci        name = self.build_template.format(self.version)
2817db96d56Sopenharmony_ci        base = name + '/'
2827db96d56Sopenharmony_ci        # force extraction into build dir
2837db96d56Sopenharmony_ci        members = tf.getmembers()
2847db96d56Sopenharmony_ci        for member in list(members):
2857db96d56Sopenharmony_ci            if member.name == name:
2867db96d56Sopenharmony_ci                members.remove(member)
2877db96d56Sopenharmony_ci            elif not member.name.startswith(base):
2887db96d56Sopenharmony_ci                raise ValueError(member.name, base)
2897db96d56Sopenharmony_ci            member.name = member.name[len(base):].lstrip('/')
2907db96d56Sopenharmony_ci        log.info("Unpacking files to {}".format(self.build_dir))
2917db96d56Sopenharmony_ci        tf.extractall(self.build_dir, members)
2927db96d56Sopenharmony_ci
2937db96d56Sopenharmony_ci    def _build_src(self, config_args=()):
2947db96d56Sopenharmony_ci        """Now build openssl"""
2957db96d56Sopenharmony_ci        log.info("Running build in {}".format(self.build_dir))
2967db96d56Sopenharmony_ci        cwd = self.build_dir
2977db96d56Sopenharmony_ci        cmd = [
2987db96d56Sopenharmony_ci            "./config", *config_args,
2997db96d56Sopenharmony_ci            "shared", "--debug",
3007db96d56Sopenharmony_ci            "--prefix={}".format(self.install_dir)
3017db96d56Sopenharmony_ci        ]
3027db96d56Sopenharmony_ci        # cmd.extend(["no-deprecated", "--api=1.1.0"])
3037db96d56Sopenharmony_ci        env = os.environ.copy()
3047db96d56Sopenharmony_ci        # set rpath
3057db96d56Sopenharmony_ci        env["LD_RUN_PATH"] = self.lib_dir
3067db96d56Sopenharmony_ci        if self.system:
3077db96d56Sopenharmony_ci            env['SYSTEM'] = self.system
3087db96d56Sopenharmony_ci        self._subprocess_call(cmd, cwd=cwd, env=env)
3097db96d56Sopenharmony_ci        if self.depend_target:
3107db96d56Sopenharmony_ci            self._subprocess_call(
3117db96d56Sopenharmony_ci                ["make", "-j1", self.depend_target], cwd=cwd, env=env
3127db96d56Sopenharmony_ci            )
3137db96d56Sopenharmony_ci        self._subprocess_call(["make", f"-j{self.jobs}"], cwd=cwd, env=env)
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ci    def _make_install(self):
3167db96d56Sopenharmony_ci        self._subprocess_call(
3177db96d56Sopenharmony_ci            ["make", "-j1", self.install_target],
3187db96d56Sopenharmony_ci            cwd=self.build_dir
3197db96d56Sopenharmony_ci        )
3207db96d56Sopenharmony_ci        self._post_install()
3217db96d56Sopenharmony_ci        if not self.args.keep_sources:
3227db96d56Sopenharmony_ci            shutil.rmtree(self.build_dir)
3237db96d56Sopenharmony_ci
3247db96d56Sopenharmony_ci    def _post_install(self):
3257db96d56Sopenharmony_ci        pass
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ci    def install(self):
3287db96d56Sopenharmony_ci        log.info(self.openssl_cli)
3297db96d56Sopenharmony_ci        if not self.has_openssl or self.args.force:
3307db96d56Sopenharmony_ci            if not self.has_src:
3317db96d56Sopenharmony_ci                self._download_src()
3327db96d56Sopenharmony_ci            else:
3337db96d56Sopenharmony_ci                log.debug("Already has src {}".format(self.src_file))
3347db96d56Sopenharmony_ci            self._unpack_src()
3357db96d56Sopenharmony_ci            self._build_src()
3367db96d56Sopenharmony_ci            self._make_install()
3377db96d56Sopenharmony_ci        else:
3387db96d56Sopenharmony_ci            log.info("Already has installation {}".format(self.install_dir))
3397db96d56Sopenharmony_ci        # validate installation
3407db96d56Sopenharmony_ci        version = self.openssl_version
3417db96d56Sopenharmony_ci        if self.version not in version:
3427db96d56Sopenharmony_ci            raise ValueError(version)
3437db96d56Sopenharmony_ci
3447db96d56Sopenharmony_ci    def recompile_pymods(self):
3457db96d56Sopenharmony_ci        log.warning("Using build from {}".format(self.build_dir))
3467db96d56Sopenharmony_ci        # force a rebuild of all modules that use OpenSSL APIs
3477db96d56Sopenharmony_ci        for fname in self.module_files:
3487db96d56Sopenharmony_ci            os.utime(fname, None)
3497db96d56Sopenharmony_ci        # remove all build artefacts
3507db96d56Sopenharmony_ci        for root, dirs, files in os.walk('build'):
3517db96d56Sopenharmony_ci            for filename in files:
3527db96d56Sopenharmony_ci                if filename.startswith(self.module_libs):
3537db96d56Sopenharmony_ci                    os.unlink(os.path.join(root, filename))
3547db96d56Sopenharmony_ci
3557db96d56Sopenharmony_ci        # overwrite header and library search paths
3567db96d56Sopenharmony_ci        env = os.environ.copy()
3577db96d56Sopenharmony_ci        env["CPPFLAGS"] = "-I{}".format(self.include_dir)
3587db96d56Sopenharmony_ci        env["LDFLAGS"] = "-L{}".format(self.lib_dir)
3597db96d56Sopenharmony_ci        # set rpath
3607db96d56Sopenharmony_ci        env["LD_RUN_PATH"] = self.lib_dir
3617db96d56Sopenharmony_ci
3627db96d56Sopenharmony_ci        log.info("Rebuilding Python modules")
3637db96d56Sopenharmony_ci        cmd = [sys.executable, os.path.join(PYTHONROOT, "setup.py"), "build"]
3647db96d56Sopenharmony_ci        self._subprocess_call(cmd, env=env)
3657db96d56Sopenharmony_ci        self.check_imports()
3667db96d56Sopenharmony_ci
3677db96d56Sopenharmony_ci    def check_imports(self):
3687db96d56Sopenharmony_ci        cmd = [sys.executable, "-c", "import _ssl; import _hashlib"]
3697db96d56Sopenharmony_ci        self._subprocess_call(cmd)
3707db96d56Sopenharmony_ci
3717db96d56Sopenharmony_ci    def check_pyssl(self):
3727db96d56Sopenharmony_ci        version = self.pyssl_version
3737db96d56Sopenharmony_ci        if self.version not in version:
3747db96d56Sopenharmony_ci            raise ValueError(version)
3757db96d56Sopenharmony_ci
3767db96d56Sopenharmony_ci    def run_python_tests(self, tests, network=True):
3777db96d56Sopenharmony_ci        if not tests:
3787db96d56Sopenharmony_ci            cmd = [
3797db96d56Sopenharmony_ci                sys.executable,
3807db96d56Sopenharmony_ci                os.path.join(PYTHONROOT, 'Lib/test/ssltests.py'),
3817db96d56Sopenharmony_ci                '-j0'
3827db96d56Sopenharmony_ci            ]
3837db96d56Sopenharmony_ci        elif sys.version_info < (3, 3):
3847db96d56Sopenharmony_ci            cmd = [sys.executable, '-m', 'test.regrtest']
3857db96d56Sopenharmony_ci        else:
3867db96d56Sopenharmony_ci            cmd = [sys.executable, '-m', 'test', '-j0']
3877db96d56Sopenharmony_ci        if network:
3887db96d56Sopenharmony_ci            cmd.extend(['-u', 'network', '-u', 'urlfetch'])
3897db96d56Sopenharmony_ci        cmd.extend(['-w', '-r'])
3907db96d56Sopenharmony_ci        cmd.extend(tests)
3917db96d56Sopenharmony_ci        self._subprocess_call(cmd, stdout=None)
3927db96d56Sopenharmony_ci
3937db96d56Sopenharmony_ci
3947db96d56Sopenharmony_ciclass BuildOpenSSL(AbstractBuilder):
3957db96d56Sopenharmony_ci    library = "OpenSSL"
3967db96d56Sopenharmony_ci    url_templates = (
3977db96d56Sopenharmony_ci        "https://www.openssl.org/source/openssl-{v}.tar.gz",
3987db96d56Sopenharmony_ci        "https://www.openssl.org/source/old/{s}/openssl-{v}.tar.gz"
3997db96d56Sopenharmony_ci    )
4007db96d56Sopenharmony_ci    src_template = "openssl-{}.tar.gz"
4017db96d56Sopenharmony_ci    build_template = "openssl-{}"
4027db96d56Sopenharmony_ci    # only install software, skip docs
4037db96d56Sopenharmony_ci    install_target = 'install_sw'
4047db96d56Sopenharmony_ci    depend_target = 'depend'
4057db96d56Sopenharmony_ci
4067db96d56Sopenharmony_ci    def _post_install(self):
4077db96d56Sopenharmony_ci        if self.version.startswith("3."):
4087db96d56Sopenharmony_ci            self._post_install_3xx()
4097db96d56Sopenharmony_ci
4107db96d56Sopenharmony_ci    def _build_src(self, config_args=()):
4117db96d56Sopenharmony_ci        if self.version.startswith("3."):
4127db96d56Sopenharmony_ci            config_args += ("enable-fips",)
4137db96d56Sopenharmony_ci        super()._build_src(config_args)
4147db96d56Sopenharmony_ci
4157db96d56Sopenharmony_ci    def _post_install_3xx(self):
4167db96d56Sopenharmony_ci        # create ssl/ subdir with example configs
4177db96d56Sopenharmony_ci        # Install FIPS module
4187db96d56Sopenharmony_ci        self._subprocess_call(
4197db96d56Sopenharmony_ci            ["make", "-j1", "install_ssldirs", "install_fips"],
4207db96d56Sopenharmony_ci            cwd=self.build_dir
4217db96d56Sopenharmony_ci        )
4227db96d56Sopenharmony_ci        if not os.path.isdir(self.lib_dir):
4237db96d56Sopenharmony_ci            # 3.0.0-beta2 uses lib64 on 64 bit platforms
4247db96d56Sopenharmony_ci            lib64 = self.lib_dir + "64"
4257db96d56Sopenharmony_ci            os.symlink(lib64, self.lib_dir)
4267db96d56Sopenharmony_ci
4277db96d56Sopenharmony_ci    @property
4287db96d56Sopenharmony_ci    def short_version(self):
4297db96d56Sopenharmony_ci        """Short version for OpenSSL download URL"""
4307db96d56Sopenharmony_ci        mo = re.search(r"^(\d+)\.(\d+)\.(\d+)", self.version)
4317db96d56Sopenharmony_ci        parsed = tuple(int(m) for m in mo.groups())
4327db96d56Sopenharmony_ci        if parsed < (1, 0, 0):
4337db96d56Sopenharmony_ci            return "0.9.x"
4347db96d56Sopenharmony_ci        if parsed >= (3, 0, 0):
4357db96d56Sopenharmony_ci            # OpenSSL 3.0.0 -> /old/3.0/
4367db96d56Sopenharmony_ci            parsed = parsed[:2]
4377db96d56Sopenharmony_ci        return ".".join(str(i) for i in parsed)
4387db96d56Sopenharmony_ci
4397db96d56Sopenharmony_ciclass BuildLibreSSL(AbstractBuilder):
4407db96d56Sopenharmony_ci    library = "LibreSSL"
4417db96d56Sopenharmony_ci    url_templates = (
4427db96d56Sopenharmony_ci        "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-{v}.tar.gz",
4437db96d56Sopenharmony_ci    )
4447db96d56Sopenharmony_ci    src_template = "libressl-{}.tar.gz"
4457db96d56Sopenharmony_ci    build_template = "libressl-{}"
4467db96d56Sopenharmony_ci
4477db96d56Sopenharmony_ci
4487db96d56Sopenharmony_cidef configure_make():
4497db96d56Sopenharmony_ci    if not os.path.isfile('Makefile'):
4507db96d56Sopenharmony_ci        log.info('Running ./configure')
4517db96d56Sopenharmony_ci        subprocess.check_call([
4527db96d56Sopenharmony_ci            './configure', '--config-cache', '--quiet',
4537db96d56Sopenharmony_ci            '--with-pydebug'
4547db96d56Sopenharmony_ci        ])
4557db96d56Sopenharmony_ci
4567db96d56Sopenharmony_ci    log.info('Running make')
4577db96d56Sopenharmony_ci    subprocess.check_call(['make', '--quiet'])
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_ci
4607db96d56Sopenharmony_cidef main():
4617db96d56Sopenharmony_ci    args = parser.parse_args()
4627db96d56Sopenharmony_ci    if not args.openssl and not args.libressl:
4637db96d56Sopenharmony_ci        args.openssl = list(OPENSSL_RECENT_VERSIONS)
4647db96d56Sopenharmony_ci        args.libressl = list(LIBRESSL_RECENT_VERSIONS)
4657db96d56Sopenharmony_ci        if not args.disable_ancient:
4667db96d56Sopenharmony_ci            args.openssl.extend(OPENSSL_OLD_VERSIONS)
4677db96d56Sopenharmony_ci            args.libressl.extend(LIBRESSL_OLD_VERSIONS)
4687db96d56Sopenharmony_ci
4697db96d56Sopenharmony_ci    logging.basicConfig(
4707db96d56Sopenharmony_ci        level=logging.DEBUG if args.debug else logging.INFO,
4717db96d56Sopenharmony_ci        format="*** %(levelname)s %(message)s"
4727db96d56Sopenharmony_ci    )
4737db96d56Sopenharmony_ci
4747db96d56Sopenharmony_ci    start = datetime.now()
4757db96d56Sopenharmony_ci
4767db96d56Sopenharmony_ci    if args.steps in {'modules', 'tests'}:
4777db96d56Sopenharmony_ci        for name in ['setup.py', 'Modules/_ssl.c']:
4787db96d56Sopenharmony_ci            if not os.path.isfile(os.path.join(PYTHONROOT, name)):
4797db96d56Sopenharmony_ci                parser.error(
4807db96d56Sopenharmony_ci                    "Must be executed from CPython build dir"
4817db96d56Sopenharmony_ci                )
4827db96d56Sopenharmony_ci        if not os.path.samefile('python', sys.executable):
4837db96d56Sopenharmony_ci            parser.error(
4847db96d56Sopenharmony_ci                "Must be executed with ./python from CPython build dir"
4857db96d56Sopenharmony_ci            )
4867db96d56Sopenharmony_ci        # check for configure and run make
4877db96d56Sopenharmony_ci        configure_make()
4887db96d56Sopenharmony_ci
4897db96d56Sopenharmony_ci    # download and register builder
4907db96d56Sopenharmony_ci    builds = []
4917db96d56Sopenharmony_ci
4927db96d56Sopenharmony_ci    for version in args.openssl:
4937db96d56Sopenharmony_ci        build = BuildOpenSSL(
4947db96d56Sopenharmony_ci            version,
4957db96d56Sopenharmony_ci            args
4967db96d56Sopenharmony_ci        )
4977db96d56Sopenharmony_ci        build.install()
4987db96d56Sopenharmony_ci        builds.append(build)
4997db96d56Sopenharmony_ci
5007db96d56Sopenharmony_ci    for version in args.libressl:
5017db96d56Sopenharmony_ci        build = BuildLibreSSL(
5027db96d56Sopenharmony_ci            version,
5037db96d56Sopenharmony_ci            args
5047db96d56Sopenharmony_ci        )
5057db96d56Sopenharmony_ci        build.install()
5067db96d56Sopenharmony_ci        builds.append(build)
5077db96d56Sopenharmony_ci
5087db96d56Sopenharmony_ci    if args.steps in {'modules', 'tests'}:
5097db96d56Sopenharmony_ci        for build in builds:
5107db96d56Sopenharmony_ci            try:
5117db96d56Sopenharmony_ci                build.recompile_pymods()
5127db96d56Sopenharmony_ci                build.check_pyssl()
5137db96d56Sopenharmony_ci                if args.steps == 'tests':
5147db96d56Sopenharmony_ci                    build.run_python_tests(
5157db96d56Sopenharmony_ci                        tests=args.tests,
5167db96d56Sopenharmony_ci                        network=args.network,
5177db96d56Sopenharmony_ci                    )
5187db96d56Sopenharmony_ci            except Exception as e:
5197db96d56Sopenharmony_ci                log.exception("%s failed", build)
5207db96d56Sopenharmony_ci                print("{} failed: {}".format(build, e), file=sys.stderr)
5217db96d56Sopenharmony_ci                sys.exit(2)
5227db96d56Sopenharmony_ci
5237db96d56Sopenharmony_ci    log.info("\n{} finished in {}".format(
5247db96d56Sopenharmony_ci            args.steps.capitalize(),
5257db96d56Sopenharmony_ci            datetime.now() - start
5267db96d56Sopenharmony_ci        ))
5277db96d56Sopenharmony_ci    print('Python: ', sys.version)
5287db96d56Sopenharmony_ci    if args.steps == 'tests':
5297db96d56Sopenharmony_ci        if args.tests:
5307db96d56Sopenharmony_ci            print('Executed Tests:', ' '.join(args.tests))
5317db96d56Sopenharmony_ci        else:
5327db96d56Sopenharmony_ci            print('Executed all SSL tests.')
5337db96d56Sopenharmony_ci
5347db96d56Sopenharmony_ci    print('OpenSSL / LibreSSL versions:')
5357db96d56Sopenharmony_ci    for build in builds:
5367db96d56Sopenharmony_ci        print("    * {0.library} {0.version}".format(build))
5377db96d56Sopenharmony_ci
5387db96d56Sopenharmony_ci
5397db96d56Sopenharmony_ciif __name__ == "__main__":
5407db96d56Sopenharmony_ci    main()
541