Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions include/Builder/NinjaPlan.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include <filesystem>
#include <string>
#include <utility>
#include <vector>

namespace cabin {

struct NinjaEdge {
std::vector<std::string> outputs;
std::string rule;
std::vector<std::string> inputs;
std::vector<std::string> implicitInputs;
std::vector<std::string> orderOnlyInputs;
std::vector<std::pair<std::string, std::string>> bindings;
};

struct NinjaToolchain {
std::string cxx;
std::string cxxFlags;
std::string defines;
std::string includes;
std::string ldFlags;
std::string libs;
std::string archiver;
};

class NinjaPlan {
public:
explicit NinjaPlan(std::filesystem::path outBasePath);

void reset();
void addEdge(NinjaEdge edge);
void addDefaultTarget(std::string target);
void setTestTargets(std::vector<std::string> testTargets);
void writeFiles(const NinjaToolchain& toolchain) const;

private:
void writeBuildNinja() const;
void writeConfigNinja(const NinjaToolchain& toolchain) const;
void writeRulesNinja() const;
void writeTargetsNinja() const;

std::filesystem::path outBasePath_;
std::vector<NinjaEdge> edges_;
std::vector<std::string> defaultTargets_;
std::vector<std::string> testTargets_;
};

} // namespace cabin
119 changes: 119 additions & 0 deletions lib/Builder/NinjaPlan.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "Builder/NinjaPlan.hpp"

#include <fmt/format.h>
#include <fmt/ranges.h>
#include <fstream>
#include <string>
#include <string_view>
#include <utility>

namespace cabin {

template <typename Range>
static std::string joinWithSpace(const Range& range) {
if (range.empty()) {
return "";
}
return fmt::format("{}", fmt::join(range, " "));
}

NinjaPlan::NinjaPlan(std::filesystem::path outBasePath)
: outBasePath_(std::move(outBasePath)) {}

void NinjaPlan::reset() {
edges_.clear();
defaultTargets_.clear();
testTargets_.clear();
}

void NinjaPlan::addEdge(NinjaEdge edge) { edges_.push_back(std::move(edge)); }

void NinjaPlan::addDefaultTarget(std::string target) {
defaultTargets_.push_back(std::move(target));
}

void NinjaPlan::setTestTargets(std::vector<std::string> testTargets) {
testTargets_ = std::move(testTargets);
}

void NinjaPlan::writeFiles(const NinjaToolchain& toolchain) const {
writeBuildNinja();
writeConfigNinja(toolchain);
writeRulesNinja();
writeTargetsNinja();
}

void NinjaPlan::writeBuildNinja() const {
std::ofstream buildFile(outBasePath_ / "build.ninja");
buildFile << "# Generated by Cabin\n";
buildFile << "ninja_required_version = 1.11\n\n";
buildFile << "include config.ninja\n";
buildFile << "include rules.ninja\n";
buildFile << "include targets.ninja\n\n";
if (!defaultTargets_.empty()) {
buildFile << "default " << joinWithSpace(defaultTargets_) << '\n';
}
}

void NinjaPlan::writeConfigNinja(const NinjaToolchain& toolchain) const {
std::ofstream cfg(outBasePath_ / "config.ninja");
cfg << "# Build variables\n";
cfg << "CXX = " << toolchain.cxx << '\n';
cfg << "CXXFLAGS = " << toolchain.cxxFlags << '\n';
cfg << "DEFINES = " << toolchain.defines << '\n';
cfg << "INCLUDES = " << toolchain.includes << '\n';
cfg << "LDFLAGS = " << toolchain.ldFlags << '\n';
cfg << "LIBS = " << toolchain.libs << '\n';
cfg << "AR = " << toolchain.archiver << '\n';
}

void NinjaPlan::writeRulesNinja() const {
std::ofstream rules(outBasePath_ / "rules.ninja");

rules << "rule cxx_compile\n";
rules << " command = $CXX $DEFINES $INCLUDES $CXXFLAGS $extra_flags -c $in "
"-o $out\n";
rules << " description = Building CXX object $out\n\n";

rules << "rule cxx_link_exe\n";
rules << " command = $CXX $in $LDFLAGS $LIBS -o $out\n";
rules << " description = Linking CXX executable $out\n\n";

rules << "rule cxx_link_static_lib\n";
rules << " command = rm -f $out && $AR rcs $out $in\n";
rules << " description = Linking CXX static library $out\n\n";
}

void NinjaPlan::writeTargetsNinja() const {
std::ofstream targetsFile(outBasePath_ / "targets.ninja");

for (const NinjaEdge& edge : edges_) {
targetsFile << "build " << joinWithSpace(edge.outputs);
targetsFile << ": " << edge.rule;
if (!edge.inputs.empty()) {
targetsFile << ' ' << joinWithSpace(edge.inputs);
}
if (!edge.implicitInputs.empty()) {
targetsFile << " | " << joinWithSpace(edge.implicitInputs);
}
if (!edge.orderOnlyInputs.empty()) {
targetsFile << " || " << joinWithSpace(edge.orderOnlyInputs);
}
targetsFile << '\n';
for (const auto& [key, value] : edge.bindings) {
targetsFile << " " << key << " = " << value << '\n';
}
targetsFile << '\n';
}

if (!defaultTargets_.empty()) {
targetsFile << "build all: phony " << joinWithSpace(defaultTargets_) << '\n'
<< '\n';
}
if (!testTargets_.empty()) {
targetsFile << "build tests: phony " << joinWithSpace(testTargets_) << '\n'
<< '\n';
}
}

} // namespace cabin
120 changes: 26 additions & 94 deletions src/BuildConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,6 @@ std::string BuildConfig::mapHeaderToObj(const fs::path& headerPath) const {
return fallback.generic_string();
}

