Skip to content
Draft
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
4 changes: 2 additions & 2 deletions configure.h.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
* SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
Expand All @@ -26,7 +26,7 @@
// The directory where the package install hooks reside.
#define LINGLONG_INSTALL_HOOKS_DIR LINGLONG_SYSCONFDIR "/config.d"

#define LINGLONG_EXPORT_VERSION "1.0.0.2"
#define LINGLONG_EXPORT_VERSION "1.0.0.3"
// The package's locale domain.
#define PACKAGE_LOCALE_DOMAIN "@GETTEXT_DOMAIN_NAME@"

Expand Down
12 changes: 11 additions & 1 deletion libs/linglong/src/linglong/package_manager/package_update.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand Down Expand Up @@ -241,6 +241,16 @@ utils::error::Result<void> PackageUpdateAction::updateApp(Task &task,
return LINGLONG_ERR(res);
}
}

if (!modules.empty()) {
auto pkgInfo = modules.front().second.getPackageInfo();
if (!pkgInfo) {
return LINGLONG_ERR(pkgInfo);
}
if (pkgInfo->kind == "base" || pkgInfo->kind == "runtime") {
repo.exportReferencePaths(refRepo.reference, { "share/deepin-elf-verify" });
}
}
}

if (!depsOnly && newAppInfo) {
Expand Down
73 changes: 66 additions & 7 deletions libs/linglong/src/linglong/repo/ostree_repo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1852,9 +1852,28 @@
this->updateSharedInfo();
}

void OSTreeRepo::exportReferencePaths(const package::Reference &ref,
const std::vector<std::string> &paths) noexcept
{
QDir entriesDir(this->getEntriesDir().c_str());
if (!entriesDir.exists()) {
entriesDir.mkpath(".");
}
auto item = this->getLayerItem(ref);
if (!item.has_value()) {
LogE("Failed to export paths for {}: {}", ref.toString(), item.error().message());
return;
}
auto ret = exportEntries(entriesDir.absolutePath().toStdString(), *item, paths);
if (!ret.has_value()) {
LogE("Failed to export paths for {}: {}", ref.toString(), ret.error().message());
return;
}
}

// 递归源目录所有文件,并在目标目录创建软链接,max_depth 控制递归深度以避免环形链接导致的无限递归
utils::error::Result<void> OSTreeRepo::exportDir(const std::string &appID,
const std::filesystem::path &source,

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable

Check warning on line 1876 in libs/linglong/src/linglong/repo/ostree_repo.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'ret' shadows outer variable
const std::filesystem::path &destination,
const int &max_depth)
{
Expand Down Expand Up @@ -2026,7 +2045,8 @@

utils::error::Result<void>
OSTreeRepo::exportEntries(const std::filesystem::path &rootEntriesDir,
const api::types::v1::RepositoryCacheLayersItem &item) noexcept
const api::types::v1::RepositoryCacheLayersItem &item,
const std::optional<std::vector<std::string>> &exportPathsFilter) noexcept
{
LINGLONG_TRACE(fmt::format("export {}", item.info.id));
auto layerDir = getLayerDir(item);
Expand All @@ -2045,6 +2065,38 @@
return LINGLONG_OK;
}

// 如果指定了导出路径过滤器,仅导出指定的路径
if (exportPathsFilter.has_value()) {
for (const auto &path : *exportPathsFilter) {
auto normalizedPath = std::filesystem::path(path).lexically_normal();
if (normalizedPath.is_absolute()) {
return LINGLONG_ERR(fmt::format(
"Failed to export {}: absolute path is not allowed in export filter: {}",
path));
}

auto source = appEntriesDir / normalizedPath;
auto destination = rootEntriesDir / normalizedPath;

if (normalizedPath == "share/deepin-elf-verify") {
destination = rootEntriesDir / "share/deepin-elf-verify" / item.commit;
}

exists = std::filesystem::exists(source, ec);
if (ec) {
return LINGLONG_ERR(fmt::format("Failed to check file existence: {}", source), ec);
}
if (!exists) {
continue;
}
auto ret = this->exportDir(item.info.id, source, destination, 10);
if (!ret.has_value()) {
return ret;
}
}
return LINGLONG_OK;
}

