aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Maennich <maennich@google.com>2021-12-08 20:26:49 +0000
committerMatthias Männich <maennich@google.com>2021-12-09 09:56:41 +0000
commitb519019a0ebf6f65611327f572cf751360550078 (patch)
tree4511c1b4706ce5989a9a2e19f77b7d1301c7baab
parentbf0addcb425c10a3bf9fd70a494066956f0b5347 (diff)
downloadinterceptor-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.cc33
-rw-r--r--interceptor.h1
-rw-r--r--main.cc11
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 {
diff --git a/main.cc b/main.cc
index 587886d..dce5abb 100644
--- a/main.cc
+++ b/main.cc
@@ -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() {