diff --git a/src/BuildConfig.cc b/src/BuildConfig.cc index 5ceffcd48..70ff2fbed 100644 --- a/src/BuildConfig.cc +++ b/src/BuildConfig.cc @@ -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"; @@ -486,12 +491,21 @@ BuildConfig::defineCompileTarget( } void -BuildConfig::defineLinkTarget( - const std::string& binTarget, const std::unordered_set& deps +BuildConfig::defineOutputTarget( + const std::unordered_set& buildObjTargets, + const std::string& targetInputPath, + const std::vector& commands, + const std::string& targetOutputPath ) { - std::vector commands; - commands.emplace_back("$(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@"); - defineTarget(binTarget, commands, deps); + // Project binary target. + std::unordered_set 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. @@ -760,7 +774,8 @@ BuildConfig::processUnittestSrc( ); // Test binary target. - defineLinkTarget(testTarget, testTargetDeps); + const std::vector commands = { LINK_BIN_COMMAND }; + defineTarget(testTarget, commands, testTargetDeps); testTargets.insert(testTarget); if (mtx) { @@ -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(); @@ -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)) { @@ -817,8 +853,16 @@ BuildConfig::configureBuild() { setVariables(); + std::unordered_set all = {}; + if (hasBinaryTarget) { + all.insert(packageName); + } + if (hasLibraryTarget) { + all.insert(libName); + } + // Build rules - setAll({ packageName }); + setAll(all); addPhony("all"); std::vector sourceFilePaths = listSourceFilePaths(srcDir); @@ -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(); } @@ -842,16 +895,20 @@ BuildConfig::configureBuild() { const std::unordered_set buildObjTargets = processSources(sourceFilePaths); - // Project binary target. - const std::string mainObjTarget = buildOutPath / "main.o"; - std::unordered_set projTargetDeps = { mainObjTarget }; - collectBinDepObjs( - projTargetDeps, "", - targets.at(mainObjTarget).remDeps, // we don't need sourceFile - buildObjTargets - ); + if (hasBinaryTarget) { + const std::vector commands = { LINK_BIN_COMMAND }; + defineOutputTarget( + buildObjTargets, buildOutPath / "main.o", commands, + outBasePath / packageName + ); + } - defineLinkTarget(outBasePath / packageName, projTargetDeps); + if (hasLibraryTarget) { + const std::vector commands = { ARCHIVE_LIB_COMMAND }; + defineOutputTarget( + buildObjTargets, buildOutPath / "lib.o", commands, outBasePath / libName + ); + } // Test Pass std::unordered_set testTargets; diff --git a/src/BuildConfig.hpp b/src/BuildConfig.hpp index e7e14389d..7b2944b2a 100644 --- a/src/BuildConfig.hpp +++ b/src/BuildConfig.hpp @@ -22,6 +22,10 @@ inline const std::unordered_set 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, // := @@ -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 variables; std::unordered_map> varDeps; std::unordered_map targets; @@ -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& dependsOn = {} @@ -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& dependsOn = {} ) { defineVar(name, { .value = value, .type = VarType::Simple }, dependsOn); } + void defineCondVar( const std::string& name, const std::string& value, const std::unordered_set& dependsOn = {} @@ -142,8 +164,12 @@ struct BuildConfig { const std::string& objTarget, const std::string& sourceFile, const std::unordered_set& remDeps, bool isTest = false ); - void defineLinkTarget( - const std::string& binTarget, const std::unordered_set& deps + + void defineOutputTarget( + const std::unordered_set& buildObjTargets, + const std::string& targetInputPath, + const std::vector& commands, + const std::string& targetOutputPath ); void collectBinDepObjs( // NOLINT(misc-no-recursion) diff --git a/src/Cmd/Build.cc b/src/Cmd/Build.cc index 74d1b4ba3..b3126842f 100644 --- a/src/Cmd/Build.cc +++ b/src/Cmd/Build.cc @@ -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 elapsed = end - start;