blight.actions

Actions supported by blight.

 1"""
 2Actions supported by blight.
 3"""
 4
 5from .benchmark import Benchmark
 6from .cc_for_cxx import CCForCXX
 7from .demo import Demo
 8from .embed_bitcode import EmbedBitcode
 9from .find_inputs import FindInputs
10from .find_outputs import FindOutputs
11from .ignore_flags import IgnoreFlags
12from .ignore_flto import IgnoreFlto
13from .ignore_werror import IgnoreWerror
14from .inject_flags import InjectFlags
15from .lint import Lint
16from .record import Record
17from .skip_strip import SkipStrip
18
19__all__ = [
20    "Benchmark",
21    "CCForCXX",
22    "Demo",
23    "EmbedBitcode",
24    "FindInputs",
25    "FindOutputs",
26    "IgnoreFlags",
27    "IgnoreFlto",
28    "IgnoreWerror",
29    "InjectFlags",
30    "Lint",
31    "Record",
32    "SkipStrip",
33]
class Benchmark(blight.action.Action):
44class Benchmark(Action):
45    def before_run(self, tool: Tool) -> None:
46        self._start_nanos = time.monotonic_ns()
47
48    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
49        elapsed = (time.monotonic_ns() - self._start_nanos) // 1000
50        bench = BenchmarkRecord(tool=tool, elapsed=elapsed, run_skipped=run_skipped)
51
52        if tool.is_journaling():
53            self._result = bench.dict()
54        else:
55            bench_file = Path(self._config["output"])
56            with flock_append(bench_file) as io:
57                print(bench.json(), file=io)

