Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
msvc.py615 linesDownload Raw Back to C
1"""distutils._msvccompiler2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class4for Microsoft Visual Studio 2015.5 6This module requires VS 2015 or later.7"""8 9# Written by Perry Stoll10# hacked by Robin Becker and Thomas Heller to do a better job of11#   finding DevStudio (through the registry)12# ported to VS 2005 and VS 2008 by Christian Heimes13# ported to VS 2015 by Steve Dower14from __future__ import annotations15 16import contextlib17import os18import subprocess19import unittest.mock as mock20import warnings21from collections.abc import Iterable22 23with contextlib.suppress(ImportError):24    import winreg25 26from itertools import count27 28from ..._log import log29from ...errors import (30    DistutilsExecError,31    DistutilsPlatformError,32)33from ...util import get_host_platform, get_platform34from . import base35from .base import gen_lib_options36from .errors import (37    CompileError,38    LibError,39    LinkError,40)41 42 43def _find_vc2015():44    try:45        key = winreg.OpenKeyEx(46            winreg.HKEY_LOCAL_MACHINE,47            r"Software\Microsoft\VisualStudio\SxS\VC7",48            access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,49        )50    except OSError:51        log.debug("Visual C++ is not registered")52        return None, None53 54    best_version = 055    best_dir = None56    with key:57        for i in count():58            try:59                v, vc_dir, vt = winreg.EnumValue(key, i)60            except OSError:61                break62            if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):63                try:64                    version = int(float(v))65                except (ValueError, TypeError):66                    continue67                if version >= 14 and version > best_version:68                    best_version, best_dir = version, vc_dir69    return best_version, best_dir70 71 72def _find_vc2017():73    """Returns "15, path" based on the result of invoking vswhere.exe74    If no install is found, returns "None, None"75 76    The version is returned to avoid unnecessarily changing the function77    result. It may be ignored when the path is not None.78 79    If vswhere.exe is not available, by definition, VS 2017 is not80    installed.81    """82    root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")83    if not root:84        return None, None85 86    variant = 'arm64' if get_platform() == 'win-arm64' else 'x86.x64'87    suitable_components = (88        f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",89        "Microsoft.VisualStudio.Workload.WDExpress",90    )91 92    for component in suitable_components:93        # Workaround for `-requiresAny` (only available on VS 2017 > 15.6)94        with contextlib.suppress(95            subprocess.CalledProcessError, OSError, UnicodeDecodeError96        ):97            path = (98                subprocess.check_output([99                    os.path.join(100                        root, "Microsoft Visual Studio", "Installer", "vswhere.exe"101                    ),102                    "-latest",103                    "-prerelease",104                    "-requires",105                    component,106                    "-property",107                    "installationPath",108                    "-products",109                    "*",110                ])111                .decode(encoding="mbcs", errors="strict")112                .strip()113            )114 115            path = os.path.join(path, "VC", "Auxiliary", "Build")116            if os.path.isdir(path):117                return 15, path118 119    return None, None  # no suitable component found120 121 122PLAT_SPEC_TO_RUNTIME = {123    'x86': 'x86',124    'x86_amd64': 'x64',125    'x86_arm': 'arm',126    'x86_arm64': 'arm64',127}128 129 130def _find_vcvarsall(plat_spec):131    # bpo-38597: Removed vcruntime return value132    _, best_dir = _find_vc2017()133 134    if not best_dir:135        best_version, best_dir = _find_vc2015()136 137    if not best_dir:138        log.debug("No suitable Visual C++ version found")139        return None, None140 141    vcvarsall = os.path.join(best_dir, "vcvarsall.bat")142    if not os.path.isfile(vcvarsall):143        log.debug("%s cannot be found", vcvarsall)144        return None, None145 146    return vcvarsall, None147 148 149def _get_vc_env(plat_spec):150    if os.getenv("DISTUTILS_USE_SDK"):151        return {key.lower(): value for key, value in os.environ.items()}152 153    vcvarsall, _ = _find_vcvarsall(plat_spec)154    if not vcvarsall:155        raise DistutilsPlatformError(156            'Microsoft Visual C++ 14.0 or greater is required. '157            'Get it with "Microsoft C++ Build Tools": '158            'https://visualstudio.microsoft.com/visual-cpp-build-tools/'159        )160 161    try:162        out = subprocess.check_output(163            f'cmd /u /c "{vcvarsall}" {plat_spec} && set',164            stderr=subprocess.STDOUT,165        ).decode('utf-16le', errors='replace')166    except subprocess.CalledProcessError as exc:167        log.error(exc.output)168        raise DistutilsPlatformError(f"Error executing {exc.cmd}")169 170    env = {171        key.lower(): value172        for key, _, value in (line.partition('=') for line in out.splitlines())173        if key and value174    }175 176    return env177 178 179def _find_exe(exe, paths=None):180    """Return path to an MSVC executable program.181 182    Tries to find the program in several places: first, one of the183    MSVC program search paths from the registry; next, the directories184    in the PATH environment variable.  If any of those work, return an185    absolute path that is known to exist.  If none of them work, just186    return the original program name, 'exe'.187    """188    if not paths:189        paths = os.getenv('path').split(os.pathsep)190    for p in paths:191        fn = os.path.join(os.path.abspath(p), exe)192        if os.path.isfile(fn):193            return fn194    return exe195 196 197_vcvars_names = {198    'win32': 'x86',199    'win-amd64': 'amd64',200    'win-arm32': 'arm',201    'win-arm64': 'arm64',202}203 204 205def _get_vcvars_spec(host_platform, platform):206    """207    Given a host platform and platform, determine the spec for vcvarsall.208 209    Uses the native MSVC host if the host platform would need expensive210    emulation for x86.211 212    >>> _get_vcvars_spec('win-arm64', 'win32')213    'arm64_x86'214    >>> _get_vcvars_spec('win-arm64', 'win-amd64')215    'arm64_amd64'216 217    Otherwise, always cross-compile from x86 to work with the218    lighter-weight MSVC installs that do not include native 64-bit tools.219 220    >>> _get_vcvars_spec('win32', 'win32')221    'x86'222    >>> _get_vcvars_spec('win-arm32', 'win-arm32')223    'x86_arm'224    >>> _get_vcvars_spec('win-amd64', 'win-arm64')225    'x86_arm64'226    """227    if host_platform != 'win-arm64':228        host_platform = 'win32'229    vc_hp = _vcvars_names[host_platform]230    vc_plat = _vcvars_names[platform]231    return vc_hp if vc_hp == vc_plat else f'{vc_hp}_{vc_plat}'232 233 234class Compiler(base.Compiler):235    """Concrete class that implements an interface to Microsoft Visual C++,236    as defined by the CCompiler abstract class."""237 238    compiler_type = 'msvc'239 240    # Just set this so CCompiler's constructor doesn't barf.  We currently241    # don't use the 'set_executables()' bureaucracy provided by CCompiler,242    # as it really isn't necessary for this sort of single-compiler class.243    # Would be nice to have a consistent interface with UnixCCompiler,244    # though, so it's worth thinking about.245    executables = {}246 247    # Private class data (need to distinguish C from C++ source for compiler)248    _c_extensions = ['.c']249    _cpp_extensions = ['.cc', '.cpp', '.cxx']250    _rc_extensions = ['.rc']251    _mc_extensions = ['.mc']252 253    # Needed for the filename generation methods provided by the254    # base class, CCompiler.255    src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions256    res_extension = '.res'257    obj_extension = '.obj'258    static_lib_extension = '.lib'259    shared_lib_extension = '.dll'260    static_lib_format = shared_lib_format = '%s%s'261    exe_extension = '.exe'262 263    def __init__(self, verbose=False, force=False) -> None:264        super().__init__(verbose, force=force)265        # target platform (.plat_name is consistent with 'bdist')266        self.plat_name = None267        self.initialized = False268 269    @classmethod270    def _configure(cls, vc_env):271        """272        Set class-level include/lib dirs.273        """274        cls.include_dirs = cls._parse_path(vc_env.get('include', ''))275        cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))276 277    @staticmethod278    def _parse_path(val):279        return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]280 281    def initialize(self, plat_name: str | None = None) -> None:282        # multi-init means we would need to check platform same each time...283        assert not self.initialized, "don't init multiple times"284        if plat_name is None:285            plat_name = get_platform()286        # sanity check for platforms to prevent obscure errors later.287        if plat_name not in _vcvars_names:288            raise DistutilsPlatformError(289                f"--plat-name must be one of {tuple(_vcvars_names)}"290            )291 292        plat_spec = _get_vcvars_spec(get_host_platform(), plat_name)293 294        vc_env = _get_vc_env(plat_spec)295        if not vc_env:296            raise DistutilsPlatformError(297                "Unable to find a compatible Visual Studio installation."298            )299        self._configure(vc_env)300 301        self._paths = vc_env.get('path', '')302        paths = self._paths.split(os.pathsep)303        self.cc = _find_exe("cl.exe", paths)304        self.linker = _find_exe("link.exe", paths)305        self.lib = _find_exe("lib.exe", paths)306        self.rc = _find_exe("rc.exe", paths)  # resource compiler307        self.mc = _find_exe("mc.exe", paths)  # message compiler308        self.mt = _find_exe("mt.exe", paths)  # message compiler309 310        self.preprocess_options = None311        # bpo-38597: Always compile with dynamic linking312        # Future releases of Python 3.x will include all past313        # versions of vcruntime*.dll for compatibility.314        self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD']315 316        self.compile_options_debug = [317            '/nologo',318            '/Od',319            '/MDd',320            '/Zi',321            '/W3',322            '/D_DEBUG',323        ]324 325        ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']326 327        ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']328 329        self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']330        self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']331        self.ldflags_shared = [332            *ldflags,333            '/DLL',334            '/MANIFEST:EMBED,ID=2',335            '/MANIFESTUAC:NO',336        ]337        self.ldflags_shared_debug = [338            *ldflags_debug,339            '/DLL',340            '/MANIFEST:EMBED,ID=2',341            '/MANIFESTUAC:NO',342        ]343        self.ldflags_static = [*ldflags]344        self.ldflags_static_debug = [*ldflags_debug]345 346        self._ldflags = {347            (base.Compiler.EXECUTABLE, None): self.ldflags_exe,348            (base.Compiler.EXECUTABLE, False): self.ldflags_exe,349            (base.Compiler.EXECUTABLE, True): self.ldflags_exe_debug,350            (base.Compiler.SHARED_OBJECT, None): self.ldflags_shared,351            (base.Compiler.SHARED_OBJECT, False): self.ldflags_shared,352            (base.Compiler.SHARED_OBJECT, True): self.ldflags_shared_debug,353            (base.Compiler.SHARED_LIBRARY, None): self.ldflags_static,354            (base.Compiler.SHARED_LIBRARY, False): self.ldflags_static,355            (base.Compiler.SHARED_LIBRARY, True): self.ldflags_static_debug,356        }357 358        self.initialized = True359 360    # -- Worker methods ------------------------------------------------361 362    @property363    def out_extensions(self) -> dict[str, str]:364        return {365            **super().out_extensions,366            **{367                ext: self.res_extension368                for ext in self._rc_extensions + self._mc_extensions369            },370        }371 372    def compile(  # noqa: C901373        self,374        sources,375        output_dir=None,376        macros=None,377        include_dirs=None,378        debug=False,379        extra_preargs=None,380        extra_postargs=None,381        depends=None,382    ):383        if not self.initialized:384            self.initialize()385        compile_info = self._setup_compile(386            output_dir, macros, include_dirs, sources, depends, extra_postargs387        )388        macros, objects, extra_postargs, pp_opts, build = compile_info389 390        compile_opts = extra_preargs or []391        compile_opts.append('/c')392        if debug:393            compile_opts.extend(self.compile_options_debug)394        else:395            compile_opts.extend(self.compile_options)396 397        add_cpp_opts = False398 399        for obj in objects:400            try:401                src, ext = build[obj]402            except KeyError:403                continue404            if debug:405                # pass the full pathname to MSVC in debug mode,406                # this allows the debugger to find the source file407                # without asking the user to browse for it408                src = os.path.abspath(src)409 410            if ext in self._c_extensions:411                input_opt = f"/Tc{src}"412            elif ext in self._cpp_extensions:413                input_opt = f"/Tp{src}"414                add_cpp_opts = True415            elif ext in self._rc_extensions:416                # compile .RC to .RES file417                input_opt = src418                output_opt = "/fo" + obj419                try:420                    self.spawn([self.rc] + pp_opts + [output_opt, input_opt])421                except DistutilsExecError as msg:422                    raise CompileError(msg)423                continue424            elif ext in self._mc_extensions:425                # Compile .MC to .RC file to .RES file.426                #   * '-h dir' specifies the directory for the427                #     generated include file428                #   * '-r dir' specifies the target directory of the429                #     generated RC file and the binary message resource430                #     it includes431                #432                # For now (since there are no options to change this),433                # we use the source-directory for the include file and434                # the build directory for the RC file and message435                # resources. This works at least for win32all.436                h_dir = os.path.dirname(src)437                rc_dir = os.path.dirname(obj)438                try:439                    # first compile .MC to .RC and .H file440                    self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])441                    base, _ = os.path.splitext(os.path.basename(src))442                    rc_file = os.path.join(rc_dir, base + '.rc')443                    # then compile .RC to .RES file444                    self.spawn([self.rc, "/fo" + obj, rc_file])445 446                except DistutilsExecError as msg:447                    raise CompileError(msg)448                continue449            else:450                # how to handle this file?451                raise CompileError(f"Don't know how to compile {src} to {obj}")452 453            args = [self.cc] + compile_opts + pp_opts454            if add_cpp_opts:455                args.append('/EHsc')456            args.extend((input_opt, "/Fo" + obj))457            args.extend(extra_postargs)458 459            try:460                self.spawn(args)461            except DistutilsExecError as msg:462                raise CompileError(msg)463 464        return objects465 466    def create_static_lib(467        self,468        objects: list[str] | tuple[str, ...],469        output_libname: str,470        output_dir: str | None = None,471        debug: bool = False,472        target_lang: str | None = None,473    ) -> None:474        if not self.initialized:475            self.initialize()476        objects, output_dir = self._fix_object_args(objects, output_dir)477        output_filename = self.library_filename(output_libname, output_dir=output_dir)478 479        if self._need_link(objects, output_filename):480            lib_args = objects + ['/OUT:' + output_filename]481            if debug:482                pass  # XXX what goes here?483            try:484                log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))485                self.spawn([self.lib] + lib_args)486            except DistutilsExecError as msg:487                raise LibError(msg)488        else:489            log.debug("skipping %s (up-to-date)", output_filename)490 491    def link(492        self,493        target_desc: str,494        objects: list[str] | tuple[str, ...],495        output_filename: str,496        output_dir: str | None = None,497        libraries: list[str] | tuple[str, ...] | None = None,498        library_dirs: list[str] | tuple[str, ...] | None = None,499        runtime_library_dirs: list[str] | tuple[str, ...] | None = None,500        export_symbols: Iterable[str] | None = None,501        debug: bool = False,502        extra_preargs: list[str] | None = None,503        extra_postargs: Iterable[str] | None = None,504        build_temp: str | os.PathLike[str] | None = None,505        target_lang: str | None = None,506    ) -> None:507        if not self.initialized:508            self.initialize()509        objects, output_dir = self._fix_object_args(objects, output_dir)510        fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)511        libraries, library_dirs, runtime_library_dirs = fixed_args512 513        if runtime_library_dirs:514            self.warn(515                "I don't know what to do with 'runtime_library_dirs': "516                + str(runtime_library_dirs)517            )518 519        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)520        if output_dir is not None:521            output_filename = os.path.join(output_dir, output_filename)522 523        if self._need_link(objects, output_filename):524            ldflags = self._ldflags[target_desc, debug]525 526            export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]527 528            ld_args = (529                ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]530            )531 532            # The MSVC linker generates .lib and .exp files, which cannot be533            # suppressed by any linker switches. The .lib files may even be534            # needed! Make sure they are generated in the temporary build535            # directory. Since they have different names for debug and release536            # builds, they can go into the same directory.537            build_temp = os.path.dirname(objects[0])538            if export_symbols is not None:539                (dll_name, dll_ext) = os.path.splitext(540                    os.path.basename(output_filename)541                )542                implib_file = os.path.join(build_temp, self.library_filename(dll_name))543                ld_args.append('/IMPLIB:' + implib_file)544 545            if extra_preargs:546                ld_args[:0] = extra_preargs547            if extra_postargs:548                ld_args.extend(extra_postargs)549 550            output_dir = os.path.dirname(os.path.abspath(output_filename))551            self.mkpath(output_dir)552            try:553                log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))554                self.spawn([self.linker] + ld_args)555            except DistutilsExecError as msg:556                raise LinkError(msg)557        else:558            log.debug("skipping %s (up-to-date)", output_filename)559 560    def spawn(self, cmd):561        env = dict(os.environ, PATH=self._paths)562        with self._fallback_spawn(cmd, env) as fallback:563            return super().spawn(cmd, env=env)564        return fallback.value565 566    @contextlib.contextmanager567    def _fallback_spawn(self, cmd, env):568        """569        Discovered in pypa/distutils#15, some tools monkeypatch the compiler,570        so the 'env' kwarg causes a TypeError. Detect this condition and571        restore the legacy, unsafe behavior.572        """573        bag = type('Bag', (), {})()574        try:575            yield bag576        except TypeError as exc:577            if "unexpected keyword argument 'env'" not in str(exc):578                raise579        else:580            return581        warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")582        with mock.patch.dict('os.environ', env):583            bag.value = super().spawn(cmd)584 585    # -- Miscellaneous methods -----------------------------------------586    # These are all used by the 'gen_lib_options() function, in587    # ccompiler.py.588 589    def library_dir_option(self, dir):590        return "/LIBPATH:" + dir591 592    def runtime_library_dir_option(self, dir):593        raise DistutilsPlatformError(594            "don't know how to set runtime library search path for MSVC"595        )596 597    def library_option(self, lib):598        return self.library_filename(lib)599 600    def find_library_file(self, dirs, lib, debug=False):601        # Prefer a debugging library if found (and requested), but deal602        # with it if we don't have one.603        if debug:604            try_names = [lib + "_d", lib]605        else:606            try_names = [lib]607        for dir in dirs:608            for name in try_names:609                libfile = os.path.join(dir, self.library_filename(name))610                if os.path.isfile(libfile):611                    return libfile612        else:613            # Oops, didn't find it in *any* of 'dirs'614            return None615 
codekingpro/portable-devtools · Team Ai