diff --git a/source/citnames/CMakeLists.txt b/source/citnames/CMakeLists.txt index 789ca508b..41d1ac3d2 100644 --- a/source/citnames/CMakeLists.txt +++ b/source/citnames/CMakeLists.txt @@ -33,6 +33,8 @@ target_sources(citnames_a source/semantic/ToolIntelFortran.cc source/semantic/ToolWrapper.cc source/semantic/ToolExtendingWrapper.cc + source/semantic/ToolLinker.cc + source/semantic/ToolAr.cc INTERFACE $ ) diff --git a/source/citnames/source/Citnames.cc b/source/citnames/source/Citnames.cc index 098573d82..d25a1596e 100644 --- a/source/citnames/source/Citnames.cc +++ b/source/citnames/source/Citnames.cc @@ -187,6 +187,32 @@ namespace { } return output.size(); } + + size_t transform_links(cs::semantic::Build &build, const db::EventsDatabaseReader::Ptr& events, std::list &output) { + for (const auto &event : *events) { + const auto entries = build.recognize(event) + .map>([](const auto &semantic) -> std::list { + const auto candidate = dynamic_cast(semantic.get()); + return (candidate != nullptr) ? candidate->into_link_entries() : std::list(); + }) + .unwrap_or({}); + std::copy(entries.begin(), entries.end(), std::back_inserter(output)); + } + return output.size(); + } + + size_t transform_ar(cs::semantic::Build &build, const db::EventsDatabaseReader::Ptr& events, std::list &output) { + for (const auto &event : *events) { + const auto entries = build.recognize(event) + .map>([](const auto &semantic) -> std::list { + const auto candidate = dynamic_cast(semantic.get()); + return (candidate != nullptr) ? candidate->into_ar_entries() : std::list(); + }) + .unwrap_or({}); + std::copy(entries.begin(), entries.end(), std::back_inserter(output)); + } + return output.size(); + } } namespace cs { @@ -194,9 +220,11 @@ namespace cs { rust::Result Command::execute() const { cs::CompilationDatabase output(configuration_.output.format, configuration_.output.content); std::list entries; + std::list link_entries; + std::list ar_entries; // get current compilations from the input. - return db::EventsDatabaseReader::from(arguments_.input) + auto compile_result = db::EventsDatabaseReader::from(arguments_.input) .map([this, &entries](const auto &commands) { cs::semantic::Build build(configuration_.compilation); return transform(build, commands, entries); @@ -212,7 +240,7 @@ namespace cs { }) : rust::Result(rust::Ok(new_entries_count)); }) - .and_then([this, &output, &entries](const size_t & size) { + .and_then([this, &output, &entries](auto size) { // write the entries into the output file. spdlog::debug("compilation entries to output. [size: {}]", size); @@ -221,12 +249,73 @@ namespace cs { return rename_file(temporary_file, arguments_.output) ? result : rust::Err(std::runtime_error(fmt::format("Failed to rename file: {}", arguments_.output))); + }); + + auto link_result = rust::Result(rust::Ok(0)); + if (!configuration_.output.link_commands_output.empty()) { + link_result = db::EventsDatabaseReader::from(arguments_.input) + .map([this, &link_entries](const auto &commands) { + cs::semantic::Build build(configuration_.compilation); + return transform_links(build, commands, link_entries); }) - .map([](auto size) { - // just map to success exit code if it was successful. - spdlog::debug("compilation entries written. [size: {}]", size); - return EXIT_SUCCESS; + .and_then([this, &output, &link_entries](auto new_entries_count) { + spdlog::debug("link entries created. [size: {}]", new_entries_count); + return (arguments_.append) + ? output.from_link_json(arguments_.output, link_entries) + .template map([&new_entries_count](auto old_entries_count) { + spdlog::debug("link entries have read. [size: {}]", old_entries_count); + return new_entries_count + old_entries_count; + }) + : rust::Result(rust::Ok(new_entries_count)); + }) + .and_then([this, &output, &link_entries](auto size) { + // write the link entries into a separate output file if configured + spdlog::debug("link entries to output. [size: {}]", size); + + const fs::path link_temp_file(configuration_.output.link_commands_output.string() + ".tmp"); + auto link_write_result = output.to_link_json(link_temp_file, link_entries); + return rename_file(link_temp_file, configuration_.output.link_commands_output) + ? link_write_result + : rust::Err(std::runtime_error(fmt::format("Failed to rename file: {}", configuration_.output.link_commands_output))); }); + } + + auto ar_result = rust::Result(rust::Ok(0)); + if (!configuration_.output.ar_commands_output.empty()) { + ar_result = db::EventsDatabaseReader::from(arguments_.input) + .map([this, &ar_entries](const auto &commands) { + cs::semantic::Build build(configuration_.compilation); + return transform_ar(build, commands, ar_entries); + }) + .and_then([this, &output, &ar_entries](auto new_entries_count) { + spdlog::debug("ar entries created. [size: {}]", new_entries_count); + return (arguments_.append) + ? output.from_ar_json(arguments_.output, ar_entries) + .template map([&new_entries_count](auto old_entries_count) { + spdlog::debug("ar entries have read. [size: {}]", old_entries_count); + return new_entries_count + old_entries_count; + }) + : rust::Result(rust::Ok(new_entries_count)); + }) + .and_then([this, &output, &ar_entries](auto size) { + // write the ar entries into a separate output file if configured + spdlog::debug("ar entries to output. [size: {}]", size); + + const fs::path ar_temp_file(configuration_.output.ar_commands_output.string() + ".tmp"); + auto ar_write_result = output.to_ar_json(ar_temp_file, ar_entries); + return rename_file(ar_temp_file, configuration_.output.ar_commands_output) + ? ar_write_result + : rust::Err(std::runtime_error(fmt::format("Failed to rename file: {}", configuration_.output.ar_commands_output))); + }); + } + + return rust::merge(compile_result, rust::merge(link_result, ar_result)) + .map([](const auto &sizes) -> int { + const auto&[compile_size, link_and_ar_sizes] = sizes; + const auto&[link_size, ar_size] = link_and_ar_sizes; + spdlog::debug("compilation entries written. [size: {}]", compile_size); + return compile_size + link_size + ar_size; + }); } Command::Command(Arguments arguments, cs::Configuration configuration) noexcept diff --git a/source/citnames/source/Configuration.cc b/source/citnames/source/Configuration.cc index cab7d5aa8..78087f8c3 100644 --- a/source/citnames/source/Configuration.cc +++ b/source/citnames/source/Configuration.cc @@ -67,8 +67,18 @@ namespace cs { } void from_json(const nlohmann::json &j, Output &rhs) { - j.at("format").get_to(rhs.format); - j.at("content").get_to(rhs.content); + if (j.contains("format")) { + j.at("format").get_to(rhs.format); + } + if (j.contains("content")) { + j.at("content").get_to(rhs.content); + } + if (j.contains("link_commands_output")) { + j.at("link_commands_output").get_to(rhs.link_commands_output); + } + if (j.contains("ar_commands_output")) { + j.at("ar_commands_output").get_to(rhs.ar_commands_output); + } } void to_json(nlohmann::json &j, const Output &rhs) { @@ -76,6 +86,12 @@ namespace cs { {"format", rhs.format}, {"content", rhs.content}, }; + if (!rhs.link_commands_output.empty()) { + j["link_commands_output"] = rhs.link_commands_output; + } + if (!rhs.ar_commands_output.empty()) { + j["ar_commands_output"] = rhs.ar_commands_output; + } } void from_json(const nlohmann::json &j, CompilerWrapper &rhs) { diff --git a/source/citnames/source/Configuration.h b/source/citnames/source/Configuration.h index b897c2341..4676c3e3b 100644 --- a/source/citnames/source/Configuration.h +++ b/source/citnames/source/Configuration.h @@ -64,6 +64,8 @@ namespace cs { struct Output { Format format; Content content; + fs::path link_commands_output; // Path to the link commands output file + fs::path ar_commands_output; // Path to the ar commands output file }; // Represents a compiler wrapper that the tool will recognize. diff --git a/source/citnames/source/Output.cc b/source/citnames/source/Output.cc index 04a9ef41d..6008dbc36 100644 --- a/source/citnames/source/Output.cc +++ b/source/citnames/source/Output.cc @@ -327,4 +327,342 @@ namespace cs { return rust::Err(std::runtime_error(error.what())); } } + + void to_json(nlohmann::json &j, const Entry &entry, const Format &format) { + j = nlohmann::json{ + {"file", entry.file}, + {"directory", entry.directory}, + }; + if (!format.drop_output_field && entry.output) { + j["output"] = entry.output.value(); + } + if (format.command_as_array) { + j["arguments"] = entry.arguments; + } else { + j["command"] = sh::join(entry.arguments); + } + } + + nlohmann::json to_json(const LinkEntry &rhs, const Format &format) { + nlohmann::json json; + json["directory"] = rhs.directory; + json["input_files"] = rhs.input_files; + if (!format.drop_output_field && rhs.output.has_value()) { + json["output"] = rhs.output.value(); + } + if (format.command_as_array) { + json["arguments"] = rhs.arguments; + } else { + json["command"] = sh::join(rhs.arguments); + } + return json; + } + + void to_json(nlohmann::json &j, const LinkEntry &entry, const Format &format) { + j = nlohmann::json{ + {"directory", entry.directory}, + {"input_files", entry.input_files}, + }; + if (!format.drop_output_field && entry.output.has_value()) { + j["output"] = entry.output.value(); + } + if (format.command_as_array) { + j["arguments"] = entry.arguments; + } else { + j["command"] = sh::join(entry.arguments); + } + } + + bool operator==(const LinkEntry &lhs, const LinkEntry &rhs) { + return (lhs.directory == rhs.directory) + && (lhs.output == rhs.output) + && (lhs.arguments == rhs.arguments) + && (lhs.input_files == rhs.input_files); + } + + std::ostream &operator<<(std::ostream &os, const LinkEntry &entry) { + const Format format; + nlohmann::json j; + to_json(j, entry, format); + os << j; + return os; + } + + rust::Result CompilationDatabase::to_link_json(const fs::path &file, const LinkEntries &rhs) const { + try { + std::ofstream target(file); + return to_link_json(target, rhs) + .map_err([&file](auto error) { + return std::runtime_error( + fmt::format("Failed to write file: {}, cause: {}", + file.string(), + error.what())); + }); + + } catch (const std::exception &error) { + return rust::Err(std::runtime_error( + fmt::format("Failed to write file: {}, cause: {}", + file.string(), + error.what()))); + } + } + + rust::Result CompilationDatabase::to_link_json(std::ostream &ostream, const LinkEntries &entries) const { + try { + size_t count = 0; + nlohmann::json json = nlohmann::json::array(); + for (const auto &entry : entries) { + nlohmann::json j; + cs::to_json(j, entry, format); + json.emplace_back(std::move(j)); + ++count; + } + + ostream << std::setw(2) << json << std::endl; + + return rust::Ok(count); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error(error.what())); + } + } + + rust::Result CompilationDatabase::from_link_json(const fs::path &file, LinkEntries &entries) const { + try { + std::ifstream source(file); + return from_link_json(source, entries) + .map_err([&file](auto error) { + return std::runtime_error( + fmt::format("Failed to read file: {}, cause: {}", + file.string(), + error.what())); + }); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error( + fmt::format("Failed to read file: {}, cause: {}", + file.string(), + error.what()))); + } + } + + rust::Result CompilationDatabase::from_link_json(std::istream &istream, LinkEntries &entries) const { + try { + nlohmann::json in; + istream >> in; + + for (const auto &e : in) { + LinkEntry entry; + cs::from_json(e, entry); + entries.emplace_back(std::move(entry)); + } + + return rust::Ok(in.size()); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error(error.what())); + } + } + + void validate(const LinkEntry &entry) { + if (entry.directory.empty()) { + throw std::runtime_error("Field 'directory' is empty"); + } + if (entry.arguments.empty()) { + throw std::runtime_error("Field 'arguments' is empty"); + } + if (entry.input_files.empty()) { + throw std::runtime_error("Field 'input_files' is empty"); + } + if (entry.output.has_value() && entry.output.value().empty()) { + throw std::runtime_error("Field 'output' is empty string."); + } + } + + void from_json(const nlohmann::json &j, LinkEntry &entry) { + j.at("directory").get_to(entry.directory); + j.at("input_files").get_to(entry.input_files); + if (j.contains("output")) { + std::string output; + j.at("output").get_to(output); + entry.output.emplace(output); + } + if (j.contains("arguments")) { + std::list arguments; + j.at("arguments").get_to(arguments); + entry.arguments.swap(arguments); + } else if (j.contains("command")) { + std::string command; + j.at("command").get_to(command); + + sh::split(command) + .on_success([&entry](auto arguments) { + entry.arguments = arguments; + }) + .on_error([](auto error) { + throw error; + }); + } else { + throw std::runtime_error("Field 'command' or 'arguments' not found"); + } + + validate(entry); + } + + void validate(const ArEntry &entry) { + if (entry.directory.empty()) { + throw std::runtime_error("Field 'directory' is empty"); + } + if (entry.arguments.empty()) { + throw std::runtime_error("Field 'arguments' is empty"); + } + if (entry.input_files.empty()) { + throw std::runtime_error("Field 'input_files' is empty"); + } + if (entry.output.has_value() && entry.output.value().empty()) { + throw std::runtime_error("Field 'output' is empty string."); + } + if (entry.operation.empty()) { + throw std::runtime_error("Field 'operation' is empty"); + } + } + + void from_json(const nlohmann::json &j, ArEntry &entry) { + j.at("directory").get_to(entry.directory); + j.at("input_files").get_to(entry.input_files); + j.at("operation").get_to(entry.operation); + if (j.contains("output")) { + std::string output; + j.at("output").get_to(output); + entry.output.emplace(output); + } + if (j.contains("arguments")) { + std::list arguments; + j.at("arguments").get_to(arguments); + entry.arguments.swap(arguments); + } else if (j.contains("command")) { + std::string command; + j.at("command").get_to(command); + + sh::split(command) + .on_success([&entry](auto arguments) { + entry.arguments = arguments; + }) + .on_error([](auto error) { + throw error; + }); + } else { + throw std::runtime_error("Field 'command' or 'arguments' not found"); + } + + validate(entry); + } + + bool operator==(const ArEntry &lhs, const ArEntry &rhs) { + return (lhs.directory == rhs.directory) + && (lhs.output == rhs.output) + && (lhs.arguments == rhs.arguments) + && (lhs.input_files == rhs.input_files) + && (lhs.operation == rhs.operation); + } + + std::ostream &operator<<(std::ostream &os, const ArEntry &entry) { + const Format format; + nlohmann::json j; + to_json(j, entry, format); + os << j; + return os; + } + + void to_json(nlohmann::json &j, const ArEntry &entry, const Format &format) { + j = nlohmann::json{ + {"directory", entry.directory}, + {"input_files", entry.input_files}, + {"operation", entry.operation} + }; + if (!format.drop_output_field && entry.output.has_value()) { + j["output"] = entry.output.value(); + } + if (format.command_as_array) { + j["arguments"] = entry.arguments; + } else { + j["command"] = sh::join(entry.arguments); + } + } + + nlohmann::json to_json(const ArEntry &rhs, const Format &format) { + nlohmann::json json; + to_json(json, rhs, format); + return json; + } + + rust::Result CompilationDatabase::to_ar_json(const fs::path &file, const ArEntries &entries) const { + try { + std::ofstream target(file); + return to_ar_json(target, entries) + .map_err([&file](auto error) { + return std::runtime_error( + fmt::format("Failed to write file: {}, cause: {}", + file.string(), + error.what())); + }); + + } catch (const std::exception &error) { + return rust::Err(std::runtime_error( + fmt::format("Failed to write file: {}, cause: {}", + file.string(), + error.what()))); + } + } + + rust::Result CompilationDatabase::to_ar_json(std::ostream &ostream, const ArEntries &entries) const { + try { + size_t count = 0; + nlohmann::json json = nlohmann::json::array(); + for (const auto &entry : entries) { + nlohmann::json j; + cs::to_json(j, entry, format); + json.emplace_back(std::move(j)); + ++count; + } + + ostream << std::setw(2) << json << std::endl; + + return rust::Ok(count); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error(error.what())); + } + } + + rust::Result CompilationDatabase::from_ar_json(const fs::path &file, ArEntries &entries) const { + try { + std::ifstream source(file); + return from_ar_json(source, entries) + .map_err([&file](auto error) { + return std::runtime_error( + fmt::format("Failed to read file: {}, cause: {}", + file.string(), + error.what())); + }); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error( + fmt::format("Failed to read file: {}, cause: {}", + file.string(), + error.what()))); + } + } + + rust::Result CompilationDatabase::from_ar_json(std::istream &istream, ArEntries &entries) const { + try { + nlohmann::json in; + istream >> in; + + for (const auto &e : in) { + ArEntry entry; + cs::from_json(e, entry); + entries.emplace_back(std::move(entry)); + } + + return rust::Ok(in.size()); + } catch (const std::exception &error) { + return rust::Err(std::runtime_error(error.what())); + } + } } diff --git a/source/citnames/source/Output.h b/source/citnames/source/Output.h index 5661372c2..db91f053b 100644 --- a/source/citnames/source/Output.h +++ b/source/citnames/source/Output.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -51,9 +52,46 @@ namespace cs { std::list arguments; }; + // Represents a linking command in the database + struct LinkEntry { + fs::path directory; + std::optional output; + std::list arguments; + std::list input_files; + }; + + // Represents an ar command in the database for creating static libraries + struct ArEntry { + fs::path directory; + std::optional output; // The output .a file + std::list arguments; + std::list input_files; // Object files to archive + std::string operation; // The ar operation (e.g., "r", "q", etc.) + }; + // Convenient methods for these types. bool operator==(const Entry& lhs, const Entry& rhs); + bool operator==(const LinkEntry& lhs, const LinkEntry& rhs); + bool operator==(const ArEntry& lhs, const ArEntry& rhs); std::ostream& operator<<(std::ostream&, const Entry&); + std::ostream& operator<<(std::ostream&, const LinkEntry&); + std::ostream& operator<<(std::ostream&, const ArEntry&); + + // JSON serialization functions + void from_json(const nlohmann::json &j, Entry &entry); + void from_json(const nlohmann::json &j, LinkEntry &entry); + void from_json(const nlohmann::json &j, ArEntry &entry); + void to_json(nlohmann::json &j, const Entry &entry, const Format &format); + void to_json(nlohmann::json &j, const LinkEntry &entry, const Format &format); + void to_json(nlohmann::json &j, const ArEntry &entry, const Format &format); + nlohmann::json to_json(const Entry &rhs, const Format &format); + nlohmann::json to_json(const LinkEntry &rhs, const Format &format); + nlohmann::json to_json(const ArEntry &rhs, const Format &format); + + // Validation functions + void validate(const Entry &entry); + void validate(const LinkEntry &entry); + void validate(const ArEntry &entry); // Utility class to persists JSON compilation database. // @@ -65,6 +103,8 @@ namespace cs { // parameters. And basic content filtering is also available. struct CompilationDatabase { using Entries = std::list; + using LinkEntries = std::list; + using ArEntries = std::list; CompilationDatabase(Format, Content); virtual ~CompilationDatabase() noexcept = default; @@ -72,9 +112,17 @@ namespace cs { // Serialization methods with error mapping. [[nodiscard]] virtual rust::Result to_json(const fs::path& file, const Entries &entries) const; [[nodiscard]] virtual rust::Result to_json(std::ostream &ostream, const Entries &entries) const; + [[nodiscard]] virtual rust::Result to_link_json(const fs::path& file, const LinkEntries &entries) const; + [[nodiscard]] virtual rust::Result to_link_json(std::ostream &ostream, const LinkEntries &entries) const; + [[nodiscard]] virtual rust::Result to_ar_json(const fs::path& file, const ArEntries &entries) const; + [[nodiscard]] virtual rust::Result to_ar_json(std::ostream &ostream, const ArEntries &entries) const; [[nodiscard]] virtual rust::Result from_json(const fs::path& file, Entries &entries) const; [[nodiscard]] virtual rust::Result from_json(std::istream &istream, Entries &entries) const; + [[nodiscard]] virtual rust::Result from_link_json(const fs::path& file, LinkEntries &entries) const; + [[nodiscard]] virtual rust::Result from_link_json(std::istream &istream, LinkEntries &entries) const; + [[nodiscard]] virtual rust::Result from_ar_json(const fs::path& file, ArEntries &entries) const; + [[nodiscard]] virtual rust::Result from_ar_json(std::istream &istream, ArEntries &entries) const; private: Format format; diff --git a/source/citnames/source/semantic/Build.cc b/source/citnames/source/semantic/Build.cc index e8ef21ad4..be281a6d4 100644 --- a/source/citnames/source/semantic/Build.cc +++ b/source/citnames/source/semantic/Build.cc @@ -23,7 +23,9 @@ #include "ToolClang.h" #include "ToolCuda.h" #include "ToolIntelFortran.h" +#include "ToolLinker.h" #include "ToolWrapper.h" +#include "ToolAr.h" #include "ToolExtendingWrapper.h" #include "Convert.h" @@ -46,6 +48,8 @@ namespace { std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), + std::make_shared(), }; for (auto && compiler : cfg.compilers_to_recognize) { tools.emplace_front(std::make_shared(std::move(compiler))); diff --git a/source/citnames/source/semantic/Common.cc b/source/citnames/source/semantic/Common.cc index d24ac5d4a..49922f522 100644 --- a/source/citnames/source/semantic/Common.cc +++ b/source/citnames/source/semantic/Common.cc @@ -17,10 +17,14 @@ along with this program. If not, see . */ +#include +#include + #include "Common.h" #include #include +#include using namespace cs::semantic; @@ -79,6 +83,87 @@ namespace { return (flag.type == CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING); }); } + + std::tuple< + Arguments, + std::vector, + std::optional> + split_linker_flags(const CompilerFlags& flags) + { + Arguments arguments; + std::vector inputs; + std::optional output; + + for (const auto& flag : flags) { + switch (flag.type) { + case CompilerFlagType::LINKER_OBJECT_FILE: + case CompilerFlagType::LINKER_STATIC_LIBRARY: + case CompilerFlagType::LINKER_SHARED_LIBRARY: { + // For linking, we consider object files, and libraries as inputs + auto candidate = fs::path(flag.arguments.front()); + inputs.emplace_back(std::move(candidate)); + break; + } + case CompilerFlagType::KIND_OF_OUTPUT_OUTPUT: { + auto candidate = fs::path(flag.arguments.back()); + output = std::make_optional(std::move(candidate)); + break; + } + default: { + std::copy(flag.arguments.begin(), flag.arguments.end(), std::back_inserter(arguments)); + break; + } + } + } + return std::make_tuple(arguments, inputs, output); + } + + std::tuple< + Arguments, + std::vector, + std::optional> + split_archiving_flags(const CompilerFlags& flags) + { + Arguments arguments; + std::vector inputs; + std::optional output; + + // Find the operation flag first + std::string operation; + for (const auto& flag : flags) { + const auto& arg = flag.arguments.front(); + if (arg == "r" || arg == "q" || arg == "t" || arg == "x" || + arg == "d" || arg == "m" || arg == "p") { + operation = arg; + break; + } + } + + // Then process all flags + for (const auto& flag : flags) { + switch (flag.type) { + case CompilerFlagType::LINKER_STATIC_LIBRARY: { + auto candidate = fs::path(flag.arguments.front()); + output = std::make_optional(std::move(candidate)); + break; + } + case CompilerFlagType::LINKER_OBJECT_FILE: { + auto candidate = fs::path(flag.arguments.front()); + inputs.emplace_back(std::move(candidate)); + break; + } + default: { + std::copy(flag.arguments.begin(), flag.arguments.end(), std::back_inserter(arguments)); + break; + } + } + } + + // Construct final arguments in correct order: operation, modifiers + Arguments final_arguments; + std::copy(arguments.begin(), arguments.end(), std::back_inserter(final_arguments)); + return std::make_tuple(final_arguments, inputs, output); + } } rust::Result cs::semantic::compilation_impl(const FlagsByName& flags, const Execution& execution, @@ -122,3 +207,108 @@ rust::Result cs::semantic::compilation_impl(const FlagsByName& flag return rust::Ok(std::move(result)); }); } + +rust::Result cs::semantic::linking_impl(const FlagsByName& flags, const Execution& execution) +{ + const auto& parser = Repeat( + OneOf( + FlagParser(flags), + SourceMatcher(), + ObjectAndLibraryMatcher(), + EverythingElseFlagMatcher())); + + const Arguments input_arguments(execution.arguments.begin(), execution.arguments.end()); + return parse(parser, input_arguments) + .and_then([&execution](auto flags) -> rust::Result { + // Add debug logging for parsed flags + spdlog::debug("Parsed {} flags for linking", flags.size()); + for (const auto& flag : flags) { + spdlog::debug("Flag type: {}, arguments: {}", + static_cast(flag.type), + fmt::join(flag.arguments.begin(), flag.arguments.end(), " ")); + } + + auto [arguments, inputs, output] = split_linker_flags(flags); + + // Add debug logging for split results + spdlog::debug("Split linker flags:"); + spdlog::debug("Arguments: {}", fmt::join(arguments, " ")); + + // Convert paths to strings for logging + std::vector input_strings; + input_strings.reserve(inputs.size()); + std::transform(inputs.begin(), inputs.end(), + std::back_inserter(input_strings), + [](const fs::path& p) { return p.string(); }); + spdlog::debug("Input files: {}", fmt::join(input_strings, ", ")); + + spdlog::debug("Output file: {}", + output.has_value() ? output.value().string() : "not specified"); + + // Validate: must have input files + if (inputs.empty()) { + spdlog::error("No input files found for linking"); + return rust::Err(std::runtime_error("Input files not found for linking.")); + } + + std::list input_files(inputs.begin(), inputs.end()); + + SemanticPtr result = std::make_shared( + execution.working_dir, + execution.executable, + std::move(arguments), + std::move(input_files), + std::move(output)); + return rust::Ok(std::move(result)); + }); +} + +rust::Result cs::semantic::archiving_impl(const FlagsByName& flags, const Execution& execution) +{ + const auto& parser = Repeat( + OneOf( + FlagParser(flags), + SourceMatcher(), + ObjectAndLibraryMatcher(), + EverythingElseFlagMatcher())); + + const Arguments input_arguments(execution.arguments.begin(), execution.arguments.end()); + return parse(parser, input_arguments) + .and_then([&execution](auto flags) -> rust::Result { + // Find the operation flag + std::string operation; + for (const auto& flag : flags) { + const auto& arg = flag.arguments.front(); + if (arg == "r" || arg == "q" || arg == "t" || arg == "x" || + arg == "d" || arg == "m" || arg == "p") { + operation = arg; + break; + } + } + + if (operation.empty()) { + return rust::Err(std::runtime_error("No valid ar operation found.")); + } + + auto [arguments, inputs, output] = split_archiving_flags(flags); + + // Validate based on operation requirements + if (operation == "r" || operation == "q" || operation == "m") { + if (inputs.empty()) { + return rust::Err(std::runtime_error("Input files required for this ar operation.")); + } + } + if (!output) { + return rust::Err(std::runtime_error("Archive file required for ar operation.")); + } + + SemanticPtr result = std::make_shared( + execution.working_dir, + execution.executable, + operation, + std::move(arguments), + std::list(inputs.begin(), inputs.end()), + std::move(output)); + return rust::Ok(std::move(result)); + }); +} diff --git a/source/citnames/source/semantic/Common.h b/source/citnames/source/semantic/Common.h index dd531a7f2..a90273976 100644 --- a/source/citnames/source/semantic/Common.h +++ b/source/citnames/source/semantic/Common.h @@ -28,4 +28,6 @@ namespace cs::semantic { rust::Result compilation_impl(const FlagsByName& flags, const Execution& execution, std::function create_argument_list_func, std::function is_preprocessor_func); + rust::Result linking_impl(const FlagsByName& flags, const Execution& execution); + rust::Result archiving_impl(const FlagsByName& flags, const Execution& execution); } diff --git a/source/citnames/source/semantic/Parsers.cc b/source/citnames/source/semantic/Parsers.cc index 0cf3424fe..a2682b128 100644 --- a/source/citnames/source/semantic/Parsers.cc +++ b/source/citnames/source/semantic/Parsers.cc @@ -299,16 +299,49 @@ namespace cs::semantic { return rust::Err(input); } + rust::Result, ArgumentsView> ObjectAndLibraryMatcher::parse(const ArgumentsView &input) { + if (input.empty()) { + return rust::Err(input); + } + const auto &candidate = input.front(); + const auto &extension = take_extension(candidate); + + if (extension == ".o") { + const auto &[arguments, remainder] = input.take(1); + if (arguments.empty()) { + return rust::Err(input); + } + auto flag = CompilerFlag { arguments, CompilerFlagType::LINKER_OBJECT_FILE }; + return rust::Ok(std::make_pair(flag, remainder)); + } else if (extension == ".a") { + const auto &[arguments, remainder] = input.take(1); + if (arguments.empty()) { + return rust::Err(input); + } + auto flag = CompilerFlag { arguments, CompilerFlagType::LINKER_STATIC_LIBRARY }; + return rust::Ok(std::make_pair(flag, remainder)); + } else if (extension == ".so") { + const auto &[arguments, remainder] = input.take(1); + if (arguments.empty()) { + return rust::Err(input); + } + auto flag = CompilerFlag { arguments, CompilerFlagType::LINKER_SHARED_LIBRARY }; + return rust::Ok(std::make_pair(flag, remainder)); + } + return rust::Err(input); + } + rust::Result, ArgumentsView> EverythingElseFlagMatcher::parse(const ArgumentsView &input) { if (input.empty()) { return rust::Err(input); } if (const auto &front = input.front(); !front.empty()) { + // For ar commands, treat any non-flag argument as OTHER type const auto &[arguments, remainder] = input.take(1); if (arguments.empty()) { return rust::Err(input); } - auto flag = CompilerFlag { arguments, CompilerFlagType::LINKER_OBJECT_FILE }; + auto flag = CompilerFlag { arguments, CompilerFlagType::OTHER }; return rust::Ok(std::make_pair(flag, remainder)); } return rust::Err(input); diff --git a/source/citnames/source/semantic/Parsers.h b/source/citnames/source/semantic/Parsers.h index 34203a176..317112290 100644 --- a/source/citnames/source/semantic/Parsers.h +++ b/source/citnames/source/semantic/Parsers.h @@ -108,6 +108,8 @@ namespace cs::semantic { PREPROCESSOR_MAKE, LINKER, LINKER_OBJECT_FILE, + LINKER_STATIC_LIBRARY, // For .a files + LINKER_SHARED_LIBRARY, // For .so files DIRECTORY_SEARCH, DIRECTORY_SEARCH_LINKER, SOURCE, @@ -178,6 +180,12 @@ namespace cs::semantic { static rust::Result, ArgumentsView> parse(const ArgumentsView &input); }; + // A parser combinator which recognizes object files and libraries. + struct ObjectAndLibraryMatcher { + [[nodiscard]] + static rust::Result, ArgumentsView> parse(const ArgumentsView &input); + }; + // A parser combinator, which recognize a single compiler flag without any conditions. struct EverythingElseFlagMatcher { [[nodiscard]] diff --git a/source/citnames/source/semantic/Semantic.cc b/source/citnames/source/semantic/Semantic.cc index 54d3e62d3..852ae4b13 100644 --- a/source/citnames/source/semantic/Semantic.cc +++ b/source/citnames/source/semantic/Semantic.cc @@ -134,4 +134,140 @@ namespace cs::semantic { } return results; } + + std::list Link::into_entries() const { + return {}; + } + + std::list Link::into_link_entries() const { + const auto abspath = [this](const fs::path &path) -> fs::path { + auto candidate = (path.is_absolute()) ? path : working_dir / path; + // Create canonical path without checking of file existence. + fs::path result; + for (const auto& part : candidate) { + if (part == ".") + continue; + if (part == "..") + result = result.parent_path(); + else + result = result / part; + } + return result; + }; + std::list results; + cs::LinkEntry result { + working_dir, + output ? std::optional(abspath(output.value())) : std::nullopt, + { linker.string() }, + {} + }; + 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()); + } + for (const auto& input : input_files) { + result.input_files.push_back(abspath(input)); + result.arguments.push_back(input.string()); + } + results.emplace_back(std::move(result)); + return results; + } + + std::ostream &Link::operator<<(std::ostream &os) const { + os << "Link { working_dir: " << working_dir + << ", linker: " << linker + << ", flags: " << fmt::format("[{}]", fmt::join(flags.begin(), flags.end(), ", ")) + << ", input_files: " << fmt::format("[{}]", fmt::join(input_files.begin(), input_files.end(), ", ")) + << ", output: " << (output ? output.value().string() : "") + << " }"; + return os; + } + + bool Link::operator==(const Semantic &rhs) const { + if (this == &rhs) { + return true; + } + if (const auto *const ptr = dynamic_cast(&rhs)) { + return (working_dir == ptr->working_dir) + && (linker == ptr->linker) + && (flags == ptr->flags) + && (input_files == ptr->input_files) + && (output == ptr->output); + } + return false; + } + + bool Ar::operator==(const Semantic &rhs) const { + if (this == &rhs) { + return true; + } + if (const auto *const ptr = dynamic_cast(&rhs)) { + return (working_dir == ptr->working_dir) + && (ar_tool == ptr->ar_tool) + && (operation == ptr->operation) + && (flags == ptr->flags) + && (input_files == ptr->input_files) + && (output == ptr->output); + } + return false; + } + + std::list Ar::into_entries() const { + return {}; + } + + std::list Ar::into_ar_entries() const { + const auto abspath = [this](const fs::path &path) -> fs::path { + auto candidate = (path.is_absolute()) ? path : working_dir / path; + // Create canonical path without checking of file existence. + fs::path result; + for (const auto& part : candidate) { + if (part == ".") + continue; + if (part == "..") + result = result.parent_path(); + else + result = result / part; + } + return result; + }; + std::list results; + cs::ArEntry result { + working_dir, + output ? std::optional(abspath(output.value())) : std::nullopt, + { ar_tool.string() }, + {}, + operation + }; + + // Build arguments list in correct order: + // 1. Copy all flags (which include operation, modifiers) + std::copy(flags.begin(), flags.end(), std::back_inserter(result.arguments)); + + // 2. Add archive file + if (output) { + result.arguments.push_back(output.value().string()); + } + + // 3. Add input files and record them in input_files + for (const auto& input : input_files) { + result.input_files.push_back(abspath(input)); + result.arguments.push_back(input.string()); + } + + results.emplace_back(std::move(result)); + return results; + } + + std::ostream &Ar::operator<<(std::ostream &os) const { + os << "Ar { working_dir: " << working_dir + << ", ar_tool: " << ar_tool + << ", operation: " << operation + << ", flags: " << fmt::format("[{}]", fmt::join(flags.begin(), flags.end(), ", ")) + << ", input_files: " << fmt::format("[{}]", fmt::join(input_files.begin(), input_files.end(), ", ")) + << ", output: " << (output ? output.value().string() : "") + << " }"; + return os; + } } diff --git a/source/citnames/source/semantic/Semantic.h b/source/citnames/source/semantic/Semantic.h index 3eea8f43f..626df0c5c 100644 --- a/source/citnames/source/semantic/Semantic.h +++ b/source/citnames/source/semantic/Semantic.h @@ -101,4 +101,61 @@ namespace cs::semantic { std::vector sources; std::optional output; }; + + class Link : public CompilerCall { + public: + Link(fs::path working_dir, + fs::path linker, + std::list flags, + std::list input_files, + std::optional output = std::nullopt) + : working_dir(std::move(working_dir)) + , linker(std::move(linker)) + , flags(std::move(flags)) + , input_files(std::move(input_files)) + , output(std::move(output)) + { } + + bool operator==(Semantic const& rhs) const override; + std::list into_entries() const override; + std::list into_link_entries() const; + std::ostream& operator<<(std::ostream& os) const override; + + private: + fs::path working_dir; + fs::path linker; + std::list flags; + std::list input_files; + std::optional output; + }; + + class Ar : public CompilerCall { + public: + Ar(fs::path working_dir, + fs::path ar_tool, + std::string operation, + std::list flags, + std::list input_files, + std::optional output = std::nullopt) + : working_dir(std::move(working_dir)) + , ar_tool(std::move(ar_tool)) + , operation(std::move(operation)) + , flags(std::move(flags)) + , input_files(std::move(input_files)) + , output(std::move(output)) + { } + + bool operator==(Semantic const& rhs) const override; + std::list into_entries() const override; + std::list into_ar_entries() const; + std::ostream& operator<<(std::ostream& os) const override; + + private: + fs::path working_dir; + fs::path ar_tool; + std::string operation; // Keep this for internal use + std::list flags; // This will include the operation + std::list input_files; + std::optional output; + }; } diff --git a/source/citnames/source/semantic/ToolAr.cc b/source/citnames/source/semantic/ToolAr.cc new file mode 100644 index 000000000..0b102147d --- /dev/null +++ b/source/citnames/source/semantic/ToolAr.cc @@ -0,0 +1,77 @@ +#include "ToolAr.h" +#include "Semantic.h" +#include "Common.h" +#include "libshell/Command.h" +#include "Parsers.h" + +#include +#include +#include +#include +#include + +namespace { + const std::regex AR_PATTERN(R"(^(ar|llvm-ar)$)"); +} + +namespace cs::semantic { + + const FlagsByName ToolAr::FLAG_DEFINITION = { + // Main operation flags - all operations should be part of arguments + {"r", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Replace or insert files + {"q", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Quick append + {"t", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // List contents + {"x", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Extract + {"d", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Delete + {"m", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Move + {"p", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Print + + // Operation modifiers + {"a", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Put files after [member-name] + {"b", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Put files before [member-name] + {"i", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Same as [b] + {"D", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Use zero for timestamps and uids/gids + {"U", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Use actual timestamps and uids/gids + {"N", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Use instance [count] of name + {"f", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Truncate inserted file names + {"P", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Use full path names when matching + {"o", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Preserve original dates + {"O", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Display offsets of files + {"u", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Only replace newer files + + // Generic modifiers + {"c", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Don't warn if library had to be created + {"s", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Create an archive index + {"S", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Don't build a symbol table + {"T", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Deprecated, use --thin instead + {"v", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Be verbose + {"V", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, // Display version number + + // Long options + {"--thin", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, // Make a thin archive + {"--plugin", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, // Load specified plugin + {"--target", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, // Specify target object format + {"--output", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, // Specify output directory + {"--record-libdeps", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, // Specify library dependencies + }; + + bool ToolAr::is_ar_call(const fs::path& program) const { + const auto name = program.filename().string(); + return std::regex_match(name, AR_PATTERN); + } + + rust::Result ToolAr::recognize(const Execution &execution) const { + if (is_ar_call(execution.executable)) { + return archiving(execution); + } + return rust::Ok(SemanticPtr()); + } + + rust::Result ToolAr::archiving(const Execution &execution) const { + return archiving(FLAG_DEFINITION, execution); + } + + rust::Result ToolAr::archiving(const FlagsByName &flags, const Execution &execution) { + return archiving_impl(flags, execution); + } +} \ No newline at end of file diff --git a/source/citnames/source/semantic/ToolAr.h b/source/citnames/source/semantic/ToolAr.h new file mode 100644 index 000000000..72fb1714e --- /dev/null +++ b/source/citnames/source/semantic/ToolAr.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Tool.h" +#include "Parsers.h" + +namespace cs::semantic { + + struct ToolAr : public Tool { + + [[nodiscard]] + rust::Result recognize(const Execution &execution) const override; + + protected: + [[nodiscard]] + bool is_ar_call(const fs::path& program) const; + + [[nodiscard]] + rust::Result archiving(const Execution &execution) const; + + [[nodiscard]] + static rust::Result archiving(const FlagsByName &flags, const Execution &execution); + + static const FlagsByName FLAG_DEFINITION; + }; +} \ No newline at end of file diff --git a/source/citnames/source/semantic/ToolLinker.cc b/source/citnames/source/semantic/ToolLinker.cc new file mode 100644 index 000000000..45cfd3d7b --- /dev/null +++ b/source/citnames/source/semantic/ToolLinker.cc @@ -0,0 +1,75 @@ +#include "ToolLinker.h" +#include "Semantic.h" +#include "Common.h" +#include "libshell/Command.h" +#include "Parsers.h" + +#include +#include +#include +#include +#include + +namespace { + const std::regex LINKER_PATTERN(R"(^(ld|ld\.gold|ld\.lld|gold|lld)$)"); +} + +namespace cs::semantic { + + const FlagsByName ToolLinker::FLAG_DEFINITION = { + // Output flags + {"-o", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, + {"--output", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, + + // Library flags + {"-l", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"-L", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH_LINKER}}, + {"--library", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--library-path", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH_LINKER}}, + + // Runtime path flags + {"-rpath", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--rpath", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + + // Shared library flags + {"-soname", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--soname", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + + // Version script flags + {"-version-script", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--version-script", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + + // Dynamic linker flags + {"-dynamic-linker", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--dynamic-linker", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + + // Other common linker flags + {"-z", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"-m", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--hash-style", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, + {"--build-id", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, + {"--eh-frame-hdr", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, + {"--as-needed", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, + {"--no-as-needed", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, + }; + + bool ToolLinker::is_linker_call(const fs::path& program) const { + const auto name = program.filename().string(); + return std::regex_match(name, LINKER_PATTERN); + } + + rust::Result ToolLinker::recognize(const Execution &execution) const { + if (is_linker_call(execution.executable)) { + return linking(execution); + } + return rust::Ok(SemanticPtr()); + } + + rust::Result ToolLinker::linking(const Execution &execution) const { + return linking(FLAG_DEFINITION, execution); + } + + rust::Result ToolLinker::linking(const FlagsByName &flags, const Execution &execution) { + return linking_impl(flags, execution); + } +} \ No newline at end of file diff --git a/source/citnames/source/semantic/ToolLinker.h b/source/citnames/source/semantic/ToolLinker.h new file mode 100644 index 000000000..24ba75bc9 --- /dev/null +++ b/source/citnames/source/semantic/ToolLinker.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Tool.h" +#include "Parsers.h" + +namespace cs::semantic { + + struct ToolLinker : public Tool { + + [[nodiscard]] + rust::Result recognize(const Execution &execution) const override; + + protected: + [[nodiscard]] + bool is_linker_call(const fs::path& program) const; + + [[nodiscard]] + rust::Result linking(const Execution &execution) const; + + [[nodiscard]] + static rust::Result linking(const FlagsByName &flags, const Execution &execution); + + static const FlagsByName FLAG_DEFINITION; + }; +} \ No newline at end of file diff --git a/source/citnames/test/ParserTest.cc b/source/citnames/test/ParserTest.cc index 0ae47f6e0..eb5350c8a 100644 --- a/source/citnames/test/ParserTest.cc +++ b/source/citnames/test/ParserTest.cc @@ -49,6 +49,27 @@ namespace cs::semantic { namespace { + TEST(Parser, ObjectAndLibraryMatcher) { + const auto sut = Repeat(ObjectAndLibraryMatcher()); + + { + const Arguments input = {"compiler", "file.o", "lib.a", "lib.so"}; + const auto flags = parse(sut, input); + EXPECT_TRUE(flags.is_ok()); + const CompilerFlags expected = { + CompilerFlag{slice(input, 1), CompilerFlagType::LINKER_OBJECT_FILE}, + CompilerFlag{slice(input, 2), CompilerFlagType::LINKER_STATIC_LIBRARY}, + CompilerFlag{slice(input, 3), CompilerFlagType::LINKER_SHARED_LIBRARY}, + }; + EXPECT_EQ(expected, flags.unwrap()); + } + { + const Arguments input = {"compiler", "file.txt"}; + const auto flags = parse(sut, input); + EXPECT_TRUE(flags.is_err()); + } + } + TEST(Parser, EverythingElseFlagMatcher) { const auto sut = Repeat(EverythingElseFlagMatcher()); @@ -56,10 +77,10 @@ namespace { const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { - CompilerFlag{slice(input, 1), CompilerFlagType::LINKER_OBJECT_FILE}, - CompilerFlag{slice(input, 2), CompilerFlagType::LINKER_OBJECT_FILE}, - CompilerFlag{slice(input, 3), CompilerFlagType::LINKER_OBJECT_FILE}, - CompilerFlag{slice(input, 4), CompilerFlagType::LINKER_OBJECT_FILE}, + CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, + CompilerFlag{slice(input, 2), CompilerFlagType::OTHER}, + CompilerFlag{slice(input, 3), CompilerFlagType::OTHER}, + CompilerFlag{slice(input, 4), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); }