diff --git a/apps/ll-builder/src/main.cpp b/apps/ll-builder/src/main.cpp index bdb6169bc..287b21c94 100644 --- a/apps/ll-builder/src/main.cpp +++ b/apps/ll-builder/src/main.cpp @@ -38,62 +38,6 @@ namespace { -QStringList projectBuildConfigPaths() -{ - QStringList result{}; - - auto pwd = QDir::current(); - - do { - auto configPath = - QStringList{ pwd.absolutePath(), ".ll-builder", "config.yaml" }.join(QDir::separator()); - result << configPath; - } while (pwd.cdUp()); - - return result; -} - -QStringList nonProjectBuildConfigPaths() -{ - QStringList result{}; - - auto configLocations = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); - configLocations.append(SYSCONFDIR); - - for (const auto &configLocation : std::as_const(configLocations)) { - result << QStringList{ configLocation, "linglong", "builder", "config.yaml" }.join( - QDir::separator()); - } - - result << QStringList{ DATADIR, "linglong", "builder", "config.yaml" }.join(QDir::separator()); - - return result; -} - -void initDefaultBuildConfig() -{ - // ~/.cache - QDir cacheLocation = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); - // ~/.config/ - QDir configLocations = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - if (!QDir().mkpath(configLocations.filePath("linglong/builder"))) { - qWarning() << "init BuildConfig directory failed." - << configLocations.filePath("linglong/builder"); - } - QString configFilePath = configLocations.filePath("linglong/builder/config.yaml"); - if (QFile::exists(configFilePath)) { - return; - } - linglong::api::types::v1::BuilderConfig config; - config.version = 1; - config.repo = cacheLocation.filePath("linglong-builder").toStdString(); - auto ret = linglong::builder::saveConfig(config, configFilePath); - if (!ret) { - qCritical() << "failed to save default build config file" << configFilePath << ":" - << ret.error(); - } -} - std::string validateNonEmptyString(const std::string ¶meter) { if (parameter.empty()) { @@ -1056,15 +1000,9 @@ You can report bugs to the linyaps team under this project: https://github.com/O } // following command need repo - QStringList configPaths = {}; - // 初始化 build config - initDefaultBuildConfig(); - configPaths << projectBuildConfigPaths(); - configPaths << nonProjectBuildConfigPaths(); - - auto builderCfg = linglong::builder::loadConfig(configPaths); + auto builderCfg = linglong::builder::loadConfig(); if (!builderCfg) { - qCritical() << builderCfg.error(); + LogE("failed to load build config {}", builderCfg.error()); return -1; } diff --git a/libs/linglong/src/linglong/builder/config.cpp b/libs/linglong/src/linglong/builder/config.cpp index ee4ce2b70..e93900eee 100644 --- a/libs/linglong/src/linglong/builder/config.cpp +++ b/libs/linglong/src/linglong/builder/config.cpp @@ -7,7 +7,10 @@ #include "linglong/builder/config.h" #include "linglong/api/types/v1/BuilderConfig.hpp" +#include "linglong/common/dir.h" +#include "linglong/common/xdg.h" #include "linglong/utils/error/error.h" +#include "linglong/utils/log/log.h" #include "linglong/utils/serialize/yaml.h" #include @@ -16,24 +19,46 @@ namespace linglong::builder { -auto loadConfig(const QString &file) noexcept -> utils::error::Result +utils::error::Result +initDefaultBuildConfig(const std::filesystem::path &path) { - LINGLONG_TRACE(fmt::format("load build config from {}", file.toStdString())); + LINGLONG_TRACE("init default build config"); + + auto cacheLocation = common::xdg::getXDGCacheHomeDir(); + if (cacheLocation.empty()) { + return LINGLONG_ERR("failed to get cache dir, neither XDG_CACHE_HOME nor HOME is set"); + } + + std::error_code ec; + std::filesystem::create_directories(path.parent_path(), ec); + if (ec) { + return LINGLONG_ERR("failed to create config dir", ec); + } + + linglong::api::types::v1::BuilderConfig config; + config.version = 1; + config.repo = cacheLocation / "linglong-builder"; + auto ret = linglong::builder::saveConfig(config, path); + if (!ret) { + return LINGLONG_ERR("failed to save default build config file", ret.error()); + } + + return config; +} + +auto loadConfig(const std::filesystem::path &path) noexcept + -> utils::error::Result +{ + LINGLONG_TRACE(fmt::format("load build config from {}", path)); try { - QFile f(file); - qDebug() << "read build config file" << file; - if (!f.open(QIODevice::ReadOnly)) { - return LINGLONG_ERR("read build config file", f); - } - auto data = f.readAll().toStdString(); - auto config = utils::serialize::LoadYAML(data); + auto config = utils::serialize::LoadYAMLFile(path); if (!config) { return LINGLONG_ERR("parse build config", config); } if (config->version != 1) { return LINGLONG_ERR( - QString("wrong configuration file version %1").arg(config->version)); + fmt::format("wrong configuration file version {}", config->version)); } return config; @@ -42,32 +67,42 @@ auto loadConfig(const QString &file) noexcept -> utils::error::Result utils::error::Result +auto loadConfig() noexcept -> utils::error::Result { - LINGLONG_TRACE(fmt::format("load build config from {}", files.join(" ").toStdString())); + LINGLONG_TRACE("load build config"); + + std::filesystem::path builderConfigPath = "builder/config.yaml"; + auto configDir = common::dir::getUserRuntimeConfigDir(); + if (configDir.empty()) { + return LINGLONG_ERR("failed to get config dir"); + } - for (const auto &file : files) { - auto config = loadConfig(file); - if (!config.has_value()) { - qDebug() << "Failed to load build config from" << file << ":" << config.error(); - continue; + std::error_code ec; + auto path = configDir / builderConfigPath; + if (!std::filesystem::exists(path, ec)) { + if (ec) { + return LINGLONG_ERR(fmt::format("failed to check build config file {}", path), ec); } - qDebug() << "Load build config from" << file; - return config; + return initDefaultBuildConfig(path); + } + + auto config = loadConfig(path); + if (!config) { + return LINGLONG_ERR(config); } - return LINGLONG_ERR("all failed"); + LogD("Load build config from {}", path); + return config; } -auto saveConfig(const api::types::v1::BuilderConfig &cfg, const QString &path) noexcept - -> utils::error::Result +auto saveConfig(const api::types::v1::BuilderConfig &cfg, + const std::filesystem::path &path) noexcept -> utils::error::Result { - LINGLONG_TRACE(fmt::format("save config to {}", path.toStdString())); + LINGLONG_TRACE(fmt::format("save config to {}", path)); try { - auto ofs = std::ofstream(path.toLocal8Bit()); + auto ofs = std::ofstream(path); if (!ofs.is_open()) { return LINGLONG_ERR("open failed"); } @@ -80,4 +115,5 @@ auto saveConfig(const api::types::v1::BuilderConfig &cfg, const QString &path) n return LINGLONG_ERR(e); } } + } // namespace linglong::builder diff --git a/libs/linglong/src/linglong/builder/config.h b/libs/linglong/src/linglong/builder/config.h index 3bd29ecdc..c8f8226b2 100644 --- a/libs/linglong/src/linglong/builder/config.h +++ b/libs/linglong/src/linglong/builder/config.h @@ -9,15 +9,16 @@ #include "linglong/api/types/v1/BuilderConfig.hpp" #include "linglong/utils/error/error.h" -#include +#include namespace linglong::builder { -auto loadConfig(const QString &file) noexcept +utils::error::Result +initDefaultBuildConfig(const std::filesystem::path &path); +auto loadConfig(const std::filesystem::path &file) noexcept -> utils::error::Result; -auto loadConfig(const QStringList &files) noexcept - -> utils::error::Result; -auto saveConfig(const api::types::v1::BuilderConfig &cfg, const QString &path) noexcept - -> utils::error::Result; +auto loadConfig() noexcept -> utils::error::Result; +auto saveConfig(const api::types::v1::BuilderConfig &cfg, + const std::filesystem::path &path) noexcept -> utils::error::Result; } // namespace linglong::builder diff --git a/libs/linglong/tests/ll-tests/CMakeLists.txt b/libs/linglong/tests/ll-tests/CMakeLists.txt index abbfab78c..23a49e5cf 100644 --- a/libs/linglong/tests/ll-tests/CMakeLists.txt +++ b/libs/linglong/tests/ll-tests/CMakeLists.txt @@ -13,6 +13,7 @@ pfl_add_executable( SOURCES # find -regex '\./src/.+\.[ch]\(pp\)?' -type f -printf '%P\n'| sort src/common/tempdir.h + src/linglong/builder/config_test.cpp src/linglong/builder/linglong_builder_test.cpp src/linglong/builder/source_fetcher_test.cpp src/linglong/cli/cli_test.cpp diff --git a/libs/linglong/tests/ll-tests/src/linglong/builder/config_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/builder/config_test.cpp new file mode 100644 index 000000000..4cda47978 --- /dev/null +++ b/libs/linglong/tests/ll-tests/src/linglong/builder/config_test.cpp @@ -0,0 +1,199 @@ +/* + * SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#include + +#include "common/tempdir.h" +#include "linglong/api/types/v1/BuilderConfig.hpp" +#include "linglong/builder/config.h" +#include "linglong/utils/env.h" + +#include +#include +#include + +using namespace linglong::builder; +using namespace linglong::api::types::v1; +using linglong::utils::EnvironmentVariableGuard; + +class BuilderConfigTest : public ::testing::Test +{ +protected: + void SetUp() override { tempDir = std::make_unique("ll_builder_test_"); } + + std::filesystem::path testDir() const { return tempDir->path(); } + +private: + std::unique_ptr tempDir; +}; + +TEST_F(BuilderConfigTest, SaveConfig) +{ + BuilderConfig config; + config.version = 1; + config.repo = "/test/repo"; + config.arch = "x86_64"; + config.cache = "/test/cache"; + config.offline = true; + + auto configPath = testDir() / "test_config.yaml"; + auto result = saveConfig(config, configPath); + + ASSERT_TRUE(result.has_value()) << "Failed to save config: " << result.error().message(); + ASSERT_TRUE(std::filesystem::exists(configPath)); +} + +TEST_F(BuilderConfigTest, SaveConfigInvalidPath) +{ + BuilderConfig config; + config.version = 1; + config.repo = "/test/repo"; + + // Use an invalid path (directory that doesn't exist and can't be created) + auto invalidPath = testDir() / "nonexistent/config.yaml"; + auto result = saveConfig(config, invalidPath); + + ASSERT_FALSE(result.has_value()); +} + +TEST_F(BuilderConfigTest, LoadConfigFromPath) +{ + // First create a config file + BuilderConfig originalConfig; + originalConfig.version = 1; + originalConfig.repo = "/test/repo"; + originalConfig.arch = "x86_64"; + originalConfig.cache = "/test/cache"; + originalConfig.offline = false; + + auto configPath = testDir() / "test_config.yaml"; + auto saveResult = saveConfig(originalConfig, configPath); + ASSERT_TRUE(saveResult.has_value()); + + // Now load it + auto loadResult = loadConfig(configPath); + ASSERT_TRUE(loadResult.has_value()) + << "Failed to load config: " << loadResult.error().message(); + EXPECT_EQ(loadResult->version, 1); + EXPECT_EQ(loadResult->repo, "/test/repo"); + EXPECT_EQ(loadResult->arch, "x86_64"); + EXPECT_EQ(loadResult->cache, "/test/cache"); + EXPECT_EQ(loadResult->offline, false); +} + +TEST_F(BuilderConfigTest, LoadConfigFromPathInvalidVersion) +{ + // Create a config file with wrong version + BuilderConfig config; + config.version = 2; // Wrong version + config.repo = "/test/repo"; + + auto configPath = testDir() / "invalid_version_config.yaml"; + auto saveResult = saveConfig(config, configPath); + ASSERT_TRUE(saveResult.has_value()); + + // Try to load it - should fail + auto loadResult = loadConfig(configPath); + ASSERT_FALSE(loadResult.has_value()); + EXPECT_TRUE(loadResult.error().message().find("wrong configuration file version") + != std::string::npos); +} + +TEST_F(BuilderConfigTest, LoadConfigFromPathNonExistent) +{ + auto nonExistentPath = testDir() / "nonexistent.yaml"; + auto result = loadConfig(nonExistentPath); + ASSERT_FALSE(result.has_value()); +} + +TEST_F(BuilderConfigTest, InitDefaultBuildConfig) +{ + EnvironmentVariableGuard homeGuard("HOME", testDir().string()); + EnvironmentVariableGuard xdgCacheGuard("XDG_CACHE_HOME", (testDir() / "cache").string()); + + auto configPath = testDir() / "builder" / "config.yaml"; + auto result = initDefaultBuildConfig(configPath); + + ASSERT_TRUE(result.has_value()) + << "Failed to init default config: " << result.error().message(); + + EXPECT_EQ(result->version, 1); + EXPECT_TRUE(result->repo.find("linglong-builder") != std::string::npos); + EXPECT_TRUE(std::filesystem::exists(configPath)); +} + +TEST_F(BuilderConfigTest, InitDefaultBuildConfigNoCacheDir) +{ + EnvironmentVariableGuard homeGuard("HOME", ""); + EnvironmentVariableGuard xdgCacheGuard("XDG_CACHE_HOME", ""); + + auto configPath = testDir() / "builder" / "config.yaml"; + auto result = initDefaultBuildConfig(configPath); + + ASSERT_FALSE(result.has_value()); + EXPECT_TRUE(result.error().message().find("failed to get cache dir") != std::string::npos); +} + +TEST_F(BuilderConfigTest, InitDefaultBuildConfigCreateDirFailure) +{ + EnvironmentVariableGuard homeGuard("HOME", testDir().string()); + EnvironmentVariableGuard xdgCacheGuard("XDG_CACHE_HOME", (testDir() / "cache").string()); + + // Use a path where parent directory creation would fail + auto invalidConfigPath = "config.yaml"; + auto result = initDefaultBuildConfig(invalidConfigPath); + + ASSERT_FALSE(result.has_value()); +} + +TEST_F(BuilderConfigTest, LoadConfigDefaultPath) +{ + EnvironmentVariableGuard homeGuard("HOME", testDir().string()); + EnvironmentVariableGuard xdgRuntimeGuard("XDG_CONFIG_HOME", (testDir() / "config").string()); + EnvironmentVariableGuard xdgCacheGuard("XDG_CACHE_HOME", (testDir() / "cache").string()); + + // This should create a default config since none exists + auto result = loadConfig(); + ASSERT_TRUE(result.has_value()) + << "Failed to load default config: " << result.error().message(); + + EXPECT_EQ(result->version, 1); + EXPECT_EQ(result->repo, testDir() / "cache/linglong-builder"); +} + +TEST_F(BuilderConfigTest, LoadConfigDefaultPathMissingConfigDir) +{ + EnvironmentVariableGuard homeGuard("HOME", ""); + EnvironmentVariableGuard xdgRuntimeGuard("XDG_CONFIG_HOME", ""); + + auto result = loadConfig(); + ASSERT_FALSE(result.has_value()); + EXPECT_TRUE(result.error().message().find("failed to get config dir") != std::string::npos); +} + +TEST_F(BuilderConfigTest, ConfigWithOptionalFields) +{ + // Test config with minimal required fields only + BuilderConfig minimalConfig; + minimalConfig.version = 1; + minimalConfig.repo = "/minimal/repo"; + // arch, cache, offline are optional and left unset + + auto configPath = testDir() / "minimal_config.yaml"; + + // Save and load minimal config + auto saveResult = saveConfig(minimalConfig, configPath); + ASSERT_TRUE(saveResult.has_value()); + + auto loadResult = loadConfig(configPath); + ASSERT_TRUE(loadResult.has_value()); + + EXPECT_EQ(loadResult->version, 1); + EXPECT_EQ(loadResult->repo, "/minimal/repo"); + EXPECT_FALSE(loadResult->arch.has_value()); + EXPECT_FALSE(loadResult->cache.has_value()); + EXPECT_FALSE(loadResult->offline.has_value()); +}