import lit.util
import lit.formats
from lit.llvm import llvm_config
-from lit.llvm import ToolFilter
+from lit.llvm.subst import FindTool
+from lit.llvm.subst import ToolSubst
# name: The name of this test suite.
config.name = 'LLVM'
return found_dylibs[0]
-lli = 'lli'
+llvm_config.use_default_substitutions()
+
+# Add site-specific substitutions.
+config.substitutions.append(('%llvmshlibdir', config.llvm_shlib_dir))
+config.substitutions.append(('%shlibext', config.llvm_shlib_ext))
+config.substitutions.append(('%exeext', config.llvm_exe_ext))
+config.substitutions.append(('%host_cc', config.host_cc))
+
+
+lli_args = []
# The target triple used by default by lli is the process target triple (some
# triple appropriate for generating code for the current process) but because
# we don't support COFF in MCJIT well enough for the tests, force ELF format on
# Windows. FIXME: the process target triple should be used here, but this is
# difficult to obtain on Windows.
if re.search(r'cygwin|mingw32|windows-gnu|windows-msvc|win32', config.host_triple):
- lli += ' -mtriple=' + config.host_triple + '-elf'
-config.substitutions.append(('%lli', lli))
+ lli_args = ['-mtriple=' + config.host_triple + '-elf']
+
+llc_args = []
# Similarly, have a macro to use llc with DWARF even when the host is win32.
-llc_dwarf = 'llc'
if re.search(r'win32', config.target_triple):
- llc_dwarf += ' -mtriple=' + \
- config.target_triple.replace('-win32', '-mingw32')
-config.substitutions.append(('%llc_dwarf', llc_dwarf))
-
-# Add site-specific substitutions.
-config.substitutions.append(('%gold', config.gold_executable))
-config.substitutions.append(('%go', config.go_executable))
-config.substitutions.append(('%llvmshlibdir', config.llvm_shlib_dir))
-config.substitutions.append(('%shlibext', config.llvm_shlib_ext))
-config.substitutions.append(('%exeext', config.llvm_exe_ext))
-config.substitutions.append(('%python', config.python_executable))
-config.substitutions.append(('%host_cc', config.host_cc))
+ llc_args = [' -mtriple=' +
+ config.target_triple.replace('-win32', '-mingw32')]
# Provide the path to asan runtime lib if available. On darwin, this lib needs
# to be loaded via DYLD_INSERT_LIBRARIES before libLTO.dylib in case the files
asan_rtlib = get_asan_rtlib()
if asan_rtlib:
ld64_cmd = 'DYLD_INSERT_LIBRARIES={} {}'.format(asan_rtlib, ld64_cmd)
-config.substitutions.append(('%ld64', ld64_cmd))
-# OCaml substitutions.
-# Support tests for both native and bytecode builds.
-config.substitutions.append(('%ocamlc',
- '%s ocamlc -cclib -L%s %s' %
- (config.ocamlfind_executable, config.llvm_lib_dir, config.ocaml_flags)))
+ocamlc_command = '%s ocamlc -cclib -L%s %s' % (
+ config.ocamlfind_executable, config.llvm_lib_dir, config.ocaml_flags)
+ocamlopt_command = 'true'
if config.have_ocamlopt:
- config.substitutions.append(('%ocamlopt',
- '%s ocamlopt -cclib -L%s -cclib -Wl,-rpath,%s %s' %
- (config.ocamlfind_executable, config.llvm_lib_dir, config.llvm_lib_dir, config.ocaml_flags)))
-else:
- config.substitutions.append(('%ocamlopt', 'true'))
-
-# For each occurrence of an llvm tool name as its own word, replace it
-# with the full path to the build directory holding that tool. This
-# ensures that we are testing the tools just built and not some random
-# tools that might happen to be in the user's PATH. Thus this list
-# includes every tool placed in $(LLVM_OBJ_ROOT)/$(BuildMode)/bin
-# (llvm_tools_dir in lit parlance).
-
-# Avoid matching RUN line fragments that are actually part of
-# path names or options or whatever.
-# The regex is a pre-assertion to avoid matching a preceding
-# dot, hyphen, carat, or slash (.foo, -foo, etc.). Some patterns
-# also have a post-assertion to not match a trailing hyphen (foo-).
-JUNKCHARS = r".-^/<"
-
-required_tools = [
- 'lli', 'llvm-ar', 'llvm-as', 'llvm-bcanalyzer', 'llvm-config', 'llvm-cov',
+ ocamlopt_command = '%s ocamlopt -cclib -L%s -cclib -Wl,-rpath,%s %s' % (
+ config.ocamlfind_executable, config.llvm_lib_dir, config.llvm_lib_dir, config.ocaml_flags)
+
+
+tools = [
+ ToolSubst('%lli', FindTool('lli'), post='.', extra_args=lli_args),
+ ToolSubst('%llc_dwarf', FindTool('llc'), extra_args=llc_args),
+ ToolSubst('%go', config.go_executable, unresolved='ignore'),
+ ToolSubst('%gold', config.gold_executable, unresolved='ignore'),
+ ToolSubst('%ld64', ld64_cmd, unresolved='ignore'),
+ ToolSubst('%ocamlc', ocamlc_command, unresolved='ignore'),
+ ToolSubst('%ocamlopt', ocamlopt_command, unresolved='ignore'),
+]
+
+# FIXME: Why do we have both `lli` and `%lli` that do slightly different things?
+tools.extend([
+ 'lli', 'lli-child-target', 'llvm-ar', 'llvm-as', 'llvm-bcanalyzer', 'llvm-config', 'llvm-cov',
'llvm-cxxdump', 'llvm-cvtres', 'llvm-diff', 'llvm-dis', 'llvm-dsymutil',
'llvm-dwarfdump', 'llvm-extract', 'llvm-isel-fuzzer', 'llvm-lib',
'llvm-link', 'llvm-lto', 'llvm-lto2', 'llvm-mc', 'llvm-mcmarkup',
'llvm-pdbutil', 'llvm-profdata', 'llvm-ranlib', 'llvm-readobj',
'llvm-rtdyld', 'llvm-size', 'llvm-split', 'llvm-strings', 'llvm-tblgen',
'llvm-c-test', 'llvm-cxxfilt', 'llvm-xray', 'yaml2obj', 'obj2yaml',
- 'FileCheck', 'yaml-bench', 'verify-uselistorder',
- ToolFilter('bugpoint', post='-'),
- ToolFilter('llc', pre=JUNKCHARS),
- ToolFilter('llvm-symbolizer', pre=JUNKCHARS),
- ToolFilter('opt', JUNKCHARS),
- ToolFilter('sancov', pre=JUNKCHARS),
- ToolFilter('sanstats', pre=JUNKCHARS),
- # Handle these specially as they are strings searched for during testing.
- ToolFilter(r'\| \bcount\b', verbatim=True),
- ToolFilter(r'\| \bnot\b', verbatim=True)]
-
-llvm_config.add_tool_substitutions(required_tools, config.llvm_tools_dir)
-
-# For tools that are optional depending on the config, we won't warn
-# if they're missing.
-
-optional_tools = [
- 'llvm-go', 'llvm-mt', 'Kaleidoscope-Ch3', 'Kaleidoscope-Ch4',
- 'Kaleidoscope-Ch5', 'Kaleidoscope-Ch6', 'Kaleidoscope-Ch7',
- 'Kaleidoscope-Ch8']
-llvm_config.add_tool_substitutions(optional_tools, config.llvm_tools_dir,
- warn_missing=False)
+ 'yaml-bench', 'verify-uselistorder',
+ 'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats'])
+
+# The following tools are optional
+tools.extend([
+ ToolSubst('llvm-go', unresolved='ignore'),
+ ToolSubst('llvm-mt', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch3', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch4', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch5', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch6', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch7', unresolved='ignore'),
+ ToolSubst('Kaleidoscope-Ch8', unresolved='ignore')])
+
+llvm_config.add_tool_substitutions(tools, config.llvm_tools_dir)
# Targets
from lit.llvm import config
-import lit.util
-import re
llvm_config = None
-class ToolFilter(object):
- """
- String-like class used to build regex substitution patterns for
- llvm tools. Handles things like adding word-boundary patterns,
- and filtering characters from the beginning an end of a tool name
- """
-
- def __init__(self, name, pre=None, post=None, verbatim=False):
- """
- Construct a ToolFilter.
-
- name: the literal name of the substitution to look for.
-
- pre: If specified, the substitution will not find matches where
- the character immediately preceding the word-boundary that begins
- `name` is any of the characters in the string `pre`.
-
- post: If specified, the substitution will not find matches where
- the character immediately after the word-boundary that ends `name`
- is any of the characters specified in the string `post`.
-
- verbatim: If True, `name` is an exact regex that is passed to the
- underlying substitution
- """
- if verbatim:
- self.regex = name
- return
-
- def not_in(chars, where=''):
- if not chars:
- return ''
- pattern_str = '|'.join(re.escape(x) for x in chars)
- return r'(?{}!({}))'.format(where, pattern_str)
-
- self.regex = not_in(pre, '<') + r'\b' + name + r'\b' + not_in(post)
-
- def __str__(self):
- return self.regex
-
def initialize(lit_config, test_config):
global llvm_config
llvm_config = config.LLVMConfig(lit_config, test_config)
-
import sys
import lit.util
+from lit.llvm.subst import FindTool
+from lit.llvm.subst import ToolSubst
def binary_feature(on, feature, off_prefix):
# -win32 is not supported for non-x86 targets; use a default.
return 'i686-pc-win32'
- def add_tool_substitutions(self, tools, search_dirs, warn_missing=True):
+ 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:
- # Extract the tool name from the pattern. This relies on the tool
- # name being surrounded by \b word match operators. If the
- # pattern starts with "| ", include it in the string to be
- # substituted.
- if lit.util.is_string(tool):
- tool = lit.util.make_word_regex(tool)
- else:
- tool = str(tool)
+ match = tool.resolve(self, search_dirs)
- tool_match = re.match(r"^(\\)?((\| )?)\W+b([0-9A-Za-z-_\.]+)\\b\W*$",
- tool)
- if not tool_match:
+ # Either no match occurred, or there was an unresolved match that
+ # is ignored.
+ if not match:
continue
- tool_pipe = tool_match.group(2)
- tool_name = tool_match.group(4)
- tool_path = lit.util.which(tool_name, search_dirs)
- if not tool_path:
- if warn_missing:
- # Warn, but still provide a substitution.
- self.lit_config.note(
- 'Did not find ' + tool_name + ' in %s' % search_dirs)
- tool_path = self.config.llvm_tools_dir + '/' + tool_name
-
- if tool_name == 'llc' and os.environ.get('LLVM_ENABLE_MACHINE_VERIFIER') == '1':
- tool_path += ' -verify-machineinstrs'
- if tool_name == 'llvm-go':
- exe = getattr(self.config, 'go_executable', None)
- if exe:
- tool_path += ' go=' + exe
-
- self.config.substitutions.append((tool, tool_pipe + tool_path))
+ subst_key, tool_pipe, command = match
+
+ # An unresolved match occurred that can't be ignored. Fail without
+ # adding any of the previously-discovered substitutions.
+ if not command:
+ return False
+
+ substitutions.append((subst_key, tool_pipe + command))
+
+ self.config.substitutions.extend(substitutions)
+ return True
+
+ def use_default_substitutions(self):
+ tool_patterns = [
+ ToolSubst('FileCheck', unresolved='fatal'),
+ # Handle these specially as they are strings searched for during testing.
+ 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', sys.executable))
+ self.add_tool_substitutions(
+ tool_patterns, [self.config.llvm_tools_dir])
--- /dev/null
+import os
+import re
+
+import lit.util
+
+expr = re.compile(r"^(\\)?((\| )?)\W+b(\S+)\\b\W*$")
+wordifier = re.compile(r"(\W*)(\b[^\b]+\b)")
+
+
+class FindTool(object):
+ def __init__(self, name):
+ self.name = name
+
+ def resolve(self, config, dirs):
+ command = lit.util.which(self.name, dirs)
+ if not command:
+ return None
+
+ if self.name == 'llc' and os.environ.get('LLVM_ENABLE_MACHINE_VERIFIER') == '1':
+ command += ' -verify-machineinstrs'
+ elif self.name == 'llvm-go':
+ exe = getattr(config.config, 'go_executable', None)
+ if exe:
+ command += ' go=' + exe
+ return command
+
+
+class ToolSubst(object):
+ """String-like class used to build regex substitution patterns for llvm
+ tools.
+
+ Handles things like adding word-boundary patterns, and filtering
+ characters from the beginning an end of a tool name
+
+ """
+
+ def __init__(self, key, command=None, pre=r'.-^/\<', post='-.', verbatim=False,
+ unresolved='warn', extra_args=None):
+ """Construct a ToolSubst.
+
+ key: The text which is to be substituted.
+
+ command: The command to substitute when the key is matched. By default,
+ this will treat `key` as a tool name and search for it. If it is
+ a string, it is intereprted as an exact path. If it is an instance of
+ FindTool, the specified tool name is searched for on disk.
+
+ pre: If specified, the substitution will not find matches where
+ the character immediately preceding the word-boundary that begins
+ `key` is any of the characters in the string `pre`.
+
+ post: If specified, the substitution will not find matches where
+ the character immediately after the word-boundary that ends `key`
+ is any of the characters specified in the string `post`.
+
+ verbatim: If True, `key` is an exact regex that is passed to the
+ underlying substitution
+
+ unresolved: Action to take if the tool substitution cannot be
+ resolved. Valid values:
+ 'warn' - log a warning but add the substitution anyway.
+ 'fatal' - Exit the test suite and log a fatal error.
+ 'break' - Don't add any of the substitutions from the current
+ group, and return a value indicating a failure.
+ 'ignore' - Don't add the substitution, and don't log an error
+
+ extra_args: If specified, represents a list of arguments that will be
+ appended to the tool's substitution.
+
+ explicit_path: If specified, the exact path will be used as a substitution.
+ Otherwise, the tool will be searched for as if by calling which(tool)
+
+ """
+ self.unresolved = unresolved
+ self.extra_args = extra_args
+ self.key = key
+ self.command = command if command is not None else FindTool(key)
+ if verbatim:
+ self.regex = key
+ return
+
+ def not_in(chars, where=''):
+ if not chars:
+ return ''
+ pattern_str = '|'.join(re.escape(x) for x in chars)
+ return r'(?{}!({}))'.format(where, pattern_str)
+
+ def wordify(word):
+ match = wordifier.match(word)
+ introducer = match.group(1)
+ word = match.group(2)
+ return introducer + r'\b' + word + r'\b'
+
+ self.regex = not_in(pre, '<') + wordify(key) + not_in(post)
+
+ def resolve(self, config, search_dirs):
+ # Extract the tool name from the pattern. This relies on the tool
+ # name being surrounded by \b word match operators. If the
+ # pattern starts with "| ", include it in the string to be
+ # substituted.
+
+ tool_match = expr.match(self.regex)
+ if not tool_match:
+ return None
+
+ tool_pipe = tool_match.group(2)
+ tool_name = tool_match.group(4)
+
+ if isinstance(self.command, FindTool):
+ command_str = self.command.resolve(config, search_dirs)
+ else:
+ command_str = str(self.command)
+
+ if command_str:
+ if self.extra_args:
+ command_str = ' '.join([command_str] + self.extra_args)
+ else:
+ if self.unresolved == 'warn':
+ # Warn, but still provide a substitution.
+ config.lit_config.note(
+ 'Did not find ' + tool_name + ' in %s' % search_dirs)
+ command_str = os.path.join(
+ config.config.llvm_tools_dir, tool_name)
+ elif self.unresolved == 'fatal':
+ # The function won't even return in this case, this leads to
+ # sys.exit
+ config.lit_config.fatal(
+ 'Did not find ' + tool_name + ' in %s' % search_dirs)
+ elif self.unresolved == 'break':
+ # By returning a valid result with an empty command, the
+ # caller treats this as a failure.
+ pass
+ elif self.unresolved == 'ignore':
+ # By returning None, the caller just assumes there was no
+ # match in the first place.
+ return None
+ else:
+ raise 'Unexpected value for ToolSubst.unresolved'
+
+ return (self.regex, tool_pipe, command_str)
# Check for absolute match first.
if os.path.isfile(command):
- return command
+ return os.path.normpath(command)
# Would be nice if Python had a lib function for this.
if not paths:
for ext in pathext:
p = os.path.join(path, command + ext)
if os.path.exists(p) and not os.path.isdir(p):
- return p
+ return os.path.normpath(p)
return None