// TODO: The current whitelist logic is not very flexible.
// The application configuration file can be exported after configuring it in the build
// configuration file(linglong.yaml).
Expand Down Expand Up @@ -2138,12 +2190,19 @@
// 导出所有layer到新entries目录
auto items = this->cache->queryExistingLayerItem();
for (const auto &item : items) {
if (item.info.kind != "app") {
continue;
}
auto ret = exportEntries(entriesDir, item);
if (!ret.has_value()) {
return ret;
if (item.info.kind == "app") {
auto ret = exportEntries(entriesDir, item);
if (!ret.has_value()) {
return ret;
}
} else if (item.info.kind == "base" || item.info.kind == "runtime") {
// base和runtime仅导出deepin-elf-verify目录
auto ret = exportEntries(entriesDir,
item,
std::vector<std::string>{ "share/deepin-elf-verify" });
if (!ret.has_value()) {
return ret;
}
}
}
// 用新的entries目录替换旧的
Expand Down
7 changes: 6 additions & 1 deletion libs/linglong/src/linglong/repo/ostree_repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ class OSTreeRepo : public QObject

// exportReference should be called when LayerDir of ref is existed in local repo
void exportReference(const package::Reference &ref) noexcept;
// exportReferencePaths exports only the specified paths for a given reference
virtual void exportReferencePaths(const package::Reference &ref,
const std::vector<std::string> &paths) noexcept;
// unexportReference should be called when LayerDir of ref is existed in local repo
void unexportReference(const package::Reference &ref) noexcept;
void unexportReference(const std::string &layerDir) noexcept;
Expand Down Expand Up @@ -281,7 +284,9 @@ class OSTreeRepo : public QObject
const std::filesystem::path &destination,
const int &max_depth);
utils::error::Result<void> exportEntries(
const std::filesystem::path &, const api::types::v1::RepositoryCacheLayersItem &) noexcept;
const std::filesystem::path &,
const api::types::v1::RepositoryCacheLayersItem &,
const std::optional<std::vector<std::string>> &exportPathsFilter = std::nullopt) noexcept;
};

} // namespace linglong::repo
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "linglong/api/types/v1/PackageInfoV2.hpp"
#include "linglong/api/types/v1/Repo.hpp"
#include "linglong/api/types/v1/RepoConfigV2.hpp"

Check warning on line 5 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/PackageInfoV2.hpp" not found.

Check warning on line 5 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/PackageInfoV2.hpp" not found.
#include "linglong/repo/client_factory.h"
#include "linglong/repo/ostree_repo.h"

Check warning on line 6 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/Repo.hpp" not found.

Check warning on line 6 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/Repo.hpp" not found.
#include "linglong/utils/error/error.h"

Check warning on line 7 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/RepoConfigV2.hpp" not found.

Check warning on line 7 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/RepoConfigV2.hpp" not found.

Check warning on line 8 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/repo/client_factory.h" not found.

Check warning on line 8 in libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/repo/client_factory.h" not found.
#include <filesystem>
#include <string>

Expand All @@ -31,6 +28,14 @@
return this->OSTreeRepo::exportDir(appID, source, destination, max_depth);
}

utils::error::Result<void> exportEntries(
const std::filesystem::path &rootEntriesDir,
const api::types::v1::RepositoryCacheLayersItem &item,
const std::optional<std::vector<std::string>> &exportPathsFilter = std::nullopt) noexcept
{
return this->OSTreeRepo::exportEntries(rootEntriesDir, item, exportPathsFilter);
}

// mock getOverlayShareDir
std::function<std::filesystem::path()> wrapGetOverlayShareDirFunc;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ class MockRepo : public repo::OSTreeRepo
{
}

std::function<void(const package::Reference &ref, const std::vector<std::string> &paths)>
exportReferencePathsHook;

