diff --git a/configure.h.in b/configure.h.in index 558ec66a1..0118cc072 100644 --- a/configure.h.in +++ b/configure.h.in @@ -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 */ @@ -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@" diff --git a/libs/linglong/src/linglong/package_manager/package_update.cpp b/libs/linglong/src/linglong/package_manager/package_update.cpp index b07910a7f..5150249db 100644 --- a/libs/linglong/src/linglong/package_manager/package_update.cpp +++ b/libs/linglong/src/linglong/package_manager/package_update.cpp @@ -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 @@ -241,6 +241,16 @@ utils::error::Result 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) { diff --git a/libs/linglong/src/linglong/repo/ostree_repo.cpp b/libs/linglong/src/linglong/repo/ostree_repo.cpp index 3885a846c..705892d28 100644 --- a/libs/linglong/src/linglong/repo/ostree_repo.cpp +++ b/libs/linglong/src/linglong/repo/ostree_repo.cpp @@ -1852,6 +1852,25 @@ void OSTreeRepo::exportReference(const package::Reference &ref) noexcept this->updateSharedInfo(); } +void OSTreeRepo::exportReferencePaths(const package::Reference &ref, + const std::vector &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 OSTreeRepo::exportDir(const std::string &appID, const std::filesystem::path &source, @@ -2026,7 +2045,8 @@ utils::error::Result OSTreeRepo::exportDir(const std::string &appID, utils::error::Result OSTreeRepo::exportEntries(const std::filesystem::path &rootEntriesDir, - const api::types::v1::RepositoryCacheLayersItem &item) noexcept + const api::types::v1::RepositoryCacheLayersItem &item, + const std::optional> &exportPathsFilter) noexcept { LINGLONG_TRACE(fmt::format("export {}", item.info.id)); auto layerDir = getLayerDir(item); @@ -2045,6 +2065,38 @@ OSTreeRepo::exportEntries(const std::filesystem::path &rootEntriesDir, 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). @@ -2138,12 +2190,19 @@ utils::error::Result OSTreeRepo::exportAllEntries() noexcept // 导出所有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{ "share/deepin-elf-verify" }); + if (!ret.has_value()) { + return ret; + } } } // 用新的entries目录替换旧的 diff --git a/libs/linglong/src/linglong/repo/ostree_repo.h b/libs/linglong/src/linglong/repo/ostree_repo.h index 804361bfa..49a2600da 100644 --- a/libs/linglong/src/linglong/repo/ostree_repo.h +++ b/libs/linglong/src/linglong/repo/ostree_repo.h @@ -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 &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; @@ -281,7 +284,9 @@ class OSTreeRepo : public QObject const std::filesystem::path &destination, const int &max_depth); utils::error::Result exportEntries( - const std::filesystem::path &, const api::types::v1::RepositoryCacheLayersItem &) noexcept; + const std::filesystem::path &, + const api::types::v1::RepositoryCacheLayersItem &, + const std::optional> &exportPathsFilter = std::nullopt) noexcept; }; } // namespace linglong::repo diff --git a/libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h b/libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h index 5a6d191d8..ee0aae168 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h +++ b/libs/linglong/tests/ll-tests/src/linglong/mocks/ostree_repo_mock.h @@ -2,10 +2,7 @@ // // 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" -#include "linglong/repo/client_factory.h" #include "linglong/repo/ostree_repo.h" #include "linglong/utils/error/error.h" @@ -31,6 +28,14 @@ class MockOstreeRepo : public repo::OSTreeRepo return this->OSTreeRepo::exportDir(appID, source, destination, max_depth); } + utils::error::Result exportEntries( + const std::filesystem::path &rootEntriesDir, + const api::types::v1::RepositoryCacheLayersItem &item, + const std::optional> &exportPathsFilter = std::nullopt) noexcept + { + return this->OSTreeRepo::exportEntries(rootEntriesDir, item, exportPathsFilter); + } + // mock getOverlayShareDir std::function wrapGetOverlayShareDirFunc; diff --git a/libs/linglong/tests/ll-tests/src/linglong/package_manager/package_update_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/package_manager/package_update_test.cpp index 352c57514..e4e84556b 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/package_manager/package_update_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/package_manager/package_update_test.cpp @@ -156,6 +156,19 @@ class MockRepo : public repo::OSTreeRepo { } + std::function &paths)> + exportReferencePathsHook; + + void exportReferencePaths(const package::Reference &ref, + const std::vector &paths) noexcept override + { + if (exportReferencePathsHook) { + exportReferencePathsHook(ref, paths); + return; + } + OSTreeRepo::exportReferencePaths(ref, paths); + } + MOCK_METHOD(utils::error::Result>, listLocalApps, (), @@ -294,6 +307,109 @@ TEST_F(PackageUpdateActionTest, Update) return utils::error::Result{}; }); + repo->exportReferencePathsHook = [](const package::Reference &, + const std::vector &) { + // no-op: 跳过真实导出逻辑 + }; + + EXPECT_CALL(*repo, mergeModules()).WillOnce([]() { + return utils::error::Result{}; + }); + + EXPECT_CALL(*pm, switchAppVersion(_, _, true)).WillOnce(Return(utils::error::Result{})); + + service::PackageTask task({}); + res = action->doAction(task); + ASSERT_TRUE(res.has_value()); +} + +TEST_F(PackageUpdateActionTest, BaseUpgradeExportsDeepinElfVerify) +{ + auto action = + service::PackageUpdateAction::create(std::vector(), + false, + false, + *pm, + *repo); + + EXPECT_CALL(*repo, listLocalApps()) + .WillOnce(Return(std::vector{ + 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{ "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{ "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{ .archived = 1024, .needed_archived = 512 } + }; + }); + + EXPECT_CALL(*repo, getLayerItem(_, _, _)) + .WillOnce(Return(utils::error::Result{ + api::types::v1::RepositoryCacheLayersItem{ .info = testdata::runtimeV100 } })) + .WillOnce(Return(utils::error::Result{ + api::types::v1::RepositoryCacheLayersItem{ .info = testdata::baseV101 } })) + .WillOnce(Return(utils::error::Result{ + api::types::v1::RepositoryCacheLayersItem{ .info = testdata::runtimeV100 } })); + + EXPECT_CALL(*pm, installRefModule(_, _, _)) + .WillRepeatedly([](service::Task &, const package::ReferenceWithRepo &, const std::string &) { + return utils::error::Result{}; + }); + + bool exportReferencePathsCalled = false; + std::string exportedRefStr; + std::vector exportedPaths; + repo->exportReferencePathsHook = [&](const package::Reference &ref, + const std::vector &paths) { + exportReferencePathsCalled = true; + exportedRefStr = ref.toString(); + exportedPaths = paths; + }; + EXPECT_CALL(*repo, mergeModules()).WillOnce([]() { return utils::error::Result{}; }); @@ -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(), + true, // appOnly=true,不升级依赖 + false, + *pm, + *repo); + + EXPECT_CALL(*repo, listLocalApps()) + .WillOnce(Return(std::vector{ 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{ "binary" }))); + + api::types::v1::PackageInfoV2 id1V110{ + .arch = std::vector{ "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{ .archived = 1024, .needed_archived = 512 } + }; + }); + + EXPECT_CALL(*pm, installRefModule(_, _, _)) + .WillRepeatedly([](service::Task &, const package::ReferenceWithRepo &, const std::string &) { + return utils::error::Result{}; + }); + + bool exportReferencePathsCalled = false; + repo->exportReferencePathsHook = [&](const package::Reference &, + const std::vector &) { + exportReferencePathsCalled = true; + }; + + EXPECT_CALL(*repo, mergeModules()).WillOnce([]() { + return utils::error::Result{}; + }); + + EXPECT_CALL(*pm, switchAppVersion(_, _, true)).WillOnce(Return(utils::error::Result{})); + + service::PackageTask task({}); + res = action->doAction(task); + ASSERT_TRUE(res.has_value()); + + EXPECT_FALSE(exportReferencePathsCalled); } } // namespace diff --git a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp index ab488f2da..e3df50d83 100644 --- a/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp +++ b/libs/linglong/tests/ll-tests/src/linglong/repo/ostree_repo_test.cpp @@ -845,6 +845,192 @@ TEST(OSTreeRepoTest, matchRemoteByPriority_UseHighestPriority) EXPECT_EQ(repoPackages.back().second[0].version, "3.0.0"); } +static void createLayerEntries(const fs::path &layersDir, + const std::string &commit, + const std::vector &entryPaths) +{ + auto layerDir = layersDir / commit / "entries"; + fs::create_directories(layerDir); + for (const auto &relPath : entryPaths) { + auto fullPath = layerDir / relPath; + fs::create_directories(fullPath.parent_path()); + std::ofstream(fullPath) << "test-content"; + } +} + +static api::types::v1::RepositoryCacheLayersItem makeLayerItem(const std::string &commit, + const std::string &id, + const std::string &kind) +{ + return api::types::v1::RepositoryCacheLayersItem{ + .commit = commit, + .info = + api::types::v1::PackageInfoV2{ + .id = id, + .kind = kind, + .name = id, + .version = "1.0.0", + }, + }; +} + +TEST_F(RepoTest, ExportEntriesWithFilterExportsOnlyFilteredPaths) +{ + TempDir tempDir("export_entries_test_"); + ASSERT_TRUE(tempDir.isValid()); + std::error_code ec; + + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(tempDir.path(), config); + + std::string commit = "abc123commit"; + createLayerEntries(tempDir.path() / "layers", + commit, + { "share/deepin-elf-verify/.elfsign/sign.tar", + "share/applications/test.desktop", + "share/icons/hicolor/test.png" }); + + auto item = makeLayerItem(commit, "org.test.app", "app"); + auto entriesDir = tempDir.path() / "entries"; + fs::create_directories(entriesDir, ec); + ASSERT_FALSE(ec) << ec.message(); + + auto ret = ostreeRepo->exportEntries(entriesDir, + item, + std::vector{ "share/deepin-elf-verify" }); + ASSERT_TRUE(ret.has_value()) << ret.error().message(); + + EXPECT_TRUE(fs::exists(entriesDir / "share/deepin-elf-verify" / commit / ".elfsign/sign.tar")); + + EXPECT_FALSE(fs::exists(entriesDir / "share/applications")); + EXPECT_FALSE(fs::exists(entriesDir / "share/icons")); +} + +TEST_F(RepoTest, ExportEntriesWithFilterSkipsNonExistentPath) +{ + TempDir tempDir("export_entries_test_"); + ASSERT_TRUE(tempDir.isValid()); + std::error_code ec; + + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(tempDir.path(), config); + + std::string commit = "noelfcommit"; + createLayerEntries(tempDir.path() / "layers", commit, { "share/applications/test.desktop" }); + + auto item = makeLayerItem(commit, "org.test.base", "base"); + auto entriesDir = tempDir.path() / "entries"; + fs::create_directories(entriesDir, ec); + ASSERT_FALSE(ec) << ec.message(); + + auto ret = ostreeRepo->exportEntries(entriesDir, + item, + std::vector{ "share/deepin-elf-verify" }); + ASSERT_TRUE(ret.has_value()) << ret.error().message(); + + EXPECT_FALSE(fs::exists(entriesDir / "share/deepin-elf-verify")); +} + +TEST_F(RepoTest, ExportEntriesWithFilterDeepinElfVerifyCommitNamespacing) +{ + TempDir tempDir("export_entries_test_"); + ASSERT_TRUE(tempDir.isValid()); + std::error_code ec; + + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(tempDir.path(), config); + + std::string commit1 = "commit_v1"; + std::string commit2 = "commit_v2"; + createLayerEntries(tempDir.path() / "layers", + commit1, + { "share/deepin-elf-verify/.elfsign/sign_v1.tar" }); + createLayerEntries(tempDir.path() / "layers", + commit2, + { "share/deepin-elf-verify/.elfsign/sign_v2.tar" }); + + auto item1 = makeLayerItem(commit1, "org.test.base", "base"); + auto item2 = makeLayerItem(commit2, "org.test.base", "base"); + + auto entriesDir = tempDir.path() / "entries"; + fs::create_directories(entriesDir, ec); + ASSERT_FALSE(ec) << ec.message(); + + auto ret1 = ostreeRepo->exportEntries(entriesDir, + item1, + std::vector{ "share/deepin-elf-verify" }); + ASSERT_TRUE(ret1.has_value()) << ret1.error().message(); + + auto ret2 = ostreeRepo->exportEntries(entriesDir, + item2, + std::vector{ "share/deepin-elf-verify" }); + ASSERT_TRUE(ret2.has_value()) << ret2.error().message(); + + EXPECT_TRUE( + fs::exists(entriesDir / "share/deepin-elf-verify" / commit1 / ".elfsign/sign_v1.tar")); + EXPECT_TRUE( + fs::exists(entriesDir / "share/deepin-elf-verify" / commit2 / ".elfsign/sign_v2.tar")); +} + +TEST_F(RepoTest, ExportEntriesWithFilterMultiplePaths) +{ + TempDir tempDir("export_entries_test_"); + ASSERT_TRUE(tempDir.isValid()); + std::error_code ec; + + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(tempDir.path(), config); + + std::string commit = "multipath"; + createLayerEntries(tempDir.path() / "layers", + commit, + { "share/deepin-elf-verify/.elfsign/sign.tar", + "share/applications/test.desktop", + "share/icons/hicolor/test.png" }); + + auto item = makeLayerItem(commit, "org.test.app", "app"); + auto entriesDir = tempDir.path() / "entries"; + fs::create_directories(entriesDir, ec); + ASSERT_FALSE(ec) << ec.message(); + + auto ret = ostreeRepo->exportEntries( + entriesDir, + item, + std::vector{ "share/deepin-elf-verify", "share/applications" }); + ASSERT_TRUE(ret.has_value()) << ret.error().message(); + + EXPECT_TRUE(fs::exists(entriesDir / "share/deepin-elf-verify" / commit / ".elfsign/sign.tar")); + + const auto desktopExportPath = + ostreeRepo->resolveDesktopFileExportPath("applications/test.desktop"); + EXPECT_TRUE(fs::exists(desktopExportPath)); + + EXPECT_FALSE(fs::exists(entriesDir / "share/icons")); +} + +TEST_F(RepoTest, ExportEntriesWithFilterNoLayerEntriesDir) +{ + TempDir tempDir("export_entries_test_"); + ASSERT_TRUE(tempDir.isValid()); + std::error_code ec; + + auto config = api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 }; + auto ostreeRepo = std::make_unique(tempDir.path(), config); + + std::string commit = "noentries"; + fs::create_directories(tempDir.path() / "layers" / commit, ec); + ASSERT_FALSE(ec) << ec.message(); + + auto item = makeLayerItem(commit, "org.test.app", "app"); + + auto entriesDir = tempDir.path() / "entries"; + + auto ret = ostreeRepo->exportEntries(entriesDir, + item, + std::vector{ "share/deepin-elf-verify" }); + ASSERT_TRUE(ret.has_value()); +} + } // namespace } // namespace