Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions distutils/_msvccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
__all__ = ["MSVCCompiler"]

MSVCCompiler = msvc.Compiler
ClangCLCompiler = msvc.ClangCLCompiler


def __getattr__(name):
Expand Down
1 change: 1 addition & 0 deletions distutils/compilers/C/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ def get_default_compiler(osname: str | None = None, platform: str | None = None)
),
'bcpp': ('bcppcompiler', 'BCPPCompiler', "Borland C++ Compiler"),
'zos': ('zosccompiler', 'zOSCCompiler', 'IBM XL C/C++ Compilers'),
'clangcl': ('_msvccompiler', 'ClangCLCompiler', 'LLVM Clang-CL compiler'),
}


Expand Down
79 changes: 71 additions & 8 deletions distutils/compilers/C/msvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,21 +176,37 @@ def _get_vc_env(plat_spec):
return env


def _find_exe(exe, paths=None):
def _find_exe(exe, paths=None, clangcl=False):
"""Return path to an MSVC executable program.

Tries to find the program in several places: first, one of the
MSVC program search paths from the registry; next, the directories
in the PATH environment variable. If any of those work, return an
absolute path that is known to exist. If none of them work, just
return the original program name, 'exe'.

If clangcl is set to true, look for the LLVM clang-cl executables,
as well as look for them without the extension (eg. on Linux)
"""
if clangcl:
if exe == 'cl.exe':
exe = 'clang-{}'.format(exe)
elif exe == 'link.exe':
exe = 'lld-{}'.format(exe)
elif exe == 'mc.exe':
exe = 'llvm-ml.exe'
else:
exe = 'llvm-{}'.format(exe)
if not paths:
paths = os.getenv('path').split(os.pathsep)
for p in paths:
fn = os.path.join(os.path.abspath(p), exe)
if os.path.isfile(fn):
return fn
elif clangcl:
fn = os.path.splitext(fn)[0]
if os.path.isfile(fn):
return fn
return exe


Expand All @@ -201,6 +217,32 @@ def _find_exe(exe, paths=None):
'win-arm64': 'arm64',
}

_clang_targets = {
'win32': 'i686',
'win-amd64': 'x86_64',
'win-arm32': 'armv7',
'win-arm64': 'aarch64',
}


def _get_external_sdk(linker=False):
sdk = []
vctoolsdir = os.environ.get('VCTOOLSINSTALLDIR', None)
winsdkdir = os.environ.get('WINDOWSSDKDIR', None)
if vctoolsdir:
_vctoolsdir = ['/vctoolsdir', vctoolsdir]
if linker:
sdk.append(":".join(_vctoolsdir))
else:
sdk += _vctoolsdir
if winsdkdir:
_winsdkdir = ['/winsdkdir', winsdkdir]
if linker:
sdk.append(":".join(_winsdkdir))
else:
sdk += _winsdkdir
return sdk


def _get_vcvars_spec(host_platform, platform):
"""
Expand Down Expand Up @@ -298,14 +340,16 @@ def initialize(self, plat_name: str | None = None) -> None:
)
self._configure(vc_env)

clangcl = True if self.compiler_type == "clangcl" else False

self._paths = vc_env.get('path', '')
paths = self._paths.split(os.pathsep)
self.cc = _find_exe("cl.exe", paths)
self.linker = _find_exe("link.exe", paths)
self.lib = _find_exe("lib.exe", paths)
self.rc = _find_exe("rc.exe", paths) # resource compiler
self.mc = _find_exe("mc.exe", paths) # message compiler
self.mt = _find_exe("mt.exe", paths) # message compiler
self.cc = _find_exe("cl.exe", paths, clangcl)
self.linker = _find_exe("link.exe", paths, clangcl)
self.lib = _find_exe("lib.exe", paths, clangcl)
self.rc = _find_exe("rc.exe", paths, clangcl) # resource compiler
self.mc = _find_exe("mc.exe", paths, clangcl) # message compiler
self.mt = _find_exe("mt.exe", paths, clangcl) # message compiler

self.preprocess_options = None
# bpo-38597: Always compile with dynamic linking
Expand All @@ -326,6 +370,16 @@ def initialize(self, plat_name: str | None = None) -> None:

ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']

if clangcl:
target = '--target={}-windows-msvc'.format(_clang_targets[plat_name])
compile_sdk = _get_external_sdk()
self.compile_options.remove('/GL')
self.compile_options += ['/FA', target] + compile_sdk
self.compile_options_debug += ['/FA', target] + compile_sdk
linker_sdk = _get_external_sdk(linker=True)
ldflags += linker_sdk
ldflags_debug += linker_sdk

self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']
self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']
self.ldflags_shared = [
Expand Down Expand Up @@ -437,7 +491,11 @@ def compile( # noqa: C901
rc_dir = os.path.dirname(obj)
try:
# first compile .MC to .RC and .H file
self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
mc_cmd = [self.mc]
if clangcl and '64' in plat_name:
mc_cmd.append('--m64')
mc_cmd += ['-h', h_dir, '-r', rc_dir, src]
self.spawn(mc_cmd)
base, _ = os.path.splitext(os.path.basename(src))
rc_file = os.path.join(rc_dir, base + '.rc')
# then compile .RC to .RES file
Expand Down Expand Up @@ -612,3 +670,8 @@ def find_library_file(self, dirs, lib, debug=False):
else:
# Oops, didn't find it in *any* of 'dirs'
return None


class ClangCLCompiler(Compiler):

compiler_type = 'clangcl'
26 changes: 26 additions & 0 deletions distutils/compilers/C/tests/test_clangcl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
from .. import msvc


class TestClangCLCompiler:
def test_compiler_type(self):
compiler = msvc.ClangCLCompiler()
assert compiler.compiler_type == 'clangcl'

def test_set_executables(self):
compiler = msvc.ClangCLCompiler()
compiler.initialize()

cc, cc_ext = os.path.splitext(compiler.cc)
linker, linker_ext = os.path.splitext(compiler.linker)
lib, lib_ext = os.path.splitext(compiler.lib)
rc, rc_ext = os.path.splitext(compiler.rc)
mc, mc_ext = os.path.splitext(compiler.mc)
mt, mt_ext = os.path.splitext(compiler.mt)

assert compiler.cc == 'clang-cl' + cc_ext
assert compiler.linker == 'lld-link' + linker_ext
assert compiler.lib == 'llvm-lib' + lib_ext
assert compiler.rc == 'llvm-rc' + rc_ext
assert compiler.mc == 'llvm-ml' + mc_ext
assert compiler.mt == 'llvm-mt' + mt_ext
4 changes: 2 additions & 2 deletions distutils/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ def missing_compiler_executable(cmd_names: Sequence[str] = []): # pragma: no co

compiler = ccompiler.new_compiler()
sysconfig.customize_compiler(compiler)
if compiler.compiler_type == "msvc":
if compiler.compiler_type == "msvc" or compiler.compiler_type == "clangcl":
# MSVC has no executables, so check whether initialization succeeds
try:
compiler.initialize()
except errors.DistutilsPlatformError:
return "msvc"
return compiler.compiler_type
for name in compiler.executables:
if cmd_names and name not in cmd_names:
continue
Expand Down
1 change: 1 addition & 0 deletions newsfragments/376.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ClangCL compiler support.