void exportReferencePaths(const package::Reference &ref,
const std::vector<std::string> &paths) noexcept override
{
if (exportReferencePathsHook) {
exportReferencePathsHook(ref, paths);
return;
}
OSTreeRepo::exportReferencePaths(ref, paths);
}

MOCK_METHOD(utils::error::Result<std::vector<api::types::v1::PackageInfoV2>>,
listLocalApps,
(),
Expand Down Expand Up @@ -294,6 +307,109 @@ TEST_F(PackageUpdateActionTest, Update)
return utils::error::Result<void>{};
});

repo->exportReferencePathsHook = [](const package::Reference &,
const std::vector<std::string> &) {
// no-op: 跳过真实导出逻辑
};

EXPECT_CALL(*repo, mergeModules()).WillOnce([]() {
return utils::error::Result<void>{};
});

EXPECT_CALL(*pm, switchAppVersion(_, _, true)).WillOnce(Return(utils::error::Result<void>{}));

service::PackageTask task({});
res = action->doAction(task);
ASSERT_TRUE(res.has_value());
}

TEST_F(PackageUpdateActionTest, BaseUpgradeExportsDeepinElfVerify)
{
auto action =
service::PackageUpdateAction::create(std::vector<api::types::v1::PackageManager1Package>(),
false,
false,
*pm,
*repo);

EXPECT_CALL(*repo, listLocalApps())
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
testdata::idV100,
testdata::id2V100,
}));

auto res = action->prepare();
ASSERT_TRUE(res.has_value());

auto baseRef = package::Reference::fromPackageInfo(testdata::baseV101);
ASSERT_TRUE(baseRef.has_value());

auto runtimeRef = package::Reference::fromPackageInfo(testdata::runtimeV100);
ASSERT_TRUE(runtimeRef.has_value());

EXPECT_CALL(*pm, needToUpgrade(_, _, false))
// id1: app has no updates
.WillOnce(Return(std::nullopt))
// id1: runtime's extension has no updates
.WillOnce(Return(std::nullopt))
// id2: app has updates
.WillOnce(Return(std::make_pair(
package::ReferenceWithRepo{ .repo = api::types::v1::Repo{ .name = "repo" },
.reference =
package::Reference::parse("main:id2/1.1.0/x86_64").value() },
std::vector<std::string>{ "binary", "develop" })))
// id2's dependencies: runtime's extension has no updates
.WillOnce(Return(std::nullopt));

EXPECT_CALL(*pm, needToUpgrade(_, _, true))
// id1: base has updates
.WillOnce(Return(std::make_pair(
package::ReferenceWithRepo{ .repo = api::types::v1::Repo{ .name = "repo" },
.reference =
package::Reference::parse("main:base/1.0.1/x86_64").value() },
std::vector<std::string>{ "binary" })))
// id1: runtime has no updates
.WillOnce(DoAll(SetArgReferee<1>(*runtimeRef), Return(std::nullopt)))
// id2's dependencies: base has no updates
.WillOnce(DoAll(SetArgReferee<1>(*baseRef), Return(std::nullopt)))
// id2's dependencies: runtime has no updates
.WillOnce(DoAll(SetArgReferee<1>(*runtimeRef), Return(std::nullopt)));

EXPECT_CALL(*repo, fetchRefMetaData(_, "binary", true))
.WillOnce(Return(repo::RefMetaData{ "rev1", nlohmann::json(testdata::baseV101).dump() }))
.WillOnce(Return(repo::RefMetaData{ "rev2", nlohmann::json(testdata::id2V110).dump() }));
EXPECT_CALL(*repo, fetchRefMetaData(_, "develop", false))
.WillOnce(Return(repo::RefMetaData{ "rev3", nlohmann::json(testdata::id2V110).dump() }));

EXPECT_CALL(*repo, getRefStatistics(_)).WillRepeatedly([](const repo::RefMetaData &) {
return utils::error::Result<repo::RefStatistics>{
repo::RefStatistics{ .archived = 1024, .needed_archived = 512 }
};
});

