#include #include "Ninja.hpp" #include "../../include/FileUtil.hpp" using namespace std; using namespace sibs; namespace backend { string join(const vector &list, const char *joinStr) { if(list.empty()) return ""; string result; long stringSize = 0; long joinStrLen = strlen(joinStr); int i = 0; for(const string &str : list) { stringSize += str.size(); if(!str.empty() && i > 0) stringSize += joinStrLen; ++i; } result.reserve(stringSize); i = 0; for(const string &str : list) { if(i > 0); result += joinStr; result += str; ++i; } return move(result); } struct ExecResult { string stdout; int exitCode; }; Result exec(const char *cmd, bool print = false) { char buffer[128]; string result; FILE *pipe = popen(cmd, "r"); if(!pipe) return Result::Err("popen() failed"); while(!feof(pipe)) { if(fgets(buffer, 128, pipe)) { result += buffer; if(print) printf("%s", buffer); } } ExecResult execResult; execResult.stdout = result; execResult.exitCode = WEXITSTATUS(pclose(pipe)); return Result::Ok(execResult); } void Ninja::addSourceFile(const char *filepath) { if(filepath && !containsSourceFile(filepath)) sourceFiles.emplace_back(filepath); } bool Ninja::containsSourceFile(const char *filepath) const { for(const string &sourceFile : sourceFiles) { if(sourceFile == filepath) return true; } return false; } Result Ninja::validatePkgConfigPackageExists(const string &name) const { string command = "pkg-config --exists '"; command += name; command += "'"; Result execResult = exec(command.c_str()); if(execResult.isErr()) { return Result::Err(execResult.getErrMsg()); } if(execResult.unwrap().exitCode == 1) { string errMsg = "Dependency not found: "; errMsg += name; return Result::Err(errMsg); } else if(execResult.unwrap().exitCode == 127) { return Result::Err("pkg-config is not installed"); } else if(execResult.unwrap().exitCode != 0) { string errMsg = "Failed to check if dependency exists, Unknown error, exit code: "; errMsg += to_string(execResult.unwrap().exitCode); return Result::Err(errMsg); } return Result::Ok(true); } Result Ninja::validatePkgConfigPackageVersionAtLeast(const string &name, const string &version) const { // Use --modversion instead and check if the version returned is newer or equal to dependency version. // This way we can output installed version vs expected dependency version string command = "pkg-config '--atleast-version="; command += version; command += "' '"; command += name; command += "'"; Result execResult = exec(command.c_str()); if(execResult.isErr()) { return Result::Err(execResult.getErrMsg()); } if(execResult.unwrap().exitCode == 1) { string errMsg = "Dependency "; errMsg += name; errMsg += " is installed but the version older than "; errMsg += version; return Result::Err(errMsg); } else if(execResult.unwrap().exitCode == 127) { return Result::Err("pkg-config is not installed"); } else if(execResult.unwrap().exitCode != 0) { string errMsg = "Failed to check dependency version, Unknown error, exit code: "; errMsg += to_string(execResult.unwrap().exitCode); return Result::Err(errMsg); } return Result::Ok(true); } // TODO: First check if pkg-config is installed. If it's not, only check dependencies that exists in the dependencies sub directory. // If pkg-config is installed and dependency is not installed, check in dependencies sub directory. Result Ninja::getLinkerFlags(const vector &dependencies) const { if(dependencies.empty()) return Result::Ok(""); for(const sibs::Dependency &dependency : dependencies) { Result dependencyValidationResult = validatePkgConfigPackageExists(dependency.name); if(dependencyValidationResult.isErr()) return Result::Err(dependencyValidationResult.getErrMsg()); Result dependencyVersionValidationResult = validatePkgConfigPackageVersionAtLeast(dependency.name, dependency.version); if(dependencyVersionValidationResult.isErr()) return Result::Err(dependencyVersionValidationResult.getErrMsg()); } string args; for(const sibs::Dependency &dependency : dependencies) { args += " '"; args += dependency.name; args += "'"; } string command = "pkg-config --libs"; command += args; Result execResult = exec(command.c_str()); if(execResult.isErr()) return Result::Err(execResult.getErrMsg()); if(execResult.unwrap().exitCode == 0) { return Result::Ok(execResult.unwrap().stdout); } else if(execResult.unwrap().exitCode == 1) { // TODO: This shouldn't happen because we check if each dependency is installed before this, // but maybe the package is uninstalled somewhere between here... // Would be better to recheck if each package is installed here again // to know which package was uninstalled return Result::Err("Dependencies not found"); } else if(execResult.unwrap().exitCode == 127) { return Result::Err("pkg-config is not installed"); } else { string errMsg = "Failed to get dependencies linking flags, Unknown error, exit code: "; errMsg += to_string(execResult.unwrap().exitCode); return Result::Err(errMsg); } } Result Ninja::createBuildFile(const std::string &packageName, const vector &dependencies, const char *savePath) { if(sourceFiles.empty()) return Result::Err("No source files provided"); printf("Package name: %s\n", packageName.c_str()); string result; result.reserve(16384); result += "cflags = -Wall -Werror\n\n"; result += "rule cpp_COMPILER\n"; result += " command = ccache c++ $ARGS -c $in -o $out\n\n"; result += "rule cpp_LINKER\n"; result += " command = ccache c++ $ARGS -o $out $in $LINK_ARGS $aliasing\n\n"; vector objectNames; for(const string &sourceFile : sourceFiles) { //string sourceFileEncoded = sourceFile; //replace(sourceFileEncoded, '/', '@'); string objectName = packageName + "@exe/" + sourceFile + ".o"; result += "build "; result += objectName; result += ": cpp_COMPILER ../../"; result += sourceFile; result += "\n"; result += " ARGS = '-I" + packageName + "@exe' '-I.' '-I..' '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-Wall' '-Winvalid-pch' '-Wnon-virtual-dtor' '-O0' '-g'\n\n"; objectNames.emplace_back(objectName); } Result linkerFlags = getLinkerFlags(dependencies); if(linkerFlags.isErr()) return Result::Err(linkerFlags.getErrMsg()); result += "build "; result += packageName; result += ": cpp_LINKER "; result += join(objectNames, " "); result += "\n"; result += " LINK_ARGS = '-Wl,--no-undefined' '-Wl,--as-needed' "; result += linkerFlags.unwrap(); result += "\n\n"; bool fileOverwritten = sibs::fileOverwrite(savePath, sibs::StringView(result.data(), result.size())); if(!fileOverwritten) { string errMsg = "Failed to overwrite ninja build file: "; errMsg += savePath; return Result::Err(errMsg); } printf("Created ninja build file: %s\n", savePath); return Result::Ok(true); } Result Ninja::build(const char *buildFilePath) { string command = "ninja -C '"; command += buildFilePath; command += "'"; Result execResult = exec(command.c_str(), true); if(execResult.isOk()) return Result::Ok(true); else return Result::Err(execResult.getErrMsg()); } }