diff options
| author | Dan Engelbrecht <[email protected]> | 2026-01-27 14:53:37 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-01-27 14:53:37 +0100 |
| commit | 16a4ce30ef1a4461ea39f1b821974ed01b80cd1f (patch) | |
| tree | 8305ea81771c3e7793bd0f84f7bb3cd5a68be66a /src | |
| parent | avoid big ioworker backlog (#733) (diff) | |
| download | zen-16a4ce30ef1a4461ea39f1b821974ed01b80cd1f.tar.xz zen-16a4ce30ef1a4461ea39f1b821974ed01b80cd1f.zip | |
allow download specification for zen builds download (#734)
- Feature: `zen builds download` now supports `--download-spec-path` to determine what content to download from a build
- The unstructured format expects one line per file relative to the root with '/' as a path delimiter
- The structured format uses JSon format and the `--download-spec-path` must have extension `.json` to enable structured input
{
"parts": {
"default" : {
"files": [
"foo/bar",
"baz.exe"
]
},
"symbols": {
"files": [
"baz.pdb"
]
}
}
}
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/builds_cmd.cpp | 66 | ||||
| -rw-r--r-- | src/zen/cmds/builds_cmd.h | 3 | ||||
| -rw-r--r-- | src/zenremotestore/builds/buildmanifest.cpp | 173 | ||||
| -rw-r--r-- | src/zenremotestore/builds/buildstorageoperations.cpp | 162 | ||||
| -rw-r--r-- | src/zenremotestore/include/zenremotestore/builds/buildmanifest.h | 27 | ||||
| -rw-r--r-- | src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h | 16 | ||||
| -rw-r--r-- | src/zenremotestore/zenremotestore.cpp | 2 |
7 files changed, 323 insertions, 126 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp index 508d2ba60..176c2374c 100644 --- a/src/zen/cmds/builds_cmd.cpp +++ b/src/zen/cmds/builds_cmd.cpp @@ -24,6 +24,7 @@ #include <zenhttp/httpclientauth.h> #include <zenhttp/httpcommon.h> #include <zenremotestore/builds/buildcontent.h> +#include <zenremotestore/builds/buildmanifest.h> #include <zenremotestore/builds/buildsavedstate.h> #include <zenremotestore/builds/buildstoragecache.h> #include <zenremotestore/builds/buildstorageoperations.h> @@ -1251,6 +1252,7 @@ namespace { const Oid& BuildId, const std::vector<Oid>& BuildPartIds, std::span<const std::string> BuildPartNames, + const std::filesystem::path& DownloadSpecPath, const std::filesystem::path& Path, const DownloadOptions& Options) { @@ -1288,6 +1290,14 @@ namespace { std::vector<std::pair<Oid, std::string>> AllBuildParts = ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize); + BuildManifest Manifest; + if (!DownloadSpecPath.empty()) + { + const std::filesystem::path AbsoluteDownloadSpecPath = + DownloadSpecPath.is_relative() ? MakeSafeAbsolutePath(Path / DownloadSpecPath) : MakeSafeAbsolutePath(DownloadSpecPath); + Manifest = ParseBuildManifest(DownloadSpecPath); + } + std::vector<ChunkedFolderContent> PartContents; std::unique_ptr<ChunkingController> ChunkController; @@ -1301,6 +1311,7 @@ namespace { Storage, BuildId, AllBuildParts, + Manifest, Options.IncludeWildcards, Options.ExcludeWildcards, ChunkController, @@ -1310,9 +1321,7 @@ namespace { IsQuiet, IsVerbose, DoExtraContentVerify); -#if ZEN_BUILD_DEBUG - ValidateChunkedFolderContent(RemoteContent, BlockDescriptions, LooseChunkHashes, {}, {}); -#endif // ZEN_BUILD_DEBUG + const std::uint64_t LargeAttachmentSize = Options.AllowMultiparts ? PreferredMultipartChunkSize * 4u : (std::uint64_t)-1; GetFolderContentStatistics LocalFolderScanStats; ChunkingStatistics ChunkingStats; @@ -2329,9 +2338,11 @@ BuildsCommand::BuildsCommand() m_UploadOptions.add_option("", "", "manifest-path", - "Path to a text file with one line of <local path>[TAB]<modification date> per file to include.", + "Path to a text file with one line of <local path>[TAB]<modification date> per file to include or a " + "structured .json file describing the parts", cxxopts::value(m_ManifestPath), "<manifestpath>"); + m_UploadOptions .add_option("", "", "verify", "Enable post upload verify of all uploaded data", cxxopts::value(m_PostUploadVerify), "<verify>"); m_UploadOptions.add_option("", @@ -2402,6 +2413,14 @@ BuildsCommand::BuildsCommand() AddPartialBlockRequestOptions(m_DownloadOptions); + m_DownloadOptions.add_option( + "", + "", + "download-spec-path", + "Path to a text file with one line of <local path> per file to include or a structured .json file describing what to download.", + cxxopts::value(m_DownloadSpecPath), + "<downloadspecpath>"); + m_DownloadOptions .add_option("", "", "verify", "Enable post download verify of all tracked files", cxxopts::value(m_PostDownloadVerify), "<verify>"); m_DownloadOptions.add_option("", @@ -3560,6 +3579,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) BuildId, BuildPartIds, BuildPartNames, + m_DownloadSpecPath, m_Path, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = m_ZenFolderPath, @@ -3932,8 +3952,9 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) Storage, StorageCacheStats, BuildId, - {}, - {}, + /*BuildPartIds,*/ {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, m_Path, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = m_ZenFolderPath, @@ -4170,7 +4191,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, @@ -4201,7 +4223,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, @@ -4228,7 +4251,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, @@ -4256,7 +4280,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, @@ -4281,7 +4306,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, @@ -4398,7 +4424,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, @@ -4461,7 +4488,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, @@ -4486,7 +4514,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId2, {BuildPartId2}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, @@ -4510,7 +4539,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId2, {BuildPartId2}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath / ZenFolderName, @@ -4534,7 +4564,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath2, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath2 / ZenFolderName, @@ -4558,7 +4589,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) StorageCacheStats, BuildId, {BuildPartId}, - {}, + /*BuildPartNames*/ {}, + /*ManifestPath*/ {}, DownloadPath3, DownloadOptions{.SystemRootDir = m_SystemRootDir, .ZenFolderPath = DownloadPath3 / ZenFolderName, diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h index f87cd01ae..f5c44ab55 100644 --- a/src/zen/cmds/builds_cmd.h +++ b/src/zen/cmds/builds_cmd.h @@ -72,7 +72,6 @@ private: uint8_t m_BlockReuseMinPercentLimit = 85; bool m_AllowMultiparts = true; std::string m_AllowPartialBlockRequests = "mixed"; - std::string m_ManifestPath; // Not a std::filesystem::path since it can be relative to m_Path AuthCommandLineOptions m_AuthOptions; @@ -100,12 +99,14 @@ private: uint64_t m_FindBlockMaxCount = 10000; bool m_PostUploadVerify = false; std::filesystem::path m_ChunkingCachePath; + std::filesystem::path m_ManifestPath; cxxopts::Options m_DownloadOptions{"download", "Download a folder"}; std::vector<std::string> m_BuildPartNames; std::vector<std::string> m_BuildPartIds; bool m_PostDownloadVerify = false; bool m_EnableScavenging = true; + std::filesystem::path m_DownloadSpecPath; cxxopts::Options m_LsOptions{"ls", "List the content of uploaded build"}; std::filesystem::path m_LsResultPath; diff --git a/src/zenremotestore/builds/buildmanifest.cpp b/src/zenremotestore/builds/buildmanifest.cpp new file mode 100644 index 000000000..051436e96 --- /dev/null +++ b/src/zenremotestore/builds/buildmanifest.cpp @@ -0,0 +1,173 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zenremotestore/builds/buildmanifest.h> + +#include <zencore/compactbinary.h> +#include <zencore/fmtutils.h> + +#if ZEN_WITH_TESTS +# include <zencore/basicfile.h> +# include <zencore/testing.h> +# include <zencore/testutils.h> +#endif // ZEN_WITH_TESTS + +namespace zen { + +using namespace std::literals; + +BuildManifest +ParseBuildManifest(const std::filesystem::path& ManifestPath) +{ + BuildManifest Result; + { + IoBuffer ManifestContent = ReadFile(ManifestPath).Flatten(); + + if (ToLower(ManifestPath.extension().string()) == ".json") + { + IoBuffer MetaDataJson = ReadFile(ManifestPath).Flatten(); + std::string_view Json(reinterpret_cast<const char*>(MetaDataJson.GetData()), MetaDataJson.GetSize()); + std::string JsonError; + CbObject Manifest = LoadCompactBinaryFromJson(Json, JsonError).AsObject(); + if (!JsonError.empty()) + { + throw std::runtime_error(fmt::format("Invalid manifest file at {}. '{}'", ManifestPath, JsonError)); + } + CbObjectView PartsObject = Manifest["parts"sv].AsObjectView(); + for (CbFieldView PartsField : PartsObject) + { + std::string_view PartName = PartsField.GetName(); + if (PartName.empty()) + { + throw std::runtime_error(fmt::format("Part {} in manifest file at {} does not have a name. '{}'", + Result.Parts.size() + 1, + ManifestPath, + JsonError)); + } + CbObjectView Part = PartsField.AsObjectView(); + Oid PartId = Part["partId"sv].AsObjectId(); + CbArrayView FilesArray = Part["files"sv].AsArrayView(); + std::vector<std::filesystem::path> Files; + Files.reserve(FilesArray.Num()); + for (CbFieldView FileField : FilesArray) + { + std::filesystem::path File(FileField.AsU8String()); + Files.push_back(File); + } + + Result.Parts.push_back(BuildManifest::Part{.PartId = PartId, .PartName = std::string(PartName), .Files = std::move(Files)}); + } + return Result; + } + else + { + Result.Parts.resize(1); + BuildManifest::Part& SinglePart = Result.Parts.front(); + + std::string_view ManifestString((const char*)ManifestContent.GetView().GetData(), ManifestContent.GetSize()); + std::string_view::size_type Offset = 0; + while (Offset < ManifestContent.GetSize()) + { + size_t PathBreakOffset = ManifestString.find_first_of("\t\r\n", Offset); + if (PathBreakOffset == std::string_view::npos) + { + PathBreakOffset = ManifestContent.GetSize(); + } + std::string_view AssetPath = ManifestString.substr(Offset, PathBreakOffset - Offset); + if (!AssetPath.empty()) + { + SinglePart.Files.push_back(std::filesystem::path(AssetPath)); + } + Offset = PathBreakOffset; + size_t EolOffset = ManifestString.find_first_of("\r\n", Offset); + if (EolOffset == std::string_view::npos) + { + break; + } + Offset = EolOffset; + size_t LineBreakOffset = ManifestString.find_first_not_of("\t\r\n", Offset); + if (LineBreakOffset == std::string_view::npos) + { + break; + } + Offset = LineBreakOffset; + } + } + } + return Result; +} +#if ZEN_WITH_TESTS + +TEST_CASE("buildmanifest.unstructured") +{ + ScopedTemporaryDirectory Root; + std::vector<std::filesystem::path> Files = {"fileA", "dirA/FileB", "dirB/FileC", "dirB/FileD"}; + + { + ExtendableStringBuilder<512> SB; + for (const std::filesystem::path& File : Files) + { + SB << File.generic_string() << "\n"; + } + WriteFile(Root.Path() / "manifest.txt", IoBuffer(IoBuffer::Wrap, SB.ToView().data(), SB.ToView().length())); + } + + BuildManifest Manifest = ParseBuildManifest(Root.Path() / "manifest.txt"); + CHECK_EQ(Manifest.Parts.size(), 1u); + CHECK_EQ(Manifest.Parts[0].PartId, Oid::Zero); + CHECK_EQ(Manifest.Parts[0].PartName, ""); + CHECK_EQ(Manifest.Parts[0].Files, Files); +} + +TEST_CASE("buildmanifest.structured") +{ + ScopedTemporaryDirectory Root; + + std::string Id = Oid::NewOid().ToString(); + + std::string ManifestString = + "{\n" + " \"parts\": {\n" + " \"default\": {\n" + " \"partId\": \"098a2742d46c22a67ab57457\",\n" + " \"files\": [\n" + " \"foo/bar\",\n" + " \"baz.exe\"\n" + " ]\n" + " },\n" + " \"symbols\": {\n" + " \"files\": [\n" + " \"baz.pdb\"\n" + " ]\n" + " }\n" + " }\n" + "}\n"; + + WriteFile(Root.Path() / "manifest.json", IoBuffer(IoBuffer::Wrap, ManifestString.data(), ManifestString.length())); + + const Oid DefaultPartExpectedId = Oid::FromHexString("098a2742d46c22a67ab57457"); + const std::string DefaultPartExpectedName = "default"; + const Oid SymbolPartExpectedId = Oid::Zero; + const std::string SymbolsPartExpectedName = "symbols"; + + BuildManifest Manifest = ParseBuildManifest(Root.Path() / "manifest.json"); + CHECK_EQ(Manifest.Parts.size(), 2u); + CHECK_EQ(Manifest.Parts[0].PartId, DefaultPartExpectedId); + CHECK_EQ(Manifest.Parts[0].PartName, DefaultPartExpectedName); + CHECK_EQ(Manifest.Parts[0].Files.size(), 2u); + CHECK_EQ(Manifest.Parts[0].Files[0].generic_string(), "foo/bar"); + CHECK_EQ(Manifest.Parts[0].Files[1].generic_string(), "baz.exe"); + + CHECK_EQ(Manifest.Parts[1].PartId, SymbolPartExpectedId); + CHECK_EQ(Manifest.Parts[1].PartName, SymbolsPartExpectedName); + CHECK_EQ(Manifest.Parts[1].Files.size(), 1u); + CHECK_EQ(Manifest.Parts[1].Files[0].generic_string(), "baz.pdb"); +} + +void +buildmanifest_forcelink() +{ +} + +#endif // ZEN_WITH_TESTS + +} // namespace zen diff --git a/src/zenremotestore/builds/buildstorageoperations.cpp b/src/zenremotestore/builds/buildstorageoperations.cpp index 76cbe6540..5368c7df4 100644 --- a/src/zenremotestore/builds/buildstorageoperations.cpp +++ b/src/zenremotestore/builds/buildstorageoperations.cpp @@ -3,6 +3,7 @@ #include <zenremotestore/builds/buildstorageoperations.h> #include <zenremotestore/builds/buildcontent.h> +#include <zenremotestore/builds/buildmanifest.h> #include <zenremotestore/builds/buildsavedstate.h> #include <zenremotestore/builds/buildstorage.h> #include <zenremotestore/builds/buildstoragecache.h> @@ -4654,7 +4655,9 @@ BuildsOperationUploadFolder::ReadFolder() tsl::robin_set<std::string> ExcludeAssetPaths; if (IsFile(ExcludeManifestPath)) { - PartManifest Manifest = ParseManifest(m_Path, ExcludeManifestPath); + std::filesystem::path AbsoluteExcludeManifestPath = + MakeSafeAbsolutePath(ExcludeManifestPath.is_absolute() ? ExcludeManifestPath : m_Path / ExcludeManifestPath); + BuildManifest Manifest = ParseBuildManifest(AbsoluteExcludeManifestPath); const std::vector<std::filesystem::path>& AssetPaths = Manifest.Parts.front().Files; ExcludeAssetPaths.reserve(AssetPaths.size()); for (const std::filesystem::path& AssetPath : AssetPaths) @@ -4700,7 +4703,8 @@ BuildsOperationUploadFolder::ReadManifestParts(const std::filesystem::path& Mani { std::vector<UploadPart> UploadParts; Stopwatch ManifestParseTimer; - PartManifest Manifest = ParseManifest(m_Path, ManifestPath); + std::filesystem::path AbsoluteManifestPath = MakeSafeAbsolutePath(ManifestPath.is_absolute() ? ManifestPath : m_Path / ManifestPath); + BuildManifest Manifest = ParseBuildManifest(AbsoluteManifestPath); if (Manifest.Parts.empty()) { throw std::runtime_error(fmt::format("Manifest file at '{}' is invalid", ManifestPath)); @@ -4709,7 +4713,7 @@ BuildsOperationUploadFolder::ReadManifestParts(const std::filesystem::path& Mani UploadParts.resize(Manifest.Parts.size()); for (size_t PartIndex = 0; PartIndex < Manifest.Parts.size(); PartIndex++) { - PartManifest::Part& PartManifest = Manifest.Parts[PartIndex]; + BuildManifest::Part& PartManifest = Manifest.Parts[PartIndex]; if (ManifestPath.is_relative()) { PartManifest.Files.push_back(ManifestPath); @@ -4869,92 +4873,6 @@ BuildsOperationUploadFolder::Execute(const Oid& BuildPartId, } } -BuildsOperationUploadFolder::PartManifest -BuildsOperationUploadFolder::ParseManifest(const std::filesystem::path& Path, const std::filesystem::path& ManifestPath) -{ - PartManifest Result; - { - std::filesystem::path AbsoluteManifestPath = MakeSafeAbsolutePath(ManifestPath.is_absolute() ? ManifestPath : Path / ManifestPath); - IoBuffer ManifestContent = ReadFile(AbsoluteManifestPath).Flatten(); - - if (ToLower(ManifestPath.extension().string()) == ".json") - { - IoBuffer MetaDataJson = ReadFile(ManifestPath).Flatten(); - std::string_view Json(reinterpret_cast<const char*>(MetaDataJson.GetData()), MetaDataJson.GetSize()); - std::string JsonError; - CbObject Manifest = LoadCompactBinaryFromJson(Json, JsonError).AsObject(); - if (!JsonError.empty()) - { - throw std::runtime_error(fmt::format("Invalid manifest file at {}. '{}'", ManifestPath, JsonError)); - } - CbObjectView PartsObject = Manifest["parts"sv].AsObjectView(); - for (CbFieldView PartsField : PartsObject) - { - std::string_view PartName = PartsField.GetName(); - if (PartName.empty()) - { - throw std::runtime_error(fmt::format("Part {} in manifest file at {} does not have a name. '{}'", - Result.Parts.size() + 1, - ManifestPath, - JsonError)); - } - CbObjectView Part = PartsField.AsObjectView(); - Oid PartId = Part["partId"sv].AsObjectId(); - if (PartId == Oid::Zero) - { - PartId = Oid::NewOid(); - } - CbArrayView FilesArray = Part["files"sv].AsArrayView(); - std::vector<std::filesystem::path> Files; - Files.reserve(FilesArray.Num()); - for (CbFieldView FileField : FilesArray) - { - std::filesystem::path File(FileField.AsU8String()); - Files.push_back(File); - } - - Result.Parts.push_back(PartManifest::Part{.PartId = PartId, .PartName = std::string(PartName), .Files = std::move(Files)}); - } - return Result; - } - else - { - Result.Parts.resize(1); - PartManifest::Part& SinglePart = Result.Parts.front(); - - std::string_view ManifestString((const char*)ManifestContent.GetView().GetData(), ManifestContent.GetSize()); - std::string_view::size_type Offset = 0; - while (Offset < ManifestContent.GetSize()) - { - size_t PathBreakOffset = ManifestString.find_first_of("\t\r\n", Offset); - if (PathBreakOffset == std::string_view::npos) - { - PathBreakOffset = ManifestContent.GetSize(); - } - std::string_view AssetPath = ManifestString.substr(Offset, PathBreakOffset - Offset); - if (!AssetPath.empty()) - { - SinglePart.Files.emplace_back(std::filesystem::path(AssetPath)); - } - Offset = PathBreakOffset; - size_t EolOffset = ManifestString.find_first_of("\r\n", Offset); - if (EolOffset == std::string_view::npos) - { - break; - } - Offset = EolOffset; - size_t LineBreakOffset = ManifestString.find_first_not_of("\t\r\n", Offset); - if (LineBreakOffset == std::string_view::npos) - { - break; - } - Offset = LineBreakOffset; - } - } - } - return Result; -} - bool BuildsOperationUploadFolder::IsAcceptedFolder(const std::string_view& RelativePath) const { @@ -7444,6 +7362,7 @@ GetRemoteContent(OperationLogOutput& Output, StorageInstance& Storage, const Oid& BuildId, const std::vector<std::pair<Oid, std::string>>& BuildParts, + const BuildManifest& Manifest, std::span<const std::string> IncludeWildcards, std::span<const std::string> ExcludeWildcards, std::unique_ptr<ChunkingController>& OutChunkController, @@ -7485,6 +7404,7 @@ GetRemoteContent(OperationLogOutput& Output, CbObject BuildPartManifest, std::span<const std::string> IncludeWildcards, std::span<const std::string> ExcludeWildcards, + const BuildManifest::Part* OptionalManifest, ChunkedFolderContent& OutRemoteContent, std::vector<ChunkBlockDescription>& OutBlockDescriptions, std::vector<IoHash>& OutLooseChunkHashes) { @@ -7529,29 +7449,78 @@ GetRemoteContent(OperationLogOutput& Output, OutRemoteContent.ChunkedContent.ChunkOrders, DoExtraContentVerify); - if (!IncludeWildcards.empty() || !ExcludeWildcards.empty()) + std::vector<std::filesystem::path> DeletedPaths; + + if (OptionalManifest) { - std::vector<std::filesystem::path> DeletedPaths; + tsl::robin_set<std::string> PathsInManifest; + PathsInManifest.reserve(OptionalManifest->Files.size()); + for (const std::filesystem::path& ManifestPath : OptionalManifest->Files) + { + PathsInManifest.insert(ToLower(ManifestPath.generic_string())); + } for (const std::filesystem::path& RemotePath : OutRemoteContent.Paths) { - if (!IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(RemotePath.generic_string()), /*CaseSensitive*/ true)) + if (!PathsInManifest.contains(ToLower(RemotePath.generic_string()))) { DeletedPaths.push_back(RemotePath); } } + } - if (!DeletedPaths.empty()) + if (!IncludeWildcards.empty() || !ExcludeWildcards.empty()) + { + for (const std::filesystem::path& RemotePath : OutRemoteContent.Paths) { - OutRemoteContent = DeletePathsFromChunkedContent(OutRemoteContent, DeletedPaths); - InlineRemoveUnusedHashes(OutLooseChunkHashes, OutRemoteContent.ChunkedContent.ChunkHashes); + if (!IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(RemotePath.generic_string()), /*CaseSensitive*/ true)) + { + DeletedPaths.push_back(RemotePath); + } } } + if (!DeletedPaths.empty()) + { + OutRemoteContent = DeletePathsFromChunkedContent(OutRemoteContent, DeletedPaths); + InlineRemoveUnusedHashes(OutLooseChunkHashes, OutRemoteContent.ChunkedContent.ChunkHashes); + } + #if ZEN_BUILD_DEBUG ValidateChunkedFolderContent(OutRemoteContent, OutBlockDescriptions, OutLooseChunkHashes, IncludeWildcards, ExcludeWildcards); #endif // ZEN_BUILD_DEBUG }; + auto FindManifest = [&Manifest](const Oid& BuildPartId, std::string_view BuildPartName) -> const BuildManifest::Part* { + if (Manifest.Parts.empty()) + { + return nullptr; + } + if (Manifest.Parts.size() == 1) + { + if (Manifest.Parts[0].PartId == Oid::Zero && Manifest.Parts[0].PartName.empty()) + { + return &Manifest.Parts[0]; + } + } + + auto It = std::find_if(Manifest.Parts.begin(), Manifest.Parts.end(), [BuildPartId, BuildPartName](const BuildManifest::Part& Part) { + if (Part.PartId != Oid::Zero) + { + return Part.PartId == BuildPartId; + } + if (!Part.PartName.empty()) + { + return Part.PartName == BuildPartName; + } + return false; + }); + if (It != Manifest.Parts.end()) + { + return &(*It); + } + return nullptr; + }; + OutPartContents.resize(1); ParseBuildPartManifest(Storage, BuildId, @@ -7559,6 +7528,7 @@ GetRemoteContent(OperationLogOutput& Output, BuildPartManifest, IncludeWildcards, ExcludeWildcards, + FindManifest(BuildPartId, BuildPartName), OutPartContents[0], OutBlockDescriptions, OutLooseChunkHashes); @@ -7593,6 +7563,7 @@ GetRemoteContent(OperationLogOutput& Output, OverlayBuildPartManifest, IncludeWildcards, ExcludeWildcards, + FindManifest(OverlayBuildPartId, OverlayBuildPartName), OverlayPartContent, OverlayPartBlockDescriptions, OverlayPartLooseChunkHashes); @@ -7859,6 +7830,7 @@ namespace buildstorageoperations_testutils { AllBuildParts, {}, {}, + {}, ChunkController, PartContents, BlockDescriptions, diff --git a/src/zenremotestore/include/zenremotestore/builds/buildmanifest.h b/src/zenremotestore/include/zenremotestore/builds/buildmanifest.h new file mode 100644 index 000000000..a0d9a7691 --- /dev/null +++ b/src/zenremotestore/include/zenremotestore/builds/buildmanifest.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/filesystem.h> +#include <zencore/uid.h> + +namespace zen { + +struct BuildManifest +{ + struct Part + { + Oid PartId = Oid::Zero; + std::string PartName; + std::vector<std::filesystem::path> Files; + }; + std::vector<Part> Parts; +}; + +BuildManifest ParseBuildManifest(const std::filesystem::path& ManifestPath); + +#if ZEN_WITH_TESTS +void buildmanifest_forcelink(); +#endif // ZEN_WITH_TESTS + +} // namespace zen diff --git a/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h b/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h index 9f7d93398..47f402e15 100644 --- a/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h +++ b/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h @@ -577,17 +577,6 @@ private: PrepareBuildResult PrepareBuild(); - struct PartManifest - { - struct Part - { - Oid PartId; - std::string PartName; - std::vector<std::filesystem::path> Files; - }; - std::vector<Part> Parts; - }; - struct UploadPart { Oid PartId = Oid::Zero; @@ -600,8 +589,6 @@ private: std::vector<BuildsOperationUploadFolder::UploadPart> ReadFolder(); std::vector<UploadPart> ReadManifestParts(const std::filesystem::path& ManifestPath); - PartManifest ParseManifest(const std::filesystem::path& Path, const std::filesystem::path& ManifestPath); - bool IsAcceptedFolder(const std::string_view& RelativePath) const; bool IsAcceptedFile(const std::string_view& RelativePath) const; @@ -811,10 +798,13 @@ std::vector<std::pair<Oid, std::string>> ResolveBuildPartNames(CbObjectView std::span<const std::string> BuildPartNames, std::uint64_t& OutPreferredMultipartChunkSize); +struct BuildManifest; + ChunkedFolderContent GetRemoteContent(OperationLogOutput& Output, StorageInstance& Storage, const Oid& BuildId, const std::vector<std::pair<Oid, std::string>>& BuildParts, + const BuildManifest& Manifest, std::span<const std::string> IncludeWildcards, std::span<const std::string> ExcludeWildcards, std::unique_ptr<ChunkingController>& OutChunkController, diff --git a/src/zenremotestore/zenremotestore.cpp b/src/zenremotestore/zenremotestore.cpp index 0d008ec40..a0bb17260 100644 --- a/src/zenremotestore/zenremotestore.cpp +++ b/src/zenremotestore/zenremotestore.cpp @@ -2,6 +2,7 @@ #include <zenremotestore/zenremotestore.h> +#include <zenremotestore/builds/buildmanifest.h> #include <zenremotestore/builds/buildsavedstate.h> #include <zenremotestore/builds/buildstorageoperations.h> #include <zenremotestore/chunking/chunkedcontent.h> @@ -17,6 +18,7 @@ namespace zen { void zenremotestore_forcelinktests() { + buildmanifest_forcelink(); buildsavedstate_forcelink(); buildstorageoperations_forcelink(); chunkblock_forcelink(); |