Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_msvccompiler.py569 linesDownload Raw Back to _distutils
1"""distutils._msvccompiler2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class4for Microsoft Visual Studio 2015.5 6The module is compatible with VS 2015 and later. You can find legacy support7for older versions in distutils.msvc9compiler and distutils.msvccompiler.8"""9 10# Written by Perry Stoll11# hacked by Robin Becker and Thomas Heller to do a better job of12#   finding DevStudio (through the registry)13# ported to VS 2005 and VS 2008 by Christian Heimes14# ported to VS 2015 by Steve Dower15 16import contextlib17import os18import subprocess19import unittest.mock as mock20import warnings21 22with contextlib.suppress(ImportError):23    import winreg24 25from itertools import count26 27from ._log import log28from .ccompiler import CCompiler, gen_lib_options29from .errors import (30    CompileError,31    DistutilsExecError,32    DistutilsPlatformError,33    LibError,34    LinkError,35)36from .util import get_platform37 38 39def _find_vc2015():40    try:41        key = winreg.OpenKeyEx(42            winreg.HKEY_LOCAL_MACHINE,43            r"Software\Microsoft\VisualStudio\SxS\VC7",44            access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,45        )46    except OSError:47        log.debug("Visual C++ is not registered")48        return None, None49 50    best_version = 051    best_dir = None52    with key:53        for i in count():54            try:55                v, vc_dir, vt = winreg.EnumValue(key, i)56            except OSError:57                break58            if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):59                try:60                    version = int(float(v))61                except (ValueError, TypeError):62                    continue63                if version >= 14 and version > best_version:64                    best_version, best_dir = version, vc_dir65    return best_version, best_dir66 67 68def _find_vc2017():69    """Returns "15, path" based on the result of invoking vswhere.exe70    If no install is found, returns "None, None"71 72    The version is returned to avoid unnecessarily changing the function73    result. It may be ignored when the path is not None.74 75    If vswhere.exe is not available, by definition, VS 2017 is not76    installed.77    """78    root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")79    if not root:80        return None, None81 82    try:83        path = subprocess.check_output(84            [85                os.path.join(86                    root, "Microsoft Visual Studio", "Installer", "vswhere.exe"87                ),88                "-latest",89                "-prerelease",90                "-requires",91                "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",92                "-property",93                "installationPath",94                "-products",95                "*",96            ],97            encoding="mbcs",98            errors="strict",99        ).strip()100    except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):101        return None, None102 103    path = os.path.join(path, "VC", "Auxiliary", "Build")104    if os.path.isdir(path):105        return 15, path106 107    return None, None108 109 110PLAT_SPEC_TO_RUNTIME = {111    'x86': 'x86',112    'x86_amd64': 'x64',113    'x86_arm': 'arm',114    'x86_arm64': 'arm64',115}116 117 118def _find_vcvarsall(plat_spec):119    # bpo-38597: Removed vcruntime return value120    _, best_dir = _find_vc2017()121 122    if not best_dir:123        best_version, best_dir = _find_vc2015()124 125    if not best_dir:126        log.debug("No suitable Visual C++ version found")127        return None, None128 129    vcvarsall = os.path.join(best_dir, "vcvarsall.bat")130    if not os.path.isfile(vcvarsall):131        log.debug("%s cannot be found", vcvarsall)132        return None, None133 134    return vcvarsall, None135 136 137def _get_vc_env(plat_spec):138    if os.getenv("DISTUTILS_USE_SDK"):139        return {key.lower(): value for key, value in os.environ.items()}140 141    vcvarsall, _ = _find_vcvarsall(plat_spec)142    if not vcvarsall:143        raise DistutilsPlatformError("Unable to find vcvarsall.bat")144 145    try:146        out = subprocess.check_output(147            f'cmd /u /c "{vcvarsall}" {plat_spec} && set',148            stderr=subprocess.STDOUT,149        ).decode('utf-16le', errors='replace')150    except subprocess.CalledProcessError as exc:151        log.error(exc.output)152        raise DistutilsPlatformError(f"Error executing {exc.cmd}")153 154    env = {155        key.lower(): value156        for key, _, value in (line.partition('=') for line in out.splitlines())157        if key and value158    }159 160    return env161 162 163def _find_exe(exe, paths=None):164    """Return path to an MSVC executable program.165 166    Tries to find the program in several places: first, one of the167    MSVC program search paths from the registry; next, the directories168    in the PATH environment variable.  If any of those work, return an169    absolute path that is known to exist.  If none of them work, just170    return the original program name, 'exe'.171    """172    if not paths:173        paths = os.getenv('path').split(os.pathsep)174    for p in paths:175        fn = os.path.join(os.path.abspath(p), exe)176        if os.path.isfile(fn):177            return fn178    return exe179 180 181# A map keyed by get_platform() return values to values accepted by182# 'vcvarsall.bat'. Always cross-compile from x86 to work with the183# lighter-weight MSVC installs that do not include native 64-bit tools.184PLAT_TO_VCVARS = {185    'win32': 'x86',186    'win-amd64': 'x86_amd64',187    'win-arm32': 'x86_arm',188    'win-arm64': 'x86_arm64',189}190 191 192class MSVCCompiler(CCompiler):193    """Concrete class that implements an interface to Microsoft Visual C++,194    as defined by the CCompiler abstract class."""195 196    compiler_type = 'msvc'197 198    # Just set this so CCompiler's constructor doesn't barf.  We currently199    # don't use the 'set_executables()' bureaucracy provided by CCompiler,200    # as it really isn't necessary for this sort of single-compiler class.201    # Would be nice to have a consistent interface with UnixCCompiler,202    # though, so it's worth thinking about.203    executables = {}204 205    # Private class data (need to distinguish C from C++ source for compiler)206    _c_extensions = ['.c']207    _cpp_extensions = ['.cc', '.cpp', '.cxx']208    _rc_extensions = ['.rc']209    _mc_extensions = ['.mc']210 211    # Needed for the filename generation methods provided by the212    # base class, CCompiler.213    src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions214    res_extension = '.res'215    obj_extension = '.obj'216    static_lib_extension = '.lib'217    shared_lib_extension = '.dll'218    static_lib_format = shared_lib_format = '%s%s'219    exe_extension = '.exe'220 221    def __init__(self, verbose=0, dry_run=0, force=0):222        super().__init__(verbose, dry_run, force)223        # target platform (.plat_name is consistent with 'bdist')224        self.plat_name = None225        self.initialized = False226 227    @classmethod228    def _configure(cls, vc_env):229        """230        Set class-level include/lib dirs.231        """232        cls.include_dirs = cls._parse_path(vc_env.get('include', ''))233        cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))234 235    @staticmethod236    def _parse_path(val):237        return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]238 239    def initialize(self, plat_name=None):240        # multi-init means we would need to check platform same each time...241        assert not self.initialized, "don't init multiple times"242        if plat_name is None:243            plat_name = get_platform()244        # sanity check for platforms to prevent obscure errors later.245        if plat_name not in PLAT_TO_VCVARS:246            raise DistutilsPlatformError(247                f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}"248            )249 250        # Get the vcvarsall.bat spec for the requested platform.251        plat_spec = PLAT_TO_VCVARS[plat_name]252 253        vc_env = _get_vc_env(plat_spec)254        if not vc_env:255            raise DistutilsPlatformError(256                "Unable to find a compatible Visual Studio installation."257            )258        self._configure(vc_env)259 260        self._paths = vc_env.get('path', '')261        paths = self._paths.split(os.pathsep)262        self.cc = _find_exe("cl.exe", paths)263        self.linker = _find_exe("link.exe", paths)264        self.lib = _find_exe("lib.exe", paths)265        self.rc = _find_exe("rc.exe", paths)  # resource compiler266        self.mc = _find_exe("mc.exe", paths)  # message compiler267        self.mt = _find_exe("mt.exe", paths)  # message compiler268 269        self.preprocess_options = None270        # bpo-38597: Always compile with dynamic linking271        # Future releases of Python 3.x will include all past272        # versions of vcruntime*.dll for compatibility.273        self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD']274 275        self.compile_options_debug = [276            '/nologo',277            '/Od',278            '/MDd',279            '/Zi',280            '/W3',281            '/D_DEBUG',282        ]283 284        ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']285 286        ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']287 288        self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']289        self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']290        self.ldflags_shared = [291            *ldflags,292            '/DLL',293            '/MANIFEST:EMBED,ID=2',294            '/MANIFESTUAC:NO',295        ]296        self.ldflags_shared_debug = [297            *ldflags_debug,298            '/DLL',299            '/MANIFEST:EMBED,ID=2',300            '/MANIFESTUAC:NO',301        ]302        self.ldflags_static = [*ldflags]303        self.ldflags_static_debug = [*ldflags_debug]304 305        self._ldflags = {306            (CCompiler.EXECUTABLE, None): self.ldflags_exe,307            (CCompiler.EXECUTABLE, False): self.ldflags_exe,308            (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug,309            (CCompiler.SHARED_OBJECT, None): self.ldflags_shared,310            (CCompiler.SHARED_OBJECT, False): self.ldflags_shared,311            (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug,312            (CCompiler.SHARED_LIBRARY, None): self.ldflags_static,313            (CCompiler.SHARED_LIBRARY, False): self.ldflags_static,314            (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug,315        }316 317        self.initialized = True318 319    # -- Worker methods ------------------------------------------------320 321    @property322    def out_extensions(self):323        return {324            **super().out_extensions,325            **{326                ext: self.res_extension327                for ext in self._rc_extensions + self._mc_extensions328            },329        }330 331    def compile(  # noqa: C901332        self,333        sources,334        output_dir=None,335        macros=None,336        include_dirs=None,337        debug=0,338        extra_preargs=None,339        extra_postargs=None,340        depends=None,341    ):342        if not self.initialized:343            self.initialize()344        compile_info = self._setup_compile(345            output_dir, macros, include_dirs, sources, depends, extra_postargs346        )347        macros, objects, extra_postargs, pp_opts, build = compile_info348 349        compile_opts = extra_preargs or []350        compile_opts.append('/c')351        if debug:352            compile_opts.extend(self.compile_options_debug)353        else:354            compile_opts.extend(self.compile_options)355 356        add_cpp_opts = False357 358        for obj in objects:359            try:360                src, ext = build[obj]361            except KeyError:362                continue363            if debug:364                # pass the full pathname to MSVC in debug mode,365                # this allows the debugger to find the source file366                # without asking the user to browse for it367                src = os.path.abspath(src)368 369            if ext in self._c_extensions:370                input_opt = "/Tc" + src371            elif ext in self._cpp_extensions:372                input_opt = "/Tp" + src373                add_cpp_opts = True374            elif ext in self._rc_extensions:375                # compile .RC to .RES file376                input_opt = src377                output_opt = "/fo" + obj378                try:379                    self.spawn([self.rc] + pp_opts + [output_opt, input_opt])380                except DistutilsExecError as msg:381                    raise CompileError(msg)382                continue383            elif ext in self._mc_extensions:384                # Compile .MC to .RC file to .RES file.385                #   * '-h dir' specifies the directory for the386                #     generated include file387                #   * '-r dir' specifies the target directory of the388                #     generated RC file and the binary message resource389                #     it includes390                #391                # For now (since there are no options to change this),392                # we use the source-directory for the include file and393                # the build directory for the RC file and message394                # resources. This works at least for win32all.395                h_dir = os.path.dirname(src)396                rc_dir = os.path.dirname(obj)397                try:398                    # first compile .MC to .RC and .H file399                    self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])400                    base, _ = os.path.splitext(os.path.basename(src))401                    rc_file = os.path.join(rc_dir, base + '.rc')402                    # then compile .RC to .RES file403                    self.spawn([self.rc, "/fo" + obj, rc_file])404 405                except DistutilsExecError as msg:406                    raise CompileError(msg)407                continue408            else:409                # how to handle this file?410                raise CompileError(f"Don't know how to compile {src} to {obj}")411 412            args = [self.cc] + compile_opts + pp_opts413            if add_cpp_opts:414                args.append('/EHsc')415            args.extend((input_opt, "/Fo" + obj))416            args.extend(extra_postargs)417 418            try:419                self.spawn(args)420            except DistutilsExecError as msg:421                raise CompileError(msg)422 423        return objects424 425    def create_static_lib(426        self, objects, output_libname, output_dir=None, debug=0, target_lang=None427    ):428        if not self.initialized:429            self.initialize()430        objects, output_dir = self._fix_object_args(objects, output_dir)431        output_filename = self.library_filename(output_libname, output_dir=output_dir)432 433        if self._need_link(objects, output_filename):434            lib_args = objects + ['/OUT:' + output_filename]435            if debug:436                pass  # XXX what goes here?437            try:438                log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))439                self.spawn([self.lib] + lib_args)440            except DistutilsExecError as msg:441                raise LibError(msg)442        else:443            log.debug("skipping %s (up-to-date)", output_filename)444 445    def link(446        self,447        target_desc,448        objects,449        output_filename,450        output_dir=None,451        libraries=None,452        library_dirs=None,453        runtime_library_dirs=None,454        export_symbols=None,455        debug=0,456        extra_preargs=None,457        extra_postargs=None,458        build_temp=None,459        target_lang=None,460    ):461        if not self.initialized:462            self.initialize()463        objects, output_dir = self._fix_object_args(objects, output_dir)464        fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)465        libraries, library_dirs, runtime_library_dirs = fixed_args466 467        if runtime_library_dirs:468            self.warn(469                "I don't know what to do with 'runtime_library_dirs': "470                + str(runtime_library_dirs)471            )472 473        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)474        if output_dir is not None:475            output_filename = os.path.join(output_dir, output_filename)476 477        if self._need_link(objects, output_filename):478            ldflags = self._ldflags[target_desc, debug]479 480            export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]481 482            ld_args = (483                ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]484            )485 486            # The MSVC linker generates .lib and .exp files, which cannot be487            # suppressed by any linker switches. The .lib files may even be488            # needed! Make sure they are generated in the temporary build489            # directory. Since they have different names for debug and release490            # builds, they can go into the same directory.491            build_temp = os.path.dirname(objects[0])492            if export_symbols is not None:493                (dll_name, dll_ext) = os.path.splitext(494                    os.path.basename(output_filename)495                )496                implib_file = os.path.join(build_temp, self.library_filename(dll_name))497                ld_args.append('/IMPLIB:' + implib_file)498 499            if extra_preargs:500                ld_args[:0] = extra_preargs501            if extra_postargs:502                ld_args.extend(extra_postargs)503 504            output_dir = os.path.dirname(os.path.abspath(output_filename))505            self.mkpath(output_dir)506            try:507                log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))508                self.spawn([self.linker] + ld_args)509            except DistutilsExecError as msg:510                raise LinkError(msg)511        else:512            log.debug("skipping %s (up-to-date)", output_filename)513 514    def spawn(self, cmd):515        env = dict(os.environ, PATH=self._paths)516        with self._fallback_spawn(cmd, env) as fallback:517            return super().spawn(cmd, env=env)518        return fallback.value519 520    @contextlib.contextmanager521    def _fallback_spawn(self, cmd, env):522        """523        Discovered in pypa/distutils#15, some tools monkeypatch the compiler,524        so the 'env' kwarg causes a TypeError. Detect this condition and525        restore the legacy, unsafe behavior.526        """527        bag = type('Bag', (), {})()528        try:529            yield bag530        except TypeError as exc:531            if "unexpected keyword argument 'env'" not in str(exc):532                raise533        else:534            return535        warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")536        with mock.patch.dict('os.environ', env):537            bag.value = super().spawn(cmd)538 539    # -- Miscellaneous methods -----------------------------------------540    # These are all used by the 'gen_lib_options() function, in541    # ccompiler.py.542 543    def library_dir_option(self, dir):544        return "/LIBPATH:" + dir545 546    def runtime_library_dir_option(self, dir):547        raise DistutilsPlatformError(548            "don't know how to set runtime library search path for MSVC"549        )550 551    def library_option(self, lib):552        return self.library_filename(lib)553 554    def find_library_file(self, dirs, lib, debug=0):555        # Prefer a debugging library if found (and requested), but deal556        # with it if we don't have one.557        if debug:558            try_names = [lib + "_d", lib]559        else:560            try_names = [lib]561        for dir in dirs:562            for name in try_names:563                libfile = os.path.join(dir, self.library_filename(name))564                if os.path.isfile(libfile):565                    return libfile566        else:567            # Oops, didn't find it in *any* of 'dirs'568            return None569 
codekingpro/portable-devtools · Team Ai