summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/buildtool/main/TARGETS13
-rw-r--r--src/buildtool/main/archive.cpp180
-rw-r--r--src/buildtool/main/archive.hpp33
3 files changed, 226 insertions, 0 deletions
diff --git a/src/buildtool/main/TARGETS b/src/buildtool/main/TARGETS
index 1dc35eaa..626fe9f2 100644
--- a/src/buildtool/main/TARGETS
+++ b/src/buildtool/main/TARGETS
@@ -281,4 +281,17 @@
, ["src/buildtool/logging", "log_level"]
]
}
+, "archive":
+ { "type": ["@", "rules", "CC", "library"]
+ , "name": ["archive"]
+ , "hdrs": ["archive.hpp"]
+ , "srcs": ["archive.cpp"]
+ , "deps": [["src/buildtool/common", "common"]]
+ , "stage": ["src", "buildtool", "main"]
+ , "private-deps":
+ [ ["", "libarchive"]
+ , ["src/buildtool/file_system", "git_repo"]
+ , ["src/utils/cpp", "hex_string"]
+ ]
+ }
}
diff --git a/src/buildtool/main/archive.cpp b/src/buildtool/main/archive.cpp
new file mode 100644
index 00000000..2f6fc099
--- /dev/null
+++ b/src/buildtool/main/archive.cpp
@@ -0,0 +1,180 @@
+// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BOOTSTRAP_BUILD_TOOL
+
+#include "src/buildtool/main/archive.hpp"
+
+#include <unistd.h>
+
+#include "src/buildtool/file_system/git_repo.hpp"
+#include "src/buildtool/logging/log_level.hpp"
+#include "src/buildtool/logging/logger.hpp"
+#include "src/utils/cpp/hex_string.hpp"
+
+extern "C" {
+#include <archive.h>
+#include <archive_entry.h>
+}
+
+namespace {
+
+void archive_write_closer(archive* a) {
+ if (a != nullptr) {
+ archive_write_close(a);
+ archive_write_free(a);
+ }
+}
+
+void archive_entry_cleanup(archive_entry* entry) {
+ if (entry != nullptr) {
+ archive_entry_free(entry);
+ }
+}
+
+// NOLINTNEXTLINE(misc-no-recursion)
+auto add_to_archive(archive* archive,
+ gsl::not_null<IExecutionApi*> const& api,
+ const Artifact::ObjectInfo& artifact,
+ const std::filesystem::path& location) -> bool {
+ auto constexpr kExecutable = 0555;
+ auto constexpr kFile = 0444;
+
+ auto payload = api->RetrieveToMemory(artifact);
+ if (not payload) {
+ Logger::Log(LogLevel::Error,
+ "Failed to retrieve artifact {}",
+ artifact.ToString());
+ return false;
+ }
+ switch (artifact.type) {
+ case ObjectType::File:
+ case ObjectType::Executable: {
+ std::unique_ptr<archive_entry, decltype(&archive_entry_cleanup)>
+ entry{archive_entry_new(), archive_entry_cleanup};
+ archive_entry_set_pathname(entry.get(), location.string().c_str());
+ archive_entry_set_size(entry.get(), payload->size());
+ archive_entry_set_filetype(entry.get(), AE_IFREG);
+ archive_entry_set_perm(
+ entry.get(),
+ artifact.type == ObjectType::Executable ? kExecutable : kFile);
+ archive_write_header(archive, entry.get());
+ auto data = *payload;
+ archive_write_data(archive, data.c_str(), data.size());
+ } break;
+ case ObjectType::Symlink: {
+ std::unique_ptr<archive_entry, decltype(&archive_entry_cleanup)>
+ entry{archive_entry_new(), archive_entry_cleanup};
+ archive_entry_set_pathname(entry.get(), location.string().c_str());
+ archive_entry_set_filetype(entry.get(), AE_IFLNK);
+ archive_entry_set_symlink(entry.get(), payload->c_str());
+ archive_write_header(archive, entry.get());
+ } break;
+ case ObjectType::Tree: {
+ auto git_tree = GitRepo::ReadTreeData(
+ *payload,
+ artifact.digest.hash(),
+ [](auto const& /*unused*/) { return true; },
+ /*is_hex_id=*/true);
+ if (not git_tree) {
+ Logger::Log(LogLevel::Error,
+ "Failed to parse {} as git tree for path {}",
+ artifact.ToString(),
+ location.string());
+ return false;
+ }
+ // Reorder entries to be keyed and sorted by name
+ std::map<std::string, Artifact::ObjectInfo> tree{};
+ for (auto const& [hash, entries] : *git_tree) {
+ auto hex_hash = ToHexString(hash);
+ for (auto const& entry : entries) {
+ tree[entry.name] = Artifact::ObjectInfo{
+ .digest = ArtifactDigest(
+ hex_hash, 0, entry.type == ObjectType::Tree),
+ .type = entry.type,
+ .failed = false};
+ }
+ }
+ for (auto const& [name, obj] : tree) {
+ if (not add_to_archive(archive, api, obj, location / name)) {
+ return false;
+ }
+ }
+
+ } break;
+ }
+ return true;
+}
+
+} // namespace
+
+[[nodiscard]] auto GenerateArchive(
+ gsl::not_null<IExecutionApi*> const& api,
+ const Artifact::ObjectInfo& artifact,
+ const std::optional<std::filesystem::path>& output_path) -> bool {
+
+ constexpr int kTarBlockSize = 512;
+
+ std::unique_ptr<archive, decltype(&archive_write_closer)> archive{
+ archive_write_new(), archive_write_closer};
+ if (archive == nullptr) {
+ Logger::Log(LogLevel::Error,
+ "Internal error: Call to archive_write_new() failed");
+ return false;
+ }
+ if (archive_write_set_format_pax_restricted(archive.get()) != ARCHIVE_OK) {
+ Logger::Log(LogLevel::Error,
+ "Internal error: Call to "
+ "archive_write_set_format_pax_restriced() failed");
+ return false;
+ }
+ if (archive_write_set_bytes_per_block(archive.get(), kTarBlockSize) !=
+ ARCHIVE_OK) {
+ Logger::Log(LogLevel::Error,
+ "Internal error: Call to "
+ "archive_write_set_bytes_per_block() failed");
+ return false;
+ }
+ if (output_path) {
+ if (archive_write_open_filename(
+ archive.get(), output_path->string().c_str()) != ARCHIVE_OK) {
+ Logger::Log(LogLevel::Error,
+ "Failed to open archive for writing at {}",
+ output_path->string());
+ return false;
+ }
+ }
+ else {
+ if (archive_write_open_fd(archive.get(), STDOUT_FILENO) != ARCHIVE_OK) {
+ Logger::Log(LogLevel::Error,
+ "Failed to open stdout for writing archive to");
+ return false;
+ }
+ }
+
+ if (not add_to_archive(
+ archive.get(), api, artifact, std::filesystem::path{""})) {
+ return false;
+ }
+ if (output_path) {
+ Logger::Log(LogLevel::Info,
+ "Archive of {} was installed to {}",
+ artifact.ToString(),
+ output_path->string());
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/buildtool/main/archive.hpp b/src/buildtool/main/archive.hpp
new file mode 100644
index 00000000..c60aa337
--- /dev/null
+++ b/src/buildtool/main/archive.hpp
@@ -0,0 +1,33 @@
+// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef INCLUDED_SRC_BUILDTOOL_MAIN_ARCHIVE_HPP
+#define INCLUDED_SRC_BUILDTOOL_MAIN_ARCHIVE_HPP
+
+#ifndef BOOTSTRAP_BUILD_TOOL
+
+#include <filesystem>
+#include <optional>
+
+#include "src/buildtool/common/artifact.hpp"
+#include "src/buildtool/execution_api/common/execution_api.hpp"
+
+[[nodiscard]] auto GenerateArchive(
+ gsl::not_null<IExecutionApi*> const& api,
+ const Artifact::ObjectInfo& artifact,
+ const std::optional<std::filesystem::path>& output_path) -> bool;
+
+#endif
+
+#endif