Skip to content

Commit

Permalink
build: support lib.cc (#1035)
Browse files Browse the repository at this point in the history
A simple implementation for static library building. It functions nearly
identically to executable building, except that we look for files named
`lib.cc` (or the other acceptable C++ extensions) and resolved the
dependencies for that file. Instead of linking everything into an
executable at the end, we instead use `ar` to build an object archive
called `lib<package-name>.a`.

---------

Co-authored-by: Krist Pregracke <[email protected]>
Co-authored-by: Ken Matsui <[email protected]>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent 619d92c commit 2e0da2c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 34 deletions.
99 changes: 78 additions & 21 deletions src/BuildConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ operator<<(std::ostream& os, VarType type) {

BuildConfig::BuildConfig(const std::string& packageName, const bool isDebug)
: packageName{ packageName }, isDebug{ isDebug } {
if (packageName.starts_with("lib")) {
libName = fmt::format("{}.a", packageName);
} else {
libName = fmt::format("lib{}.a", packageName);
}
const fs::path projectBasePath = getProjectBasePath();
if (isDebug) {
outBasePath = projectBasePath / "poac-out" / "debug";
Expand Down Expand Up @@ -486,12 +491,21 @@ BuildConfig::defineCompileTarget(
}

void
BuildConfig::defineLinkTarget(
const std::string& binTarget, const std::unordered_set<std::string>& deps
BuildConfig::defineOutputTarget(
const std::unordered_set<std::string>& buildObjTargets,
const std::string& targetInputPath,
const std::vector<std::string>& commands,
const std::string& targetOutputPath
) {
std::vector<std::string> commands;
commands.emplace_back("$(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@");
defineTarget(binTarget, commands, deps);
// Project binary target.
std::unordered_set<std::string> projTargetDeps = { targetInputPath };
collectBinDepObjs(
projTargetDeps, "",
targets.at(targetInputPath).remDeps, // we don't need sourceFile
buildObjTargets
);

defineTarget(targetOutputPath, commands, projTargetDeps);
}

// Map a path to header file to the corresponding object file.
Expand Down Expand Up @@ -760,7 +774,8 @@ BuildConfig::processUnittestSrc(
);

// Test binary target.
defineLinkTarget(testTarget, testTargetDeps);
const std::vector<std::string> commands = { LINK_BIN_COMMAND };
defineTarget(testTarget, commands, testTargetDeps);

testTargets.insert(testTarget);
if (mtx) {
Expand Down Expand Up @@ -791,6 +806,9 @@ BuildConfig::configureBuild() {
const auto isMainSource = [](const fs::path& file) {
return file.filename().stem() == "main";
};
const auto isLibSource = [](const fs::path& file) {
return file.filename().stem() == "lib";
};
fs::path mainSource;
for (const auto& entry : fs::directory_iterator(srcDir)) {
const fs::path& path = entry.path();
Expand All @@ -800,15 +818,33 @@ BuildConfig::configureBuild() {
if (!isMainSource(path)) {
continue;
}
if (mainSource.empty()) {
mainSource = path;
} else {
if (!mainSource.empty()) {
throw PoacError("multiple main sources were found");
}
mainSource = path;
hasBinaryTarget = true;
}

if (mainSource.empty()) {
throw PoacError(fmt::format("src/main{} was not found", SOURCE_FILE_EXTS));
fs::path libSource;
for (const auto& entry : fs::directory_iterator(srcDir)) {
const fs::path& path = entry.path();
if (!SOURCE_FILE_EXTS.contains(path.extension())) {
continue;
}
if (!isLibSource(path)) {
continue;
}
if (!libSource.empty()) {
throw PoacError("multiple lib sources were found");
}
libSource = path;
hasLibraryTarget = true;
}

if (!hasBinaryTarget && !hasLibraryTarget) {
throw PoacError(
fmt::format("src/(main|lib){} was not found", SOURCE_FILE_EXTS)
);
}

if (!fs::exists(outBasePath)) {
Expand All @@ -817,8 +853,16 @@ BuildConfig::configureBuild() {

setVariables();

std::unordered_set<std::string> all = {};
if (hasBinaryTarget) {
all.insert(packageName);
}
if (hasLibraryTarget) {
all.insert(libName);
}

// Build rules
setAll({ packageName });
setAll(all);
addPhony("all");

std::vector<fs::path> sourceFilePaths = listSourceFilePaths(srcDir);
Expand All @@ -832,7 +876,16 @@ BuildConfig::configureBuild() {
"Move it directly to 'src/' if intended as such.",
sourceFilePath.string()
);
} else if (sourceFilePath != libSource && isLibSource(sourceFilePath)) {
logger::warn(
"source file `{}` is named `lib` but is not located directly in the "
"`src/` directory. "
"This file will not be treated as a hasLibraryTarget. "
"Move it directly to 'src/' if intended as such.",
sourceFilePath.string()
);
}

srcs += ' ' + sourceFilePath.string();
}

Expand All @@ -842,16 +895,20 @@ BuildConfig::configureBuild() {
const std::unordered_set<std::string> buildObjTargets =
processSources(sourceFilePaths);

// Project binary target.
const std::string mainObjTarget = buildOutPath / "main.o";
std::unordered_set<std::string> projTargetDeps = { mainObjTarget };
collectBinDepObjs(
projTargetDeps, "",
targets.at(mainObjTarget).remDeps, // we don't need sourceFile
buildObjTargets
);
if (hasBinaryTarget) {
const std::vector<std::string> commands = { LINK_BIN_COMMAND };
defineOutputTarget(
buildObjTargets, buildOutPath / "main.o", commands,
outBasePath / packageName
);
}

defineLinkTarget(outBasePath / packageName, projTargetDeps);
if (hasLibraryTarget) {
const std::vector<std::string> commands = { ARCHIVE_LIB_COMMAND };
defineOutputTarget(
buildObjTargets, buildOutPath / "lib.o", commands, outBasePath / libName
);
}

// Test Pass
std::unordered_set<std::string> testTargets;
Expand Down
30 changes: 28 additions & 2 deletions src/BuildConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ inline const std::unordered_set<std::string> HEADER_FILE_EXTS{
};
// clang-format on

inline const std::string LINK_BIN_COMMAND =
"$(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@";
inline const std::string ARCHIVE_LIB_COMMAND = "ar rcs $@ $^";

enum class VarType : uint8_t {
Recursive, // =
Simple, // :=
Expand All @@ -47,10 +51,16 @@ struct BuildConfig {

private:
std::string packageName;
std::string libName;
fs::path buildOutPath;
fs::path unittestOutPath;
bool isDebug;

// if we are building an binary
bool hasBinaryTarget{ false };
// if we are building a hasLibraryTarget
bool hasLibraryTarget{ false };

std::unordered_map<std::string, Variable> variables;
std::unordered_map<std::string, std::vector<std::string>> varDeps;
std::unordered_map<std::string, Target> targets;
Expand All @@ -67,6 +77,16 @@ struct BuildConfig {
public:
explicit BuildConfig(const std::string& packageName, bool isDebug = true);

bool hasBinTarget() const {
return hasBinaryTarget;
}
bool hasLibTarget() const {
return hasLibraryTarget;
}
const std::string& getLibName() const {
return this->libName;
}

void defineVar(
const std::string& name, const Variable& value,
const std::unordered_set<std::string>& dependsOn = {}
Expand All @@ -77,12 +97,14 @@ struct BuildConfig {
varDeps[dep].push_back(name);
}
}

void defineSimpleVar(
const std::string& name, const std::string& value,
const std::unordered_set<std::string>& dependsOn = {}
) {
defineVar(name, { .value = value, .type = VarType::Simple }, dependsOn);
}

void defineCondVar(
const std::string& name, const std::string& value,
const std::unordered_set<std::string>& dependsOn = {}
Expand Down Expand Up @@ -142,8 +164,12 @@ struct BuildConfig {
const std::string& objTarget, const std::string& sourceFile,
const std::unordered_set<std::string>& remDeps, bool isTest = false
);
void defineLinkTarget(
const std::string& binTarget, const std::unordered_set<std::string>& deps

void defineOutputTarget(
const std::unordered_set<std::string>& buildObjTargets,
const std::string& targetInputPath,
const std::vector<std::string>& commands,
const std::string& targetOutputPath
);

void collectBinDepObjs( // NOLINT(misc-no-recursion)
Expand Down
38 changes: 27 additions & 11 deletions src/Cmd/Build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,45 @@ const Subcmd BUILD_CMD =
.setMainFn(buildMain);

int
buildImpl(std::string& outDir, const bool isDebug) {
const auto start = std::chrono::steady_clock::now();

const BuildConfig config = emitMakefile(isDebug, /*includeDevDeps=*/false);
outDir = config.outBasePath;

const std::string& packageName = getPackageName();
runBuildCommand(
const std::string& outDir, const BuildConfig& config,
const std::string& targetName
) {
const Command makeCmd = getMakeCommand().addArg("-C").addArg(outDir).addArg(
(config.outBasePath / packageName).string()
(config.outBasePath / targetName).string()
);
Command checkUpToDateCmd = makeCmd;
checkUpToDateCmd.addArg("--question");

int exitCode = execCmd(checkUpToDateCmd);
if (exitCode != EXIT_SUCCESS) {
// If packageName binary is not up-to-date, compile it.
// If `targetName` is not up-to-date, compile it.
logger::info(
"Compiling", "{} v{} ({})", packageName, getPackageVersion().toString(),
"Compiling", "{} v{} ({})", targetName, getPackageVersion().toString(),
getProjectBasePath().string()

);
exitCode = execCmd(makeCmd);
}
return exitCode;
}

int
buildImpl(std::string& outDir, const bool isDebug) {
const auto start = std::chrono::steady_clock::now();

const BuildConfig config = emitMakefile(isDebug, /*includeDevDeps=*/false);
outDir = config.outBasePath;

const std::string& packageName = getPackageName();
int exitCode = 0;
if (config.hasBinTarget()) {
exitCode = runBuildCommand(outDir, config, packageName);
}

if (config.hasLibTarget() && exitCode == 0) {
const std::string& libName = config.getLibName();
exitCode = runBuildCommand(outDir, config, libName);
}

const auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> elapsed = end - start;
Expand Down

0 comments on commit 2e0da2c

Please sign in to comment.