From e7384a7672e4449bc194ca3ec66cdd4fcc63801e Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 9 Dec 2017 16:36:23 +0100 Subject: Add support for dependencies (including version check) This currently only works using pkg-config and it only adds linking flags. Need to check with a library that also includes other types of flags. TODO: Fallback to dependencies sub directory and github/server if package not found in pkg-config. --- backend/ninja/Ninja.cpp | 174 ++++++++++++++++++++++++++++++++++++++++++++++-- backend/ninja/Ninja.hpp | 8 ++- include/Dependency.hpp | 16 +++++ src/Conf.cpp | 16 +++-- src/FileUtil.cpp | 1 + src/main.cpp | 33 +++++++-- 6 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 include/Dependency.hpp diff --git a/backend/ninja/Ninja.cpp b/backend/ninja/Ninja.cpp index 7006d30..683fb67 100644 --- a/backend/ninja/Ninja.cpp +++ b/backend/ninja/Ninja.cpp @@ -3,6 +3,7 @@ #include "../../include/FileUtil.hpp" using namespace std; +using namespace sibs; namespace backend { @@ -35,6 +36,32 @@ namespace backend return move(result); } + struct ExecResult + { + string stdout; + int exitCode; + }; + + Result exec(const char *cmd) + { + 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; + } + + ExecResult execResult; + execResult.stdout = result; + execResult.exitCode = WEXITSTATUS(pclose(pipe)); + return Result::Ok(execResult); + } + void Ninja::addSourceFile(const char *filepath) { if(filepath && !containsSourceFile(filepath)) @@ -51,9 +78,134 @@ namespace backend return false; } - void Ninja::build(const std::string &packageName, const char *savePath) + Result Ninja::validatePkgConfigPackageExists(const string &name) const { - if(sourceFiles.empty()) return; + 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; @@ -82,14 +234,28 @@ namespace backend 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'\n\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); + } - sibs::fileOverwrite(savePath, sibs::StringView(result.data(), result.size())); printf("Created ninja build file: %s\n", savePath); + return Result::Ok(true); } } \ No newline at end of file diff --git a/backend/ninja/Ninja.hpp b/backend/ninja/Ninja.hpp index ad71c80..1ba20c7 100644 --- a/backend/ninja/Ninja.hpp +++ b/backend/ninja/Ninja.hpp @@ -1,18 +1,24 @@ #ifndef BACKEND_NINJA_HPP #define BACKEND_NINJA_HPP +#include "../../include/Dependency.hpp" +#include "../../include/Result.hpp" #include #include + namespace backend { class Ninja { public: void addSourceFile(const char *filepath); - void build(const std::string &packageName, const char *savePath); + sibs::Result createBuildFile(const std::string &packageName, const std::vector &dependencies, const char *savePath); private: bool containsSourceFile(const char *filepath) const; + sibs::Result getLinkerFlags(const std::vector &dependencies) const; + sibs::Result validatePkgConfigPackageExists(const std::string &name) const; + sibs::Result validatePkgConfigPackageVersionAtLeast(const std::string &name, const std::string &version) const; private: std::vector sourceFiles; }; diff --git a/include/Dependency.hpp b/include/Dependency.hpp new file mode 100644 index 0000000..b97c362 --- /dev/null +++ b/include/Dependency.hpp @@ -0,0 +1,16 @@ +#ifndef SIBS_DEPENDENCY_HPP +#define SIBS_DEPENDENCY_HPP + +#include + +namespace sibs +{ + class Dependency + { + public: + std::string name; + std::string version; + }; +} + +#endif //SIBS_DEPENDENCY_HPP diff --git a/src/Conf.cpp b/src/Conf.cpp index 56b1e2a..6383c30 100644 --- a/src/Conf.cpp +++ b/src/Conf.cpp @@ -68,7 +68,7 @@ namespace sibs char *startOfIdentifier = code.base(); ++code; c = *code; - while(isAlpha(c) || c == '_' || isDigit(c)) + while(isAlpha(c) || isDigit(c) || c == '_' || c == '-') { ++code; c = *code; @@ -95,19 +95,21 @@ namespace sibs } else if(c == '"') { - u32 escapeCount = 0; + bool escapeQuote = false; ++code; char *startOfStr = code.base(); - while(escapeCount > 0 || *code != '"') + while(true) { c = *code; - if(c == '\0') - return Token::END_OF_FILE; + if(c == '"' && !escapeQuote) + break; else if(c == '\\') - ++escapeCount; + escapeQuote = !escapeQuote; + else if(c == '\0') + throw UnexpectedTokenException("Reached end of file before string end"); else - escapeCount = min(0, escapeCount - 1); + escapeQuote = false; ++code; } diff --git a/src/FileUtil.cpp b/src/FileUtil.cpp index 8502e84..59132dc 100644 --- a/src/FileUtil.cpp +++ b/src/FileUtil.cpp @@ -75,5 +75,6 @@ namespace sibs } fwrite(data.data, 1, data.size, file); fclose(file); + return true; } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 866d691..9f14c67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include #include "../include/FileUtil.hpp" #include "../include/Conf.hpp" +#include "../include/Dependency.hpp" #include "../backend/ninja/Ninja.hpp" #include #include @@ -60,6 +61,11 @@ public: assert(finishedProcessing); return packageName; } + + const std::vector& getDependencies() const + { + return dependencies; + } protected: void processObject(StringView name) override { @@ -96,6 +102,19 @@ protected: else throw ParserException("Expected package.name to be a single value, was a list"); } + else if(currentObject.equals("dependencies")) + { + if(value.isSingle()) + { + // TODO: Validate version is number in correct format + Dependency dependency; + dependency.name = string(name.data, name.size); + dependency.version = string(value.asSingle().data, value.asSingle().size); + dependencies.emplace_back(dependency); + } + else + throw ParserException("Expected field under dependencies to be a single value, was a list"); + } } void finished() override @@ -105,6 +124,7 @@ protected: private: StringView currentObject; string packageName; + std::vector dependencies; bool finishedProcessing; }; @@ -143,11 +163,11 @@ int main(int argc, const char **argv) exit(6); } - string projectSrcPath = projectPath + "/src"; - validateDirectoryPath(projectSrcPath.c_str()); + //string projectSrcPath = projectPath + "/src"; + //validateDirectoryPath(projectSrcPath.c_str()); backend::Ninja ninja; - walkDirFiles(projectSrcPath.c_str(), [&ninja, &projectPath](tinydir_file *file) + walkDirFiles(projectPath.c_str(), [&ninja, &projectPath](tinydir_file *file) { if (isSourceFile(file)) { @@ -163,7 +183,12 @@ int main(int argc, const char **argv) // TODO: Create build path if it doesn't exist string debugBuildPath = projectPath + "/build/debug"; string buildFilePath = debugBuildPath + "/build.ninja"; - ninja.build(sibsConfig.getPackageName(), buildFilePath.c_str()); + Result buildFileResult = ninja.createBuildFile(sibsConfig.getPackageName(), sibsConfig.getDependencies(), buildFilePath.c_str()); + if(buildFileResult.isErr()) + { + printf("Failed to build ninja file: %s\n", buildFileResult.getErrMsg().c_str()); + exit(7); + } return 0; } \ No newline at end of file -- cgit v1.2.3