void BuildConfig::addEdge(NinjaEdge edge) {
ninjaEdges.push_back(std::move(edge));
}

void BuildConfig::registerCompileUnit(
const std::string& objTarget, const std::string& sourceFile,
const std::unordered_set<std::string>& dependencies, const bool isTest) {
Expand All @@ -217,92 +213,21 @@ void BuildConfig::registerCompileUnit(
std::ranges::sort(edge.implicitInputs);
edge.bindings.emplace_back("out_dir", parentDirOrDot(objTarget));
edge.bindings.emplace_back("extra_flags", isTest ? "-DCABIN_TEST" : "");
addEdge(std::move(edge));
ninjaPlan.addEdge(std::move(edge));
}

void BuildConfig::writeBuildFiles() const {
writeBuildNinja();
writeConfigNinja();
writeRulesNinja();
writeTargetsNinja();
}

void BuildConfig::writeBuildNinja() const {
std::ofstream buildFile(outBasePath / "build.ninja");
buildFile << "# Generated by Cabin\n";
buildFile << "ninja_required_version = 1.11\n\n";
buildFile << "include config.ninja\n";
buildFile << "include rules.ninja\n";
buildFile << "include targets.ninja\n\n";
if (!defaultTargets.empty()) {
buildFile << "default " << joinFlags(defaultTargets) << '\n';
}
}

void BuildConfig::writeConfigNinja() const {
std::ofstream cfg(outBasePath / "config.ninja");
cfg << "# Build variables\n";
cfg << "CXX = " << compiler.cxx << '\n';
cfg << "CXXFLAGS = " << cxxFlags << '\n';
cfg << "DEFINES = " << defines << '\n';
cfg << "INCLUDES = " << includes << '\n';
cfg << "LDFLAGS = " << ldFlags << '\n';
cfg << "LIBS = " << libs << '\n';
cfg << "AR = " << archiver << '\n';
}

void BuildConfig::writeRulesNinja() const {
std::ofstream rules(outBasePath / "rules.ninja");

rules << "rule cxx_compile\n";
rules << " command = $CXX $DEFINES $INCLUDES $CXXFLAGS $extra_flags -c $in "
"-o $out\n";
rules << " description = Building CXX object $out\n\n";

rules << "rule cxx_link_exe\n";
rules << " command = $CXX $in $LDFLAGS $LIBS -o $out\n";
rules << " description = Linking CXX executable $out\n\n";

rules << "rule cxx_link_static_lib\n";
rules << " command = rm -f $out && $AR rcs $out $in\n";
rules << " description = Linking CXX static library $out\n\n";
}

void BuildConfig::writeTargetsNinja() const {
std::ofstream targetsFile(outBasePath / "targets.ninja");

for (const NinjaEdge& edge : ninjaEdges) {
targetsFile << "build " << joinFlags(edge.outputs);
targetsFile << ": " << edge.rule;
if (!edge.inputs.empty()) {
targetsFile << ' ' << joinFlags(edge.inputs);
}
if (!edge.implicitInputs.empty()) {
targetsFile << " | " << joinFlags(edge.implicitInputs);
}
if (!edge.orderOnlyInputs.empty()) {
targetsFile << " || " << joinFlags(edge.orderOnlyInputs);
}
targetsFile << '\n';
for (const auto& [key, value] : edge.bindings) {
targetsFile << " " << key << " = " << value << '\n';
}
targetsFile << '\n';
}
const NinjaToolchain toolchain{
.cxx = compiler.cxx,
.cxxFlags = cxxFlags,
.defines = defines,
.includes = includes,
.ldFlags = ldFlags,
.libs = libs,
.archiver = archiver,
};

if (!defaultTargets.empty()) {
targetsFile << "build all: phony " << joinFlags(defaultTargets) << '\n'
<< '\n';
}
if (!testTargets.empty()) {
std::vector<std::string> testTargetNames;
testTargetNames.reserve(testTargets.size());
for (const TestTarget& target : testTargets) {
testTargetNames.push_back(target.ninjaTarget);
}
targetsFile << "build tests: phony " << joinFlags(testTargetNames) << '\n'
<< '\n';
}
ninjaPlan.writeFiles(toolchain);
}

Result<std::string> BuildConfig::runMM(const std::string& sourceFile,
Expand Down Expand Up @@ -529,7 +454,7 @@ BuildConfig::processUnittestSrc(const fs::path& sourceFilePath,
linkEdge.inputs = std::move(linkInputs);
linkEdge.bindings.emplace_back("out_dir", parentDirOrDot(testBinary));

addEdge(std::move(linkEdge));
ninjaPlan.addEdge(std::move(linkEdge));

TestTarget testTarget;
testTarget.ninjaTarget = testBinary;
Expand Down Expand Up @@ -578,7 +503,7 @@ BuildConfig::processIntegrationTestSrc(const fs::path& sourceFilePath,
}
registerCompileUnit(testObjTarget, sourceFilePath.string(), objTargetDeps,
/*isTest=*/true);
addEdge(std::move(linkEdge));
ninjaPlan.addEdge(std::move(linkEdge));
if (mtx) {
mtx->unlock();
}
Expand Down Expand Up @@ -676,8 +601,7 @@ Result<void> BuildConfig::configureBuild() {
}

compileUnits.clear();
ninjaEdges.clear();
defaultTargets.clear();
ninjaPlan.reset();
testTargets.clear();

cxxFlags = joinFlags(project.compilerOpts.cFlags.others);
Expand Down Expand Up @@ -769,8 +693,8 @@ Result<void> BuildConfig::configureBuild() {
linkEdge.inputs = std::move(inputs);
linkEdge.bindings.emplace_back(
"out_dir", parentDirOrDot(project.manifest.package.name));
addEdge(std::move(linkEdge));
defaultTargets.push_back(project.manifest.package.name);
ninjaPlan.addEdge(std::move(linkEdge));
ninjaPlan.addDefaultTarget(project.manifest.package.name);
}

if (hasLibraryTarget) {
Expand All @@ -789,8 +713,8 @@ Result<void> BuildConfig::configureBuild() {
archiveEdge.rule = "cxx_link_static_lib";
archiveEdge.inputs = std::move(libraryInputs);
archiveEdge.bindings.emplace_back("out_dir", parentDirOrDot(libName));
addEdge(std::move(archiveEdge));
defaultTargets.push_back(libName);
ninjaPlan.addEdge(std::move(archiveEdge));
ninjaPlan.addDefaultTarget(libName);
}

if (buildProfile == BuildProfile::Test) {
Expand Down Expand Up @@ -827,8 +751,16 @@ Result<void> BuildConfig::configureBuild() {
return lhs.ninjaTarget < rhs.ninjaTarget;
});
testTargets = std::move(discoveredTests);

std::vector<std::string> testTargetNames;
testTargetNames.reserve(testTargets.size());
for (const TestTarget& target : testTargets) {
testTargetNames.push_back(target.ninjaTarget);
}
ninjaPlan.setTestTargets(std::move(testTargetNames));
} else {
testTargets.clear();
ninjaPlan.setTestTargets({});
}

return Ok();
Expand Down
Loading
Loading