diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | .vscode/c_cpp_properties.json | 17 | ||||
-rw-r--r-- | .vscode/settings.json | 49 | ||||
-rw-r--r-- | include/ninja/Ninja.hpp | 100 | ||||
-rw-r--r-- | project.conf | 9 | ||||
-rw-r--r-- | src/Ninja.cpp | 121 | ||||
-rw-r--r-- | test.build | 20 | ||||
-rw-r--r-- | tests/main.cpp | 37 |
8 files changed, 358 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..636c6b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Compiled sibs files +sibs-build/ +compile_commands.json +tests/sibs-build/ +tests/compile_commands.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..3c26c84 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++14", + "intelliSenseMode": "clang-x64", + "compileCommands": "${workspaceFolder}/tests/compile_commands.json" + } + ], + "version": 4 +}
\ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b91803e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,49 @@ +{ + "files.associations": { + "*.tcc": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "deque": "cpp", + "list": "cpp", + "__config": "cpp", + "__nullptr": "cpp", + "typeinfo": "cpp", + "array": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "type_traits": "cpp", + "tuple": "cpp", + "utility": "cpp" + } +}
\ No newline at end of file diff --git a/include/ninja/Ninja.hpp b/include/ninja/Ninja.hpp new file mode 100644 index 0000000..cd9be0a --- /dev/null +++ b/include/ninja/Ninja.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include <string> +#include <unordered_map> +#include <stdexcept> +#include <vector> + +namespace ninja +{ + enum class NinjaError + { + NONE, + VARIABLE_ALREADY_DEFINED, + RULE_ALREADY_DEFINED, + RULE_AREADY_BUILT + }; + + class NinjaException : public std::runtime_error + { + public: + NinjaException(const NinjaError _errorCode, const std::string &errMsg = "") : + std::runtime_error(errMsg), + errorCode(_errorCode) + { + + } + + const NinjaError errorCode; + }; + + struct NinjaVariable + { + const std::string name; + }; + + class NinjaBuildFile; + + class NinjaArg + { + public: + enum class Type + { + NONE, + VALUE, + VARIABLE + }; + + NinjaArg() : type(Type::NONE) {} + NinjaArg(const std::string &value) : type(Type::VALUE), arg(value) {} + NinjaArg(const NinjaVariable &var) : type(Type::VARIABLE), arg(var.name) {} + + Type type; + std::string arg; + }; + + struct NinjaArgValue + { + const NinjaVariable arg; + const std::string value; + }; + + // Functions throw NinjaException on failure + class NinjaRule + { + public: + NinjaRule(NinjaBuildFile *buildFile, const std::string &name, const std::string &command); + void build(const std::string &in, const std::string &out, const std::vector<ninja::NinjaArgValue> &additionalArgs); + + const std::string name; + const std::string command; + std::string depFile; + private: + NinjaBuildFile *buildFile; + }; + + struct NinjaBuild + { + const NinjaRule *rule; + const std::string in; + const std::string out; + const std::vector<ninja::NinjaArgValue> additionalArgs; + }; + + // Functions throw NinjaException on failure + class NinjaBuildFile + { + public: + ~NinjaBuildFile(); + + void defineGlobalVariable(const std::string &name, const std::string &value); + NinjaRule* createRule(const std::string &name, const std::vector<NinjaArg> &commandArgs); + void build(const NinjaRule *rule, const std::string &in, const std::string &out, const std::vector<ninja::NinjaArgValue> &additionalArgs); + + std::string generate() const; + private: + std::unordered_map<std::string, std::string> globalVariables; + std::unordered_map<std::string, NinjaRule*> rules; + std::vector<NinjaBuild> builds; + }; +}
\ No newline at end of file diff --git a/project.conf b/project.conf new file mode 100644 index 0000000..d5a3d2e --- /dev/null +++ b/project.conf @@ -0,0 +1,9 @@ +[package] +name = "libninja" +type = "static" +version = "0.1.0" +platforms = ["any"] +tests = "tests" + +[config] +expose_include_dirs = ["include"] diff --git a/src/Ninja.cpp b/src/Ninja.cpp new file mode 100644 index 0000000..7b3212e --- /dev/null +++ b/src/Ninja.cpp @@ -0,0 +1,121 @@ +#include "../include/ninja/Ninja.hpp" +#include <cassert> + +namespace ninja +{ + static std::string joinArgs(const std::vector<NinjaArg> &args) + { + std::string result; + for(const NinjaArg &arg : args) + { + if(!result.empty()) + result += ' '; + result += '\''; + if(arg.type == NinjaArg::Type::VARIABLE) + result += "$"; + result += arg.arg; + result += '\''; + } + return result; + } + + NinjaRule::NinjaRule(NinjaBuildFile *_buildFile, const std::string &_name, const std::string &_command) : + buildFile(_buildFile), + name(_name), + command(_command) + { + assert(buildFile); + } + + NinjaBuildFile::~NinjaBuildFile() + { + for(auto &rule : rules) + { + delete rule.second; + } + } + + void NinjaRule::build(const std::string &in, const std::string &out, const std::vector<ninja::NinjaArgValue> &additionalArgs) + { + buildFile->build(this, in, out, additionalArgs); + } + + void NinjaBuildFile::defineGlobalVariable(const std::string &name, const std::string &value) + { + auto it = globalVariables.find(name); + if(it != globalVariables.end()) + throw NinjaException(NinjaError::VARIABLE_ALREADY_DEFINED, "Global variable already defined: " + name); + globalVariables[name] = value; + } + + NinjaRule* NinjaBuildFile::createRule(const std::string &name, const std::vector<NinjaArg> &commandArgs) + { + auto it = rules.find(name); + if(it != rules.end()) + throw NinjaException(NinjaError::RULE_ALREADY_DEFINED, "Rule already defined: " + name); + + NinjaRule *rule = new NinjaRule(this, name, joinArgs(commandArgs)); + rules[name] = rule; + return rule; + } + + void NinjaBuildFile::build(const NinjaRule *rule, const std::string &in, const std::string &out, const std::vector<ninja::NinjaArgValue> &additionalArgs) + { + for(const NinjaBuild &build : builds) + { + if(build.rule == rule) + throw NinjaException(NinjaError::RULE_AREADY_BUILT, "Rule already built: " + rule->name); + } + builds.push_back({ rule, in, out, additionalArgs }); + } + + std::string NinjaBuildFile::generate() const + { + std::string result; + for(const auto &globalVar : globalVariables) + { + result += globalVar.first; + result += " = "; + result += globalVar.second; + result += '\n'; + } + + if(!globalVariables.empty()) + result += '\n'; + + for(const auto &rule : rules) + { + result += "rule "; + result += rule.first; + result += "\n command = "; + result += rule.second->command; + if(!rule.second->depFile.empty()) + { + result += "\n depfile = "; + result += rule.second->depFile; + } + result += "\n\n"; + } + + // TODO: enclose all names with quotes to escape whitespace? + for(const NinjaBuild &build : builds) + { + result += "build "; + result += build.out; + result += ": "; + result += build.rule->name; + result += ' '; + result += build.in; + for(const NinjaArgValue &additionalArg : build.additionalArgs) + { + result += "\n "; + result += additionalArg.arg.name; + result += " = "; + result += additionalArg.value; + + } + result += "\n\n"; + } + return result; + } +} diff --git a/test.build b/test.build new file mode 100644 index 0000000..f34a1e7 --- /dev/null +++ b/test.build @@ -0,0 +1,20 @@ + + +rule c_COMPILER + command = 'cc' '$ARGS' '-c' '$in' '-o' '$out' + +rule cpp_COMPILER + command = 'c++' '$ARGS' '-c' '$in' '-o' '$out' + depfile = $out.d + +rule BUILD_EXEC + command = 'c++' '$ARGS' '-o' '$out' '$in' '$LINK_ARGS' '$aliasing' + depfile = $out.d + +build test@exe/main.cpp.o: cpp_COMPILER ../../main.cpp + ARGS = $globalIncDir -std=c++14 -pedantic -fpie -MMD -MP '-Itest@exe' '-I..' -Wall -Wextra -Werror=return-type -fdiagnostics-show-option '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-Winvalid-pch' -fstack-protector '-Og' -fexceptions -Wnon-virtual-dtor -g3 -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fasynchronous-unwind-tables + +build test: BUILD_EXEC test@exe/main.cpp.o + ARGS = $globalIncDir -std=c++14 -pedantic -fpie -MMD -MP '-Itest@exe' '-I..' -Wall -Wextra -Werror=return-type -fdiagnostics-show-option '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-Winvalid-pch' -fstack-protector '-Og' -fexceptions -Wnon-virtual-dtor -g3 -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fasynchronous-unwind-tables + + diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..9af7241 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,37 @@ +#include <cstdio> +#include <ninja/Ninja.hpp> + +using namespace ninja; + +int main(int argc, char **argv) +{ + NinjaBuildFile ninjaBuildFile; + ninjaBuildFile.defineGlobalVariable("globalIncDir", "'-I/home/dec05eba/git/libninja/include'"); + + NinjaVariable argsVar = { "ARGS" }; + NinjaVariable linkArgsVar = { "LINK_ARGS" }; + + NinjaRule *compileCppRule = ninjaBuildFile.createRule("cpp_COMPILER", + { NinjaArg("c++"), NinjaArg(argsVar), NinjaArg("-c"), NinjaArg("$in"), NinjaArg("-o"), NinjaArg("$out") }); + compileCppRule->depFile = "$out.d"; + + NinjaRule *buildExeRule = ninjaBuildFile.createRule("BUILD_EXEC", + { NinjaArg("c++"), NinjaArg(argsVar), NinjaArg("-o"), NinjaArg("$out"), NinjaArg("$in"), NinjaArg(linkArgsVar), NinjaArg("$aliasing") }); + buildExeRule->depFile = "$out.d"; + + NinjaRule *compileCRule = ninjaBuildFile.createRule("c_COMPILER", + { NinjaArg("cc"), NinjaArg(argsVar), NinjaArg("-c"), NinjaArg("$in"), NinjaArg("-o"), NinjaArg("$out") }); + buildExeRule->depFile = "$out.d"; + + + NinjaArgValue argsValue = { argsVar, "$globalIncDir -std=c++14 -pedantic -fpie -MMD -MP '-Itest@exe' '-I..' -Wall -Wextra -Werror=return-type -fdiagnostics-show-option '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-Winvalid-pch' -fstack-protector '-Og' -fexceptions -Wnon-virtual-dtor -g3 -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fasynchronous-unwind-tables" }; + compileCppRule->build("../../main.cpp", "test@exe/main.cpp.o", { argsValue }); + + NinjaArgValue linkArgsValue = { linkArgsVar, "-Wl,--no-undefined,--as-needed -ldl -lm -pthread -pthread \"/home/dec05eba/git/libninja/sibs-build/debug/liblibninja.so\"" }; + buildExeRule->build("test@exe/main.cpp.o", "test", { argsValue }); + + std::string generatedNinjaFile = ninjaBuildFile.generate(); + + printf("%s\n", generatedNinjaFile.c_str()); + return 0; +} |