Skip to content

Commit 945c511

Browse files
committed
ClangCL compiler support
1 parent 603b94e commit 945c511

File tree

6 files changed

+79
-10
lines changed

6 files changed

+79
-10
lines changed

distutils/_msvccompiler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
__all__ = ["MSVCCompiler"]
66

77
MSVCCompiler = msvc.Compiler
8+
ClangCLCompiler = msvc.ClangCLCompiler
89

910

1011
def __getattr__(name):

distutils/compilers/C/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,7 @@ def get_default_compiler(osname: str | None = None, platform: str | None = None)
12381238
),
12391239
'bcpp': ('bcppcompiler', 'BCPPCompiler', "Borland C++ Compiler"),
12401240
'zos': ('zosccompiler', 'zOSCCompiler', 'IBM XL C/C++ Compilers'),
1241+
'clangcl': ('_msvccompiler', 'ClangCLCompiler', 'LLVM Clang-CL compiler'),
12411242
}
12421243

12431244

distutils/compilers/C/msvc.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,21 +176,37 @@ def _get_vc_env(plat_spec):
176176
return env
177177

178178

179-
def _find_exe(exe, paths=None):
179+
def _find_exe(exe, paths=None, clangcl=False):
180180
"""Return path to an MSVC executable program.
181181
182182
Tries to find the program in several places: first, one of the
183183
MSVC program search paths from the registry; next, the directories
184184
in the PATH environment variable. If any of those work, return an
185185
absolute path that is known to exist. If none of them work, just
186186
return the original program name, 'exe'.
187+
188+
If clangcl is set to true, look for the LLVM clang-cl executables,
189+
as well as look for them without the extension (eg. on Linux)
187190
"""
191+
if clangcl:
192+
if exe == 'cl.exe':
193+
exe = 'clang-{}'.format(exe)
194+
elif exe == 'link.exe':
195+
exe = 'lld-{}'.format(exe)
196+
elif exe == 'mc.exe':
197+
exe = 'llvm-ml.exe'
198+
else:
199+
exe = 'llvm-{}'.format(exe)
188200
if not paths:
189201
paths = os.getenv('path').split(os.pathsep)
190202
for p in paths:
191203
fn = os.path.join(os.path.abspath(p), exe)
192204
if os.path.isfile(fn):
193205
return fn
206+
elif clangcl:
207+
fn = os.path.splitext(fn)[0]
208+
if os.path.isfile(fn):
209+
return fn
194210
return exe
195211

196212

@@ -201,6 +217,13 @@ def _find_exe(exe, paths=None):
201217
'win-arm64': 'arm64',
202218
}
203219

220+
_clang_targets = {
221+
'win32': 'i686',
222+
'win-amd64': 'x86_64',
223+
'win-arm32': 'armv7',
224+
'win-arm64': 'aarch64',
225+
}
226+
204227

205228
def _get_vcvars_spec(host_platform, platform):
206229
"""
@@ -298,14 +321,16 @@ def initialize(self, plat_name: str | None = None) -> None:
298321
)
299322
self._configure(vc_env)
300323

324+
clangcl = True if self.compiler_type == "clangcl" else False
325+
301326
self._paths = vc_env.get('path', '')
302327
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 compiler
307-
self.mc = _find_exe("mc.exe", paths) # message compiler
308-
self.mt = _find_exe("mt.exe", paths) # message compiler
328+
self.cc = _find_exe("cl.exe", paths, clangcl)
329+
self.linker = _find_exe("link.exe", paths, clangcl)
330+
self.lib = _find_exe("lib.exe", paths, clangcl)
331+
self.rc = _find_exe("rc.exe", paths, clangcl) # resource compiler
332+
self.mc = _find_exe("mc.exe", paths, clangcl) # message compiler
333+
self.mt = _find_exe("mt.exe", paths, clangcl) # message compiler
309334

310335
self.preprocess_options = None
311336
# bpo-38597: Always compile with dynamic linking
@@ -322,6 +347,12 @@ def initialize(self, plat_name: str | None = None) -> None:
322347
'/D_DEBUG',
323348
]
324349

350+
if clangcl:
351+
target = '--target={}-windows-msvc'.format(_clang_targets[plat_name])
352+
self.compile_options.remove('/GL')
353+
self.compile_options += ['/FA', target]
354+
self.compile_options_debug += ['/FA', target]
355+
325356
ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
326357

327358
ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']
@@ -437,7 +468,11 @@ def compile( # noqa: C901
437468
rc_dir = os.path.dirname(obj)
438469
try:
439470
# first compile .MC to .RC and .H file
440-
self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
471+
mc_cmd = [self.mc]
472+
if clangcl and '64' in plat_name:
473+
mc_cmd.append('--m64')
474+
mc_cmd += ['-h', h_dir, '-r', rc_dir, src]
475+
self.spawn(mc_cmd)
441476
base, _ = os.path.splitext(os.path.basename(src))
442477
rc_file = os.path.join(rc_dir, base + '.rc')
443478
# then compile .RC to .RES file
@@ -612,3 +647,8 @@ def find_library_file(self, dirs, lib, debug=False):
612647
else:
613648
# Oops, didn't find it in *any* of 'dirs'
614649
return None
650+
651+
652+
class ClangCLCompiler(Compiler):
653+
654+
compiler_type = 'clangcl'
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import os
2+
from .. import msvc
3+
4+
5+
class TestClangCLCompiler:
6+
def test_compiler_type(self):
7+
compiler = msvc.ClangCLCompiler()
8+
assert compiler.compiler_type == 'clangcl'
9+
10+
def test_set_executables(self):
11+
compiler = msvc.ClangCLCompiler()
12+
compiler.initialize()
13+
14+
cc, cc_ext = os.path.splitext(compiler.cc)
15+
linker, linker_ext = os.path.splitext(compiler.linker)
16+
lib, lib_ext = os.path.splitext(compiler.lib)
17+
rc, rc_ext = os.path.splitext(compiler.rc)
18+
mc, mc_ext = os.path.splitext(compiler.mc)
19+
mt, mt_ext = os.path.splitext(compiler.mt)
20+
21+
assert compiler.cc == 'clang-cl' + cc_ext
22+
assert compiler.linker == 'lld-link' + linker_ext
23+
assert compiler.lib == 'llvm-lib' + lib_ext
24+
assert compiler.rc == 'llvm-rc' + rc_ext
25+
assert compiler.mc == 'llvm-ml' + mc_ext
26+
assert compiler.mt == 'llvm-mt' + mt_ext

distutils/tests/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ def missing_compiler_executable(cmd_names: Sequence[str] = []): # pragma: no co
2424

2525
compiler = ccompiler.new_compiler()
2626
sysconfig.customize_compiler(compiler)
27-
if compiler.compiler_type == "msvc":
27+
if compiler.compiler_type == "msvc" or compiler.compiler_type == "clangcl":
2828
# MSVC has no executables, so check whether initialization succeeds
2929
try:
3030
compiler.initialize()
3131
except errors.DistutilsPlatformError:
32-
return "msvc"
32+
return compiler.compiler_type
3333
for name in compiler.executables:
3434
if cmd_names and name not in cmd_names:
3535
continue

newsfragments/376.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added ClangCL compiler support.

0 commit comments

Comments
 (0)