llvm.org GIT mirror llvm / 19ac6f8
[lit] Improve tool substitution in lit. This addresses two sources of inconsistency in test configuration files. 1. Substitution boundaries. Previously you would specify a substitution, such as 'lli', and then additionally a set of characters that should fail to match before and after the tool. This was used, for example, so that matches that are parts of full paths would not be replaced. But not all tools did this, and those that did would often re-invent the set of characters themselves, leading to inconsistency. Now, every tool substitution defaults to using a sane set of reasonable defaults and you have to explicitly opt out of it. This actually fixed a few latent bugs that were never being surfaced, but only on accident. 2. There was no standard way for the system to decide how to locate a tool. Sometimes you have an explicit path, sometimes we would search for it and build up a path ourselves, and sometimes we would build up a full command line. Furthermore, there was no standardized way to handle missing tools. Do we warn, fail, ignore, etc? All of this is now encapsulated in the ToolSubst class. You either specify an exact command to run, or an instance of FindTool('<tool-name>') and everything else just works. Furthermore, you can specify an action to take if the tool cannot be resolved. Differential Revision: https://reviews.llvm.org/D38565 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@315085 91177308-0d34-0410-b5e6-96231b3b80d8 Zachary Turner 2 years ago
5 changed file(s) with 234 addition(s) and 143 deletion(s). Raw diff Collapse all Expand all
1010 import lit.util
1111 import lit.formats
1212 from lit.llvm import llvm_config
13 from lit.llvm import ToolFilter
13 from lit.llvm.subst import FindTool
14 from lit.llvm.subst import ToolSubst
1415
1516 # name: The name of this test suite.
1617 config.name = 'LLVM'
8182 return found_dylibs[0]
8283
8384
84 lli = 'lli'
85 llvm_config.use_default_substitutions()
86
87 # Add site-specific substitutions.
88 config.substitutions.append(('%llvmshlibdir', config.llvm_shlib_dir))
89 config.substitutions.append(('%shlibext', config.llvm_shlib_ext))
90 config.substitutions.append(('%exeext', config.llvm_exe_ext))
91 config.substitutions.append(('%host_cc', config.host_cc))
92
93
94 lli_args = []
8595 # The target triple used by default by lli is the process target triple (some
8696 # triple appropriate for generating code for the current process) but because
8797 # we don't support COFF in MCJIT well enough for the tests, force ELF format on
8898 # Windows. FIXME: the process target triple should be used here, but this is
8999 # difficult to obtain on Windows.
90100 if re.search(r'cygwin|mingw32|windows-gnu|windows-msvc|win32', config.host_triple):
91 lli += ' -mtriple=' + config.host_triple + '-elf'
92 config.substitutions.append(('%lli', lli))
101 lli_args = ['-mtriple=' + config.host_triple + '-elf']
102
103 llc_args = []
93104
94105 # Similarly, have a macro to use llc with DWARF even when the host is win32.
95 llc_dwarf = 'llc'
96106 if re.search(r'win32', config.target_triple):
97 llc_dwarf += ' -mtriple=' + \
98 config.target_triple.replace('-win32', '-mingw32')
99 config.substitutions.append(('%llc_dwarf', llc_dwarf))
100
101 # Add site-specific substitutions.
102 config.substitutions.append(('%gold', config.gold_executable))
103 config.substitutions.append(('%go', config.go_executable))
104 config.substitutions.append(('%llvmshlibdir', config.llvm_shlib_dir))
105 config.substitutions.append(('%shlibext', config.llvm_shlib_ext))
106 config.substitutions.append(('%exeext', config.llvm_exe_ext))
107 config.substitutions.append(('%python', config.python_executable))
108 config.substitutions.append(('%host_cc', config.host_cc))
107 llc_args = [' -mtriple=' +
108 config.target_triple.replace('-win32', '-mingw32')]
109109
110110 # Provide the path to asan runtime lib if available. On darwin, this lib needs
111111 # to be loaded via DYLD_INSERT_LIBRARIES before libLTO.dylib in case the files
114114 asan_rtlib = get_asan_rtlib()
115115 if asan_rtlib:
116116 ld64_cmd = 'DYLD_INSERT_LIBRARIES={} {}'.format(asan_rtlib, ld64_cmd)
117 config.substitutions.append(('%ld64', ld64_cmd))
118
119 # OCaml substitutions.
120 # Support tests for both native and bytecode builds.
121 config.substitutions.append(('%ocamlc',
122 '%s ocamlc -cclib -L%s %s' %
123 (config.ocamlfind_executable, config.llvm_lib_dir, config.ocaml_flags)))
117
118 ocamlc_command = '%s ocamlc -cclib -L%s %s' % (
119 config.ocamlfind_executable, config.llvm_lib_dir, config.ocaml_flags)
120 ocamlopt_command = 'true'
124121 if config.have_ocamlopt:
125 config.substitutions.append(('%ocamlopt',
126 '%s ocamlopt -cclib -L%s -cclib -Wl,-rpath,%s %s' %
127 (config.ocamlfind_executable, config.llvm_lib_dir, config.llvm_lib_dir, config.ocaml_flags)))
128 else:
129 config.substitutions.append(('%ocamlopt', 'true'))
130
131 # For each occurrence of an llvm tool name as its own word, replace it
132 # with the full path to the build directory holding that tool. This
133 # ensures that we are testing the tools just built and not some random
134 # tools that might happen to be in the user's PATH. Thus this list
135 # includes every tool placed in $(LLVM_OBJ_ROOT)/$(BuildMode)/bin
136 # (llvm_tools_dir in lit parlance).
137
138 # Avoid matching RUN line fragments that are actually part of
139 # path names or options or whatever.
140 # The regex is a pre-assertion to avoid matching a preceding
141 # dot, hyphen, carat, or slash (.foo, -foo, etc.). Some patterns
142 # also have a post-assertion to not match a trailing hyphen (foo-).
143 JUNKCHARS = r".-^/<"
144
145 required_tools = [
146 'lli', 'llvm-ar', 'llvm-as', 'llvm-bcanalyzer', 'llvm-config', 'llvm-cov',
122 ocamlopt_command = '%s ocamlopt -cclib -L%s -cclib -Wl,-rpath,%s %s' % (
123 config.ocamlfind_executable, config.llvm_lib_dir, config.llvm_lib_dir, config.ocaml_flags)
124
125
126 tools = [
127 ToolSubst('%lli', FindTool('lli'), post='.', extra_args=lli_args),
128 ToolSubst('%llc_dwarf', FindTool('llc'), extra_args=llc_args),
129 ToolSubst('%go', config.go_executable, unresolved='ignore'),
130 ToolSubst('%gold', config.gold_executable, unresolved='ignore'),
131 ToolSubst('%ld64', ld64_cmd, unresolved='ignore'),
132 ToolSubst('%ocamlc', ocamlc_command, unresolved='ignore'),
133 ToolSubst('%ocamlopt', ocamlopt_command, unresolved='ignore'),
134 ]
135
136 # FIXME: Why do we have both `lli` and `%lli` that do slightly different things?
137 tools.extend([
138 'lli', 'lli-child-target', 'llvm-ar', 'llvm-as', 'llvm-bcanalyzer', 'llvm-config', 'llvm-cov',
147139 'llvm-cxxdump', 'llvm-cvtres', 'llvm-diff', 'llvm-dis', 'llvm-dsymutil',
148140 'llvm-dwarfdump', 'llvm-extract', 'llvm-isel-fuzzer', 'llvm-lib',
149141 'llvm-link', 'llvm-lto', 'llvm-lto2', 'llvm-mc', 'llvm-mcmarkup',
151143 'llvm-pdbutil', 'llvm-profdata', 'llvm-ranlib', 'llvm-readobj',
152144 'llvm-rtdyld', 'llvm-size', 'llvm-split', 'llvm-strings', 'llvm-tblgen',
153145 'llvm-c-test', 'llvm-cxxfilt', 'llvm-xray', 'yaml2obj', 'obj2yaml',
154 'FileCheck', 'yaml-bench', 'verify-uselistorder',
155 ToolFilter('bugpoint', post='-'),
156 ToolFilter('llc', pre=JUNKCHARS),
157 ToolFilter('llvm-symbolizer', pre=JUNKCHARS),
158 ToolFilter('opt', JUNKCHARS),
159 ToolFilter('sancov', pre=JUNKCHARS),
160 ToolFilter('sanstats', pre=JUNKCHARS),
161 # Handle these specially as they are strings searched for during testing.
162 ToolFilter(r'\| \bcount\b', verbatim=True),
163 ToolFilter(r'\| \bnot\b', verbatim=True)]
164
165 llvm_config.add_tool_substitutions(required_tools, config.llvm_tools_dir)
166
167 # For tools that are optional depending on the config, we won't warn
168 # if they're missing.
169
170 optional_tools = [
171 'llvm-go', 'llvm-mt', 'Kaleidoscope-Ch3', 'Kaleidoscope-Ch4',
172 'Kaleidoscope-Ch5', 'Kaleidoscope-Ch6', 'Kaleidoscope-Ch7',
173 'Kaleidoscope-Ch8']
174 llvm_config.add_tool_substitutions(optional_tools, config.llvm_tools_dir,
175 warn_missing=False)
146 'yaml-bench', 'verify-uselistorder',
147 'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats'])
148
149 # The following tools are optional
150 tools.extend([
151 ToolSubst('llvm-go', unresolved='ignore'),
152 ToolSubst('llvm-mt', unresolved='ignore'),
153 ToolSubst('Kaleidoscope-Ch3', unresolved='ignore'),
154 ToolSubst('Kaleidoscope-Ch4', unresolved='ignore'),
155 ToolSubst('Kaleidoscope-Ch5', unresolved='ignore'),
156 ToolSubst('Kaleidoscope-Ch6', unresolved='ignore'),
157 ToolSubst('Kaleidoscope-Ch7', unresolved='ignore'),
158 ToolSubst('Kaleidoscope-Ch8', unresolved='ignore')])
159
160 llvm_config.add_tool_substitutions(tools, config.llvm_tools_dir)
176161
177162 # Targets
178163
0 from lit.llvm import config
1 import lit.util
2 import re
31
42 llvm_config = None
5
6 class ToolFilter(object):
7 """
8 String-like class used to build regex substitution patterns for
9 llvm tools. Handles things like adding word-boundary patterns,
10 and filtering characters from the beginning an end of a tool name
11 """
12
13 def __init__(self, name, pre=None, post=None, verbatim=False):
14 """
15 Construct a ToolFilter.
16
17 name: the literal name of the substitution to look for.
18
19 pre: If specified, the substitution will not find matches where
20 the character immediately preceding the word-boundary that begins
21 `name` is any of the characters in the string `pre`.
22
23 post: If specified, the substitution will not find matches where
24 the character immediately after the word-boundary that ends `name`
25 is any of the characters specified in the string `post`.
26
27 verbatim: If True, `name` is an exact regex that is passed to the
28 underlying substitution
29 """
30 if verbatim:
31 self.regex = name
32 return
33
34 def not_in(chars, where=''):
35 if not chars:
36 return ''
37 pattern_str = '|'.join(re.escape(x) for x in chars)
38 return r'(?{}!({}))'.format(where, pattern_str)
39
40 self.regex = not_in(pre, '<') + r'\b' + name + r'\b' + not_in(post)
41
42 def __str__(self):
43 return self.regex
443
454
465 def initialize(lit_config, test_config):
476 global llvm_config
487
498 llvm_config = config.LLVMConfig(lit_config, test_config)
50
44 import sys
55
66 import lit.util
7 from lit.llvm.subst import FindTool
8 from lit.llvm.subst import ToolSubst
79
810
911 def binary_feature(on, feature, off_prefix):
224226 # -win32 is not supported for non-x86 targets; use a default.
225227 return 'i686-pc-win32'
226228
227 def add_tool_substitutions(self, tools, search_dirs, warn_missing=True):
229 def add_tool_substitutions(self, tools, search_dirs=None):
230 if not search_dirs:
231 search_dirs = [self.config.llvm_tools_dir]
232
228233 if lit.util.is_string(search_dirs):
229234 search_dirs = [search_dirs]
230235
236 tools = [x if isinstance(x, ToolSubst) else ToolSubst(x)
237 for x in tools]
238
231239 search_dirs = os.pathsep.join(search_dirs)
240 substitutions = []
241
232242 for tool in tools:
233 # Extract the tool name from the pattern. This relies on the tool
234 # name being surrounded by \b word match operators. If the
235 # pattern starts with "| ", include it in the string to be
236 # substituted.
237 if lit.util.is_string(tool):
238 tool = lit.util.make_word_regex(tool)
239 else:
240 tool = str(tool)
241
242 tool_match = re.match(r"^(\\)?((\| )?)\W+b([0-9A-Za-z-_\.]+)\\b\W*$",
243 tool)
244 if not tool_match:
243 match = tool.resolve(self, search_dirs)
244
245 # Either no match occurred, or there was an unresolved match that
246 # is ignored.
247 if not match:
245248 continue
246249
247 tool_pipe = tool_match.group(2)
248 tool_name = tool_match.group(4)
249 tool_path = lit.util.which(tool_name, search_dirs)
250 if not tool_path:
251 if warn_missing:
252 # Warn, but still provide a substitution.
253 self.lit_config.note(
254 'Did not find ' + tool_name + ' in %s' % search_dirs)
255 tool_path = self.config.llvm_tools_dir + '/' + tool_name
256
257 if tool_name == 'llc' and os.environ.get('LLVM_ENABLE_MACHINE_VERIFIER') == '1':
258 tool_path += ' -verify-machineinstrs'
259 if tool_name == 'llvm-go':
260 exe = getattr(self.config, 'go_executable', None)
261 if exe:
262 tool_path += ' go=' + exe
263
264 self.config.substitutions.append((tool, tool_pipe + tool_path))
250 subst_key, tool_pipe, command = match
251
252 # An unresolved match occurred that can't be ignored. Fail without
253 # adding any of the previously-discovered substitutions.
254 if not command:
255 return False
256
257 substitutions.append((subst_key, tool_pipe + command))
258
259 self.config.substitutions.extend(substitutions)
260 return True
261
262 def use_default_substitutions(self):
263 tool_patterns = [
264 ToolSubst('FileCheck', unresolved='fatal'),
265 # Handle these specially as they are strings searched for during testing.
266 ToolSubst(r'\| \bcount\b', command=FindTool(
267 'count'), verbatim=True, unresolved='fatal'),
268 ToolSubst(r'\| \bnot\b', command=FindTool('not'), verbatim=True, unresolved='fatal')]
269
270 self.config.substitutions.append(('%python', sys.executable))
271 self.add_tool_substitutions(
272 tool_patterns, [self.config.llvm_tools_dir])
0 import os
1 import re
2
3 import lit.util
4
5 expr = re.compile(r"^(\\)?((\| )?)\W+b(\S+)\\b\W*$")
6 wordifier = re.compile(r"(\W*)(\b[^\b]+\b)")
7
8
9 class FindTool(object):
10 def __init__(self, name):
11 self.name = name
12
13 def resolve(self, config, dirs):
14 command = lit.util.which(self.name, dirs)
15 if not command:
16 return None
17
18 if self.name == 'llc' and os.environ.get('LLVM_ENABLE_MACHINE_VERIFIER') == '1':
19 command += ' -verify-machineinstrs'
20 elif self.name == 'llvm-go':
21 exe = getattr(config.config, 'go_executable', None)
22 if exe:
23 command += ' go=' + exe
24 return command
25
26
27 class ToolSubst(object):
28 """String-like class used to build regex substitution patterns for llvm
29 tools.
30
31 Handles things like adding word-boundary patterns, and filtering
32 characters from the beginning an end of a tool name
33
34 """
35
36 def __init__(self, key, command=None, pre=r'.-^/\<', post='-.', verbatim=False,
37 unresolved='warn', extra_args=None):
38 """Construct a ToolSubst.
39
40 key: The text which is to be substituted.
41
42 command: The command to substitute when the key is matched. By default,
43 this will treat `key` as a tool name and search for it. If it is
44 a string, it is intereprted as an exact path. If it is an instance of
45 FindTool, the specified tool name is searched for on disk.
46
47 pre: If specified, the substitution will not find matches where
48 the character immediately preceding the word-boundary that begins
49 `key` is any of the characters in the string `pre`.
50
51 post: If specified, the substitution will not find matches where
52 the character immediately after the word-boundary that ends `key`
53 is any of the characters specified in the string `post`.
54
55 verbatim: If True, `key` is an exact regex that is passed to the
56 underlying substitution
57
58 unresolved: Action to take if the tool substitution cannot be
59 resolved. Valid values:
60 'warn' - log a warning but add the substitution anyway.
61 'fatal' - Exit the test suite and log a fatal error.
62 'break' - Don't add any of the substitutions from the current
63 group, and return a value indicating a failure.
64 'ignore' - Don't add the substitution, and don't log an error
65
66 extra_args: If specified, represents a list of arguments that will be
67 appended to the tool's substitution.
68
69 explicit_path: If specified, the exact path will be used as a substitution.
70 Otherwise, the tool will be searched for as if by calling which(tool)
71
72 """
73 self.unresolved = unresolved
74 self.extra_args = extra_args
75 self.key = key
76 self.command = command if command is not None else FindTool(key)
77 if verbatim:
78 self.regex = key
79 return
80
81 def not_in(chars, where=''):
82 if not chars:
83 return ''
84 pattern_str = '|'.join(re.escape(x) for x in chars)
85 return r'(?{}!({}))'.format(where, pattern_str)
86
87 def wordify(word):
88 match = wordifier.match(word)
89 introducer = match.group(1)
90 word = match.group(2)
91 return introducer + r'\b' + word + r'\b'
92
93 self.regex = not_in(pre, '<') + wordify(key) + not_in(post)
94
95 def resolve(self, config, search_dirs):
96 # Extract the tool name from the pattern. This relies on the tool
97 # name being surrounded by \b word match operators. If the
98 # pattern starts with "| ", include it in the string to be
99 # substituted.
100
101 tool_match = expr.match(self.regex)
102 if not tool_match:
103 return None
104
105 tool_pipe = tool_match.group(2)
106 tool_name = tool_match.group(4)
107
108 if isinstance(self.command, FindTool):
109 command_str = self.command.resolve(config, search_dirs)
110 else:
111 command_str = str(self.command)
112
113 if command_str:
114 if self.extra_args:
115 command_str = ' '.join([command_str] + self.extra_args)
116 else:
117 if self.unresolved == 'warn':
118 # Warn, but still provide a substitution.
119 config.lit_config.note(
120 'Did not find ' + tool_name + ' in %s' % search_dirs)
121 command_str = os.path.join(
122 config.config.llvm_tools_dir, tool_name)
123 elif self.unresolved == 'fatal':
124 # The function won't even return in this case, this leads to
125 # sys.exit
126 config.lit_config.fatal(
127 'Did not find ' + tool_name + ' in %s' % search_dirs)
128 elif self.unresolved == 'break':
129 # By returning a valid result with an empty command, the
130 # caller treats this as a failure.
131 pass
132 elif self.unresolved == 'ignore':
133 # By returning None, the caller just assumes there was no
134 # match in the first place.
135 return None
136 else:
137 raise 'Unexpected value for ToolSubst.unresolved'
138
139 return (self.regex, tool_pipe, command_str)
194194
195195 # Check for absolute match first.
196196 if os.path.isfile(command):
197 return command
197 return os.path.normpath(command)
198198
199199 # Would be nice if Python had a lib function for this.
200200 if not paths:
212212 for ext in pathext:
213213 p = os.path.join(path, command + ext)
214214 if os.path.exists(p) and not os.path.isdir(p):
215 return p
215 return os.path.normpath(p)
216216
217217 return None
218218