#include "grbc/ext_cmake.h" #include "grbc/cJSON.h" #include "grbc/ext.h" #include "grbc/helpers.h" #include "grbc/spec.h" #include "sol/raii.hpp" #include #include std::filesystem::path cmake_eval_symlink(const std::filesystem::path &build_dir, const std::filesystem::path &path) { std::filesystem::path c_path = path; while (std::filesystem::is_symlink(c_path)) { c_path = std::filesystem::read_symlink(c_path); // Make path absolute if its relative if (c_path.is_relative()) c_path = path.parent_path() / c_path; std::filesystem::path target_path = build_dir / c_path.filename(); if (std::filesystem::exists(target_path)) std::filesystem::remove(target_path); std::filesystem::copy(c_path, target_path); } return c_path; } std::string cmake_util_read_file(FILE *file) { // http://www.fundza.com/c4serious/fileIO_reading_all/index.html char line[190]; std::string result; while (fgets(line, 190, file)) result += line; if (result.back() == '\n') result.pop_back(); return result; } CMakeProject EXT_grbc_import_cmake(const std::string &cmake_path, const CMakeConfig &cfg) { CMakeProject project{}; log_msg(("configuring cmake project: " + cmake_path).c_str()); if (!std::filesystem::exists(cmake_path)) { grbc_exception("CMake cannot configure in non-existant directory: " + cmake_path); } if (!std::filesystem::exists(cmake_path + "/CMakeLists.txt")) { grbc_exception("No CMakeLists.txt file in: " + cmake_path); } std::filesystem::path old_path = std::filesystem::current_path(); // Change directory into the cmake dir std::filesystem::current_path(cmake_path); std::filesystem::create_directory("grbc_configure"); std::filesystem::current_path("./grbc_configure"); project.build_dir = std::filesystem::current_path().string(); // Create configure arg string std::string configure_arguments; for (const std::string &argument : cfg.configure_arguments) { configure_arguments += argument + " "; } // Run configure int exit_code = std::system(("cmake -S .. -B . -GNinja " + configure_arguments).c_str()); if (exit_code != EXIT_SUCCESS) grbc_exception("Failed to configure cmake project in: " + cmake_path); // Collect compilation database FILE *compdb = popen("ninja -t compdb", "r"); std::string compdb_raw = cmake_util_read_file(compdb); pclose(compdb); project.compdb = cJSON_Parse(compdb_raw.c_str()); // Build the cmake subproject, we need this so we can copy artifacts later on in the build step grbc_log("building cmake subproject..."); int build_exit_code = std::system("ninja"); if (build_exit_code != EXIT_SUCCESS) grbc_exception("Failed to build cmake project in: " + project.build_dir); std::filesystem::current_path(old_path); return project; } Package EXT_grbc_get_cmake_library(const CMakeProject &self, const std::string &library_name) { // Loop over every output in the compdb for (int i = 0; i < cJSON_GetArraySize(self.compdb); i++) { cJSON *compdb_item = cJSON_GetArrayItem(self.compdb, i); if (!cJSON_HasObjectItem(compdb_item, "output")) grbc_exception("Ninja produced invalid compilation database!"); if (!cJSON_HasObjectItem(compdb_item, "command")) grbc_exception("Compilation database item is missing 'command'"); if (!cJSON_HasObjectItem(compdb_item, "file")) grbc_exception("Compilation database item is missing 'file'"); std::string command = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "command")); // Only handle objects with an empty command // In the compilation databse objects with no command, but an output, and // file give us the info we need Info needed: Target name, output name if (!command.empty()) continue; std::string output_name = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "output")); std::string library_path = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "file")); if (output_name != library_name) continue; std::filesystem::path full_lib_path = self.build_dir + "/" + library_path; full_lib_path = cmake_eval_symlink(grbc_get_config().build_dir, full_lib_path); Package pkg{}; pkg.name = output_name; pkg.linker_flags = grbc_get_config().build_dir + "/" + full_lib_path.filename().generic_string(); grbc_log("found cmake library at: " + full_lib_path.generic_string()); return pkg; } grbc_exception("Failed to find library with name: " + library_name); return Package{}; } std::string EXT_grbc_get_cmake_library_string(const CMakeProject &self) { std::string library_string; for (int i = 0; i < cJSON_GetArraySize(self.compdb); i++) { cJSON *compdb_item = cJSON_GetArrayItem(self.compdb, i); if (!cJSON_HasObjectItem(compdb_item, "output")) grbc_exception("Ninja produced invalid compilation database!"); if (!cJSON_HasObjectItem(compdb_item, "command")) grbc_exception("Compilation database item is missing 'command'"); if (!cJSON_HasObjectItem(compdb_item, "file")) grbc_exception("Compilation database item is missing 'file'"); std::string cmd = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "command")); if (!cmd.empty()) continue; std::string file = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "file")); std::string output = cJSON_GetStringValue(cJSON_GetObjectItem(compdb_item, "output")); library_string += "Target: " + output + "\n"; library_string += "\tOutputting to file: " + output + "\n\n"; } return library_string; } void grbc_cmake_init(sol::state &lua) { lua.new_usertype( "CMakeConfig", sol::constructors(), "configure_arguments", &CMakeConfig::configure_arguments); lua.new_usertype("CMakeProject", "source_dir", &CMakeProject::source_dir, "build_dir", &CMakeProject::build_dir); lua["CMakeProject"]["get_library"] = EXT_grbc_get_cmake_library; lua["CMakeProject"]["get_library_string"] = EXT_grbc_get_cmake_library_string; lua.set("grbc_import_cmake", EXT_grbc_import_cmake); } Extension grbc_cmake() { Extension ext{}; ext.name = GRBC_EXT_cmake_NAME; ext.hook_init = grbc_cmake_init; return ext; }