EXPECT_CALL(*repo, getLayerItem(_, _, _))
.WillOnce(Return(utils::error::Result<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = testdata::runtimeV100 } }))
.WillOnce(Return(utils::error::Result<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = testdata::baseV101 } }))
.WillOnce(Return(utils::error::Result<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = testdata::runtimeV100 } }));

EXPECT_CALL(*pm, installRefModule(_, _, _))
.WillRepeatedly([](service::Task &, const package::ReferenceWithRepo &, const std::string &) {
return utils::error::Result<void>{};
});

bool exportReferencePathsCalled = false;
std::string exportedRefStr;
std::vector<std::string> exportedPaths;
repo->exportReferencePathsHook = [&](const package::Reference &ref,
const std::vector<std::string> &paths) {
exportReferencePathsCalled = true;
exportedRefStr = ref.toString();
exportedPaths = paths;
};

EXPECT_CALL(*repo, mergeModules()).WillOnce([]() {
return utils::error::Result<void>{};
});
Expand All @@ -303,6 +419,78 @@ TEST_F(PackageUpdateActionTest, Update)
service::PackageTask task({});
res = action->doAction(task);
ASSERT_TRUE(res.has_value());

EXPECT_TRUE(exportReferencePathsCalled);
ASSERT_EQ(exportedPaths.size(), 1u);
EXPECT_EQ(exportedPaths[0], "share/deepin-elf-verify");
EXPECT_EQ(exportedRefStr, "main:base/1.0.1/x86_64");
}

TEST_F(PackageUpdateActionTest, AppUpgradeDoesNotCallExportReferencePaths)
{
auto action =
service::PackageUpdateAction::create(std::vector<api::types::v1::PackageManager1Package>(),
true, // appOnly=true,不升级依赖
false,
*pm,
*repo);

EXPECT_CALL(*repo, listLocalApps())
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{ testdata::idV100 }));

auto res = action->prepare();
ASSERT_TRUE(res.has_value());

EXPECT_CALL(*pm, needToUpgrade(_, _, false))
.WillOnce(Return(std::make_pair(
package::ReferenceWithRepo{ .repo = api::types::v1::Repo{ .name = "repo" },
.reference =
package::Reference::parse("main:id1/1.1.0/x86_64").value() },
std::vector<std::string>{ "binary" })));

api::types::v1::PackageInfoV2 id1V110{
.arch = std::vector<std::string>{ "x86_64" },
.base = "main:base/1.0.0/x86_64",
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.name = "id1",
.runtime = "main:runtime/1.0.0/x86_64",
.version = "1.1.0",
};

EXPECT_CALL(*repo, fetchRefMetaData(_, "binary", true))
.WillOnce(Return(repo::RefMetaData{ "app_rev", nlohmann::json(id1V110).dump() }));

EXPECT_CALL(*repo, getRefStatistics(_)).WillRepeatedly([](const repo::RefMetaData &) {
return utils::error::Result<repo::RefStatistics>{
repo::RefStatistics{ .archived = 1024, .needed_archived = 512 }
};
});

EXPECT_CALL(*pm, installRefModule(_, _, _))
.WillRepeatedly([](service::Task &, const package::ReferenceWithRepo &, const std::string &) {
return utils::error::Result<void>{};
});

bool exportReferencePathsCalled = false;
repo->exportReferencePathsHook = [&](const package::Reference &,
const std::vector<std::string> &) {
exportReferencePathsCalled = true;
};

EXPECT_CALL(*repo, mergeModules()).WillOnce([]() {
return utils::error::Result<void>{};
});

EXPECT_CALL(*pm, switchAppVersion(_, _, true)).WillOnce(Return(utils::error::Result<void>{}));

service::PackageTask task({});
res = action->doAction(task);
ASSERT_TRUE(res.has_value());

EXPECT_FALSE(exportReferencePathsCalled);
}

} // namespace
Loading
Loading