diff options
author | Matthias Maennich <maennich@google.com> | 2021-12-08 20:26:49 +0000 |
---|---|---|
committer | Matthias Männich <maennich@google.com> | 2021-12-09 09:56:41 +0000 |
commit | b519019a0ebf6f65611327f572cf751360550078 (patch) | |
tree | 4511c1b4706ce5989a9a2e19f77b7d1301c7baab | |
parent | bf0addcb425c10a3bf9fd70a494066956f0b5347 (diff) | |
download | interceptor-b519019a0ebf6f65611327f572cf751360550078.tar.gz |
interceptor: add an initial implementation for fake execution
Add an option to allow fake execution. This is enabled by running the
interceptor with --fake | -f, which it will enable analyzers during
execution to rewrite the command before execution (but after logging it
away). The default_fake implementation replaces the entire command by an
equivalent of `sh -c "truncate --size 0 <output> <output> ..."`. That
effectively touches all outputs leaving empty files behind. This is
enabled for the existing analyzers and possibly a good default for many
more. Yet there is no guarantee that this will actually make the build
succeed if downstream rules that are not captured by analyzers stumble
when working with the empty files.
The reason for wrapping the `truncate` in /bin/sh is to avoid having to
do the PATH lookup - $(which truncate) - ourselves. Hermetic toolchains
might provide a different `truncate` than /usr/bin/truncate, e.g. the
toybox variant.
Bug: 205735739
Signed-off-by: Matthias Maennich <maennich@google.com>
Change-Id: Ib8478f52940071d07f6544d8ac4804048404ca64
-rw-r--r-- | interceptor.cc | 33 | ||||
-rw-r--r-- | interceptor.h | 1 | ||||
-rw-r--r-- | main.cc | 11 |
3 files changed, 44 insertions, 1 deletions
diff --git a/interceptor.cc b/interceptor.cc index 79f56e5..30eab52 100644 --- a/interceptor.cc +++ b/interceptor.cc @@ -177,6 +177,7 @@ class Analyzer { void set_inputs_outputs(Command* command) const; virtual bool make_relative(Command*, const fs::path&) const { return false; } virtual bool should_recurse(Command*) const { return true; } + virtual bool make_fake(Command*) const { return false; } protected: virtual InputsOutputs determine_inputs_outputs(const Command&) const { return {}; } @@ -196,6 +197,28 @@ void Analyzer::set_inputs_outputs(Command* command) const { } } +int default_fake(Command* command) { + if (command->outputs().empty()) { + return false; + } + + // rewrite the command to just produce empty files for any output + command->set_program("/bin/sh"); + command->clear_arguments(); + command->add_arguments("/bin/sh"); + command->add_arguments("-c"); + + // truncate makes sure we leave an empty file even if the output existed from + // an earlier run. + std::ostringstream command_line("truncate -s 0", std::ios_base::ate); + for (const auto& output : command->outputs()) { + command_line << ' ' << output; + } + command->add_arguments(command_line.str()); + + return true; +} + class CompileLinkerAnalyzer : public Analyzer { InputsOutputs determine_inputs_outputs(const Command& command) const final { static constexpr std::array kSkipNextArguments{ @@ -249,6 +272,7 @@ class CompileLinkerAnalyzer : public Analyzer { // do not recurse the interceptor into the subprocesses of compilers/linkers; // otherwise we will trace (and get confused by) ld.lld/cc1-plus and friends. bool should_recurse(Command*) const final { return false; } + bool make_fake(Command* command) const final { return default_fake(command); } }; class ArchiverAnalyzer : public Analyzer { @@ -273,6 +297,7 @@ class ArchiverAnalyzer : public Analyzer { return default_make_relative(command, root_directory); } bool should_recurse(Command*) const final { return false; } + bool make_fake(Command* command) const final { return default_fake(command); } }; class FixdepAnalyzer : public Analyzer { @@ -291,6 +316,8 @@ class FixdepAnalyzer : public Analyzer { return result; }; + + bool make_fake(Command* command) const final { return default_fake(command); } }; static const std::initializer_list< @@ -359,6 +386,12 @@ static std::optional<interceptor::Command> process_command(const char* filename, log(command); + // now that we have logged the command away, we can entirely rewrite it, or + // whatever the analyzer thinks a good fake execution looks like. + if (command_getenv(command, kEnvFake)) { + transformed |= analyzer->make_fake(&command); + } + if (transformed) { return command; } diff --git a/interceptor.h b/interceptor.h index bdb15f1..5afe296 100644 --- a/interceptor.h +++ b/interceptor.h @@ -26,6 +26,7 @@ constexpr static auto kEnvCommandLog = "INTERCEPTOR_command_log"; constexpr static auto kEnvRootDirectory = "INTERCEPTOR_root_directory"; constexpr static auto kEnvMakeRelative = "INTERCEPTOR_make_relative"; +constexpr static auto kEnvFake = "INTERCEPTOR_fake"; namespace interceptor { @@ -34,6 +34,7 @@ struct Options { std::string command_line; std::optional<fs::path> command_log; bool make_relative = false; + bool fake = false; }; static Options parse_args(int argc, char* argv[]) { @@ -43,12 +44,13 @@ static Options parse_args(int argc, char* argv[]) { static struct option long_options[] = { {"command-log", required_argument, 0, 'l'}, {"make-relative", no_argument, 0, 'r'}, + {"fake", no_argument, 0, 'f'}, {0, 0, 0, 0}, }; /* getopt_long stores the option index here. */ int option_index = 0; - auto c = getopt_long(argc, argv, "l:r", long_options, &option_index); + auto c = getopt_long(argc, argv, "l:rf", long_options, &option_index); /* Detect the end of the options. */ if (c == -1) { @@ -64,6 +66,10 @@ static Options parse_args(int argc, char* argv[]) { result.make_relative = true; break; + case 'f': + result.fake = true; + break; + case '?': /* getopt_long already printed an error message. */ break; @@ -89,6 +95,9 @@ static void process_options(const Options& options) { if (options.make_relative) { setenv(kEnvMakeRelative, "1", 1); } + if (options.fake) { + setenv(kEnvFake, "1", 1); + } } static void setup_interceptor_library_path() { |