Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
msvccompiler.py690 linesDownload Raw Back to _distutils
1"""distutils.msvccompiler2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class4for the Microsoft Visual Studio.5"""6 7# Written by Perry Stoll8# hacked by Robin Becker and Thomas Heller to do a better job of9#   finding DevStudio (through the registry)10 11import os12import sys13import warnings14 15from ._log import log16from .ccompiler import CCompiler, gen_lib_options17from .errors import (18    CompileError,19    DistutilsExecError,20    DistutilsPlatformError,21    LibError,22    LinkError,23)24 25_can_read_reg = False26try:27    import winreg28 29    _can_read_reg = True30    hkey_mod = winreg31 32    RegOpenKeyEx = winreg.OpenKeyEx33    RegEnumKey = winreg.EnumKey34    RegEnumValue = winreg.EnumValue35    RegError = winreg.error36 37except ImportError:38    try:39        import win32api40        import win32con41 42        _can_read_reg = True43        hkey_mod = win32con44 45        RegOpenKeyEx = win32api.RegOpenKeyEx46        RegEnumKey = win32api.RegEnumKey47        RegEnumValue = win32api.RegEnumValue48        RegError = win32api.error49    except ImportError:50        log.info(51            "Warning: Can't read registry to find the "52            "necessary compiler setting\n"53            "Make sure that Python modules winreg, "54            "win32api or win32con are installed."55        )56        pass57 58if _can_read_reg:59    HKEYS = (60        hkey_mod.HKEY_USERS,61        hkey_mod.HKEY_CURRENT_USER,62        hkey_mod.HKEY_LOCAL_MACHINE,63        hkey_mod.HKEY_CLASSES_ROOT,64    )65 66 67warnings.warn(68    "msvccompiler is deprecated and slated to be removed "69    "in the future. Please discontinue use or file an issue "70    "with pypa/distutils describing your use case.",71    DeprecationWarning,72)73 74 75def read_keys(base, key):76    """Return list of registry keys."""77    try:78        handle = RegOpenKeyEx(base, key)79    except RegError:80        return None81    L = []82    i = 083    while True:84        try:85            k = RegEnumKey(handle, i)86        except RegError:87            break88        L.append(k)89        i += 190    return L91 92 93def read_values(base, key):94    """Return dict of registry keys and values.95 96    All names are converted to lowercase.97    """98    try:99        handle = RegOpenKeyEx(base, key)100    except RegError:101        return None102    d = {}103    i = 0104    while True:105        try:106            name, value, type = RegEnumValue(handle, i)107        except RegError:108            break109        name = name.lower()110        d[convert_mbcs(name)] = convert_mbcs(value)111        i += 1112    return d113 114 115def convert_mbcs(s):116    dec = getattr(s, "decode", None)117    if dec is not None:118        try:119            s = dec("mbcs")120        except UnicodeError:121            pass122    return s123 124 125class MacroExpander:126    def __init__(self, version):127        self.macros = {}128        self.load_macros(version)129 130    def set_macro(self, macro, path, key):131        for base in HKEYS:132            d = read_values(base, path)133            if d:134                self.macros["$(%s)" % macro] = d[key]135                break136 137    def load_macros(self, version):138        vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version139        self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir")140        self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir")141        net = r"Software\Microsoft\.NETFramework"142        self.set_macro("FrameworkDir", net, "installroot")143        try:144            if version > 7.0:145                self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")146            else:147                self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")148        except KeyError:149            raise DistutilsPlatformError(150                """Python was built with Visual Studio 2003;151extensions must be built with a compiler than can generate compatible binaries.152Visual Studio 2003 was not found on this system. If you have Cygwin installed,153you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""154            )155 156        p = r"Software\Microsoft\NET Framework Setup\Product"157        for base in HKEYS:158            try:159                h = RegOpenKeyEx(base, p)160            except RegError:161                continue162            key = RegEnumKey(h, 0)163            d = read_values(base, rf"{p}\{key}")164            self.macros["$(FrameworkVersion)"] = d["version"]165 166    def sub(self, s):167        for k, v in self.macros.items():168            s = s.replace(k, v)169        return s170 171 172def get_build_version():173    """Return the version of MSVC that was used to build Python.174 175    For Python 2.3 and up, the version number is included in176    sys.version.  For earlier versions, assume the compiler is MSVC 6.177    """178    prefix = "MSC v."179    i = sys.version.find(prefix)180    if i == -1:181        return 6182    i = i + len(prefix)183    s, rest = sys.version[i:].split(" ", 1)184    majorVersion = int(s[:-2]) - 6185    if majorVersion >= 13:186        # v13 was skipped and should be v14187        majorVersion += 1188    minorVersion = int(s[2:3]) / 10.0189    # I don't think paths are affected by minor version in version 6190    if majorVersion == 6:191        minorVersion = 0192    if majorVersion >= 6:193        return majorVersion + minorVersion194    # else we don't know what version of the compiler this is195    return None196 197 198def get_build_architecture():199    """Return the processor architecture.200 201    Possible results are "Intel" or "AMD64".202    """203 204    prefix = " bit ("205    i = sys.version.find(prefix)206    if i == -1:207        return "Intel"208    j = sys.version.find(")", i)209    return sys.version[i + len(prefix) : j]210 211 212def normalize_and_reduce_paths(paths):213    """Return a list of normalized paths with duplicates removed.214 215    The current order of paths is maintained.216    """217    # Paths are normalized so things like:  /a and /a/ aren't both preserved.218    reduced_paths = []219    for p in paths:220        np = os.path.normpath(p)221        # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.222        if np not in reduced_paths:223            reduced_paths.append(np)224    return reduced_paths225 226 227class MSVCCompiler(CCompiler):228    """Concrete class that implements an interface to Microsoft Visual C++,229    as defined by the CCompiler abstract class."""230 231    compiler_type = 'msvc'232 233    # Just set this so CCompiler's constructor doesn't barf.  We currently234    # don't use the 'set_executables()' bureaucracy provided by CCompiler,235    # as it really isn't necessary for this sort of single-compiler class.236    # Would be nice to have a consistent interface with UnixCCompiler,237    # though, so it's worth thinking about.238    executables = {}239 240    # Private class data (need to distinguish C from C++ source for compiler)241    _c_extensions = ['.c']242    _cpp_extensions = ['.cc', '.cpp', '.cxx']243    _rc_extensions = ['.rc']244    _mc_extensions = ['.mc']245 246    # Needed for the filename generation methods provided by the247    # base class, CCompiler.248    src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions249    res_extension = '.res'250    obj_extension = '.obj'251    static_lib_extension = '.lib'252    shared_lib_extension = '.dll'253    static_lib_format = shared_lib_format = '%s%s'254    exe_extension = '.exe'255 256    def __init__(self, verbose=0, dry_run=0, force=0):257        super().__init__(verbose, dry_run, force)258        self.__version = get_build_version()259        self.__arch = get_build_architecture()260        if self.__arch == "Intel":261            # x86262            if self.__version >= 7:263                self.__root = r"Software\Microsoft\VisualStudio"264                self.__macros = MacroExpander(self.__version)265            else:266                self.__root = r"Software\Microsoft\Devstudio"267            self.__product = "Visual Studio version %s" % self.__version268        else:269            # Win64. Assume this was built with the platform SDK270            self.__product = "Microsoft SDK compiler %s" % (self.__version + 6)271 272        self.initialized = False273 274    def initialize(self):275        self.__paths = []276        if (277            "DISTUTILS_USE_SDK" in os.environ278            and "MSSdk" in os.environ279            and self.find_exe("cl.exe")280        ):281            # Assume that the SDK set up everything alright; don't try to be282            # smarter283            self.cc = "cl.exe"284            self.linker = "link.exe"285            self.lib = "lib.exe"286            self.rc = "rc.exe"287            self.mc = "mc.exe"288        else:289            self.__paths = self.get_msvc_paths("path")290 291            if len(self.__paths) == 0:292                raise DistutilsPlatformError(293                    "Python was built with %s, "294                    "and extensions need to be built with the same "295                    "version of the compiler, but it isn't installed." % self.__product296                )297 298            self.cc = self.find_exe("cl.exe")299            self.linker = self.find_exe("link.exe")300            self.lib = self.find_exe("lib.exe")301            self.rc = self.find_exe("rc.exe")  # resource compiler302            self.mc = self.find_exe("mc.exe")  # message compiler303            self.set_path_env_var('lib')304            self.set_path_env_var('include')305 306        # extend the MSVC path with the current path307        try:308            for p in os.environ['path'].split(';'):309                self.__paths.append(p)310        except KeyError:311            pass312        self.__paths = normalize_and_reduce_paths(self.__paths)313        os.environ['path'] = ";".join(self.__paths)314 315        self.preprocess_options = None316        if self.__arch == "Intel":317            self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GX', '/DNDEBUG']318            self.compile_options_debug = [319                '/nologo',320                '/Od',321                '/MDd',322                '/W3',323                '/GX',324                '/Z7',325                '/D_DEBUG',326            ]327        else:328            # Win64329            self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']330            self.compile_options_debug = [331                '/nologo',332                '/Od',333                '/MDd',334                '/W3',335                '/GS-',336                '/Z7',337                '/D_DEBUG',338            ]339 340        self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']341        if self.__version >= 7:342            self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']343        else:344            self.ldflags_shared_debug = [345                '/DLL',346                '/nologo',347                '/INCREMENTAL:no',348                '/pdb:None',349                '/DEBUG',350            ]351        self.ldflags_static = ['/nologo']352 353        self.initialized = True354 355    # -- Worker methods ------------------------------------------------356 357    def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):358        # Copied from ccompiler.py, extended to return .res as 'object'-file359        # for .rc input file360        if output_dir is None:361            output_dir = ''362        obj_names = []363        for src_name in source_filenames:364            (base, ext) = os.path.splitext(src_name)365            base = os.path.splitdrive(base)[1]  # Chop off the drive366            base = base[os.path.isabs(base) :]  # If abs, chop off leading /367            if ext not in self.src_extensions:368                # Better to raise an exception instead of silently continuing369                # and later complain about sources and targets having370                # different lengths371                raise CompileError("Don't know how to compile %s" % src_name)372            if strip_dir:373                base = os.path.basename(base)374            if ext in self._rc_extensions:375                obj_names.append(os.path.join(output_dir, base + self.res_extension))376            elif ext in self._mc_extensions:377                obj_names.append(os.path.join(output_dir, base + self.res_extension))378            else:379                obj_names.append(os.path.join(output_dir, base + self.obj_extension))380        return obj_names381 382    def compile(  # noqa: C901383        self,384        sources,385        output_dir=None,386        macros=None,387        include_dirs=None,388        debug=0,389        extra_preargs=None,390        extra_postargs=None,391        depends=None,392    ):393        if not self.initialized:394            self.initialize()395        compile_info = self._setup_compile(396            output_dir, macros, include_dirs, sources, depends, extra_postargs397        )398        macros, objects, extra_postargs, pp_opts, build = compile_info399 400        compile_opts = extra_preargs or []401        compile_opts.append('/c')402        if debug:403            compile_opts.extend(self.compile_options_debug)404        else:405            compile_opts.extend(self.compile_options)406 407        for obj in objects:408            try:409                src, ext = build[obj]410            except KeyError:411                continue412            if debug:413                # pass the full pathname to MSVC in debug mode,414                # this allows the debugger to find the source file415                # without asking the user to browse for it416                src = os.path.abspath(src)417 418            if ext in self._c_extensions:419                input_opt = "/Tc" + src420            elif ext in self._cpp_extensions:421                input_opt = "/Tp" + src422            elif ext in self._rc_extensions:423                # compile .RC to .RES file424                input_opt = src425                output_opt = "/fo" + obj426                try:427                    self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])428                except DistutilsExecError as msg:429                    raise CompileError(msg)430                continue431            elif ext in self._mc_extensions:432                # Compile .MC to .RC file to .RES file.433                #   * '-h dir' specifies the directory for the434                #     generated include file435                #   * '-r dir' specifies the target directory of the436                #     generated RC file and the binary message resource437                #     it includes438                #439                # For now (since there are no options to change this),440                # we use the source-directory for the include file and441                # the build directory for the RC file and message442                # resources. This works at least for win32all.443                h_dir = os.path.dirname(src)444                rc_dir = os.path.dirname(obj)445                try:446                    # first compile .MC to .RC and .H file447                    self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])448                    base, _ = os.path.splitext(os.path.basename(src))449                    rc_file = os.path.join(rc_dir, base + '.rc')450                    # then compile .RC to .RES file451                    self.spawn([self.rc] + ["/fo" + obj] + [rc_file])452 453                except DistutilsExecError as msg:454                    raise CompileError(msg)455                continue456            else:457                # how to handle this file?458                raise CompileError(f"Don't know how to compile {src} to {obj}")459 460            output_opt = "/Fo" + obj461            try:462                self.spawn(463                    [self.cc]464                    + compile_opts465                    + pp_opts466                    + [input_opt, output_opt]467                    + extra_postargs468                )469            except DistutilsExecError as msg:470                raise CompileError(msg)471 472        return objects473 474    def create_static_lib(475        self, objects, output_libname, output_dir=None, debug=0, target_lang=None476    ):477        if not self.initialized:478            self.initialize()479        (objects, output_dir) = self._fix_object_args(objects, output_dir)480        output_filename = self.library_filename(output_libname, output_dir=output_dir)481 482        if self._need_link(objects, output_filename):483            lib_args = objects + ['/OUT:' + output_filename]484            if debug:485                pass  # XXX what goes here?486            try:487                self.spawn([self.lib] + lib_args)488            except DistutilsExecError as msg:489                raise LibError(msg)490        else:491            log.debug("skipping %s (up-to-date)", output_filename)492 493    def link(  # noqa: C901494        self,495        target_desc,496        objects,497        output_filename,498        output_dir=None,499        libraries=None,500        library_dirs=None,501        runtime_library_dirs=None,502        export_symbols=None,503        debug=0,504        extra_preargs=None,505        extra_postargs=None,506        build_temp=None,507        target_lang=None,508    ):509        if not self.initialized:510            self.initialize()511        (objects, output_dir) = self._fix_object_args(objects, output_dir)512        fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)513        (libraries, library_dirs, runtime_library_dirs) = fixed_args514 515        if runtime_library_dirs:516            self.warn(517                "I don't know what to do with 'runtime_library_dirs': "518                + str(runtime_library_dirs)519            )520 521        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)522        if output_dir is not None:523            output_filename = os.path.join(output_dir, output_filename)524 525        if self._need_link(objects, output_filename):526            if target_desc == CCompiler.EXECUTABLE:527                if debug:528                    ldflags = self.ldflags_shared_debug[1:]529                else:530                    ldflags = self.ldflags_shared[1:]531            else:532                if debug:533                    ldflags = self.ldflags_shared_debug534                else:535                    ldflags = self.ldflags_shared536 537            export_opts = []538            for sym in export_symbols or []:539                export_opts.append("/EXPORT:" + sym)540 541            ld_args = (542                ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]543            )544 545            # The MSVC linker generates .lib and .exp files, which cannot be546            # suppressed by any linker switches. The .lib files may even be547            # needed! Make sure they are generated in the temporary build548            # directory. Since they have different names for debug and release549            # builds, they can go into the same directory.550            if export_symbols is not None:551                (dll_name, dll_ext) = os.path.splitext(552                    os.path.basename(output_filename)553                )554                implib_file = os.path.join(555                    os.path.dirname(objects[0]), self.library_filename(dll_name)556                )557                ld_args.append('/IMPLIB:' + implib_file)558 559            if extra_preargs:560                ld_args[:0] = extra_preargs561            if extra_postargs:562                ld_args.extend(extra_postargs)563 564            self.mkpath(os.path.dirname(output_filename))565            try:566                self.spawn([self.linker] + ld_args)567            except DistutilsExecError as msg:568                raise LinkError(msg)569 570        else:571            log.debug("skipping %s (up-to-date)", output_filename)572 573    # -- Miscellaneous methods -----------------------------------------574    # These are all used by the 'gen_lib_options() function, in575    # ccompiler.py.576 577    def library_dir_option(self, dir):578        return "/LIBPATH:" + dir579 580    def runtime_library_dir_option(self, dir):581        raise DistutilsPlatformError(582            "don't know how to set runtime library search path for MSVC++"583        )584 585    def library_option(self, lib):586        return self.library_filename(lib)587 588    def find_library_file(self, dirs, lib, debug=0):589        # Prefer a debugging library if found (and requested), but deal590        # with it if we don't have one.591        if debug:592            try_names = [lib + "_d", lib]593        else:594            try_names = [lib]595        for dir in dirs:596            for name in try_names:597                libfile = os.path.join(dir, self.library_filename(name))598                if os.path.exists(libfile):599                    return libfile600        else:601            # Oops, didn't find it in *any* of 'dirs'602            return None603 604    # Helper methods for using the MSVC registry settings605 606    def find_exe(self, exe):607        """Return path to an MSVC executable program.608 609        Tries to find the program in several places: first, one of the610        MSVC program search paths from the registry; next, the directories611        in the PATH environment variable.  If any of those work, return an612        absolute path that is known to exist.  If none of them work, just613        return the original program name, 'exe'.614        """615        for p in self.__paths:616            fn = os.path.join(os.path.abspath(p), exe)617            if os.path.isfile(fn):618                return fn619 620        # didn't find it; try existing path621        for p in os.environ['Path'].split(';'):622            fn = os.path.join(os.path.abspath(p), exe)623            if os.path.isfile(fn):624                return fn625 626        return exe627 628    def get_msvc_paths(self, path, platform='x86'):629        """Get a list of devstudio directories (include, lib or path).630 631        Return a list of strings.  The list will be empty if unable to632        access the registry or appropriate registry keys not found.633        """634        if not _can_read_reg:635            return []636 637        path = path + " dirs"638        if self.__version >= 7:639            key = rf"{self.__root}\{self.__version:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"640        else:641            key = (642                rf"{self.__root}\6.0\Build System\Components\Platforms"643                rf"\Win32 ({platform})\Directories"644            )645 646        for base in HKEYS:647            d = read_values(base, key)648            if d:649                if self.__version >= 7:650                    return self.__macros.sub(d[path]).split(";")651                else:652                    return d[path].split(";")653        # MSVC 6 seems to create the registry entries we need only when654        # the GUI is run.655        if self.__version == 6:656            for base in HKEYS:657                if read_values(base, r"%s\6.0" % self.__root) is not None:658                    self.warn(659                        "It seems you have Visual Studio 6 installed, "660                        "but the expected registry settings are not present.\n"661                        "You must at least run the Visual Studio GUI once "662                        "so that these entries are created."663                    )664                    break665        return []666 667    def set_path_env_var(self, name):668        """Set environment variable 'name' to an MSVC path type value.669 670        This is equivalent to a SET command prior to execution of spawned671        commands.672        """673 674        if name == "lib":675            p = self.get_msvc_paths("library")676        else:677            p = self.get_msvc_paths(name)678        if p:679            os.environ[name] = ';'.join(p)680 681 682if get_build_version() >= 8.0:683    log.debug("Importing new compiler from distutils.msvc9compiler")684    OldMSVCCompiler = MSVCCompiler685    # get_build_architecture not really relevant now we support cross-compile686    from distutils.msvc9compiler import (687        MacroExpander,  # noqa: F811688        MSVCCompiler,689    )690 
codekingpro/portable-devtools · Team Ai