A generic action, run with every tool (both before and after the tool's execution).

def before_run(self, tool: blight.tool.Tool) -> None:
45    def before_run(self, tool: Tool) -> None:
46        self._start_nanos = time.monotonic_ns()

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

def after_run(self, tool: blight.tool.Tool, *, run_skipped: bool = False) -> None:
48    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
49        elapsed = (time.monotonic_ns() - self._start_nanos) // 1000
50        bench = BenchmarkRecord(tool=tool, elapsed=elapsed, run_skipped=run_skipped)
51
52        if tool.is_journaling():
53            self._result = bench.dict()
54        else:
55            bench_file = Path(self._config["output"])
56            with flock_append(bench_file) as io:
57                print(bench.json(), file=io)

Invoked right after the underlying tool is run.

Args: tool: The tool that just ran

Inherited Members
blight.action.Action
Action
result
class CCForCXX(blight.action.CCAction):
10class CCForCXX(CCAction):
11    """
12    An action for detecting whether the C compiler is being used as if it's
13    a C++ compiler, and correcting the build when so.
14
15    This action is used to fix a particular kind of misconfigured C++ build,
16    where the C++ compiler is referred to as if it were a C compiler.
17
18    For example, in Make:
19
20    ```make
21    CC := clang++
22    CFLAGS := -std=c++17
23
24    all:
25        $(CC) $(CFLAGS) -o whatever foo.cpp bar.cpp
26    ```
27
28    Whereas the correct use would be:
29
30    ```make
31    CXX := clang++
32    CXXFLAGS := -std=c++17
33
34    all:
35        $(CXX) $(CXXFLAGS) -o whatever foo.cpp bar.cpp
36    ```
37
38    This action fixes these builds by checking whether `CC` is being used
39    as a C++ compiler. If it is, it explicitly injects additional flags
40    to force the compiler into C++ mode.
41    """
42
43    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
44    # substitution principle violation -- it can't see that `CompilerAction`
45    # is safely specialized for `CompilerTool`.
46    def before_run(self, tool: CC) -> None:  # type: ignore
47        # NOTE(ww): Currently, the only way we check whether CC is being used
48        # as a C++ compiler is by checking whether one of the `-std=c++XX`
49        # flags has been passed. This won't catch all cases; someone could use
50        # CC as a C++ compiler with the default C++ standard.
51        # Other options for detecting this:
52        # * Check for common C++-only linkages, like -lstdc++fs
53        # * Check whether tool.inputs contains files that look like C++
54        if tool.std.is_cxxstd():
55            tool.args[:0] = ["-x", "c++"]

An action for detecting whether the C compiler is being used as if it's a C++ compiler, and correcting the build when so.

This action is used to fix a particular kind of misconfigured C++ build, where the C++ compiler is referred to as if it were a C compiler.

For example, in Make:

CC := clang++
CFLAGS := -std=c++17

all:
    $(CC) $(CFLAGS) -o whatever foo.cpp bar.cpp

Whereas the correct use would be:

CXX := clang++
CXXFLAGS := -std=c++17

all:
    $(CXX) $(CXXFLAGS) -o whatever foo.cpp bar.cpp

This action fixes these builds by checking whether CC is being used as a C++ compiler. If it is, it explicitly injects additional flags to force the compiler into C++ mode.

def before_run(self, tool: blight.tool.CC) -> None:
46    def before_run(self, tool: CC) -> None:  # type: ignore
47        # NOTE(ww): Currently, the only way we check whether CC is being used
48        # as a C++ compiler is by checking whether one of the `-std=c++XX`
49        # flags has been passed. This won't catch all cases; someone could use
50        # CC as a C++ compiler with the default C++ standard.
51        # Other options for detecting this:
52        # * Check for common C++-only linkages, like -lstdc++fs
53        # * Check whether tool.inputs contains files that look like C++
54        if tool.std.is_cxxstd():
55            tool.args[:0] = ["-x", "c++"]

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class Demo(blight.action.Action):
12class Demo(Action):
13    def before_run(self, tool: Tool) -> None:
14        print(f"[demo] before-run: {tool.wrapped_tool()}", file=sys.stderr)
15
16    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
17        print(f"[demo] after-run: {tool.wrapped_tool()}", file=sys.stderr)

A generic action, run with every tool (both before and after the tool's execution).

def before_run(self, tool: blight.tool.Tool) -> None:
13    def before_run(self, tool: Tool) -> None:
14        print(f"[demo] before-run: {tool.wrapped_tool()}", file=sys.stderr)

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

def after_run(self, tool: blight.tool.Tool, *, run_skipped: bool = False) -> None:
16    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
17        print(f"[demo] after-run: {tool.wrapped_tool()}", file=sys.stderr)

Invoked right after the underlying tool is run.

Args: tool: The tool that just ran

Inherited Members
blight.action.Action
Action
result
class EmbedBitcode(blight.action.CompilerAction):
14class EmbedBitcode(CompilerAction):
15    """
16    An action to embed bitcode in compiler tool outputs.
17
18    This action assumes that the compiler toolchain is LLVM based, and supports
19    the `-fembed-bitcode` option. It injects `-fembed-bitcode` into each invocation,
20    and lets the compiler tools take care of the rest.
21
22    Example:
23
24    ```bash
25    export BLIGHT_ACTIONS="EmbedBitcode"
26    make CC=blight-cc
27    ```
28    """
29
30    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
31        # TODO(ww): It probably makes sense to sanity check the arguments here,
32        # just in case the build is being run with some other flags that are
33        # relevant to bitcode generation (e.g. `-emit-llvm` or `-flto`).
34        tool.args = ["-fembed-bitcode", *tool.args]

An action to embed bitcode in compiler tool outputs.

This action assumes that the compiler toolchain is LLVM based, and supports the -fembed-bitcode option. It injects -fembed-bitcode into each invocation, and lets the compiler tools take care of the rest.

Example:

export BLIGHT_ACTIONS="EmbedBitcode"
make CC=blight-cc
def before_run(self, tool: blight.tool.CompilerTool) -> None:
30    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
31        # TODO(ww): It probably makes sense to sanity check the arguments here,
32        # just in case the build is being run with some other flags that are
33        # relevant to bitcode generation (e.g. `-emit-llvm` or `-flto`).
34        tool.args = ["-fembed-bitcode", *tool.args]

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class FindInputs(blight.action.Action):
 83class FindInputs(Action):
 84    def before_run(self, tool: Tool) -> None:
 85        inputs = []
 86        for input in tool.inputs:
 87            input_path = Path(input)
 88            if not input_path.is_absolute():
 89                input_path = tool.cwd / input_path
 90
 91            kind = INPUT_SUFFIX_KIND_MAP.get(input_path.suffix, InputKind.Unknown)
 92
 93            inputs.append(Input(prenormalized_path=input, kind=kind, path=input_path))
 94
 95        self._inputs = inputs
 96
 97    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
 98        inputs = InputsRecord(tool=tool, inputs=self._inputs)
 99
100        if tool.is_journaling():
101            # NOTE(ms): The `tool` member is excluded to avoid journal bloat.
102            self._result = inputs.dict(exclude={"tool"})
103        else:
104            output_path = Path(self._config["output"])
105            with flock_append(output_path) as io:
106                print(inputs.json(), file=io)

A generic action, run with every tool (both before and after the tool's execution).

def before_run(self, tool: blight.tool.Tool) -> None:
84    def before_run(self, tool: Tool) -> None:
85        inputs = []
86        for input in tool.inputs:
87            input_path = Path(input)
88            if not input_path.is_absolute():
89                input_path = tool.cwd / input_path
90
91            kind = INPUT_SUFFIX_KIND_MAP.get(input_path.suffix, InputKind.Unknown)
92
93            inputs.append(Input(prenormalized_path=input, kind=kind, path=input_path))
94
95        self._inputs = inputs

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

def after_run(self, tool: blight.tool.Tool, *, run_skipped: bool = False) -> None:
 97    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
 98        inputs = InputsRecord(tool=tool, inputs=self._inputs)
 99
100        if tool.is_journaling():
101            # NOTE(ms): The `tool` member is excluded to avoid journal bloat.
102            self._result = inputs.dict(exclude={"tool"})
103        else:
104            output_path = Path(self._config["output"])
105            with flock_append(output_path) as io:
106                print(inputs.json(), file=io)

Invoked right after the underlying tool is run.

Args: tool: The tool that just ran

Inherited Members
blight.action.Action
Action
result
class FindOutputs(blight.action.Action):
 89class FindOutputs(Action):
 90    def before_run(self, tool: Tool) -> None:
 91        outputs = []
 92        for output in tool.outputs:
 93            output_path = Path(output)
 94            if not output_path.is_absolute():
 95                output_path = tool.cwd / output_path
 96
 97            # Special cases: a.out is produced by both the linker and compiler tools by default,
 98            # and some tools (like `install`) have modes that produce directories as outputs.
 99            if output_path.name == "a.out" and isinstance(tool, (CC, CXX, LD)):
100                kind = OutputKind.Executable
101            elif tool.__class__ == INSTALL and tool.directory_mode:  # type: ignore
102                kind = OutputKind.Directory
103            else:
104                kind = OUTPUT_SUFFIX_KIND_MAP.get(output_path.suffix, OutputKind.Unknown)
105
106                # Last attempt: try some common patterns for output kinds if we can't
107                # match the suffix precisely.
108                if kind == OutputKind.Unknown:
109                    kind = next(
110                        (
111                            k
112                            for (p, k) in OUTPUT_SUFFIX_PATTERN_MAP.items()
113                            if re.match(p, str(output_path))
114                        ),
115                        OutputKind.Unknown,
116                    )
117
118            outputs.append(Output(prenormalized_path=output, kind=kind, path=output_path))
119
120        self._outputs = outputs
121
122    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
123        store = self._config.get("store")
124        if store is not None:
125            store_path = Path(store)
126            store_path.mkdir(parents=True, exist_ok=True)
127
128            for output in self._outputs:
129                # We don't copy output directories into the store, for now.
130                if output.path.is_dir():
131                    continue
132
133                if not output.path.exists():
134                    logger.warning(f"tool={tool}'s output ({output.path}) does not exist")
135                    continue
136
137                # Outputs aren't guaranteed to have unique basenames and subsequent
138                # steps in the build system could even modify a particular output
139                # in-place, so we give each output a `store_path` based on a hash
140                # of its content.
141                content_hash = hashlib.sha256(output.path.read_bytes()).hexdigest()
142                # Append hash to the filename unless `append_hash=false` is specified in the config
143                append_hash = self._config.get("append_hash") != "false"
144                filename = f"{output.path.name}-{content_hash}" if append_hash else output.path.name
145                output_store_path = store_path / filename
146                if not output_store_path.exists():
147                    shutil.copy(output.path, output_store_path)
148                output.store_path = output_store_path
149                output.content_hash = content_hash
150
151        outputs = OutputsRecord(tool=tool, outputs=self._outputs)
152
153        if tool.is_journaling():
154            # NOTE(ms): The `tool` member is excluded to avoid journal bloat.
155            self._result = outputs.dict(exclude={"tool"})
156        else:
157            output_path = Path(self._config["output"])
158            with flock_append(output_path) as io:
159                print(outputs.json(), file=io)

A generic action, run with every tool (both before and after the tool's execution).

def before_run(self, tool: blight.tool.Tool) -> None:
 90    def before_run(self, tool: Tool) -> None:
 91        outputs = []
 92        for output in tool.outputs:
 93            output_path = Path(output)
 94            if not output_path.is_absolute():
 95                output_path = tool.cwd / output_path
 96
 97            # Special cases: a.out is produced by both the linker and compiler tools by default,
 98            # and some tools (like `install`) have modes that produce directories as outputs.
 99            if output_path.name == "a.out" and isinstance(tool, (CC, CXX, LD)):
100                kind = OutputKind.Executable
101            elif tool.__class__ == INSTALL and tool.directory_mode:  # type: ignore
102                kind = OutputKind.Directory
103            else:
104                kind = OUTPUT_SUFFIX_KIND_MAP.get(output_path.suffix, OutputKind.Unknown)
105
106                # Last attempt: try some common patterns for output kinds if we can't
107                # match the suffix precisely.
108                if kind == OutputKind.Unknown:
109                    kind = next(
110                        (
111                            k
112                            for (p, k) in OUTPUT_SUFFIX_PATTERN_MAP.items()
113                            if re.match(p, str(output_path))
114                        ),
115                        OutputKind.Unknown,
116                    )
117
118            outputs.append(Output(prenormalized_path=output, kind=kind, path=output_path))
119
120        self._outputs = outputs

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

def after_run(self, tool: blight.tool.Tool, *, run_skipped: bool = False) -> None:
122    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
123        store = self._config.get("store")
124        if store is not None:
125            store_path = Path(store)
126            store_path.mkdir(parents=True, exist_ok=True)
127
128            for output in self._outputs:
129                # We don't copy output directories into the store, for now.
130                if output.path.is_dir():
131                    continue
132
133                if not output.path.exists():
134                    logger.warning(f"tool={tool}'s output ({output.path}) does not exist")
135                    continue
136
137                # Outputs aren't guaranteed to have unique basenames and subsequent
138                # steps in the build system could even modify a particular output
139                # in-place, so we give each output a `store_path` based on a hash
140                # of its content.
141                content_hash = hashlib.sha256(output.path.read_bytes()).hexdigest()
142                # Append hash to the filename unless `append_hash=false` is specified in the config
143                append_hash = self._config.get("append_hash") != "false"
144                filename = f"{output.path.name}-{content_hash}" if append_hash else output.path.name
145                output_store_path = store_path / filename
146                if not output_store_path.exists():
147                    shutil.copy(output.path, output_store_path)
148                output.store_path = output_store_path
149                output.content_hash = content_hash
150
151        outputs = OutputsRecord(tool=tool, outputs=self._outputs)
152
153        if tool.is_journaling():
154            # NOTE(ms): The `tool` member is excluded to avoid journal bloat.
155            self._result = outputs.dict(exclude={"tool"})
156        else:
157            output_path = Path(self._config["output"])
158            with flock_append(output_path) as io:
159                print(outputs.json(), file=io)

Invoked right after the underlying tool is run.

Args: tool: The tool that just ran

Inherited Members
blight.action.Action
Action
result
class IgnoreFlags(blight.action.CompilerAction):
16class IgnoreFlags(CompilerAction):
17    """
18    An action for ignoring specific flags passed to compiler
19    commands (specifically, `cc` and `c++`).
20
21    For example:
22
23    ```bash
24    export BLIGHT_WRAPPED_CC=clang
25    export BLIGHT_ACTIONS="IgnoreFlags"
26    export BLIGHT_ACTIONS_IGNOREFLAGS="FLAGS='-Werror -ffunction-sections'"
27    make CC=blight-cc
28    ```
29
30    will cause blight to remove `-Werror` and `--ffunction-sections` arguments
31    from each `clang` invocation.
32    """
33
34    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
35    # substitution principle violation -- it can't see that `CompilerAction`
36    # is safely specialized for `CompilerTool`.
37    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
38        ignore_flags = shlex.split(self._config.get("FLAGS", ""))
39        if tool.lang in [Lang.C, Lang.Cxx]:
40            tool.args = [a for a in tool.args if a not in ignore_flags]
41        else:
42            logger.debug("not ignoring flags for an unknown language")

An action for ignoring specific flags passed to compiler commands (specifically, cc and c++).

For example:

export BLIGHT_WRAPPED_CC=clang
export BLIGHT_ACTIONS="IgnoreFlags"
export BLIGHT_ACTIONS_IGNOREFLAGS="FLAGS='-Werror -ffunction-sections'"
make CC=blight-cc

will cause blight to remove -Werror and --ffunction-sections arguments from each clang invocation.

def before_run(self, tool: blight.tool.CompilerTool) -> None:
37    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
38        ignore_flags = shlex.split(self._config.get("FLAGS", ""))
39        if tool.lang in [Lang.C, Lang.Cxx]:
40            tool.args = [a for a in tool.args if a not in ignore_flags]
41        else:
42            logger.debug("not ignoring flags for an unknown language")

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class IgnoreFlto(blight.action.CompilerAction):
14class IgnoreFlto(CompilerAction):
15    """
16    An action for ignoring the `-flto` flag passed to compiler
17    commands (specifically, `cc` and `c++`). Related commands that
18    control LTO (`-flto=...`) are also ignored.
19
20    For example:
21
22    ```bash
23    export BLIGHT_WRAPPED_CC=clang
24    export BLIGHT_ACTIONS="IgnoreFlto"
25    make CC=blight-cc
26    ```
27
28    will cause blight to remove `-flto` arguments from each `clang`
29    invocation.
30    """
31
32    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
33    # substitution principle violation -- it can't see that `CompilerAction`
34    # is safely specialized for `CompilerTool`.
35    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
36        tool.args = [a for a in tool.args if not a.startswith("-flto")]

An action for ignoring the -flto flag passed to compiler commands (specifically, cc and c++). Related commands that control LTO (-flto=...) are also ignored.

For example:

export BLIGHT_WRAPPED_CC=clang
export BLIGHT_ACTIONS="IgnoreFlto"
make CC=blight-cc

will cause blight to remove -flto arguments from each clang invocation.

def before_run(self, tool: blight.tool.CompilerTool) -> None:
35    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
36        tool.args = [a for a in tool.args if not a.startswith("-flto")]

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class IgnoreWerror(blight.action.CompilerAction):
15class IgnoreWerror(CompilerAction):
16    """
17    An action for ignoring the `-Werror` flag passed to compiler
18    commands (specifically, `cc` and `c++`).
19
20    For example:
21
22    ```bash
23    export BLIGHT_WRAPPED_CC=clang
24    export BLIGHT_ACTIONS="IgnoreWerror"
25    make CC=blight-cc
26    ```
27
28    will cause blight to remove `-Werror` arguments from each `clang`
29    invocation.
30    """
31
32    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
33    # substitution principle violation -- it can't see that `CompilerAction`
34    # is safely specialized for `CompilerTool`.
35    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
36        if tool.lang in [Lang.C, Lang.Cxx]:
37            tool.args = [a for a in tool.args if a != "-Werror"]
38        else:
39            logger.debug("not injecting flags for an unknown language")

An action for ignoring the -Werror flag passed to compiler commands (specifically, cc and c++).

For example:

export BLIGHT_WRAPPED_CC=clang
export BLIGHT_ACTIONS="IgnoreWerror"
make CC=blight-cc

will cause blight to remove -Werror arguments from each clang invocation.

def before_run(self, tool: blight.tool.CompilerTool) -> None:
35    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
36        if tool.lang in [Lang.C, Lang.Cxx]:
37            tool.args = [a for a in tool.args if a != "-Werror"]
38        else:
39            logger.debug("not injecting flags for an unknown language")

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class InjectFlags(blight.action.CompilerAction):
16class InjectFlags(CompilerAction):
17    """
18    An action for injecting flags into compiler commands (specifically, `cc` and `c++`).
19
20    This action takes the following flags in its configuration and appends them
21    (after shell-splitting) as appropriate:
22    - `CFLAGS`: Flags to append to every C compiler call
23    - `CFLAGS_LINKER`: Flags to append to every C compiler call that runs the linking
24     stage (i.e: no `-c`, `-e`, `-S`, etc. flags present)
25    - `CXXFLAGS`: Same as `CFLAGS` but for C++
26    - `CFLAGS_LINKER`: Same as `CFLAGS_LINKER`, but for C++
27    - `CPPFLAGS`: Flags to append for the preprocessor stage
28
29    For example:
30
31    ```bash
32    export BLIGHT_WRAPPED_CC=clang
33    export BLIGHT_ACTIONS="InjectFlags"
34    export BLIGHT_ACTION_INJECTFLAGS="CFLAGS='-g -O0' CPPFLAGS='-DWHATEVER'"
35    make CC=blight-cc
36    ```
37
38    will cause blight to add `-g -O0 -DWHATEVER` to each `clang` invocation
39    (unless it's a C++ invocation, e.g. via `-x c++`).
40    """
41
42    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
43    # substitution principle violation -- it can't see that `CompilerAction`
44    # is safely specialized for `CompilerTool`.
45    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
46        cflags = shlex.split(self._config.get("CFLAGS", ""))
47        cflags_linker = shlex.split(self._config.get("CFLAGS_LINKER", ""))
48        cxxflags = shlex.split(self._config.get("CXXFLAGS", ""))
49        cxxflags_linker = shlex.split(self._config.get("CXXFLAGS_LINKER", ""))
50        cppflags = shlex.split(self._config.get("CPPFLAGS", ""))
51
52        if tool.lang == Lang.C:
53            tool.args += cflags
54            tool.args += cppflags
55            if tool.stage is CompilerStage.AllStages:
56                tool.args += cflags_linker
57        elif tool.lang == Lang.Cxx:
58            tool.args += cxxflags
59            tool.args += cppflags
60            if tool.stage is CompilerStage.AllStages:
61                tool.args += cxxflags_linker
62        else:
63            logger.debug("not injecting flags for an unknown language")

An action for injecting flags into compiler commands (specifically, cc and c++).

This action takes the following flags in its configuration and appends them (after shell-splitting) as appropriate:

  • CFLAGS: Flags to append to every C compiler call
  • CFLAGS_LINKER: Flags to append to every C compiler call that runs the linking stage (i.e: no -c, -e, -S, etc. flags present)
  • CXXFLAGS: Same as CFLAGS but for C++
  • CFLAGS_LINKER: Same as CFLAGS_LINKER, but for C++
  • CPPFLAGS: Flags to append for the preprocessor stage

For example:

export BLIGHT_WRAPPED_CC=clang
export BLIGHT_ACTIONS="InjectFlags"
export BLIGHT_ACTION_INJECTFLAGS="CFLAGS='-g -O0' CPPFLAGS='-DWHATEVER'"
make CC=blight-cc

will cause blight to add -g -O0 -DWHATEVER to each clang invocation (unless it's a C++ invocation, e.g. via -x c++).

def before_run(self, tool: blight.tool.CompilerTool) -> None:
45    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
46        cflags = shlex.split(self._config.get("CFLAGS", ""))
47        cflags_linker = shlex.split(self._config.get("CFLAGS_LINKER", ""))
48        cxxflags = shlex.split(self._config.get("CXXFLAGS", ""))
49        cxxflags_linker = shlex.split(self._config.get("CXXFLAGS_LINKER", ""))
50        cppflags = shlex.split(self._config.get("CPPFLAGS", ""))
51
52        if tool.lang == Lang.C:
53            tool.args += cflags
54            tool.args += cppflags
55            if tool.stage is CompilerStage.AllStages:
56                tool.args += cflags_linker
57        elif tool.lang == Lang.Cxx:
58            tool.args += cxxflags
59            tool.args += cppflags
60            if tool.stage is CompilerStage.AllStages:
61                tool.args += cxxflags_linker
62        else:
63            logger.debug("not injecting flags for an unknown language")

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class Lint(blight.action.CompilerAction):
14class Lint(CompilerAction):
15    """
16    A "linting" action for common command-line mistakes.
17
18    At the moment, this catches mistakes like:
19
20    * `-DFORTIFY_SOURCE=...` instead of `-D_FORTIFY_SOURCE=...`
21
22    For example:
23
24    ```bash
25    export BLIGHT_WRAPPED_CC=clang
26    export BLIGHT_ACTIONS="Lint"
27    make CC=blight-cc
28    ```
29    """
30
31    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
32    # substitution principle violation -- it can't see that `CompilerAction`
33    # is safely specialized for `CompilerTool`.
34    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
35        for name, _ in tool.defines:
36            # TODO: Maybe do something more drastic here, like stopping the run.
37            if name == "FORTIFY_SOURCE":
38                logger.warning("found -DFORTIFY_SOURCE; you probably meant: -D_FORTIFY_SOURCE")

A "linting" action for common command-line mistakes.

At the moment, this catches mistakes like:

  • -DFORTIFY_SOURCE=... instead of -D_FORTIFY_SOURCE=...

For example:

export BLIGHT_WRAPPED_CC=clang
export BLIGHT_ACTIONS="Lint"
make CC=blight-cc
def before_run(self, tool: blight.tool.CompilerTool) -> None:
34    def before_run(self, tool: CompilerTool) -> None:  # type: ignore
35        for name, _ in tool.defines:
36            # TODO: Maybe do something more drastic here, like stopping the run.
37            if name == "FORTIFY_SOURCE":
38                logger.warning("found -DFORTIFY_SOURCE; you probably meant: -D_FORTIFY_SOURCE")

Invoked right before the underlying tool is run.

Args: tool: The tool about to run

class Record(blight.action.Action):
22class Record(Action):
23    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
24        # TODO(ww): Restructure this dictionary; it should be more like:
25        # { run: {...}, tool: {...}}
26        tool_record = tool.asdict()
27        tool_record["run_skipped"] = run_skipped
28
29        if tool.is_journaling():
30            self._result = tool_record
31        else:
32            record_file = Path(self._config["output"])
33            with flock_append(record_file) as io:
34                print(json.dumps(tool_record), file=io)

A generic action, run with every tool (both before and after the tool's execution).

def after_run(self, tool: blight.tool.Tool, *, run_skipped: bool = False) -> None:
23    def after_run(self, tool: Tool, *, run_skipped: bool = False) -> None:
24        # TODO(ww): Restructure this dictionary; it should be more like:
25        # { run: {...}, tool: {...}}
26        tool_record = tool.asdict()
27        tool_record["run_skipped"] = run_skipped
28
29        if tool.is_journaling():
30            self._result = tool_record
31        else:
32            record_file = Path(self._config["output"])
33            with flock_append(record_file) as io:
34                print(json.dumps(tool_record), file=io)

Invoked right after the underlying tool is run.

Args: tool: The tool that just ran

class SkipStrip(blight.action.STRIPAction):
15class SkipStrip(STRIPAction):
16    # NOTE(ww): type ignore here because mypy thinks this is a Liskov
17    # substitution principle violation -- it can't see that `CompilerAction`
18    # is safely specialized for `CompilerTool`.
19    def before_run(self, tool: STRIP) -> None:  # type: ignore
20        raise SkipRun

A strip action, run whenever the tool is a blight.tool.STRIP instance.

def before_run(self, tool: blight.tool.STRIP) -> None:
19    def before_run(self, tool: STRIP) -> None:  # type: ignore
20        raise SkipRun

Invoked right before the underlying tool is run.

Args: tool: The tool about to run