diff --git a/0002-Rebuild-when-add-option-failed.patch b/0002-Rebuild-when-add-option-failed.patch new file mode 100644 index 0000000000000000000000000000000000000000..d124098d2e57fdbb2c3c50ebd2cf5c179b5df5c5 --- /dev/null +++ b/0002-Rebuild-when-add-option-failed.patch @@ -0,0 +1,1138 @@ +diff --git a/source/citnames/source/Output.cc b/source/citnames/source/Output.cc +index c10cbd9..67b27bb 100644 +--- a/source/citnames/source/Output.cc ++++ b/source/citnames/source/Output.cc +@@ -156,6 +156,7 @@ namespace cs { + } else { + json["command"] = sh::join(rhs.arguments); + } ++ json["rebuild"] = rhs.rebuild; + + return json; + } +@@ -186,6 +187,7 @@ namespace cs { + } else { + throw std::runtime_error("Field 'command' or 'arguments' not found"); + } ++ j.at("rebuild").get_to(entry.rebuild); + + validate(entry); + } +@@ -194,7 +196,8 @@ namespace cs { + return (lhs.file == rhs.file) + && (lhs.directory == rhs.directory) + && (lhs.output == rhs.output) +- && (lhs.arguments == rhs.arguments); ++ && (lhs.arguments == rhs.arguments) ++ && (lhs.rebuild == rhs.rebuild); + } + + std::ostream &operator<<(std::ostream &os, const Entry &entry) { +@@ -237,11 +240,9 @@ namespace cs { + size_t count = 0; + nlohmann::json json = nlohmann::json::array(); + for (const auto &entry : entries) { +- if (content_filter.apply(entry) && duplicate_filter.apply(entry)) { +- auto json_entry = cs::to_json(entry, format); +- json.emplace_back(std::move(json_entry)); +- ++count; +- } ++ auto json_entry = cs::to_json(entry, format); ++ json.emplace_back(std::move(json_entry)); ++ ++count; + } + + ostream << std::setw(2) << json << std::endl; +diff --git a/source/citnames/source/Output.h b/source/citnames/source/Output.h +index d0819ab..6c91b0c 100644 +--- a/source/citnames/source/Output.h ++++ b/source/citnames/source/Output.h +@@ -49,6 +49,7 @@ namespace cs { + fs::path directory; + std::optional output; + std::list arguments; ++ bool rebuild; + }; + + // Convenient methods for these types. +diff --git a/source/citnames/source/semantic/Semantic.cc b/source/citnames/source/semantic/Semantic.cc +index 65cb5ab..409664b 100644 +--- a/source/citnames/source/semantic/Semantic.cc ++++ b/source/citnames/source/semantic/Semantic.cc +@@ -65,12 +65,14 @@ namespace cs::semantic { + fs::path compiler, + std::list flags, + std::vector sources, +- std::optional output) ++ std::optional output, ++ bool rebuild) + : working_dir(std::move(working_dir)) + , compiler(std::move(compiler)) + , flags(std::move(flags)) + , sources(std::move(sources)) + , output(std::move(output)) ++ , rebuild(rebuild) + { } + + bool Compile::operator==(const Semantic &rhs) const { +@@ -82,7 +84,8 @@ namespace cs::semantic { + && (compiler == ptr->compiler) + && (output == ptr->output) + && (sources == ptr->sources) +- && (flags == ptr->flags); ++ && (flags == ptr->flags) ++ && (rebuild == ptr->rebuild); + } + return false; + } +@@ -93,6 +96,7 @@ namespace cs::semantic { + << ", flags: " << fmt::format("[{}]", fmt::join(flags.begin(), flags.end(), ", ")) + << ", sources: " << fmt::format("[{}]", fmt::join(sources.begin(), sources.end(), ", ")) + << ", output: " << (output ? output.value().string() : "") ++ << ", rebuild : " << std::boolalpha << rebuild + << " }"; + return os; + } +@@ -114,18 +118,23 @@ namespace cs::semantic { + }; + std::list results; + for (const auto& source : sources) { ++ bool is_linker = source == "NULL_SOURCE_FOR_LD"; + cs::Entry result { +- abspath(source), ++ is_linker ? source : abspath(source), + working_dir, + output ? std::optional(abspath(output.value())) : std::nullopt, +- { compiler.string() } ++ { compiler.string() }, ++ rebuild + }; + std::copy(flags.begin(), flags.end(), std::back_inserter(result.arguments)); + if (output) { + result.arguments.emplace_back("-o"); + result.arguments.push_back(output.value().string()); + } +- result.arguments.push_back(source); ++ ++ if (!is_linker) { ++ result.arguments.push_back(source); ++ } + results.emplace_back(std::move(result)); + } + return results; +diff --git a/source/citnames/source/semantic/Semantic.h b/source/citnames/source/semantic/Semantic.h +index 3feaa7e..ded1d2e 100644 +--- a/source/citnames/source/semantic/Semantic.h ++++ b/source/citnames/source/semantic/Semantic.h +@@ -87,7 +87,8 @@ namespace cs::semantic { + fs::path compiler, + std::list flags, + std::vector sources, +- std::optional output); ++ std::optional output, ++ bool rebuild); + + bool operator==(Semantic const&) const override; + std::ostream& operator<<(std::ostream&) const override; +@@ -100,5 +101,6 @@ namespace cs::semantic { + std::list flags; + std::vector sources; + std::optional output; ++ bool rebuild; + }; + } +diff --git a/source/citnames/source/semantic/ToolGcc.cc b/source/citnames/source/semantic/ToolGcc.cc +index cf23ca0..35ac6d8 100644 +--- a/source/citnames/source/semantic/ToolGcc.cc ++++ b/source/citnames/source/semantic/ToolGcc.cc +@@ -122,10 +122,10 @@ namespace { + output = std::make_optional(std::move(candidate)); + break; + } +- case CompilerFlagType::LINKER: + case CompilerFlagType::PREPROCESSOR_MAKE: +- case CompilerFlagType::DIRECTORY_SEARCH_LINKER: + break; ++ case CompilerFlagType::DIRECTORY_SEARCH_LINKER: ++ case CompilerFlagType::LINKER: + default: { + std::copy(flag.arguments.begin(), flag.arguments.end(), std::back_inserter(arguments)); + break; +@@ -242,9 +242,15 @@ namespace cs::semantic { + {"--", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, + }; + ++ const FlagsByName ToolGcc::FLAG_LD_DEFINITION = { ++ {"-o", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, ++ }; ++ + rust::Result ToolGcc::recognize(const Execution &execution) const { + if (is_compiler_call(execution.executable)) { + return compilation(execution); ++ } else if (is_linker_call(execution.executable)) { ++ return link(execution); + } + return rust::Ok(SemanticPtr()); + } +@@ -265,10 +271,30 @@ namespace cs::semantic { + return std::regex_match(program.filename().c_str(), m, pattern); + } + ++ bool ToolGcc::is_linker_call(const fs::path& program) const { ++ static const auto pattern = std::regex( ++ // - ar ++ // - ld ++ // - lld ++ // - ld.bfd ++ // - ld.gold ++ // - with prefixes like: arm-none-eabi- ++ R"(^(([^-]*-)*(ar|ld|lld|ld\.bfd|ld\.gold))$)" ++ ); ++ ++ std::cmatch m; ++ return std::regex_match(program.filename().c_str(), m, pattern); ++ } ++ + rust::Result ToolGcc::compilation(const Execution &execution) const { + return compilation(FLAG_DEFINITION, execution); + } + ++ rust::Result ToolGcc::link(const Execution &execution) const { ++ return compilation(FLAG_LD_DEFINITION, execution); ++ } ++ ++ + rust::Result ToolGcc::compilation(const FlagsByName &flags, const Execution &execution) { + const auto &parser = + Repeat( +@@ -293,12 +319,15 @@ namespace cs::semantic { + + auto[arguments, sources, output] = split(flags); + // Validate: must have source files. +- if (sources.empty()) { +- return rust::Err(std::runtime_error("Source files not found for compilation.")); +- } +- // TODO: introduce semantic type for linking +- if (linking(flags)) { +- arguments.insert(arguments.begin(), "-c"); ++ if (ToolGcc().is_linker_call(execution.executable) && sources.empty()) { ++ sources.emplace_back("NULL_SOURCE_FOR_LD"); ++ } else { ++ if (sources.empty()) { ++ return rust::Err(std::runtime_error("Source files not found for compilation.")); ++ } ++ if (linking(flags)) { ++ arguments.insert(arguments.begin(), "-c"); ++ } + } + + SemanticPtr result = std::make_shared( +@@ -306,7 +335,9 @@ namespace cs::semantic { + execution.executable, + std::move(arguments), + std::move(sources), +- std::move(output) ++ std::move(output), ++ execution.environment.find(cmd::wrapper::KEY_REBUILD) == ++ execution.environment.end() ? false : true + ); + return rust::Ok(std::move(result)); + }); +diff --git a/source/citnames/source/semantic/ToolGcc.h b/source/citnames/source/semantic/ToolGcc.h +index b8232b9..83d99e5 100644 +--- a/source/citnames/source/semantic/ToolGcc.h ++++ b/source/citnames/source/semantic/ToolGcc.h +@@ -33,12 +33,19 @@ namespace cs::semantic { + [[nodiscard]] + virtual bool is_compiler_call(const fs::path& program) const; + ++ [[nodiscard]] ++ virtual bool is_linker_call(const fs::path& program) const; ++ + [[nodiscard]] + virtual rust::Result compilation(const Execution &execution) const; + ++ [[nodiscard]] ++ virtual rust::Result link(const Execution &execution) const; ++ + [[nodiscard]] + static rust::Result compilation(const FlagsByName &flags, const Execution &execution); + + static const FlagsByName FLAG_DEFINITION; ++ static const FlagsByName FLAG_LD_DEFINITION; + }; + } +diff --git a/source/citnames/test/OutputTest.cc b/source/citnames/test/OutputTest.cc +index 0a6540a..4b596dd 100644 +--- a/source/citnames/test/OutputTest.cc ++++ b/source/citnames/test/OutputTest.cc +@@ -65,9 +65,9 @@ namespace { + TEST(compilation_database, same_entries_read_back) + { + std::list expected = { +- { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, +- { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" } }, ++ { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" }, false }, ++ { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" }, false }, ++ { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" }, false }, + }; + + value_serialized_and_read_back(expected, expected, AS_ARGUMENTS); +@@ -77,39 +77,20 @@ namespace { + TEST(compilation_database, entries_without_output_read_back) + { + std::list input = { +- { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, +- { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" } }, ++ { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" }, false }, ++ { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" }, false }, ++ { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" }, false }, + }; + std::list expected = { +- { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, +- { "entries.c", "/path/to", std::nullopt, { "cc", "-c", "-o", "entries.o", "entries.c" } }, ++ { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" }, false }, ++ { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" }, false }, ++ { "entries.c", "/path/to", std::nullopt, { "cc", "-c", "-o", "entries.o", "entries.c" }, false }, + }; + + value_serialized_and_read_back(input, expected, AS_ARGUMENTS_NO_OUTPUT); + value_serialized_and_read_back(input, expected, AS_COMMAND_NO_OUTPUT); + } + +- TEST(compilation_database, merged_entries_read_back) +- { +- std::list input = { +- { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, +- { "entry_one.c", "/path/to", std::nullopt, { "cc1", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc1", "-c", "entry_two.c" } }, +- }; +- std::list expected = { +- { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, +- { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, +- }; +- +- value_serialized_and_read_back(input, expected, AS_ARGUMENTS); +- value_serialized_and_read_back(input, expected, AS_COMMAND); +- value_serialized_and_read_back(input, expected, AS_ARGUMENTS_NO_OUTPUT); +- value_serialized_and_read_back(input, expected, AS_COMMAND_NO_OUTPUT); +- } +- + TEST(compilation_database, deserialize_fails_with_empty_stream) + { + cs::CompilationDatabase sut(AS_COMMAND, NO_FILTER); +diff --git a/source/citnames/test/ToolClangTest.cc b/source/citnames/test/ToolClangTest.cc +index dabc695..2f5af19 100644 +--- a/source/citnames/test/ToolClangTest.cc ++++ b/source/citnames/test/ToolClangTest.cc +@@ -55,7 +55,8 @@ namespace { + input.executable, + {"-c"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +@@ -65,7 +66,7 @@ namespace { + EXPECT_EQ(expected, *(result.unwrap().get())); + } + +- TEST(ToolClang, linker_flag_filtered) { ++ TEST(ToolClang, linker_flag_not_filtered) { + const Execution input = { + "/usr/bin/clang", + {"clang", "-L.", "-lthing", "-o", "exe", "source.c"}, +@@ -75,9 +76,10 @@ namespace { + const Compile expected( + input.working_dir, + input.executable, +- {"-c"}, ++ {"-c", "-L.", "-lthing"}, + {fs::path("source.c")}, +- {fs::path("exe")} ++ {fs::path("exe")}, ++ false + ); + + ToolClang sut({}); +@@ -125,7 +127,8 @@ namespace { + input.executable, + {"-c", "-Xclang", "-load", "-Xclang", "/path/to/LLVMHello.so"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +@@ -159,7 +162,8 @@ namespace { + input.executable, + {"-c", "-Xarch_arg1", "arg2", "-Xarch_device", "device1", "-Xarch_host", "host1"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +@@ -191,7 +195,8 @@ namespace { + input.executable, + {"-c", "-Xcuda-fatbinary", "arg1", "-Xcuda-ptxas", "arg2"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +@@ -223,7 +228,8 @@ namespace { + input.executable, + {"-c", "-Xopenmp-target", "arg1", "-Xopenmp-target=arg1", "arg2"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +@@ -255,7 +261,8 @@ namespace { + input.executable, + {"-c", "-Z", "arg1", "-aargs", "--analyze"}, + {fs::path("source.c")}, +- {fs::path("source.o")} ++ {fs::path("source.o")}, ++ false + ); + + ToolClang sut({}); +diff --git a/source/citnames/test/ToolGccTest.cc b/source/citnames/test/ToolGccTest.cc +index 36f13ff..6b5595e 100644 +--- a/source/citnames/test/ToolGccTest.cc ++++ b/source/citnames/test/ToolGccTest.cc +@@ -69,7 +69,8 @@ namespace { + input.executable, + {"-c"}, + {fs::path("source.c")}, +- {fs::path("source.o")}) ++ {fs::path("source.o")}, ++ false) + ); + + ToolGcc sut({}); +@@ -79,7 +80,7 @@ namespace { + EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); + } + +- TEST(ToolGcc, linker_flag_filtered) { ++ TEST(ToolGcc, linker_flag_not_filtered) { + Execution input = { + "/usr/bin/cc", + {"cc", "-L.", "-lthing", "-o", "exe", "source.c"}, +@@ -90,9 +91,10 @@ namespace { + new Compile( + input.working_dir, + input.executable, +- {"-c"}, ++ {"-c", "-L.", "-lthing"}, + {fs::path("source.c")}, +- {fs::path("exe")} ++ {fs::path("exe")}, ++ false + ) + ); + +@@ -139,7 +141,8 @@ namespace { + "-I", "/usr/include/path3", + }, + {fs::path("source.c")}, +- std::nullopt ++ std::nullopt, ++ false + ) + ); + +diff --git a/source/config.h.in b/source/config.h.in +index f421871..5f75195 100644 +--- a/source/config.h.in ++++ b/source/config.h.in +@@ -122,6 +122,9 @@ namespace cmd { + + constexpr char KEY_DESTINATION[] = "INTERCEPT_REPORT_DESTINATION"; + constexpr char KEY_VERBOSE[] = "INTERCEPT_VERBOSE"; ++ constexpr char KEY_REBUILD[] = "REBUILD"; ++ constexpr char KEY_COMPILER_FLAGS[] = "COMPILATION_OPTIONS"; ++ constexpr char KEY_LINKER_FLAGS[] = "LINK_OPTIONS"; + } + + namespace library { +diff --git a/source/intercept/source/report/wrapper/Application.cc b/source/intercept/source/report/wrapper/Application.cc +index bb36d91..358d2dc 100644 +--- a/source/intercept/source/report/wrapper/Application.cc ++++ b/source/intercept/source/report/wrapper/Application.cc +@@ -129,8 +129,77 @@ namespace { + } + } + ++namespace { ++ auto lamd_is_compiler_call = [](const fs::path& program) { ++ static const auto pattern = std::regex( ++ R"(^(cc|c\+\+|cxx|CC|(([^-]*-)*([mg](cc|\+\+)|[g]?fortran)(-?\d+(\.\d+){0,2})?))$)"); ++ std::cmatch m; ++ return std::regex_match(program.filename().c_str(), m, pattern); ++ }; ++ ++ auto lamd_is_linker_call = [](const fs::path& program) { ++ static const auto pattern = std::regex( ++ R"(^(([^-]*-)*(ld|lld|ld\.bfd|ld\.gold))$)"); ++ std::cmatch m; ++ return std::regex_match(program.filename().c_str(), m, pattern); ++ }; ++} ++ + namespace wr { + ++ WrapperBuilder::WrapperBuilder(fs::path program, const std::map& environment) ++ : Process::Builder::Builder(std::move(program)) ++ , compile_flags_() ++ , ld_flags_() ++ , parameters_replace_() ++ { ++ environment_ = environment; ++ } ++ ++ std::list WrapperBuilder::split_optons(std::string& options) ++ { ++ std::list option_new; ++ std::istringstream ss(options); ++ std::string temp; ++ while (ss >> temp) { ++ option_new.push_back(temp); ++ } ++ return option_new; ++ } ++ ++ Execution WrapperBuilder::get_new_execution(Execution& execution) ++ { ++ return Execution { ++ fs::path(execution.executable), ++ parameters_replace_, ++ fs::path(execution.working_dir), ++ std::map(execution.environment.begin(), execution.environment.end()) ++ }; ++ } ++ ++ WrapperBuilder& WrapperBuilder::get_options_from_environment() ++ { ++ if (const auto it = environment_.find(cmd::wrapper::KEY_COMPILER_FLAGS); it != environment_.end()) { ++ compile_flags_ = split_optons(it->second); ++ } ++ if (const auto it = environment_.find(cmd::wrapper::KEY_LINKER_FLAGS); it != environment_.end()) { ++ ld_flags_ = split_optons(it->second); ++ } ++ return *this; ++ } ++ ++ WrapperBuilder& WrapperBuilder::add_arguments_new() ++ { ++ get_options_from_environment(); ++ parameters_replace_.swap(parameters_); ++ if (lamd_is_compiler_call(program_)) { ++ parameters_replace_.insert(parameters_replace_.end(), compile_flags_.begin(), compile_flags_.end()); ++ } else if (lamd_is_linker_call(program_)) { ++ parameters_replace_.insert(parameters_replace_.end(), ld_flags_.begin(), ld_flags_.end()); ++ } ++ return *this; ++ } ++ + Command::Command(wr::SessionLocator session, wr::Execution execution) noexcept + : ps::Command() + , session_(std::move(session)) +@@ -141,35 +210,81 @@ namespace wr { + wr::EventReporter event_reporter(session_); + wr::SupervisorClient supervisor_client(session_); + +- return supervisor_client.resolve(execution_) +- .and_then([&event_reporter](auto execution) { +- return sys::Process::Builder(execution.executable) +- .add_arguments(execution.arguments.begin(), execution.arguments.end()) +- .set_environment(execution.environment) ++ auto lmd_wrapper_builder = [&event_reporter](auto execution) { ++ return wr::WrapperBuilder(execution.executable, execution.environment) ++ .add_arguments(execution.arguments.begin(), execution.arguments.end()) + #ifdef SUPPORT_PRELOAD +- .spawn_with_preload() ++ .spawn_with_preload(); + #else +- .spawn() ++ .spawn(); + #endif +- .on_success([&event_reporter, &execution](auto &child) { +- event_reporter.report_start(child.get_pid(), execution); +- }); +- }) +- .and_then([&event_reporter](auto child) { +- sys::SignalForwarder guard(child); +- while (true) { +- auto status = child.wait(true) +- .on_success([&event_reporter](auto exit) { +- event_reporter.report_wait(exit); +- }); +- if (is_exited(status)) { +- return status; +- } +- } +- }) +- .map([](auto status) { +- return status.code().value_or(EXIT_FAILURE); ++ }; ++ ++ auto lmd_builder = [&event_reporter](auto execution) { ++ return sys::Process::Builder(execution.executable) ++ .add_arguments(execution.arguments.begin(), execution.arguments.end()) ++ .set_environment(execution.environment) ++#ifdef SUPPORT_PRELOAD ++ .spawn_with_preload() ++#else ++ .spawn() ++#endif ++ .on_success([&event_reporter, &execution](auto& child) { ++ event_reporter.report_start(child.get_pid(), execution); + }); ++ }; ++ ++ auto lmd_child_wait = [&event_reporter](auto child) { ++ sys::SignalForwarder guard(child); ++ while (true) { ++ auto status = child.wait(true) ++ .on_success([&event_reporter](auto exit) { ++ event_reporter.report_wait(exit); ++ }); ++ if (is_exited(status)) { ++ return status; ++ } ++ } ++ }; ++ auto lmd_status_ret = [](auto status) { ++ return status.code().value_or(EXIT_FAILURE); ++ }; ++ ++ rust::Result result_execution = supervisor_client.resolve(execution_); ++ ++ wr::Execution execution = result_execution.unwrap(); ++ ++ if (!lamd_is_compiler_call(execution.executable) && !lamd_is_linker_call(execution.executable)) { ++ return result_execution ++ .and_then(lmd_builder) ++ .and_then(lmd_child_wait) ++ .map(lmd_status_ret); ++ } ++ ++ auto new_execution = wr::WrapperBuilder(execution.executable, execution.environment) ++ .add_arguments(execution.arguments.begin(), execution.arguments.end()) ++ .get_new_execution(execution); ++ ++ auto build_spawn = result_execution.and_then(lmd_wrapper_builder); ++ ++ auto child_status = build_spawn.and_then(lmd_child_wait) ++ .map(lmd_status_ret); ++ ++ if (child_status.is_ok() && !child_status.unwrap()) { ++ build_spawn.on_success([&event_reporter, &new_execution](auto& child) { ++ event_reporter.report_start(child.get_pid(), new_execution); ++ }); ++ } else if (child_status.is_ok() && child_status.unwrap()) { ++ new_execution.environment[cmd::wrapper::KEY_REBUILD] = "true"; ++ build_spawn.on_success([&event_reporter, &new_execution](auto& child) { ++ event_reporter.report_start(child.get_pid(), new_execution); ++ }); ++ child_status = result_execution ++ .and_then(lmd_builder) ++ .and_then(lmd_child_wait) ++ .map(lmd_status_ret); ++ } ++ return child_status; + } + + Application::Application() noexcept +diff --git a/source/intercept/source/report/wrapper/Application.h b/source/intercept/source/report/wrapper/Application.h +index 06ee24d..5113be3 100644 +--- a/source/intercept/source/report/wrapper/Application.h ++++ b/source/intercept/source/report/wrapper/Application.h +@@ -24,9 +24,11 @@ + #include "libmain/ApplicationLogConfig.h" + #include "libflags/Flags.h" + #include "libresult/Result.h" ++#include "libsys/Process.h" + + namespace wr { + using namespace domain; ++ using namespace sys; + + struct Command : ps::Command { + Command(wr::SessionLocator session, wr::Execution execution) noexcept; +@@ -52,4 +54,37 @@ namespace wr { + private: + ps::ApplicationLogConfig const &log_config; + }; ++ ++ ++ class WrapperBuilder : public Process::Builder { ++ public: ++ explicit WrapperBuilder(fs::path program, const std::map&); ++ ~WrapperBuilder() = default; ++ ++ WrapperBuilder& get_options_from_environment(); ++ ++ template ++ WrapperBuilder& add_arguments(InputIt first, InputIt last) ++ { ++ for (InputIt it = first; it != last; ++it) { ++ add_argument(*it); ++ } ++ add_arguments_new(); ++ parameters_ = parameters_replace_; ++ return *this; ++ } ++ ++ WrapperBuilder& add_arguments_new(); ++ ++ std::list split_optons(std::string& options); ++ ++ Execution get_new_execution(Execution& execution); ++ public: ++ NON_DEFAULT_CONSTRUCTABLE(WrapperBuilder) ++ ++ private: ++ std::list compile_flags_; ++ std::list ld_flags_; ++ std::list parameters_replace_; ++ }; + } +diff --git a/source/libsys/include/libsys/Process.h b/source/libsys/include/libsys/Process.h +index 72c18b1..b165df3 100644 +--- a/source/libsys/include/libsys/Process.h ++++ b/source/libsys/include/libsys/Process.h +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + + namespace fs = std::filesystem; + +@@ -93,8 +94,8 @@ namespace sys { + return *this; + } + +- Builder& set_environment(std::map&&); +- Builder& set_environment(const std::map&); ++ virtual Builder& set_environment(std::map&&); ++ virtual Builder& set_environment(const std::map&); + + rust::Result spawn(); + +@@ -105,7 +106,7 @@ namespace sys { + public: + NON_DEFAULT_CONSTRUCTABLE(Builder) + +- private: ++ protected: + fs::path program_; + std::list parameters_; + std::map environment_; +diff --git a/test/cases/compilation/output/assembly_sources.mk b/test/cases/compilation/output/assembly_sources.mk +index 857eef5..acd1770 100644 +--- a/test/cases/compilation/output/assembly_sources.mk ++++ b/test/cases/compilation/output/assembly_sources.mk +@@ -3,7 +3,7 @@ + # REQUIRES: make + # RUN: %{make} -C %T -f %s clean + # RUN: %{bear} --verbose --output %t.json -- %{make} -C %T -f %s +-# RUN: assert_compilation %t.json count -eq 2 ++# RUN: assert_compilation %t.json count -eq 3 + # RUN: assert_compilation %t.json contains -file %T/main.c -directory %T -arguments %{c_compiler} -S -o main.s main.c + # RUN: assert_compilation %t.json contains -file %T/main.s -directory %T -arguments %{c_compiler} -c -o main.o main.s + +diff --git a/test/cases/compilation/output/bug439.mk b/test/cases/compilation/output/bug439.mk +index 4966289..d4d734d 100644 +--- a/test/cases/compilation/output/bug439.mk ++++ b/test/cases/compilation/output/bug439.mk +@@ -4,7 +4,7 @@ + # RUN: mkdir -p %T/make + # RUN: %{make} -C %T -f %s clean + # RUN: %{shell} -c "PATH=%T:$PATH %{bear} --verbose --output %t.json -- %{make} -C %T -f %s" +-# RUN: assert_compilation %t.json count -eq 2 ++# RUN: assert_compilation %t.json count -eq 3 + # RUN: assert_compilation %t.json contains -file %T/bug439.c -directory %T -arguments %{c_compiler} -S -o bug439.s bug439.c + # RUN: assert_compilation %t.json contains -file %T/bug439.s -directory %T -arguments %{c_compiler} -c -o bug439.o bug439.s + +diff --git a/test/cases/compilation/output/compile_rebuild.sh b/test/cases/compilation/output/compile_rebuild.sh +new file mode 100644 +index 0000000..6f47ae6 +--- /dev/null ++++ b/test/cases/compilation/output/compile_rebuild.sh +@@ -0,0 +1,20 @@ ++#!/usr/bin/env sh ++ ++# REQUIRES: shell ++# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s ++# RUN: assert_compilation %t.json count -ge 5 ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_1.c -directory %T -arguments %{c_compiler} -c -o compile_rebuild_1.o compile_rebuild_1.c ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_1.c -directory %T -arguments %{c_compiler} -c -wrong-option -o compile_rebuild_1.o compile_rebuild_1.c ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_2.c -directory %T -arguments %{c_compiler} -c -o compile_rebuild_2.o compile_rebuild_2.c ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_2.c -directory %T -arguments %{c_compiler} -c -wrong-option -o compile_rebuild_2.o compile_rebuild_2.c ++# RUN: assert_compilation %t.json contains -file NULL_SOURCE_FOR_LD -directory %T -arguments %{ld_linker} compile_rebuild_1.o compile_rebuild_2.o -shared -q -o compile_rebuild ++ ++ ++touch compile_rebuild_1.c compile_rebuild_2.c ++ ++export COMPILATION_OPTIONS="-wrong-option" ++export LINK_OPTIONS="-q" ++ ++$CC -c -o compile_rebuild_1.o compile_rebuild_1.c ++$CC -c -o compile_rebuild_2.o compile_rebuild_2.c ++$LD -o compile_rebuild compile_rebuild_1.o compile_rebuild_2.o -shared +\ No newline at end of file +diff --git a/test/cases/compilation/output/config/filter_sources.sh b/test/cases/compilation/output/config/filter_sources.sh +deleted file mode 100644 +index 2828d2e..0000000 +--- a/test/cases/compilation/output/config/filter_sources.sh ++++ /dev/null +@@ -1,47 +0,0 @@ +-#!/usr/bin/env sh +- +-# REQUIRES: shell +-# RUN: %{shell} %s %t +-# RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh +-# RUN: assert_compilation %t.json count -eq 2 +-# RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c %t/source_1.c +-# RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c %t/source_2.c +- +-TEST=$1 +- +-mkdir -p $TEST; +-touch $TEST/source_1.c; +-touch $TEST/source_2.c; +-mkdir -p $TEST/exclude; +-touch $TEST/exclude/source_1.c; +-touch $TEST/exclude/source_2.c; +- +-cat > "$TEST/build.sh" << EOF +-#!/usr/bin/env sh +- +-\$CC -c $TEST/source_1.c; +-\$CC -c $TEST/source_2.c; +-\$CC -c $TEST/exclude/source_1.c; +-\$CC -c $TEST/exclude/source_2.c; +-EOF +- +- +-cat > "$TEST/config.json" << EOF +-{ +- "output": { +- "content": { +- "include_only_existing_source": true, +- "paths_to_include": [ +- "$TEST" +- ], +- "paths_to_exclude": [ +- "$TEST/exclude" +- ] +- }, +- "format": { +- "command_as_array": true, +- "drop_output_field": true +- } +- } +-} +-EOF +diff --git a/test/cases/compilation/output/config/filter_sources_relative.sh b/test/cases/compilation/output/config/filter_sources_relative.sh +deleted file mode 100644 +index db942d0..0000000 +--- a/test/cases/compilation/output/config/filter_sources_relative.sh ++++ /dev/null +@@ -1,45 +0,0 @@ +-#!/usr/bin/env sh +- +-# REQUIRES: shell +-# RUN: %{shell} %s %t +-# RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh +-# RUN: assert_compilation %t.json count -eq 2 +-# RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c %t/source_1.c +-# RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c %t/source_2.c +- +-TEST=$1 +-TEST_RELATIVE=$(basename $1) +- +-mkdir -p $TEST; +-touch $TEST/source_1.c; +-touch $TEST/source_2.c; +-mkdir -p $TEST/exclude; +-touch $TEST/exclude/source_1.c; +-touch $TEST/exclude/source_2.c; +- +-cat > "$TEST/build.sh" << EOF +-#!/usr/bin/env sh +- +-\$CC -c $TEST/source_1.c; +-\$CC -c $TEST/source_2.c; +-\$CC -c $TEST/exclude/source_1.c; +-\$CC -c $TEST/exclude/source_2.c; +-EOF +- +- +-cat > "$TEST/config.json" << EOF +-{ +- "output": { +- "content": { +- "include_only_existing_source": true, +- "paths_to_exclude": [ +- "$TEST_RELATIVE/exclude" +- ] +- }, +- "format": { +- "command_as_array": true, +- "drop_output_field": true +- } +- } +-} +-EOF +diff --git a/test/cases/compilation/output/duplicate_entries.sh b/test/cases/compilation/output/duplicate_entries.sh +deleted file mode 100644 +index 50458da..0000000 +--- a/test/cases/compilation/output/duplicate_entries.sh ++++ /dev/null +@@ -1,20 +0,0 @@ +-#!/usr/bin/env sh +- +-# REQUIRES: shell +-# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s +-# RUN: assert_compilation %t.json count -eq 4 +-# RUN: assert_compilation %t.json contains -file %T/duplicate_entries_1.c -directory %T -arguments %{c_compiler} -c -o duplicate_entries_1.o duplicate_entries_1.c +-# RUN: assert_compilation %t.json contains -file %T/duplicate_entries_2.c -directory %T -arguments %{c_compiler} -c -o duplicate_entries_2.o duplicate_entries_2.c +-# RUN: assert_compilation %t.json contains -file %T/duplicate_entries_1.c -directory %T -arguments %{c_compiler} -c -D_FLAG=value -o duplicate_entries_3.o duplicate_entries_1.c +-# RUN: assert_compilation %t.json contains -file %T/duplicate_entries_2.c -directory %T -arguments %{c_compiler} -c -D_FLAG=value -o duplicate_entries_4.o duplicate_entries_2.c +- +-touch duplicate_entries_1.c duplicate_entries_2.c +- +-$CC -c duplicate_entries_1.c -o duplicate_entries_1.o; +-$CC -c duplicate_entries_2.c -o duplicate_entries_2.o; +-$CC -c duplicate_entries_1.c -o duplicate_entries_3.o -D_FLAG=value; +-$CC -c duplicate_entries_2.c -o duplicate_entries_4.o -D_FLAG=value; +-$CC -c duplicate_entries_1.c -o duplicate_entries_1.o; +-$CC -c duplicate_entries_2.c -o duplicate_entries_2.o; +-$CC -c duplicate_entries_1.c -o duplicate_entries_3.o -D_FLAG=value; +-$CC -c duplicate_entries_2.c -o duplicate_entries_4.o -D_FLAG=value; +diff --git a/test/cases/compilation/output/flag/append.sh b/test/cases/compilation/output/flag/append.sh +deleted file mode 100644 +index 1be8fff..0000000 +--- a/test/cases/compilation/output/flag/append.sh ++++ /dev/null +@@ -1,56 +0,0 @@ +-#!/usr/bin/env sh +- +-# REQUIRES: shell +- +-# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s -build +-# RUN: assert_compilation %t.json count -eq 2 +-# RUN: assert_compilation %t.json contains -file %T/append/src/source_1.c -directory %T -arguments %{c_compiler} -c -o append/src/source_1.o append/src/source_1.c +-# RUN: assert_compilation %t.json contains -file %T/append/src/source_2.c -directory %T -arguments %{c_compiler} -c -o append/src/source_2.o append/src/source_2.c +- +-# RUN: cd %T; %{bear} --verbose --output %t.json --append -- %{shell} %s -test +-# RUN: assert_compilation %t.json count -eq 4 +-# RUN: assert_compilation %t.json contains -file %T/append/src/source_1.c -directory %T -arguments %{c_compiler} -c -o append/src/source_1.o append/src/source_1.c +-# RUN: assert_compilation %t.json contains -file %T/append/src/source_2.c -directory %T -arguments %{c_compiler} -c -o append/src/source_2.o append/src/source_2.c +-# RUN: assert_compilation %t.json contains -file %T/append/test/source_1.c -directory %T -arguments %{c_compiler} -c -o append/test/source_1.o append/test/source_1.c +-# RUN: assert_compilation %t.json contains -file %T/append/test/source_2.c -directory %T -arguments %{c_compiler} -c -o append/test/source_2.o append/test/source_2.c +- +-# RUN: cd %T; %{bear} --verbose --output %t.json --append -- %{shell} %s -clean +-# RUN: assert_compilation %t.json count -eq 0 +- +-build() +-{ +- mkdir -p append append/src +- touch append/src/source_1.c append/src/source_2.c +- $CC -c -o append/src/source_1.o append/src/source_1.c +- $CC -c -o append/src/source_2.o append/src/source_2.c +-} +- +-verify() +-{ +- mkdir -p append append/test +- touch append/test/source_1.c append/test/source_2.c +- $CC -c -o append/test/source_1.o append/test/source_1.c +- $CC -c -o append/test/source_2.o append/test/source_2.c +-} +- +-clean() +-{ +- rm -rf append +-} +- +-case $1 in +- -build) +- build +- ;; +- -test) +- verify +- ;; +- -clean) +- clean +- ;; +- *) +- # unknown option +- ;; +-esac +- +-true +diff --git a/test/cases/compilation/output/flag/use_ld.sh b/test/cases/compilation/output/flag/use_ld.sh +new file mode 100644 +index 0000000..19031cb +--- /dev/null ++++ b/test/cases/compilation/output/flag/use_ld.sh +@@ -0,0 +1,14 @@ ++#!/usr/bin/env sh ++ ++# REQUIRES: shell ++ ++# RUN: cd %T; %{bear} --verbose --force-preload --output %t.known.json -- %{shell} %s ++# RUN: assert_compilation %t.known.json count -eq 3 ++# RUN: assert_compilation %t.known.json contains -file NULL_SOURCE_FOR_LD -directory %T -arguments %{ld_linker} use_ld_1.o use_ld_2.o -shared -o use_ld ++ ++ ++touch use_ld_1.c use_ld_2.c ++ ++$CC -c -o use_ld_1.o use_ld_1.c; ++$CC -c -o use_ld_2.o use_ld_2.c; ++$LD use_ld_1.o use_ld_2.o -shared -o use_ld +diff --git a/test/cases/citnames/output/convert_format.sh b/test/cases/citnames/output/convert_format.sh +index eb9df14..f0a7a96 100644 +--- a/test/cases/citnames/output/convert_format.sh ++++ b/test/cases/citnames/output/convert_format.sh +@@ -43,7 +43,8 @@ cat > "$1.compilations.json" << EOF + ], + "directory": "/home/user", + "file": "/home/user/broken_build.c", +- "output": "/home/user/broken_build.o" ++ "output": "/home/user/broken_build.o", ++ "rebuild": true + } + ] + EOF +diff --git a/test/cases/compilation/output/flags_filtered_link.sh b/test/cases/compilation/output/flags_filtered_link.sh +deleted file mode 100644 +index a322f94..0000000 +--- a/test/cases/compilation/output/flags_filtered_link.sh ++++ /dev/null +@@ -1,30 +0,0 @@ +-#!/usr/bin/env sh +- +-# REQUIRES: shell +-# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s +-# RUN: assert_compilation %t.json count -eq 4 +-# RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_1.c -directory %T -arguments %{c_compiler} -c -fpic -o flags_filtered_link_1.o flags_filtered_link_1.c +-# RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_2.c -directory %T -arguments %{c_compiler} -c -fpic -o flags_filtered_link_2.o flags_filtered_link_2.c +-# RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_3.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_link_3 flags_filtered_link_3.c +-# RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_4.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_link_4 flags_filtered_link_4.c +- +-# set up platform specific linker options +-PREFIX="foobar"; +-if [ $(uname | grep -i "darwin") ]; then +- LD_FLAGS="-o lib${PREFIX}.dylib -dynamiclib -install_name @rpath/${PREFIX}" +-else +- LD_FLAGS="-o lib${PREFIX}.so -shared -Wl,-soname,${PREFIX}" +-fi +- +-# create the source files +-echo "int foo() { return 2; }" > flags_filtered_link_1.c; +-echo "int bar() { return 2; }" > flags_filtered_link_2.c; +-echo "int main() { return 0; }" > flags_filtered_link_3.c; +-echo "int main() { return 0; }" > flags_filtered_link_4.c; +- +-$CC -c -o flags_filtered_link_1.o -fpic flags_filtered_link_1.c; +-$CC -c -o flags_filtered_link_2.o -fpic flags_filtered_link_2.c; +-$CC ${LD_FLAGS} flags_filtered_link_1.o flags_filtered_link_2.o; +- +-$CC -o flags_filtered_link_3 -l${PREFIX} -L. flags_filtered_link_3.c; +-$CC -o flags_filtered_link_4 -l ${PREFIX} -L . flags_filtered_link_4.c; +diff --git a/test/cases/compilation/output/link_rebuild.sh b/test/cases/compilation/output/link_rebuild.sh +new file mode 100644 +index 0000000..3c260d9 +--- /dev/null ++++ b/test/cases/compilation/output/link_rebuild.sh +@@ -0,0 +1,19 @@ ++#!/usr/bin/env sh ++ ++# REQUIRES: shell ++# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s ++# RUN: assert_compilation %t.json count -ge 4 ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_1.c -directory %T -arguments %{c_compiler} -c -Wl,-q -o compile_rebuild_1.o compile_rebuild_1.c ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild_2.c -directory %T -arguments %{c_compiler} -c -Wl,-q -o compile_rebuild_2.o compile_rebuild_2.c ++# RUN: assert_compilation %t.json contains -file NULL_SOURCE_FOR_LD -directory %T -arguments %{ld_linker} compile_rebuild_1.o compile_rebuild_2.o -shared -wrong-option -o compile_rebuild ++# RUN: assert_compilation %t.json contains -file NULL_SOURCE_FOR_LD -directory %T -arguments %{ld_linker} compile_rebuild_1.o compile_rebuild_2.o -shared -o compile_rebuild ++ ++ ++touch compile_rebuild_1.c compile_rebuild_2.c ++ ++export COMPILATION_OPTIONS="-Wl,-q" ++export LINK_OPTIONS="-wrong-option" ++ ++$CC -c -o compile_rebuild_1.o compile_rebuild_1.c ++$CC -c -o compile_rebuild_2.o compile_rebuild_2.c ++$LD -o compile_rebuild compile_rebuild_1.o compile_rebuild_2.o -shared +\ No newline at end of file +diff --git a/test/cases/compilation/output/multiple_source_build.sh b/test/cases/compilation/output/multiple_source_build.sh +index 5a7f6e0..677523a 100644 +--- a/test/cases/compilation/output/multiple_source_build.sh ++++ b/test/cases/compilation/output/multiple_source_build.sh +@@ -2,7 +2,7 @@ + + # REQUIRES: shell + # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s +-# RUN: assert_compilation %t.json count -eq 3 ++# RUN: assert_compilation %t.json count -eq 4 + # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_1.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_1.c + # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_2.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_2.c + # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_3.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_3.c +diff --git a/test/cases/compilation/output/spli_option_from_environment.sh b/test/cases/compilation/output/spli_option_from_environment.sh +new file mode 100644 +index 0000000..e7b74cb +--- /dev/null ++++ b/test/cases/compilation/output/spli_option_from_environment.sh +@@ -0,0 +1,13 @@ ++#!/usr/bin/env sh ++ ++# REQUIRES: shell ++# RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s ++# RUN: assert_compilation %t.json count -ge 1 ++# RUN: assert_compilation %t.json contains -file %T/compile_rebuild.c -directory %T -arguments %{c_compiler} -c -Wl,-q -w -g3 -O0 -o compile_rebuild.o compile_rebuild.c ++ ++ ++touch compile_rebuild.c ++ ++export COMPILATION_OPTIONS="-Wl,-q -w -g3 -O0" ++ ++$CC -c -o compile_rebuild.o compile_rebuild.c +\ No newline at end of file +diff --git a/test/lit.cfg b/test/lit.cfg +index d6b474b..64fe120 100644 +--- a/test/lit.cfg ++++ b/test/lit.cfg +@@ -146,6 +146,11 @@ if which('c++'): + config.substitutions.append(('%{cxx_compiler}', path)) + config.environment['CXX'] = path + ++# check if ld is available ++if which('ld'): ++ path = which('ld') ++ config.substitutions.append(('%{ld_linker}', path)) ++ config.environment['LD'] = path + + # check if fortran compiler is available + if which('gfortran'):