Skip to content

Commit ca066bd

Browse files
gh-81057: Vendor a Subset of distutils for the c-analyzer Tool (gh-102505)
distutils was removed in November. However, the c-analyzer relies on it. To solve that here, we vendor the parts the tool needs so it can be run against 3.12+. (Also see gh-92584.) Note that we may end up removing this code later in favor of a solution in common with the peg_generator tool (which also relies on distutils). At the least, the copy here makes sure the c-analyzer tool works on 3.12+ in the meantime.
1 parent cf6e7c5 commit ca066bd

File tree

15 files changed

+2301
-0
lines changed

15 files changed

+2301
-0
lines changed

Tools/c-analyzer/distutils/README

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This is a partial copy of distutils as it was removed in 0faa0ba240e.
2+
It only includes the parts needed by the C parser.

Tools/c-analyzer/distutils/__init__.py

Whitespace-only changes.
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""distutils._msvccompiler
2+
3+
Contains MSVCCompiler, an implementation of the abstract CCompiler class
4+
for Microsoft Visual Studio 2015.
5+
6+
The module is compatible with VS 2015 and later. You can find legacy support
7+
for older versions in distutils.msvc9compiler and distutils.msvccompiler.
8+
"""
9+
10+
# Written by Perry Stoll
11+
# hacked by Robin Becker and Thomas Heller to do a better job of
12+
# finding DevStudio (through the registry)
13+
# ported to VS 2005 and VS 2008 by Christian Heimes
14+
# ported to VS 2015 by Steve Dower
15+
16+
import os
17+
import subprocess
18+
import winreg
19+
20+
from distutils.errors import DistutilsPlatformError
21+
from distutils.ccompiler import CCompiler
22+
from distutils import log
23+
24+
from itertools import count
25+
26+
def _find_vc2015():
27+
try:
28+
key = winreg.OpenKeyEx(
29+
winreg.HKEY_LOCAL_MACHINE,
30+
r"Software\Microsoft\VisualStudio\SxS\VC7",
31+
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
32+
)
33+
except OSError:
34+
log.debug("Visual C++ is not registered")
35+
return None, None
36+
37+
best_version = 0
38+
best_dir = None
39+
with key:
40+
for i in count():
41+
try:
42+
v, vc_dir, vt = winreg.EnumValue(key, i)
43+
except OSError:
44+
break
45+
if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
46+
try:
47+
version = int(float(v))
48+
except (ValueError, TypeError):
49+
continue
50+
if version >= 14 and version > best_version:
51+
best_version, best_dir = version, vc_dir
52+
return best_version, best_dir
53+
54+
def _find_vc2017():
55+
"""Returns "15, path" based on the result of invoking vswhere.exe
56+
If no install is found, returns "None, None"
57+
58+
The version is returned to avoid unnecessarily changing the function
59+
result. It may be ignored when the path is not None.
60+
61+
If vswhere.exe is not available, by definition, VS 2017 is not
62+
installed.
63+
"""
64+
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
65+
if not root:
66+
return None, None
67+
68+
try:
69+
path = subprocess.check_output([
70+
os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
71+
"-latest",
72+
"-prerelease",
73+
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
74+
"-property", "installationPath",
75+
"-products", "*",
76+
], encoding="mbcs", errors="strict").strip()
77+
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
78+
return None, None
79+
80+
path = os.path.join(path, "VC", "Auxiliary", "Build")
81+
if os.path.isdir(path):
82+
return 15, path
83+
84+
return None, None
85+
86+
PLAT_SPEC_TO_RUNTIME = {
87+
'x86' : 'x86',
88+
'x86_amd64' : 'x64',
89+
'x86_arm' : 'arm',
90+
'x86_arm64' : 'arm64'
91+
}
92+
93+
def _find_vcvarsall(plat_spec):
94+
# bpo-38597: Removed vcruntime return value
95+
_, best_dir = _find_vc2017()
96+
97+
if not best_dir:
98+
best_version, best_dir = _find_vc2015()
99+
100+
if not best_dir:
101+
log.debug("No suitable Visual C++ version found")
102+
return None, None
103+
104+
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
105+
if not os.path.isfile(vcvarsall):
106+
log.debug("%s cannot be found", vcvarsall)
107+
return None, None
108+
109+
return vcvarsall, None
110+
111+
def _get_vc_env(plat_spec):
112+
if os.getenv("DISTUTILS_USE_SDK"):
113+
return {
114+
key.lower(): value
115+
for key, value in os.environ.items()
116+
}
117+
118+
vcvarsall, _ = _find_vcvarsall(plat_spec)
119+
if not vcvarsall:
120+
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
121+
122+
try:
123+
out = subprocess.check_output(
124+
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
125+
stderr=subprocess.STDOUT,
126+
).decode('utf-16le', errors='replace')
127+
except subprocess.CalledProcessError as exc:
128+
log.error(exc.output)
129+
raise DistutilsPlatformError("Error executing {}"
130+
.format(exc.cmd))
131+
132+
env = {
133+
key.lower(): value
134+
for key, _, value in
135+
(line.partition('=') for line in out.splitlines())
136+
if key and value
137+
}
138+
139+
return env
140+
141+
def _find_exe(exe, paths=None):
142+
"""Return path to an MSVC executable program.
143+
144+
Tries to find the program in several places: first, one of the
145+
MSVC program search paths from the registry; next, the directories
146+
in the PATH environment variable. If any of those work, return an
147+
absolute path that is known to exist. If none of them work, just
148+
return the original program name, 'exe'.
149+
"""
150+
if not paths:
151+
paths = os.getenv('path').split(os.pathsep)
152+
for p in paths:
153+
fn = os.path.join(os.path.abspath(p), exe)
154+
if os.path.isfile(fn):
155+
return fn
156+
return exe
157+
158+
# A map keyed by get_platform() return values to values accepted by
159+
# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
160+
# lighter-weight MSVC installs that do not include native 64-bit tools.
161+
PLAT_TO_VCVARS = {
162+
'win32' : 'x86',
163+
'win-amd64' : 'x86_amd64',
164+
'win-arm32' : 'x86_arm',
165+
'win-arm64' : 'x86_arm64'
166+
}
167+
168+
class MSVCCompiler(CCompiler) :
169+
"""Concrete class that implements an interface to Microsoft Visual C++,
170+
as defined by the CCompiler abstract class."""
171+
172+
compiler_type = 'msvc'
173+
174+
# Just set this so CCompiler's constructor doesn't barf. We currently
175+
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
176+
# as it really isn't necessary for this sort of single-compiler class.
177+
# Would be nice to have a consistent interface with UnixCCompiler,
178+
# though, so it's worth thinking about.
179+
executables = {}
180+
181+
# Private class data (need to distinguish C from C++ source for compiler)
182+
_c_extensions = ['.c']
183+
_cpp_extensions = ['.cc', '.cpp', '.cxx']
184+
_rc_extensions = ['.rc']
185+
_mc_extensions = ['.mc']
186+
187+
# Needed for the filename generation methods provided by the
188+
# base class, CCompiler.
189+
src_extensions = (_c_extensions + _cpp_extensions +
190+
_rc_extensions + _mc_extensions)
191+
res_extension = '.res'
192+
obj_extension = '.obj'
193+
static_lib_extension = '.lib'
194+
shared_lib_extension = '.dll'
195+
static_lib_format = shared_lib_format = '%s%s'
196+
exe_extension = '.exe'
197+
198+
199+
def __init__(self, verbose=0, dry_run=0, force=0):
200+
CCompiler.__init__ (self, verbose, dry_run, force)
201+
# target platform (.plat_name is consistent with 'bdist')
202+
self.plat_name = None
203+
self.initialized = False
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""distutils.bcppcompiler
2+
3+
Contains BorlandCCompiler, an implementation of the abstract CCompiler class
4+
for the Borland C++ compiler.
5+
"""
6+
7+
# This implementation by Lyle Johnson, based on the original msvccompiler.py
8+
# module and using the directions originally published by Gordon Williams.
9+
10+
# XXX looks like there's a LOT of overlap between these two classes:
11+
# someone should sit down and factor out the common code as
12+
# WindowsCCompiler! --GPW
13+
14+
15+
import os
16+
from distutils.errors import DistutilsExecError, CompileError
17+
from distutils.ccompiler import \
18+
CCompiler, gen_preprocess_options
19+
from distutils.dep_util import newer
20+
21+
class BCPPCompiler(CCompiler) :
22+
"""Concrete class that implements an interface to the Borland C/C++
23+
compiler, as defined by the CCompiler abstract class.
24+
"""
25+
26+
compiler_type = 'bcpp'
27+
28+
# Just set this so CCompiler's constructor doesn't barf. We currently
29+
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
30+
# as it really isn't necessary for this sort of single-compiler class.
31+
# Would be nice to have a consistent interface with UnixCCompiler,
32+
# though, so it's worth thinking about.
33+
executables = {}
34+
35+
# Private class data (need to distinguish C from C++ source for compiler)
36+
_c_extensions = ['.c']
37+
_cpp_extensions = ['.cc', '.cpp', '.cxx']
38+
39+
# Needed for the filename generation methods provided by the
40+
# base class, CCompiler.
41+
src_extensions = _c_extensions + _cpp_extensions
42+
obj_extension = '.obj'
43+
static_lib_extension = '.lib'
44+
shared_lib_extension = '.dll'
45+
static_lib_format = shared_lib_format = '%s%s'
46+
exe_extension = '.exe'
47+
48+
49+
def __init__ (self,
50+
verbose=0,
51+
dry_run=0,
52+
force=0):
53+
54+
CCompiler.__init__ (self, verbose, dry_run, force)
55+
56+
# These executables are assumed to all be in the path.
57+
# Borland doesn't seem to use any special registry settings to
58+
# indicate their installation locations.
59+
60+
self.cc = "bcc32.exe"
61+
self.linker = "ilink32.exe"
62+
self.lib = "tlib.exe"
63+
64+
self.preprocess_options = None
65+
self.compile_options = ['/tWM', '/O2', '/q', '/g0']
66+
self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']
67+
68+
self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']
69+
self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
70+
self.ldflags_static = []
71+
self.ldflags_exe = ['/Gn', '/q', '/x']
72+
self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r']
73+
74+
75+
# -- Worker methods ------------------------------------------------
76+
77+
def preprocess (self,
78+
source,
79+
output_file=None,
80+
macros=None,
81+
include_dirs=None,
82+
extra_preargs=None,
83+
extra_postargs=None):
84+
85+
(_, macros, include_dirs) = \
86+
self._fix_compile_args(None, macros, include_dirs)
87+
pp_opts = gen_preprocess_options(macros, include_dirs)
88+
pp_args = ['cpp32.exe'] + pp_opts
89+
if output_file is not None:
90+
pp_args.append('-o' + output_file)
91+
if extra_preargs:
92+
pp_args[:0] = extra_preargs
93+
if extra_postargs:
94+
pp_args.extend(extra_postargs)
95+
pp_args.append(source)
96+
97+
# We need to preprocess: either we're being forced to, or the
98+
# source file is newer than the target (or the target doesn't
99+
# exist).
100+
if self.force or output_file is None or newer(source, output_file):
101+
if output_file:
102+
self.mkpath(os.path.dirname(output_file))
103+
try:
104+
self.spawn(pp_args)
105+
except DistutilsExecError as msg:
106+
print(msg)
107+
raise CompileError(msg)
108+
109+
# preprocess()

0 commit comments

Comments
 (0)