xref: /llvm-project/llvm/utils/lit/lit/llvm/subst.py (revision bfe6b3bff1eb841c09fe16466028cf2dd18f5bbd)
1import os
2import re
3
4import lit.util
5
6expr = re.compile(r"^(\\)?((\| )?)\W+b(\S+)\\b\W*$")
7wordifier = re.compile(r"(\W*)(\b[^\b]+\b)")
8
9
10class FindTool(object):
11    def __init__(self, name):
12        self.name = name
13
14    def resolve(self, config, dirs):
15        # Check for a user explicitly overriding a tool. This allows:
16        #     llvm-lit -D llc="llc -enable-misched -verify-machineinstrs"
17        command = config.lit_config.params.get(self.name)
18        if command is None:
19            # Then check out search paths.
20            command = lit.util.which(self.name, dirs)
21            if not command:
22                return None
23
24        if self.name == "llc" and os.environ.get("LLVM_ENABLE_MACHINE_VERIFIER") == "1":
25            command += " -verify-machineinstrs"
26        return command
27
28
29class ToolSubst(object):
30    """String-like class used to build regex substitution patterns for llvm
31    tools.
32
33    Handles things like adding word-boundary patterns, and filtering
34    characters from the beginning an end of a tool name
35
36    """
37
38    def __init__(
39        self,
40        key,
41        command=None,
42        pre=r".-^/\<",
43        post="-.",
44        verbatim=False,
45        unresolved="warn",
46        extra_args=None,
47    ):
48        """Construct a ToolSubst.
49
50        key: The text which is to be substituted.
51
52        command: The command to substitute when the key is matched. By default,
53        this will treat `key` as a tool name and search for it. If it is a
54        string, it is interpreted as an exact path. If it is an instance of
55        FindTool, the specified tool name is searched for on disk.
56
57        pre: If specified, the substitution will not find matches where
58        the character immediately preceding the word-boundary that begins
59        `key` is any of the characters in the string `pre`.
60
61        post: If specified, the substitution will not find matches where
62        the character immediately after the word-boundary that ends `key`
63        is any of the characters specified in the string `post`.
64
65        verbatim: If True, `key` is an exact regex that is passed to the
66        underlying substitution
67
68        unresolved: Action to take if the tool substitution cannot be
69        resolved. Valid values:
70            'warn' - log a warning but add the substitution anyway.
71            'fatal' - Exit the test suite and log a fatal error.
72            'break' - Don't add any of the substitutions from the current
73                      group, and return a value indicating a failure.
74            'ignore' - Don't add the substitution, and don't log an error
75
76        extra_args: If specified, represents a list of arguments that will be
77        appended to the tool's substitution.
78
79        """
80        self.unresolved = unresolved
81        self.extra_args = extra_args
82        self.key = key
83        self.command = command if command is not None else FindTool(key)
84        self.was_resolved = False
85        if verbatim:
86            self.regex = key
87            return
88
89        def not_in(chars, where=""):
90            if not chars:
91                return ""
92            pattern_str = "|".join(re.escape(x) for x in chars)
93            return r"(?{}!({}))".format(where, pattern_str)
94
95        def wordify(word):
96            match = wordifier.match(word)
97            introducer = match.group(1)
98            word = match.group(2)
99            return introducer + r"\b" + word + r"\b"
100
101        self.regex = not_in(pre, "<") + wordify(key) + not_in(post)
102
103    def resolve(self, config, search_dirs):
104        # Extract the tool name from the pattern. This relies on the tool name
105        # being surrounded by \b word match operators. If the pattern starts
106        # with "| ", include it in the string to be substituted.
107
108        tool_match = expr.match(self.regex)
109        if not tool_match:
110            return None
111
112        tool_pipe = tool_match.group(2)
113        tool_name = tool_match.group(4)
114
115        if isinstance(self.command, FindTool):
116            command_str = self.command.resolve(config, search_dirs)
117        else:
118            command_str = str(self.command)
119
120        if command_str:
121            if self.extra_args:
122                command_str = " ".join([command_str] + self.extra_args)
123        else:
124            if self.unresolved == "warn":
125                # Warn, but still provide a substitution.
126                config.lit_config.note(
127                    "Did not find " + tool_name + " in %s" % search_dirs
128                )
129                command_str = os.path.join(config.config.llvm_tools_dir, tool_name)
130            elif self.unresolved == "fatal":
131                # The function won't even return in this case, this leads to
132                # sys.exit
133                config.lit_config.fatal(
134                    "Did not find " + tool_name + " in %s" % search_dirs
135                )
136            elif self.unresolved == "break":
137                # By returning a valid result with an empty command, the
138                # caller treats this as a failure.
139                pass
140            elif self.unresolved == "ignore":
141                # By returning None, the caller just assumes there was no
142                # match in the first place.
143                return None
144            else:
145                raise "Unexpected value for ToolSubst.unresolved"
146        if command_str:
147            self.was_resolved = True
148        return (self.regex, tool_pipe, command_str)
149