diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index 760f2d62..1b291b99 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -11,6 +11,15 @@ namespace clice { struct CommandOptions { + /// Ignore unknown commands. + bool ignore_unknown = true; + + /// The commands that you want to remove from original commands list. + llvm::ArrayRef remove; + + /// The commands that you want to add to original commands list. + llvm::ArrayRef append; + /// Attach resource directory to the command. bool resource_dir = false; @@ -19,7 +28,7 @@ struct CommandOptions { /// Suppress the warning log if failed to query driver info. /// Set true in unittests to avoid cluttering test output. - bool suppress_log = false; + bool suppress_logging = false; }; class CompilationDatabase { @@ -39,6 +48,12 @@ public: /// The canonical command list. llvm::ArrayRef arguments; + + /// The extra command @... + llvm::StringRef response_file; + + /// The original index of the response file argument in the command list. + std::uint32_t response_file_index = 0; }; struct DriverInfo { @@ -81,6 +96,12 @@ public: CompilationDatabase(); + CompilationDatabase(CompilationDatabase&& other); + + CompilationDatabase& operator= (CompilationDatabase&& other); + + ~CompilationDatabase(); + auto save_string(this Self& self, llvm::StringRef string) -> llvm::StringRef; auto save_cstring_list(this Self& self, llvm::ArrayRef arguments) @@ -95,13 +116,13 @@ public: /// Update with arguments. auto update_command(this Self& self, - llvm::StringRef dictionary, + llvm::StringRef directory, llvm::StringRef file, llvm::ArrayRef arguments) -> UpdateInfo; /// Update with full command. auto update_command(this Self& self, - llvm::StringRef dictionary, + llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command) -> UpdateInfo; @@ -109,6 +130,11 @@ public: auto load_commands(this Self& self, llvm::StringRef json_content, llvm::StringRef workspace) -> std::expected, std::string>; + auto process_command(this Self& self, + llvm::StringRef file, + const CommandInfo& info, + const CommandOptions& options) -> std::vector; + /// Get compile command from database. `file` should has relative path of workspace. auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {}) -> LookupInfo; @@ -124,6 +150,9 @@ private: auto guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo; private: + /// The opaque handle of `ArgumentParser`. + void* parser; + /// The memory pool to hold all cstring and command list. llvm::BumpPtrAllocator allocator; diff --git a/include/Test/Tester.h b/include/Test/Tester.h index b7016d4d..c3e40f46 100644 --- a/include/Test/Tester.h +++ b/include/Test/Tester.h @@ -40,7 +40,7 @@ struct Tester { CommandOptions options; options.resource_dir = true; options.query_driver = true; - options.suppress_log = true; + options.suppress_logging = true; params.arguments = database.get_command(src_path, options).arguments; for(auto& [file, source]: sources.all_files) { @@ -76,7 +76,7 @@ struct Tester { CommandOptions options; options.resource_dir = true; options.query_driver = true; - options.suppress_log = true; + options.suppress_logging = true; params.arguments = database.get_command(src_path, options).arguments; auto path = fs::createTemporaryFile("clice", "pch"); diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 60edf0a0..4d1b8703 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -1,37 +1,170 @@ #include "Compiler/Command.h" #include "Compiler/Compilation.h" #include "Support/FileSystem.h" +#include "Support/Logging.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Program.h" #include "clang/Driver/Driver.h" -#include "Support/Logging.h" namespace clice { +namespace { + +bool enable_dash_dash_parsing(const llvm::opt::OptTable& table); + +bool enable_grouped_short_options(const llvm::opt::OptTable& table); + +template +struct Thief { + friend bool enable_dash_dash_parsing(const llvm::opt::OptTable& table) { + return table.*MP1; + } + + friend bool enable_grouped_short_options(const llvm::opt::OptTable& table) { + return table.*MP2; + } +}; + +template struct Thief<&llvm::opt::OptTable::DashDashParsing, + &llvm::opt::OptTable::GroupedShortOptions>; + +class ArgumentParser final : public llvm::opt::ArgList { +public: + ArgumentParser(llvm::BumpPtrAllocator& allocator) : allocator(allocator) {} + + ~ArgumentParser() { + /// We never use the private `Args` field, so make sure it's empty. + if(getArgs().size() != 0) { + std::abort(); + } + } + + const char* getArgString(unsigned index) const override { + return arguments[index]; + } + + unsigned getNumInputArgStrings() const override { + return arguments.size(); + } + + const char* MakeArgStringRef(llvm::StringRef s) const override { + auto p = allocator.Allocate(s.size() + 1); + std::ranges::copy(s, p); + p[s.size()] = '\0'; + return p; + } + + inline static auto& option_table = clang::driver::getDriverOptTable(); + + void set_arguments(llvm::ArrayRef arguments) { + if(getArgs().size() != 0) { + std::abort(); + } + + this->arguments = arguments; + } + + std::unique_ptr parse_one(unsigned& index) { + assert(!enable_dash_dash_parsing(option_table)); + assert(!enable_grouped_short_options(option_table)); + return option_table.ParseOneArg(*this, index); + } + + void parse(llvm::ArrayRef arguments, const auto& on_parse, const auto& on_error) { + this->arguments = arguments; + + unsigned it = 0; + while(it != arguments.size()) { + llvm::StringRef s = arguments[it]; + + if(s.empty()) [[unlikely]] { + it += 1; + continue; + } + + auto prev = it; + auto arg = parse_one(it); + assert(it > prev && "parser failed to consume argument"); + + if(!arg) [[unlikely]] { + assert(it >= arguments.size() && "unexpected parser error!"); + assert(it - prev - 1 && "no missing arguments!"); + on_error(prev, it - prev - 1); + break; + } + + on_parse(std::move(arg)); + } + } + +private: + llvm::ArrayRef arguments; + + llvm::BumpPtrAllocator& allocator; +}; + +using QueryDriverError = CompilationDatabase::QueryDriverError; +using ErrorKind = CompilationDatabase::QueryDriverError::ErrorKind; +using options = clang::driver::options::ID; + +auto unexpected(ErrorKind kind, std::string message) { + return std::unexpected({kind, std::move(message)}); +}; + +} // namespace + CompilationDatabase::CompilationDatabase() { - using opions = clang::driver::options::ID; /// Remove the input file, we will add input file ourselves. - filtered_options.insert(opions::OPT_INPUT); + filtered_options.insert(options::OPT_INPUT); /// -c and -o are meaningless for frontend. - filtered_options.insert(opions::OPT_c); - filtered_options.insert(opions::OPT_o); - filtered_options.insert(opions::OPT_dxc_Fc); - filtered_options.insert(opions::OPT_dxc_Fo); + filtered_options.insert(options::OPT_c); + filtered_options.insert(options::OPT_o); + filtered_options.insert(options::OPT_dxc_Fc); + filtered_options.insert(options::OPT_dxc_Fo); /// Remove all options related to PCH building. - filtered_options.insert(opions::OPT_emit_pch); - filtered_options.insert(opions::OPT_include_pch); - filtered_options.insert(opions::OPT__SLASH_Yu); - filtered_options.insert(opions::OPT__SLASH_Fp); + filtered_options.insert(options::OPT_emit_pch); + filtered_options.insert(options::OPT_include_pch); + filtered_options.insert(options::OPT__SLASH_Yu); + filtered_options.insert(options::OPT__SLASH_Fp); /// Remove all options related to C++ module, we will /// build module and set deps ourselves. - filtered_options.insert(opions::OPT_fmodule_file); - filtered_options.insert(opions::OPT_fmodule_output); - filtered_options.insert(opions::OPT_fprebuilt_module_path); + filtered_options.insert(options::OPT_fmodule_file); + filtered_options.insert(options::OPT_fmodule_output); + filtered_options.insert(options::OPT_fprebuilt_module_path); + + parser = new ArgumentParser(allocator); +} + +CompilationDatabase::CompilationDatabase(CompilationDatabase&& other) : + parser(other.parser), allocator(std::move(other.allocator)), + string_cache(std::move(other.string_cache)), arguments_cache(std::move(other.arguments_cache)), + filtered_options(std::move(other.filtered_options)), + command_infos(std::move(other.command_infos)), driver_infos(std::move(other.driver_infos)) { + other.parser = nullptr; +} + +CompilationDatabase& CompilationDatabase::operator= (CompilationDatabase&& other) { + delete static_cast(parser); + parser = other.parser; + other.parser = nullptr; + + allocator = std::move(other.allocator); + string_cache = std::move(other.string_cache); + arguments_cache = std::move(other.arguments_cache); + filtered_options = std::move(other.filtered_options); + command_infos = std::move(other.command_infos); + driver_infos = std::move(other.driver_infos); + + return *this; +} + +CompilationDatabase::~CompilationDatabase() { + delete static_cast(parser); } auto CompilationDatabase::save_string(this Self& self, llvm::StringRef string) -> llvm::StringRef { @@ -95,42 +228,6 @@ std::optional CompilationDatabase::get_option_id(llvm::StringRef } } -namespace { - -llvm::SmallVector driver_invocation_argv(llvm::StringRef driver) { - /// FIXME: MSVC command:` cl /Bv`, should we support it? - /// if (driver.starts_with("gcc") || driver.starts_with("g++") || driver.starts_with("clang")) { - /// return {"-E", "-v", "-xc++", "/dev/null"}; - /// } else if (driver.starts_with("cl") || driver.starts_with("clang-cl")) { - /// return {"/Bv"}; - /// } -#if defined(_WIN32) - const llvm::StringRef null_device = "NUL"; -#else - const llvm::StringRef null_device = "/dev/null"; -#endif - return {driver, "-E", "-v", "-xc++", null_device}; -} - -llvm::SmallVector driver_invocation_env() { -#if defined(_WIN32) - /// TODO: windows support - return {}; -#else - /// Ensure driver print infomation in English - return {"LANG=C"}; -#endif -} - -using QueryDriverError = CompilationDatabase::QueryDriverError; -using ErrorKind = CompilationDatabase::QueryDriverError::ErrorKind; - -auto unexpected(ErrorKind kind, std::string message) { - return std::unexpected({kind, std::move(message)}); -}; - -} // namespace - auto CompilationDatabase::query_driver(this Self& self, llvm::StringRef driver) -> std::expected { { @@ -176,8 +273,20 @@ auto CompilationDatabase::query_driver(this Self& self, llvm::StringRef driver) std::optional redirects[] = {{""}, {""}, {""}}; redirects[is_std_err ? 2 : 1] = output_path.str(); - llvm::SmallVector argv = driver_invocation_argv(driver); - llvm::SmallVector env = driver_invocation_env(); +#ifdef _WIN32 + /// FIXME: MSVC command:` cl /Bv`, should we support it? + /// if (driver.starts_with("gcc") || driver.starts_with("g++") || driver.starts_with("clang")) { + /// {"-E", "-v", "-xc++", "/dev/null"}; + /// } else if (driver.starts_with("cl") || driver.starts_with("clang-cl")) { + /// {"/Bv"}; + /// } + llvm::SmallVector argv = {driver, "-E", "-v", "-xc++", "NUL"}; + llvm::SmallVector env; +#else + llvm::SmallVector argv = {driver, "-E", "-v", "-xc++", "/dev/null"}; + llvm::SmallVector env = {"LANG=C"}; +#endif + std::string message; if(int RC = llvm::sys::ExecuteAndWait(driver, argv, @@ -276,126 +385,109 @@ auto CompilationDatabase::update_command(this Self& self, llvm::StringRef directory, llvm::StringRef file, llvm::ArrayRef arguments) -> UpdateInfo { + auto parser = static_cast(self.parser); + parser->set_arguments(arguments); + file = self.save_string(file); directory = self.save_string(directory); - llvm::SmallVector filtered_arguments; + llvm::StringRef response_file; + std::uint32_t response_file_index = 0; - /// Append - auto add_argument = [&](llvm::StringRef argument) { - auto saved = self.save_string(argument); - filtered_arguments.emplace_back(saved.data()); - }; + llvm::SmallVector canonical_arguments; - /// Append driver sperately. - add_argument(arguments.front()); + /// We don't want to parse all arguments here, it is time-consuming. But we + /// want to remove output and input file from arguments. They are main reasons + /// causing different file have different commands. + for(unsigned it = 0; it != arguments.size(); it++) { + llvm::StringRef argument = arguments[it]; - unsigned missing_arg_index = 0; - unsigned missing_arg_count = 0; - auto& table = clang::driver::getDriverOptTable(); - - /// The driver should be discarded. - auto list = table.ParseArgs(arguments.drop_front(), missing_arg_index, missing_arg_count); - - bool remove_pch = false; - - /// Append and filter useless arguments. - for(auto arg: list.getArgs()) { - auto& opt = arg->getOption(); - auto id = opt.getID(); - - /// Filter options we don't need. - if(self.filtered_options.contains(id)) { + /// FIXME: Is it possible that file in command and field are different? + if(argument == file) { continue; } - /// For arguments -I, convert directory to absolute path. - /// i.e xmake will generate commands in this style. - if(id == clang::driver::options::OPT_I) { - if(arg->getNumValues() == 1) { - add_argument("-I"); - llvm::StringRef value = arg->getValue(0); - if(!value.empty() && !path::is_absolute(value)) { - add_argument(path::join(directory, value)); - } else { - add_argument(value); - } + /// All possible output options prefix. + constexpr static std::string_view output_options[] = { + "-o", + "--output", +#ifdef _WIN32 + "/o", + "/Fo", + "/Fe", +#endif + }; + + /// FIXME: This is a heuristic approach that covers the vast majority of cases, but + /// theoretical corner cases exist. For example, `-oxx` might be an argument for another + /// command, and processing it this way would lead to its incorrect removal. To fix these + /// corner cases, it's necessary to parse the command line fully. Additionally, detailed + /// benchmarks should be conducted to determine the time required for parsing command-line + /// arguments in order to decide if it's worth doing so. + if(ranges::any_of(output_options, + [&](llvm::StringRef option) { return argument.starts_with(option); })) { + auto prev = it; + auto arg = parser->parse_one(it); + + /// FIXME: How to handle parse error here? + if(!arg) { + it = prev; + continue; } + + auto id = arg->getOption().getID(); + if(id == options::OPT_o || id == options::OPT_dxc_Fo || id == options::OPT__SLASH_o || + id == options::OPT__SLASH_Fo || id == options::OPT__SLASH_Fe) { + /// It will point to the next argument start but it also increases + /// in the next loop. So decrease it for not skipping next argument. + it -= 1; + continue; + } + + /// This argument doesn't represent output file, just recovery it. + it = prev; + } + + /// Handle response file. + if(argument.starts_with("@")) { + if(!response_file.empty()) { + logging::warn( + "clice currently supports only one response file in the command, when loads {}", + file); + } + response_file = self.save_string(argument); + response_file_index = it; continue; } - /// A workaround to remove extra PCH when cmake - /// generate PCH flags for clang. - if(id == clang::driver::options::OPT_Xclang) { - if(arg->getNumValues() == 1) { - if(remove_pch) { - remove_pch = false; - continue; - } - - llvm::StringRef value = arg->getValue(0); - if(value == "-include-pch") { - remove_pch = true; - continue; - } - } - } - - /// Rewrite the argument to filter arguments, we basically reimplement - /// the logic of `Arg::render` to use our allocator to allocate memory. - switch(opt.getRenderStyle()) { - case llvm::opt::Option::RenderValuesStyle: { - for(auto value: arg->getValues()) { - add_argument(value); - } - break; - } - - case llvm::opt::Option::RenderSeparateStyle: { - add_argument(arg->getSpelling()); - for(auto value: arg->getValues()) { - add_argument(value); - } - break; - } - - case llvm::opt::Option::RenderJoinedStyle: { - llvm::SmallString<256> first = {arg->getSpelling(), arg->getValue(0)}; - add_argument(first); - for(auto value: llvm::ArrayRef(arg->getValues()).drop_front()) { - add_argument(value); - } - break; - } - - case llvm::opt::Option::RenderCommaJoinedStyle: { - llvm::SmallString<256> buffer = arg->getSpelling(); - for(auto i = 0; i < arg->getNumValues(); i++) { - if(i) { - buffer += ','; - } - buffer += arg->getValue(i); - } - add_argument(buffer); - break; - } - } + canonical_arguments.push_back(self.save_string(argument).data()); } - /// Save arguments. - arguments = self.save_cstring_list(filtered_arguments); + /// Cache the canonical arguments + arguments = self.save_cstring_list(canonical_arguments); UpdateKind kind = UpdateKind::Unchange; - CommandInfo info = {directory, arguments}; + CommandInfo info = { + directory, + arguments, + response_file, + response_file_index, + }; + auto [it, success] = self.command_infos.try_emplace(file.data(), info); if(success) { + /// If successfully inserted, we are loading new file. kind = UpdateKind::Create; } else { - auto& info = it->second; - if(info.directory.data() != directory.data() || info.arguments.data() != arguments.data()) { + /// If failed to insert, compare whether need to update. Because we cache + /// all the ref structure here, so just comparing the pointer is fine. + auto& old_info = it->second; + if(old_info.directory.data() != info.directory.data() || + old_info.arguments.data() != info.arguments.data() || + old_info.response_file.data() != info.response_file.data() || + old_info.response_file_index != info.response_file_index) { kind = UpdateKind::Update; - info.directory = directory; - info.arguments = arguments; + old_info = info; } } @@ -487,6 +579,158 @@ auto CompilationDatabase::load_commands(this Self& self, return infos; } +auto CompilationDatabase::process_command(this Self& self, + llvm::StringRef file, + const CommandInfo& info, + const CommandOptions& options) + -> std::vector { + + /// Store the final result arguments. + llvm::SmallVector final_arguments; + + auto add_string = [&](llvm::StringRef argument) { + auto saved = self.save_string(argument); + final_arguments.emplace_back(saved.data()); + }; + + /// Rewrite the argument to filter arguments, we basically reimplement + /// the logic of `Arg::render` to use our allocator to allocate memory. + auto add_argument = [&](llvm::opt::Arg& arg) { + switch(arg.getOption().getRenderStyle()) { + case llvm::opt::Option::RenderValuesStyle: { + for(auto value: arg.getValues()) { + add_string(value); + } + break; + } + + case llvm::opt::Option::RenderSeparateStyle: { + add_string(arg.getSpelling()); + for(auto value: arg.getValues()) { + add_string(value); + } + break; + } + + case llvm::opt::Option::RenderJoinedStyle: { + llvm::SmallString<256> first = {arg.getSpelling(), arg.getValue(0)}; + add_string(first); + for(auto value: llvm::ArrayRef(arg.getValues()).drop_front()) { + add_string(value); + } + break; + } + + case llvm::opt::Option::RenderCommaJoinedStyle: { + llvm::SmallString<256> buffer = arg.getSpelling(); + for(auto i = 0; i < arg.getNumValues(); i++) { + if(i) { + buffer += ','; + } + buffer += arg.getValue(i); + } + add_string(buffer); + break; + } + } + }; + + /// Append driver sperately + add_string(info.arguments.front()); + + using Arg = std::unique_ptr; + auto parser = static_cast(self.parser); + auto on_error = [&](int index, int count) { + logging::warn("missing argument index: {}, count: {} when parse: {}", index, count, file); + }; + + /// Prepare for removing arguments. + llvm::SmallVector remove; + for(auto& arg: options.remove) { + remove.push_back(self.save_string(arg).data()); + } + + /// FIXME: Handle unknow remove arguments. + llvm::SmallVector known_remove_args; + parser->parse( + remove, + [&known_remove_args](Arg arg) { known_remove_args.emplace_back(std::move(arg)); }, + on_error); + auto get_id = [](const Arg& arg) { + return arg->getOption().getID(); + }; + ranges::sort(known_remove_args, {}, get_id); + + bool remove_pch = false; + + /// FIXME: Append the commands from response file. + parser->parse( + info.arguments.drop_front(), + [&](Arg arg) { + auto& opt = arg->getOption(); + auto id = opt.getID(); + + /// Filter options we don't need. + if(self.filtered_options.contains(id)) { + return; + } + + /// Remove arguments in the remove list. + auto range = ranges::equal_range(known_remove_args, id, {}, get_id); + for(auto& remove: range) { + /// Match the -I*. + if(remove->getNumValues() == 1 && remove->getValue(0) == llvm::StringRef("*")) { + return; + } + + /// Compare each value, convert `const char*` to `llvm::StringRef` for comparing. + if(ranges::equal( + arg->getValues(), + remove->getValues(), + [](llvm::StringRef lhs, llvm::StringRef rhs) { return lhs == rhs; })) { + return; + } + } + + /// For arguments -I, convert directory to absolute path. + /// i.e xmake will generate commands in this style. + if(id == options::OPT_I && arg->getNumValues() == 1) { + add_string("-I"); + llvm::StringRef value = arg->getValue(0); + if(!value.empty() && !path::is_absolute(value)) { + add_string(path::join(info.directory, value)); + } else { + add_string(value); + } + return; + } + + /// A workaround to remove extra PCH when cmake generate PCH flags for clang. + if(id == options::OPT_Xclang && arg->getNumValues() == 1) { + if(remove_pch) { + remove_pch = false; + return; + } + + llvm::StringRef value = arg->getValue(0); + if(value == "-include-pch") { + remove_pch = true; + return; + } + } + + add_argument(*arg); + }, + on_error); + + /// FIXME: Do we want to parse append arguments also? + for(auto& arg: options.append) { + add_string(arg); + } + + return llvm::ArrayRef(final_arguments).vec(); +} + auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, CommandOptions options) -> LookupInfo { LookupInfo info; @@ -495,7 +739,7 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com auto it = self.command_infos.find(file.data()); if(it != self.command_infos.end()) { info.directory = it->second.directory; - info.arguments = it->second.arguments; + info.arguments = self.process_command(file, it->second, options); } else { info = self.guess_or_fallback(file); } @@ -516,7 +760,7 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com record("-I"); record(system_header); } - } else if(!options.suppress_log) { + } else if(!options.suppress_logging) { logging::warn("Failed to query driver:{}, error:{}", driver, driver_info.error()); } } diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc index 455ee6e2..6a56e40f 100644 --- a/src/Driver/unit_tests.cc +++ b/src/Driver/unit_tests.cc @@ -1,9 +1,10 @@ #include "Test/Test.h" +#include "Support/Logging.h" +#include "Support/GlobPattern.h" + #include "llvm/ADT/SmallString.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Signals.h" -#include "Support/GlobPattern.h" -#include using namespace clice; using namespace clice::testing; @@ -141,6 +142,13 @@ int Runner::run_tests() { continue; } + if(!test_filter.empty()) { + auto pos = test_filter.find_first_of('.'); + if(pos != std::string::npos && test_filter.substr(0, pos) != suite_name) { + continue; + } + } + curr_fatal = false; all_skipped = true; curr_suite_name = suite_name; @@ -182,6 +190,8 @@ int main(int argc, const char* argv[]) { llvm::cl::HideUnrelatedOptions(unittest_category); llvm::cl::ParseCommandLineOptions(argc, argv, "clice test\n"); + logging::create_stderr_logger("clice", logging::options); + if(!test_filter.empty()) { if(auto result = GlobPattern::create(test_filter)) { pattern.emplace(std::move(*result)); diff --git a/tests/unit/Compiler/Command.cpp b/tests/unit/Compiler/Command.cpp index df3c1ca2..d1a86992 100644 --- a/tests/unit/Compiler/Command.cpp +++ b/tests/unit/Compiler/Command.cpp @@ -7,9 +7,11 @@ namespace clice::testing { +inline static auto& option_table = clang::driver::getDriverOptTable(); + namespace { -std::string printArgv(llvm::ArrayRef args) { +std::string print_argv(llvm::ArrayRef args) { std::string buf; llvm::raw_string_ostream os(buf); bool Sep = false; @@ -29,31 +31,6 @@ std::string printArgv(llvm::ArrayRef args) { return std::move(os.str()); } -void parse_and_dump(llvm::StringRef command) { - llvm::BumpPtrAllocator local; - llvm::StringSaver saver(local); - - llvm::SmallVector arguments; - auto [driver, _] = command.split(' '); - driver = path::filename(driver); - - /// FIXME: Use a better to handle this. - if(driver.starts_with("cl") || driver.starts_with("clang-cl")) { - llvm::cl::TokenizeWindowsCommandLineFull(command, saver, arguments); - } else { - llvm::cl::TokenizeGNUCommandLine(command, saver, arguments); - } - - auto& table = clang::driver::getDriverOptTable(); - std::uint32_t count = 0; - std::uint32_t index = 0; - auto list = table.ParseArgs(arguments, count, index); - - for(auto arg: list.getArgs()) { - arg->dump(); - } -} - suite<"Command"> command = [] { auto expect_strip = [](llvm::StringRef argv, llvm::StringRef result) { CompilationDatabase database; @@ -61,8 +38,58 @@ suite<"Command"> command = [] { database.update_command("fake/", file, argv); CommandOptions options; - options.suppress_log = true; - expect(that % printArgv(database.get_command(file, options).arguments) == result); + options.suppress_logging = true; + expect(eq(result, print_argv(database.get_command(file, options).arguments))); + }; + + test("RemoveAppend") = [] { + llvm::SmallVector args = { + "clang++", + "--output=main.o", + "-D", + "A", + "-D", + "B=0", + "main.cpp", + }; + + CompilationDatabase database; + database.update_command("/fake", "main.cpp", args); + + CommandOptions options; + + llvm::SmallVector remove; + llvm::SmallVector append; + + remove = {"-DA"}; + options.remove = remove; + auto result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ -D B=0 main.cpp")); + + remove = {"-D", "A"}; + options.remove = remove; + result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ -D B=0 main.cpp")); + + remove = {"-DA", "-D", "B=0"}; + options.remove = remove; + result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ main.cpp")); + + remove = {"-D*"}; + options.remove = remove; + result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ main.cpp")); + + remove = {"-D", "*"}; + options.remove = remove; + result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ main.cpp")); + + append = {"-D", "C"}; + options.append = append; + result = database.get_command("main.cpp", options).arguments; + expect(eq(print_argv(result), "clang++ -D C main.cpp")); }; test("GetOptionID") = [] { @@ -106,6 +133,7 @@ suite<"Command"> command = [] { /// JoinedOrSeparateClass expect(that % GET_OPTION_ID("-o") == option::OPT_o); + expect(that % GET_OPTION_ID("-omain.o") == option::OPT_o); expect(that % GET_OPTION_ID("-I") == option::OPT_I); expect(that % GET_OPTION_ID("--include-directory=") == option::OPT_I); expect(that % GET_OPTION_ID("-x") == option::OPT_x); @@ -145,7 +173,7 @@ suite<"Command"> command = [] { database.update_command("fake", "test2.cpp", "clang++ -std=c++23 test2.cpp"sv); CommandOptions options; - options.suppress_log = true; + options.suppress_logging = true; auto command1 = database.get_command("test.cpp", options).arguments; auto command2 = database.get_command("test2.cpp", options).arguments; expect(that % command1.size() == 3); @@ -160,8 +188,13 @@ suite<"Command"> command = [] { expect(that % command2[2] == "test2.cpp"sv); }; - test("Module") = [] { + skip / test("Module") = [] { // Empty test + CompilationDatabase database; + database.update_command("/fake", + "main.cpp", + llvm::StringRef("clang++ @test.txt -std= main.cpp")); + auto info = database.get_command("main.cpp", {.query_driver = false}); }; test("QueryDriver") = [] { @@ -230,7 +263,7 @@ suite<"Command"> command = [] { expect(that % loaded.has_value()); CommandOptions options; - options.suppress_log = true; + options.suppress_logging = true; auto info = database.get_command(file, options); expect(that % info.directory == directory);