diff --git a/include/Builder/BuildGraph.hpp b/include/Builder/BuildGraph.hpp index 1822faa3e..4f2b87157 100644 --- a/include/Builder/BuildGraph.hpp +++ b/include/Builder/BuildGraph.hpp @@ -48,7 +48,7 @@ class BuildGraph { Result installDeps(bool includeDevDeps); void enableCoverage(); - Result plan(); + Result plan(bool logAnalysis = true); Result writeBuildFilesIfNeeded() const; Result generateCompdb() const; diff --git a/lib/Builder/BuildGraph.cc b/lib/Builder/BuildGraph.cc index d643bbffd..e0256eb6e 100644 --- a/lib/Builder/BuildGraph.cc +++ b/lib/Builder/BuildGraph.cc @@ -839,11 +839,9 @@ Result BuildGraph::generateCompdb() const { return Ok(); } -Result BuildGraph::plan() { - static bool loggedAnalysis = false; - if (!loggedAnalysis) { +Result BuildGraph::plan(const bool logAnalysis) { + if (logAnalysis) { Diag::info("Analyzing", "project dependencies..."); - loggedAnalysis = true; } const bool buildProj = !isUpToDate("build.ninja"); diff --git a/src/Builder/Builder.cc b/src/Builder/Builder.cc index 6113707e5..8daaa674d 100644 --- a/src/Builder/Builder.cc +++ b/src/Builder/Builder.cc @@ -29,7 +29,8 @@ Result Builder::schedule(const ScheduleOptions& options) { graphState->enableCoverage(); } Try(graphState->installDeps(options.includeDevDeps)); - Try(graphState->plan()); + const bool logAnalysis = !options.suppressAnalysisLog; + Try(graphState->plan(logAnalysis)); outDir = graphState->outBasePath(); return Ok(); } diff --git a/src/Builder/Builder.hpp b/src/Builder/Builder.hpp index b652c3282..731f43978 100644 --- a/src/Builder/Builder.hpp +++ b/src/Builder/Builder.hpp @@ -17,6 +17,7 @@ namespace fs = std::filesystem; struct ScheduleOptions { bool includeDevDeps = false; bool enableCoverage = false; + bool suppressAnalysisLog = false; }; class Builder { diff --git a/src/Cmd/New.cc b/src/Cmd/New.cc index c4af33a36..64cc82238 100644 --- a/src/Cmd/New.cc +++ b/src/Cmd/New.cc @@ -9,10 +9,12 @@ #include "Rustify/Result.hpp" #include +#include #include #include #include #include +#include namespace cabin { @@ -26,21 +28,24 @@ const Subcmd NEW_CMD = // .setArg(Arg{ "name" }) .setMainFn(newMain); -static constexpr std::string_view MAIN_CC = - "#include \n\n" - "int main(int argc, char* argv[]) {\n" - " std::println(\"Hello, world!\");\n" - " return 0;\n" - "}\n"; +static constexpr std::string_view MAIN_CC = R"(#include -static constexpr std::string_view LIB_CC = "int libFunction() { return 0; }\n"; +int main(int argc, char* argv[]) { + std::println("Hello, world!"); + return 0; +} +)"; + +static std::string toNamespaceName(std::string_view projectName) { + return replaceAll(std::string(projectName), "-", "_"); +} static std::string getAuthor() noexcept { try { git2::Config config = git2::Config(); config.openDefault(); - return config.getString("user.name") + " <" + config.getString("user.email") - + ">"; + return std::format("{} <{}>", config.getString("user.name"), + config.getString("user.email")); } catch (const git2::Exception& e) { spdlog::debug("{}", e.what()); return ""; @@ -48,35 +53,37 @@ static std::string getAuthor() noexcept { } std::string createCabinToml(const std::string_view projectName) noexcept { - std::string cabinToml = "[package]\n" - "name = \""; - cabinToml += projectName; - cabinToml += "\"\n" - "version = \"0.1.0\"\n" - "authors = [\""; - cabinToml += getAuthor(); - cabinToml += "\"]\n" - "edition = \"23\"\n"; - return cabinToml; + const std::string author = getAuthor(); + return std::format(R"([package] +name = "{}" +version = "0.1.0" +authors = ["{}"] +edition = "23" +)", + projectName, author); } static std::string getHeader(const std::string_view projectName) noexcept { const std::string projectNameUpper = toMacroName(projectName); - std::string header = "#ifndef " + projectNameUpper - + "_HPP\n" - "#define " - + projectNameUpper - + "_HPP\n\n" - "namespace "; - header += projectName; - header += " {\n}\n\n" - "#endif // !" - + projectNameUpper + "_HPP\n"; - return header; + const std::string ns = toNamespaceName(projectName); + return std::format(R"(#ifndef {0}_HPP +#define {0}_HPP + +namespace {1} {{ +void hello_world(); +}} // namespace {1} + +#endif // !{0}_HPP +)", + projectNameUpper, ns); } -static Result writeToFile(std::ofstream& ofs, const fs::path& fpath, - const std::string_view text, +struct FileTemplate { + fs::path path; + std::string contents; +}; + +static Result writeToFile(const fs::path& fpath, const std::string& text, const bool skipIfExists = false) { if (fs::exists(fpath)) { if (skipIfExists) { @@ -85,55 +92,63 @@ static Result writeToFile(std::ofstream& ofs, const fs::path& fpath, Bail("refusing to overwrite `{}`; file already exists", fpath.string()); } - ofs.open(fpath); - if (ofs.is_open()) { - ofs << text; - } - ofs.close(); - - if (!ofs) { - Bail("writing `{}` failed", fpath.string()); - } - ofs.clear(); + std::ofstream ofs(fpath, std::ios::trunc); + Ensure(ofs.is_open(), "opening `{}` failed", fpath.string()); + ofs << text; + Ensure(static_cast(ofs), "writing `{}` failed", fpath.string()); return Ok(); } Result createProjectFiles(const bool isBin, const fs::path& root, const std::string_view projectName, const bool skipExisting) { - std::ofstream ofs; - + std::vector templates; if (isBin) { - fs::create_directories(root / fs::path("src")); - fs::create_directories(root / fs::path("lib")); - Try(writeToFile(ofs, root / fs::path("cabin.toml"), - createCabinToml(projectName), skipExisting)); - Try(writeToFile(ofs, root / fs::path(".gitignore"), "/cabin-out", - skipExisting)); - Try(writeToFile(ofs, root / fs::path("src") / "main.cc", MAIN_CC, - skipExisting)); - Try(writeToFile(ofs, root / fs::path("lib") / "lib.cc", LIB_CC, - skipExisting)); - - Diag::info("Created", "binary (application) `{}` package", projectName); + fs::create_directories(root / "src"); + templates.push_back(FileTemplate{ + .path = root / "cabin.toml", + .contents = createCabinToml(projectName), + }); + templates.push_back( + FileTemplate{ .path = root / ".gitignore", .contents = "/cabin-out" }); + templates.push_back(FileTemplate{ .path = root / "src" / "main.cc", + .contents = std::string(MAIN_CC) }); } else { - fs::create_directories(root / fs::path("include") / projectName); - fs::create_directories(root / fs::path("lib")); - fs::create_directories(root / fs::path("src")); - Try(writeToFile(ofs, root / fs::path("cabin.toml"), - createCabinToml(projectName), skipExisting)); - Try(writeToFile(ofs, root / fs::path(".gitignore"), - "/cabin-out\ncabin.lock", skipExisting)); - Try(writeToFile( - ofs, - (root / fs::path("include") / projectName / projectName).string() - + ".hpp", - getHeader(projectName), skipExisting)); - Try(writeToFile(ofs, root / fs::path("lib") / "lib.cc", LIB_CC, - skipExisting)); - - Diag::info("Created", "library `{}` package", projectName); + const fs::path includeDir = root / "include" / projectName; + const fs::path libPath = (root / "lib" / projectName).string() + ".cc"; + fs::create_directories(includeDir); + fs::create_directories(root / "lib"); + const std::string ns = toNamespaceName(projectName); + templates.push_back(FileTemplate{ + .path = root / "cabin.toml", + .contents = createCabinToml(projectName), + }); + templates.push_back(FileTemplate{ .path = root / ".gitignore", + .contents = "/cabin-out\ncabin.lock" }); + templates.push_back(FileTemplate{ + .path = (includeDir / projectName).string() + ".hpp", + .contents = getHeader(projectName), + }); + const std::string libImpl = std::format( + R"(#include "{0}/{0}.hpp" +#include + +namespace {1} {{ +void hello_world() {{ + std::println("Hello, world from {0}!"); +}} +}} // namespace {1} +)", + projectName, ns); + templates.push_back(FileTemplate{ .path = libPath, .contents = libImpl }); } + + for (const FileTemplate& file : templates) { + Try(writeToFile(file.path, file.contents, skipExisting)); + } + + Diag::info("Created", "{} `{}` package", + isBin ? "binary (application)" : "library", projectName); return Ok(); } diff --git a/src/Cmd/Tidy.cc b/src/Cmd/Tidy.cc index f14fd8cdc..ae6f6065c 100644 --- a/src/Cmd/Tidy.cc +++ b/src/Cmd/Tidy.cc @@ -91,12 +91,17 @@ static Result tidyMain(const CliArgsView args) { std::string compdbDir; const std::array profiles{ BuildProfile::Dev, BuildProfile::Test }; + bool isFirstProfile = true; for (const BuildProfile& profile : profiles) { Builder builder(projectRoot, profile); const bool includeDevDeps = (profile == BuildProfile::Test); - Try(builder.schedule(ScheduleOptions{ .includeDevDeps = includeDevDeps, - .enableCoverage = false })); + Try(builder.schedule(ScheduleOptions{ + .includeDevDeps = includeDevDeps, + .enableCoverage = false, + .suppressAnalysisLog = !isFirstProfile, + })); compdbDir = builder.compdbRoot(); + isFirstProfile = false; } std::string runClangTidy = "run-clang-tidy"; diff --git a/tests/build.cc b/tests/build.cc index 8ef3bf13e..f15fff098 100644 --- a/tests/build.cc +++ b/tests/build.cc @@ -21,7 +21,7 @@ int main() { expect(tests::fs::is_regular_file(outDir / "targets.ninja")); expect(tests::fs::is_regular_file(outDir / "ninja_project")); expect(tests::fs::is_directory(outDir / "ninja_project.d")); - expect(tests::fs::is_regular_file(outDir / "libninja_project.a")); + expect(!tests::fs::exists(outDir / "libninja_project.a")); expect(!tests::fs::exists(outDir / "Makefile")); }; diff --git a/tests/fmt.cc b/tests/fmt.cc index 644608ea1..e45f15b93 100644 --- a/tests/fmt.cc +++ b/tests/fmt.cc @@ -52,7 +52,7 @@ int main() { auto sanitizedFirstOut = tests::sanitizeOutput(firstFmt.out); expect(sanitizedFirstOut.empty()); auto sanitizedFirstErr = tests::sanitizeOutput(firstFmt.err); - const std::string expectedFirstErr = " Formatted 1 out of 2 files\n"; + const std::string expectedFirstErr = " Formatted 1 out of 1 file\n"; expect(sanitizedFirstErr == expectedFirstErr); const auto afterFirst = tests::readFile(mainFile); @@ -63,7 +63,7 @@ int main() { auto sanitizedSecondOut = tests::sanitizeOutput(secondFmt.out); expect(sanitizedSecondOut.empty()); auto sanitizedSecondErr = tests::sanitizeOutput(secondFmt.err); - const std::string expectedSecondErr = " Formatted 0 out of 2 files\n"; + const std::string expectedSecondErr = " Formatted 0 out of 1 file\n"; expect(sanitizedSecondErr == expectedSecondErr); const auto afterSecond = tests::readFile(mainFile); diff --git a/tests/init.cc b/tests/init.cc index 5f807acb3..b8d50cdaf 100644 --- a/tests/init.cc +++ b/tests/init.cc @@ -60,7 +60,7 @@ int main() { expect(result.status.success()) << result.status.toString(); expect(tests::readFile(mainPath) == "int main() { return 42; }\n"); - expect(tests::fs::is_regular_file(project / "lib" / "lib.cc")); + expect(!tests::fs::exists(project / "lib" / "lib.cc")); expect(tests::fs::is_regular_file(project / "cabin.toml")); }; } diff --git a/tests/new.cc b/tests/new.cc index 511f0c3cc..0643bcc47 100644 --- a/tests/new.cc +++ b/tests/new.cc @@ -21,6 +21,8 @@ int main() { expect(tests::fs::is_regular_file(project / "cabin.toml")); expect(tests::fs::is_directory(project / "src")); expect(tests::fs::is_regular_file(project / "src/main.cc")); + expect(!tests::fs::exists(project / "lib")); + expect(!tests::fs::exists(project / "include")); auto sanitizedOut = tests::sanitizeOutput(result.out); expect(sanitizedOut.empty()); @@ -43,6 +45,9 @@ int main() { expect(tests::fs::is_regular_file(project / ".gitignore")); expect(tests::fs::is_regular_file(project / "cabin.toml")); expect(tests::fs::is_directory(project / "include")); + expect(tests::fs::is_regular_file(project + / "include/hello_world/hello_world.hpp")); + expect(tests::fs::is_regular_file(project / "lib/hello_world.cc")); auto sanitizedOut = tests::sanitizeOutput(result.out); expect(sanitizedOut.empty()); diff --git a/tests/run.cc b/tests/run.cc index 18cf63a2e..84f4d5cbb 100644 --- a/tests/run.cc +++ b/tests/run.cc @@ -35,8 +35,10 @@ int main() { sanitizedErr == analyzing + binErr + libErr + tailErr; const bool errMatchesWithLibFirst = sanitizedErr == analyzing + libErr + binErr + tailErr; + const bool errMatchesBinOnly = sanitizedErr == analyzing + binErr + tailErr; const bool errMatchesWithoutLib = sanitizedErr == analyzing + tailErr; - expect(errMatchesWithLib || errMatchesWithLibFirst || errMatchesWithoutLib) + expect(errMatchesWithLib || errMatchesWithLibFirst || errMatchesBinOnly + || errMatchesWithoutLib) << sanitizedErr; expect(tests::fs::is_directory(project / "cabin-out")); diff --git a/tests/test.cc b/tests/test.cc index 9c3cb865c..87523c7ea 100644 --- a/tests/test.cc +++ b/tests/test.cc @@ -20,17 +20,22 @@ static std::size_t countFiles(const tests::fs::path& root, return count; } -static std::string expectedTestSummary(std::string_view projectName) { - return fmt::format( - " Analyzing project dependencies...\n" - " Compiling {}(lib) v0.1.0 ()\n" - " Compiling {}(test) v0.1.0 ()\n" +static std::string expectedTestSummary(std::string_view projectName, + bool hasLib) { + std::string summary = " Analyzing project dependencies...\n"; + if (hasLib) { + summary += + fmt::format(" Compiling {}(lib) v0.1.0 ()\n", projectName); + } + summary += + fmt::format(" Compiling {}(test) v0.1.0 ()\n", projectName); + summary += " Finished `test` profile [unoptimized + debuginfo] target(s) in " "s\n" " Running unit test src/main.cc " "(cabin-out/test/unit/src/main.cc.test)\n" - " Ok 1 passed; 0 failed; finished in s\n", - projectName, projectName); + " Ok 1 passed; 0 failed; finished in s\n"; + return summary; } int main() { @@ -75,7 +80,7 @@ int main() { expect(sanitizedOut == "test test addition ... ok\n"); auto sanitizedErr = tests::sanitizeOutput( result.err, { { projectPath, "" } }); // NOLINT - expect(sanitizedErr == expectedTestSummary("test_project")); + expect(sanitizedErr == expectedTestSummary("test_project", false)); expect(tests::fs::is_directory(project / "cabin-out" / "test")); expect(tests::fs::is_directory(project / "cabin-out" / "test" / "unit")); @@ -130,7 +135,7 @@ int main() { expect(sanitizedOut == "test coverage function ... ok\n"); auto sanitizedErr = tests::sanitizeOutput( result.err, { { projectPath, "" } }); // NOLINT - expect(sanitizedErr == expectedTestSummary("coverage_project")); + expect(sanitizedErr == expectedTestSummary("coverage_project", false)); const auto outDir = project / "cabin-out" / "test"; expect(countFiles(outDir, ".gcda") > 0); @@ -169,7 +174,7 @@ int main() { expect(sanitizedOut.contains("--coverage")); auto sanitizedErr = tests::sanitizeOutput( result.err, { { projectPath, "" } }); // NOLINT - expect(sanitizedErr == expectedTestSummary("verbose_project")); + expect(sanitizedErr == expectedTestSummary("verbose_project", false)); }; "cabin test without coverage"_test = [] { @@ -201,7 +206,7 @@ int main() { expect(sanitizedOut == "test no coverage ... ok\n"); auto sanitizedErr = tests::sanitizeOutput( result.err, { { projectPath, "" } }); // NOLINT - expect(sanitizedErr == expectedTestSummary("no_coverage_project")); + expect(sanitizedErr == expectedTestSummary("no_coverage_project", false)); const auto outDir = project / "cabin-out" / "test"; expect(countFiles(outDir, ".gcda") == 0U); @@ -240,7 +245,7 @@ int main() { return 0; } tests::runCabin({ "new", "--lib", "lib_only" }, tmp.path).unwrap(); const auto project = tmp.path / "lib_only"; tests::fs::remove_all(project / "src"); - tests::writeFile(project / "lib" / "lib.cc", + tests::writeFile(project / "lib" / "lib_only.cc", R"(int libFunction() { return 1; } #ifdef CABIN_TEST @@ -253,6 +258,6 @@ int main() { const auto result = tests::runCabin({ "test" }, project).unwrap(); expect(result.status.success()) << result.status.toString(); const auto outDir = project / "cabin-out" / "test" / "unit" / "lib"; - expect(tests::fs::is_regular_file(outDir / "lib.cc.test")); + expect(tests::fs::is_regular_file(outDir / "lib_only.cc.test")); }; }