import itertools
import os
import platform
import re
import subprocess
import sys
import errno
import lit.util
from lit.llvm.subst import FindTool
from lit.llvm.subst import ToolSubst
lit_path_displayed = False
class LLVMConfig(object):
def __init__(self, lit_config, config):
self.lit_config = lit_config
self.config = config
features = config.available_features
self.use_lit_shell = False
if sys.platform == 'win32':
path = None
lit_tools_dir = getattr(config, 'lit_tools_dir', None)
required_tools = [
'cmp.exe', 'grep.exe', 'sed.exe', 'diff.exe', 'echo.exe']
path = self.lit_config.getToolsPath(lit_tools_dir,
config.environment['PATH'],
required_tools)
if path is None:
path = self._find_git_windows_unix_tools(required_tools)
if path is not None:
self.with_environment('PATH', path, append_path=True)
self.with_system_environment(
['SystemDrive', 'SystemRoot', 'TEMP', 'TMP', 'PLATFORM'])
self.use_lit_shell = True
global lit_path_displayed
if not self.lit_config.quiet and lit_path_displayed is False:
self.lit_config.note("using lit tools: {}".format(path))
lit_path_displayed = True
lit_shell_env = os.environ.get('LIT_USE_INTERNAL_SHELL')
if lit_shell_env:
self.use_lit_shell = lit.util.pythonize_bool(lit_shell_env)
if not self.use_lit_shell:
features.add('shell')
self.with_system_environment([
'ASAN_SYMBOLIZER_PATH',
'MSAN_SYMBOLIZER_PATH',
'TSAN_SYMBOLIZER_PATH',
'UBSAN_SYMBOLIZER_PATH'
'ASAN_OPTIONS',
'MSAN_OPTIONS',
'TSAN_OPTIONS',
'UBSAN_OPTIONS',
])
if platform.system() == 'Darwin':
features.add('system-linker-mach-o')
features.add('system-darwin')
elif platform.system() == 'Windows':
features.add('system-windows')
elif platform.system() == 'Linux':
features.add('system-linux')
elif platform.system() in ['FreeBSD']:
features.add('system-freebsd')
elif platform.system() == 'NetBSD':
features.add('system-netbsd')
elif platform.system() == 'AIX':
features.add('system-aix')
elif platform.system() == 'SunOS':
features.add('system-solaris')
host_triple = getattr(config, 'host_triple', None)
target_triple = getattr(config, 'target_triple', None)
if host_triple and host_triple == target_triple:
features.add('native')
sanitizers = getattr(config, 'llvm_use_sanitizer', '')
sanitizers = frozenset(x.lower() for x in sanitizers.split(';'))
if 'address' in sanitizers:
features.add('asan')
if 'memory' in sanitizers or 'memorywithorigins' in sanitizers:
features.add('msan')
if 'undefined' in sanitizers:
features.add('ubsan')
have_zlib = getattr(config, 'have_zlib', None)
if have_zlib:
features.add('zlib')
long_tests = lit_config.params.get('run_long_tests', None)
if lit.util.pythonize_bool(long_tests):
features.add('long_tests')
if target_triple:
if re.match(r'^x86_64.*-apple', target_triple):
features.add('x86_64-apple')
host_cxx = getattr(config, 'host_cxx', None)
if ('address' in sanitizers and
self.get_clang_has_lsan(host_cxx, target_triple)):
self.with_environment(
'ASAN_OPTIONS', 'detect_leaks=1', append_path=True)
if re.match(r'^x86_64.*-linux', target_triple):
features.add('x86_64-linux')
if re.match(r'^i.86.*', target_triple):
features.add('target-x86')
elif re.match(r'^x86_64.*', target_triple):
features.add('target-x86_64')
elif re.match(r'^aarch64.*', target_triple):
features.add('target-aarch64')
elif re.match(r'^arm64.*', target_triple):
features.add('target-aarch64')
elif re.match(r'^arm.*', target_triple):
features.add('target-arm')
use_gmalloc = lit_config.params.get('use_gmalloc', None)
if lit.util.pythonize_bool(use_gmalloc):
gmalloc_path_str = lit_config.params.get(
'gmalloc_path', '/usr/lib/libgmalloc.dylib')
if gmalloc_path_str is not None:
self.with_environment(
'DYLD_INSERT_LIBRARIES', gmalloc_path_str)
def _find_git_windows_unix_tools(self, tools_needed):
assert(sys.platform == 'win32')
if sys.version_info.major >= 3:
import winreg
else:
import _winreg as winreg
masks = [0, winreg.KEY_WOW64_64KEY]
hives = [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]
for mask, hive in itertools.product(masks, hives):
try:
with winreg.OpenKey(hive, r"SOFTWARE\GitForWindows", 0,
winreg.KEY_READ | mask) as key:
install_root, _ = winreg.QueryValueEx(key, 'InstallPath')
if not install_root:
continue
candidate_path = os.path.join(install_root, 'usr', 'bin')
if not lit.util.checkToolsPath(
candidate_path, tools_needed):
continue
return lit.util.to_string(candidate_path)
except:
continue
return None
def with_environment(self, variable, value, append_path=False):
if append_path:
paths_to_add = value
if lit.util.is_string(paths_to_add):
paths_to_add = [paths_to_add]
def norm(x):
return os.path.normcase(os.path.normpath(x))
current_paths = self.config.environment.get(variable, None)
if current_paths:
current_paths = current_paths.split(os.path.pathsep)
paths = [norm(p) for p in current_paths]
else:
paths = []
for p in reversed(paths_to_add):
p = norm(p)
try:
paths.remove(p)
except ValueError:
pass
paths = [p] + paths
value = os.pathsep.join(paths)
self.config.environment[variable] = value
def with_system_environment(self, variables, append_path=False):
if lit.util.is_string(variables):
variables = [variables]
for v in variables:
value = os.environ.get(v)
if value:
self.with_environment(v, value, append_path)
def clear_environment(self, variables):
for name in variables:
if name in self.config.environment:
del self.config.environment[name]
def get_process_output(self, command):
try:
cmd = subprocess.Popen(
command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=self.config.environment)
stdout, stderr = cmd.communicate()
stdout = lit.util.to_string(stdout)
stderr = lit.util.to_string(stderr)
return (stdout, stderr)
except OSError:
self.lit_config.fatal('Could not run process %s' % command)
def feature_config(self, features):
arguments = [x for (x, _) in features]
config_path = os.path.join(self.config.llvm_tools_dir, 'llvm-config')
output, _ = self.get_process_output([config_path] + arguments)
lines = output.split('\n')
for (feature_line, (_, patterns)) in zip(lines, features):
if callable(patterns):
features_to_add = patterns(feature_line)
self.config.available_features.update(features_to_add)
else:
for (re_pattern, feature) in patterns.items():
if re.search(re_pattern, feature_line):
self.config.available_features.add(feature)
def get_clang_builtin_include_dir(self, clang):
clang_dir, _ = self.get_process_output(
[clang, '-print-file-name=include'])
if not clang_dir:
print(clang)
self.lit_config.fatal(
"Couldn't find the include dir for Clang ('%s')" % clang)
clang_dir = clang_dir.strip()
if sys.platform in ['win32'] and not self.use_lit_shell:
clang_dir = clang_dir.replace('\\', '/')
return clang_dir
def get_clang_has_lsan(self, clang, triple):
if not clang:
self.lit_config.warning(
'config.host_cxx is unset but test suite is configured '
'to use sanitizers.')
return False
clang_binary = clang.split()[0]
version_string, _ = self.get_process_output(
[clang_binary, '--version'])
if not 'clang' in version_string:
self.lit_config.warning(
"compiler '%s' does not appear to be clang, " % clang_binary +
'but test suite is configured to use sanitizers.')
return False
if re.match(r'.*-linux', triple):
return True
if re.match(r'^x86_64.*-apple', triple):
version_regex = re.search(r'version ([0-9]+)\.([0-9]+).([0-9]+)',
version_string)
major_version_number = int(version_regex.group(1))
minor_version_number = int(version_regex.group(2))
patch_version_number = int(version_regex.group(3))
if ('Apple LLVM' in version_string or
'Apple clang' in version_string):
return False
return major_version_number >= 5
return False
def make_itanium_abi_triple(self, triple):
m = re.match(r'(\w+)-(\w+)-(\w+)', triple)
if not m:
self.lit_config.fatal(
"Could not turn '%s' into Itanium ABI triple" % triple)
if m.group(3).lower() != 'windows':
return triple
return m.group(1) + '-' + m.group(2) + '-' + m.group(3) + '-gnu'
def make_msabi_triple(self, triple):
m = re.match(r'(\w+)-(\w+)-(\w+)', triple)
if not m:
self.lit_config.fatal(
"Could not turn '%s' into MS ABI triple" % triple)
isa = m.group(1).lower()
vendor = m.group(2).lower()
os = m.group(3).lower()
if os == 'windows' and re.match(r'.*-msvc$', triple):
return triple
if isa.startswith('x86') or isa == 'amd64' or re.match(r'i\d86', isa):
return isa + '-' + vendor + '-windows-msvc'
return 'i686-pc-windows-msvc'
def add_tool_substitutions(self, tools, search_dirs=None):
if not search_dirs:
search_dirs = [self.config.llvm_tools_dir]
if lit.util.is_string(search_dirs):
search_dirs = [search_dirs]
tools = [x if isinstance(x, ToolSubst) else ToolSubst(x)
for x in tools]
search_dirs = os.pathsep.join(search_dirs)
substitutions = []
for tool in tools:
match = tool.resolve(self, search_dirs)
if not match:
continue
subst_key, tool_pipe, command = match
if not command:
return False
substitutions.append((subst_key, tool_pipe + command))
self.config.substitutions.extend(substitutions)
return True
def add_err_msg_substitutions(self):
errc_messages = getattr(self.config, 'errc_messages', '')
if len(errc_messages) != 0:
(errc_enoent, errc_eisdir,
errc_einval, errc_eacces) = errc_messages.split(';')
self.config.substitutions.append(
('%errc_ENOENT', '\'' + errc_enoent + '\''))
self.config.substitutions.append(
('%errc_EISDIR', '\'' + errc_eisdir + '\''))
self.config.substitutions.append(
('%errc_EINVAL', '\'' + errc_einval + '\''))
self.config.substitutions.append(
('%errc_EACCES', '\'' + errc_eacces + '\''))
else:
self.config.substitutions.append(
('%errc_ENOENT', '\'' + os.strerror(errno.ENOENT) + '\''))
self.config.substitutions.append(
('%errc_EISDIR', '\'' + os.strerror(errno.EISDIR) + '\''))
self.config.substitutions.append(
('%errc_EINVAL', '\'' + os.strerror(errno.EINVAL) + '\''))
self.config.substitutions.append(
('%errc_EACCES', '\'' + os.strerror(errno.EACCES) + '\''))
def use_default_substitutions(self):
tool_patterns = [
ToolSubst('FileCheck', unresolved='fatal'),
ToolSubst(r'\| \bcount\b', command=FindTool('count'),
verbatim=True, unresolved='fatal'),
ToolSubst(r'\| \bnot\b', command=FindTool('not'),
verbatim=True, unresolved='fatal')]
self.config.substitutions.append(('%python', '"%s"' % (sys.executable)))
self.add_tool_substitutions(
tool_patterns, [self.config.llvm_tools_dir])
self.add_err_msg_substitutions()
def use_llvm_tool(self, name, search_env=None, required=False, quiet=False,
search_paths=None, use_installed=False):
tool = None
if search_env:
tool = self.config.environment.get(search_env)
if not tool:
if search_paths is None:
search_paths = [self.config.llvm_tools_dir]
path = os.pathsep.join(search_paths)
tool = lit.util.which(name, path)
if not tool and use_installed:
tool = lit.util.which(name, self.config.environment['PATH'])
if required and not tool:
message = "couldn't find '{}' program".format(name)
if search_env:
message = message + \
', try setting {} in your environment'.format(search_env)
self.lit_config.fatal(message)
if tool:
tool = os.path.normpath(tool)
if not self.lit_config.quiet and not quiet:
self.lit_config.note('using {}: {}'.format(name, tool))
return tool
def use_clang(self, additional_tool_dirs=[], additional_flags=[],
required=True, use_installed=False):
possibly_dangerous_env_vars = [
'COMPILER_PATH', 'RC_DEBUG_OPTIONS',
'CINDEXTEST_PREAMBLE_FILE', 'LIBRARY_PATH',
'CPATH', 'C_INCLUDE_PATH', 'CPLUS_INCLUDE_PATH',
'OBJC_INCLUDE_PATH', 'OBJCPLUS_INCLUDE_PATH',
'LIBCLANG_TIMING', 'LIBCLANG_OBJTRACKING',
'LIBCLANG_LOGGING', 'LIBCLANG_BGPRIO_INDEX',
'LIBCLANG_BGPRIO_EDIT', 'LIBCLANG_NOTHREADS',
'LIBCLANG_RESOURCE_USAGE',
'LIBCLANG_CODE_COMPLETION_LOGGING',
]
if platform.system() != 'Windows':
possibly_dangerous_env_vars.append('INCLUDE')
self.clear_environment(possibly_dangerous_env_vars)
exe_dir_props = [self.config.name.lower() + '_tools_dir',
'clang_tools_dir', 'llvm_tools_dir']
paths = [getattr(self.config, pp) for pp in exe_dir_props
if getattr(self.config, pp, None)]
paths = additional_tool_dirs + paths
self.with_environment('PATH', paths, append_path=True)
lib_dir_props = [
self.config.name.lower() + '_libs_dir',
'llvm_shlib_dir',
'llvm_libs_dir',
]
lib_paths = [getattr(self.config, pp) for pp in lib_dir_props
if getattr(self.config, pp, None)]
self.with_environment('LD_LIBRARY_PATH', lib_paths, append_path=True)
shl = getattr(self.config, 'llvm_shlib_dir', None)
pext = getattr(self.config, 'llvm_plugin_ext', None)
if shl:
self.config.substitutions.append(('%llvmshlibdir', shl))
if pext:
self.config.substitutions.append(('%pluginext', pext))
self.config.clang = self.use_llvm_tool(
'clang', search_env='CLANG', required=required,
search_paths=paths, use_installed=use_installed)
if self.config.clang:
self.config.available_features.add('clang')
builtin_include_dir = self.get_clang_builtin_include_dir(
self.config.clang)
tool_substitutions = [
ToolSubst('%clang', command=self.config.clang,
extra_args=additional_flags),
ToolSubst('%clang_analyze_cc1', command='%clang_cc1',
extra_args=['-analyze', '%analyze',
'-setup-static-analyzer']+additional_flags),
ToolSubst('%clang_cc1', command=self.config.clang,
extra_args=['-cc1', '-internal-isystem',
builtin_include_dir, '-nostdsysteminc'] +
additional_flags),
ToolSubst('%clang_cpp', command=self.config.clang,
extra_args=['--driver-mode=cpp']+additional_flags),
ToolSubst('%clang_cl', command=self.config.clang,
extra_args=['--driver-mode=cl']+additional_flags),
ToolSubst('%clangxx', command=self.config.clang,
extra_args=['--driver-mode=g++']+additional_flags),
]
self.add_tool_substitutions(tool_substitutions)
self.config.substitutions.append(
('%resource_dir', builtin_include_dir))
self.config.substitutions.append(
('%itanium_abi_triple',
self.make_itanium_abi_triple(self.config.target_triple)))
self.config.substitutions.append(
('%ms_abi_triple',
self.make_msabi_triple(self.config.target_triple)))
if (self.config.host_triple and
self.config.host_triple != '@LLVM_HOST_TRIPLE@'):
self.config.substitutions.append(
('%target_itanium_abi_host_triple',
'--target=' + self.make_itanium_abi_triple(
self.config.host_triple)))
else:
self.config.substitutions.append(
('%target_itanium_abi_host_triple', ''))
def prefer(this, to):
return '''\"*** Do not use '%s' in tests, use '%s'. ***\"''' % (
to, this)
self.config.substitutions.append(
(' clang ', prefer('%clang', 'clang')))
self.config.substitutions.append(
(r' clang\+\+ ', prefer('%clangxx', 'clang++')))
self.config.substitutions.append(
(' clang-cc ', prefer('%clang_cc1', 'clang-cc')))
self.config.substitutions.append(
(' clang-cl ', prefer('%clang_cl', 'clang-cl')))
self.config.substitutions.append(
(' clang -cc1 -analyze ',
prefer('%clang_analyze_cc1', 'clang -cc1 -analyze')))
self.config.substitutions.append(
(' clang -cc1 ', prefer('%clang_cc1', 'clang -cc1')))
self.config.substitutions.append(
(' %clang-cc1 ',
'''\"*** invalid substitution, use '%clang_cc1'. ***\"'''))
self.config.substitutions.append(
(' %clang-cpp ',
'''\"*** invalid substitution, use '%clang_cpp'. ***\"'''))
self.config.substitutions.append(
(' %clang-cl ',
'''\"*** invalid substitution, use '%clang_cl'. ***\"'''))
def use_lld(self, additional_tool_dirs=[], required=True,
use_installed=False):
exe_dir_props = [self.config.name.lower() + '_tools_dir',
'lld_tools_dir', 'llvm_tools_dir']
paths = [getattr(self.config, pp) for pp in exe_dir_props
if getattr(self.config, pp, None)]
paths = additional_tool_dirs + paths
self.with_environment('PATH', paths, append_path=True)
lib_dir_props = [self.config.name.lower() + '_libs_dir',
'lld_libs_dir', 'llvm_shlib_dir', 'llvm_libs_dir']
lib_paths = [getattr(self.config, pp) for pp in lib_dir_props
if getattr(self.config, pp, None)]
self.with_environment('LD_LIBRARY_PATH', lib_paths, append_path=True)
ld_lld = self.use_llvm_tool('ld.lld', required=required,
search_paths=paths,
use_installed=use_installed)
lld_link = self.use_llvm_tool('lld-link', required=required,
search_paths=paths,
use_installed=use_installed)
ld64_lld = self.use_llvm_tool('ld64.lld', required=required,
search_paths=paths,
use_installed=use_installed)
wasm_ld = self.use_llvm_tool('wasm-ld', required=required,
search_paths=paths,
use_installed=use_installed)
was_found = ld_lld and lld_link and ld64_lld and wasm_ld
tool_substitutions = []
if ld_lld:
tool_substitutions.append(ToolSubst(r'ld\.lld', command=ld_lld))
self.config.available_features.add('ld.lld')
if lld_link:
tool_substitutions.append(ToolSubst('lld-link', command=lld_link))
self.config.available_features.add('lld-link')
if ld64_lld:
tool_substitutions.append(ToolSubst(r'ld64\.lld', command=ld64_lld))
self.config.available_features.add('ld64.lld')
if wasm_ld:
tool_substitutions.append(ToolSubst('wasm-ld', command=wasm_ld))
self.config.available_features.add('wasm-ld')
self.add_tool_substitutions(tool_substitutions)
return was_found