已合并
[feat] add asc_compile_exporter #4980
sjtulxh创建于 8月10日
[feat] add asc_compile_exporter #4980
已合并
sjtulxh创建于 8月10日
共 37 个文件变更+3572-1
@@ -15,6 +15,9 @@ set(CMAKE_MODULE_PATH
15 ${CMAKE_MODULE_PATH}15 ${CMAKE_MODULE_PATH}
16)16)
17 17 
18+add_cann_third_party(boost)
19+add_cann_third_party(json)
20+ 
18find_cann_package(unified_dlog REQUIRED)21find_cann_package(unified_dlog REQUIRED)
19find_cann_package(securec REQUIRED)22find_cann_package(securec REQUIRED)
20find_cann_package(mmpa REQUIRED)23find_cann_package(mmpa REQUIRED)
@@ -14,7 +14,7 @@ if(NOT PROJECT_SOURCE_DIR)
14 else()14 else()
15 include(FetchContent)15 include(FetchContent)
16 16 
17- set(CANN_CMAKE_TAG "master-044")17+ set(CANN_CMAKE_TAG "master-050")
18 if(CANN_3RD_LIB_PATH AND EXISTS "${CANN_3RD_LIB_PATH}/cmake-${CANN_CMAKE_TAG}.tar.gz")18 if(CANN_3RD_LIB_PATH AND EXISTS "${CANN_3RD_LIB_PATH}/cmake-${CANN_CMAKE_TAG}.tar.gz")
19 FetchContent_Declare(19 FetchContent_Declare(
20 cann-cmake20 cann-cmake
@@ -36,6 +36,7 @@
36 <path value="share" install_mod="750"/>36 <path value="share" install_mod="750"/>
37 <path value="share/info" install_mod="750"/>37 <path value="share/info" install_mod="750"/>
38 <path value="tools/opbuild" install_mod="750"/>38 <path value="tools/opbuild" install_mod="750"/>
39+ <path value="tools/asc_compile_exporter" install_mod="750"/>
39 <path value="$(TARGET_ENV)/tikcpp" install_softlink="compiler/tikcpp;tools/tikcpp"/>40 <path value="$(TARGET_ENV)/tikcpp" install_softlink="compiler/tikcpp;tools/tikcpp"/>
40 <path value="$(TARGET_ENV)/tikcpp/tikcfw"/>41 <path value="$(TARGET_ENV)/tikcpp/tikcfw"/>
41 <path value="$(TARGET_ENV)/tikcpp/tikcfw/include"/>42 <path value="$(TARGET_ENV)/tikcpp/tikcfw/include"/>
@@ -174,6 +175,9 @@
174 <file_info value="op_build" copy_type="delivery" src_path="lib/host/" dst_path="tools/opbuild" install_path="tools/opbuild" install_mod="555" feature="ascendc">175 <file_info value="op_build" copy_type="delivery" src_path="lib/host/" dst_path="tools/opbuild" install_path="tools/opbuild" install_mod="555" feature="ascendc">
175 <file value="op_build" file_type= "executable"/>176 <file value="op_build" file_type= "executable"/>
176 </file_info>177 </file_info>
178+ <file_info value="asc_compile_exporter" copy_type="delivery" src_path="lib/host/" dst_path="tools/asc_compile_exporter" install_path="tools/asc_compile_exporter" install_mod="555" feature="ascendc">
179+ <file value="asc_compile_exporter" file_type= "executable"/>
180+ </file_info>
177 181 
178 <file_info copy_type="source" src_path="build/makeself" dst_path="$(TARGET_ENV)/tikcpp/ascendc_kernel_cmake/fwk_modules/util/makeself" install_path="$(TARGET_ENV)/tikcpp/ascendc_kernel_cmake/fwk_modules/util/makeself">182 <file_info copy_type="source" src_path="build/makeself" dst_path="$(TARGET_ENV)/tikcpp/ascendc_kernel_cmake/fwk_modules/util/makeself" install_path="$(TARGET_ENV)/tikcpp/ascendc_kernel_cmake/fwk_modules/util/makeself">
179 <file value="COPYING" install_mod="550"/>183 <file value="COPYING" install_mod="550"/>
@@ -81,6 +81,9 @@ do_create_stub_softlink() {
81 if [ ! -d "$install_path/$latest_dir/tools/opbuild" ]; then81 if [ ! -d "$install_path/$latest_dir/tools/opbuild" ]; then
82 ln -sr "$install_path/$version_dir/tools/opbuild" "$install_path/$latest_dir/tools/opbuild"82 ln -sr "$install_path/$version_dir/tools/opbuild" "$install_path/$latest_dir/tools/opbuild"
83 fi83 fi
84+ if [ ! -d "$install_path/$latest_dir/tools/asc_compile_exporter" ]; then
85+ ln -sr "$install_path/$version_dir/tools/asc_compile_exporter" "$install_path/$latest_dir/tools/asc_compile_exporter"
86+ fi
84 if [ -e "$install_path/$version_dir/${arch_name}-linux/bin/asc_opc" ]; then87 if [ -e "$install_path/$version_dir/${arch_name}-linux/bin/asc_opc" ]; then
85 if [ ! -L "$install_path/$version_dir/compiler/bin/asc_opc" ]; then88 if [ ! -L "$install_path/$version_dir/compiler/bin/asc_opc" ]; then
86 ln -sr "$install_path/$version_dir/${arch_name}-linux/bin/asc_opc" "$install_path/$version_dir/compiler/bin/asc_opc"89 ln -sr "$install_path/$version_dir/${arch_name}-linux/bin/asc_opc" "$install_path/$version_dir/compiler/bin/asc_opc"
@@ -95,6 +95,9 @@ do_remove_stub_softlink() {
95 if [ -d "$install_path/$latest_dir/tools/opbuild" ]; then95 if [ -d "$install_path/$latest_dir/tools/opbuild" ]; then
96 rm -f "$install_path/$latest_dir/tools/opbuild"96 rm -f "$install_path/$latest_dir/tools/opbuild"
97 fi97 fi
98+ if [ -d "$install_path/$latest_dir/tools/asc_compile_exporter" ]; then
99+ rm -f "$install_path/$latest_dir/tools/asc_compile_exporter"
100+ fi
98 101 
99 if [ -d "$arch_linux_path/pkg_inc/asc/hccl" ]; then102 if [ -d "$arch_linux_path/pkg_inc/asc/hccl" ]; then
100 rm -rf "$arch_linux_path/pkg_inc/asc"103 rm -rf "$arch_linux_path/pkg_inc/asc"
@@ -15,4 +15,5 @@ set(GENERATE_CPP_COV ${CMAKE_CURRENT_LIST_DIR}/scripts/generate_cpp_cov.sh)
15find_package(Python3 COMPONENTS Interpreter)15find_package(Python3 COMPONENTS Interpreter)
16add_cann_third_party(gtest)16add_cann_third_party(gtest)
17add_cann_third_party(boost)17add_cann_third_party(boost)
18+add_cann_third_party(json)
18include(${CMAKE_CURRENT_LIST_DIR}/third_party/mockcpp.cmake)19include(${CMAKE_CURRENT_LIST_DIR}/third_party/mockcpp.cmake)
@@ -81,6 +81,7 @@ basic_test_three_targets=(
81 ascendc_ut_asc_runtime81 ascendc_ut_asc_runtime
82 ascendc_ut_elf_tool82 ascendc_ut_elf_tool
83 ascendc_ut_pack_kernel83 ascendc_ut_pack_kernel
84+ ascendc_ut_asc_compile_exporter
84 ascendc_ut_tpl_tiling_debug85 ascendc_ut_tpl_tiling_debug
85 ascendc_ut_tpl_tiling_release86 ascendc_ut_tpl_tiling_release
86 ascendc_ut_aicpu_api87 ascendc_ut_aicpu_api
@@ -197,6 +197,64 @@ run_llt_test(
197 TASK_NUM 1197 TASK_NUM 1
198)198)
199 199 
200+# ================ asc_compile_exporter ================
201+add_executable(ascendc_ut_asc_compile_exporter
202+ ${ASCENDC_DIR}/tests/main.cpp
203+ asc_compile_exporter/test_asc_compile_exporter_unit.cpp
204+ asc_compile_exporter/test_asc_compile_exporter.cpp
205+ asc_compile_exporter/test_build_workspace.cpp
206+ asc_compile_exporter/test_bundle_source_generator.cpp
207+ asc_compile_exporter/test_collected_manifest_repository.cpp
208+ asc_compile_exporter/test_file_utils.cpp
209+ asc_compile_exporter/test_manifest_bundle_types.cpp
210+ asc_compile_exporter/test_process_runner.cpp
211+ asc_compile_exporter/test_ascendc_tool_log.cpp
212+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp
213+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp
214+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp
215+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp
216+ ${ASCENDC_DIR}/tools/build/common/file_utils.cpp
217+ ${ASCENDC_DIR}/tools/build/common/process_runner.cpp
218+)
219+ 
220+function(configure_asc_compile_exporter_test target_name)
221+ target_compile_definitions(${target_name} PRIVATE
222+ UT_TEST
223+ )
224+ 
225+ target_include_directories(${target_name} PRIVATE
226+ ${ASCENDC_DIR}/tools/build/asc_compile_exporter
227+ ${ASCENDC_DIR}/tools/build/common
228+ ${ASC_COMPILE_EXPORTER_GENERATED_DIR}
229+ )
230+ 
231+ target_compile_options(${target_name} PRIVATE
232+ -Werror
233+ )
234+ 
235+ target_link_options(${target_name} PRIVATE
236+ -Wl,--wrap=fork
237+ -Wl,--wrap=rename
238+ -Wl,--wrap=waitpid
239+ )
240+ 
241+ target_link_libraries(${target_name} PRIVATE
242+ Boost::filesystem
243+ $<BUILD_INTERFACE:intf_llt_pub_basic>
244+ json
245+ mmpa_headers
246+ metadef_headers
247+ dl
248+ )
249+endfunction()
250+ 
251+configure_asc_compile_exporter_test(ascendc_ut_asc_compile_exporter)
252+ 
253+run_llt_test(
254+ TARGET ascendc_ut_asc_compile_exporter
255+ TASK_NUM 1
256+)
257+ 
200 258 
201# ###################################opbuild ut#################################259# ###################################opbuild ut#################################
202add_library(ops_utest SHARED ${ASCENDC_DIR}/tests/tools/utils/opbuild/test_ops.cpp)260add_library(ops_utest SHARED ${ASCENDC_DIR}/tests/tools/utils/opbuild/test_ops.cpp)
@@ -0,0 +1,72 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+#ifndef ASCENDC_MANIFEST_ABI_H
11+#define ASCENDC_MANIFEST_ABI_H
12+ 
13+#include <stdint.h>
14+ 
15+#ifdef __cplusplus
16+extern "C" {
17+#endif
18+ 
19+#define AC_COMPILE_RESOURCE_MAGIC 0x41435253u
20+#define AC_COMPILE_RESOURCE_ABI_VERSION 1u
21+#define AC_COMPILE_RESOURCE_EXTENSION_REQUIRED 0x1ULL
22+ 
23+typedef struct AcString {
24+ const char* data;
25+ uint64_t size;
26+} AcString;
27+ 
28+typedef struct AcCompileResourceExtension {
29+ uint32_t type;
30+ uint32_t version;
31+ uint64_t flags;
32+ const void* data;
33+ uint64_t dataSize;
34+} AcCompileResourceExtension;
35+ 
36+typedef struct AcCompileResourceFile {
37+ AcString fileName;
38+ AcString filePath;
39+ const uint8_t* data;
40+ uint64_t size;
41+} AcCompileResourceFile;
42+ 
43+typedef struct AcCompileResourceManifest {
44+ AcString json;
45+ const AcCompileResourceFile* files;
46+ uint64_t fileCount;
47+} AcCompileResourceManifest;
48+ 
49+typedef struct AcCompileResourceBundleHeader {
50+ uint32_t magic;
51+ uint32_t abiVersion;
52+ uint32_t structSize;
53+ uint32_t flags;
54+} AcCompileResourceBundleHeader;
55+ 
56+typedef struct AcCompileResourceBundle {
57+ AcCompileResourceBundleHeader header;
58+ const AcCompileResourceManifest* manifests;
59+ uint64_t manifestCount;
60+ const AcCompileResourceExtension* extensions;
61+ uint64_t extensionCount;
62+} AcCompileResourceBundle;
63+ 
64+typedef const AcCompileResourceBundleHeader* (*AscendcGetCompileResourceBundleFn)(void);
65+ 
66+const AcCompileResourceBundleHeader* AscendcGetCompileResourceBundle(void);
67+ 
68+#ifdef __cplusplus
69+}
70+#endif
71+ 
72+#endif // ASCENDC_MANIFEST_ABI_H
@@ -0,0 +1,489 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <dlfcn.h>
12+#include <dirent.h>
13+#include <elf.h>
14+#include <sys/stat.h>
15+#include <unistd.h>
16+ 
17+#include <cerrno>
18+#include <cstdlib>
19+#include <cstring>
20+#include <fstream>
21+#include <iterator>
22+#include <stdexcept>
23+#include <string>
24+#include <utility>
25+#include <vector>
26+ 
27+#include <gtest/gtest.h>
28+ 
29+#include "ascendc_manifest_abi.h"
30+#include "manifest_bundle_compiler.h"
31+#include "process_runner.h"
32+#include "test_support.h"
33+ 
34+namespace {
35+ 
36+using asc_compile_exporter_test::ScopedEnvironment;
37+using ascendc::ProcessRunner;
38+using ascendc::manifest_generator::BuildCollectedBundleRequest;
39+using ascendc::manifest_generator::BundleOutputKind;
40+using ascendc::manifest_generator::ManifestBundleCompiler;
41+ 
42+std::vector<int> g_renameErrors;
43+ 
44+std::string JoinTestPath(const std::string& left, const std::string& right)
45+{
46+ if (left.empty()) {
47+ return right;
48+ }
49+ return left[left.size() - 1U] == '/' ? left + right : left + "/" + right;
50+}
51+ 
52+std::string ParentTestPath(const std::string& path)
53+{
54+ const size_t separator = path.find_last_of('/');
55+ if (separator == std::string::npos) {
56+ return std::string();
57+ }
58+ return separator == 0U ? "/" : path.substr(0U, separator);
59+}
60+ 
61+bool IsTestDirectory(const std::string& path)
62+{
63+ struct stat info {};
64+ return stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode);
65+}
66+ 
67+bool IsTestRegularFile(const std::string& path)
68+{
69+ struct stat info {};
70+ return stat(path.c_str(), &info) == 0 && S_ISREG(info.st_mode);
71+}
72+ 
73+void CreateTestDirectories(const std::string& path)
74+{
75+ if (path.empty() || path == "/" || IsTestDirectory(path)) {
76+ return;
77+ }
78+ CreateTestDirectories(ParentTestPath(path));
79+ if (mkdir(path.c_str(), 0700) != 0 && !(errno == EEXIST && IsTestDirectory(path))) {
80+ throw std::runtime_error("failed to create test directory " + path + ": " + std::strerror(errno));
81+ }
82+}
83+ 
84+void RemoveTestTree(const std::string& path) noexcept
85+{
86+ struct stat info {};
87+ if (lstat(path.c_str(), &info) != 0) {
88+ return;
89+ }
90+ if (!S_ISDIR(info.st_mode) || S_ISLNK(info.st_mode)) {
91+ (void)unlink(path.c_str());
92+ return;
93+ }
94+ DIR* directory = opendir(path.c_str());
95+ if (directory != nullptr) {
96+ while (dirent* entry = readdir(directory)) {
97+ const std::string name(entry->d_name);
98+ if (name != "." && name != "..") {
99+ RemoveTestTree(JoinTestPath(path, name));
100+ }
101+ }
102+ (void)closedir(directory);
103+ }
104+ (void)rmdir(path.c_str());
105+}
106+ 
107+std::vector<std::string> ListTestDirectory(const std::string& path)
108+{
109+ DIR* directory = opendir(path.c_str());
110+ if (directory == nullptr) {
111+ throw std::runtime_error("failed to enumerate test directory: " + path);
112+ }
113+ std::vector<std::string> names;
114+ while (dirent* entry = readdir(directory)) {
115+ const std::string name(entry->d_name);
116+ if (name != "." && name != "..") {
117+ names.push_back(name);
118+ }
119+ }
120+ (void)closedir(directory);
121+ return names;
122+}
123+ 
124+void WriteFile(const std::string& path, const std::string& content)
125+{
126+ CreateTestDirectories(ParentTestPath(path));
127+ std::ofstream output(path.c_str(), std::ios::binary | std::ios::trunc);
128+ if (!output.is_open()) {
129+ throw std::runtime_error("failed to create test file: " + path);
130+ }
131+ output.write(content.data(), static_cast<std::streamsize>(content.size()));
132+ output.close();
133+ if (!output) {
134+ throw std::runtime_error("failed to write test file: " + path);
135+ }
136+}
137+ 
138+std::string ReadFile(const std::string& path)
139+{
140+ std::ifstream input(path.c_str(), std::ios::binary);
141+ return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
142+}
143+ 
144+std::string ToString(const AcString& value) { return std::string(value.data, value.size); }
145+ 
146+bool ReadElfType(const std::string& path, uint16_t& type)
147+{
148+ std::ifstream input(path.c_str(), std::ios::binary);
149+ Elf64_Ehdr header{};
150+ input.read(reinterpret_cast<char*>(&header), static_cast<std::streamsize>(sizeof(header)));
151+ if (input.gcount() != static_cast<std::streamsize>(sizeof(header)) ||
152+ std::memcmp(header.e_ident, ELFMAG, SELFMAG) != 0 || header.e_ident[EI_CLASS] != ELFCLASS64) {
153+ return false;
154+ }
155+ type = header.e_type;
156+ return true;
157+}
158+ 
159+} // namespace
160+ 
161+extern "C" int __real_rename(const char* oldPath, const char* newPath);
162+ 
163+extern "C" int __wrap_rename(const char* oldPath, const char* newPath)
164+{
165+ if (g_renameErrors.empty()) {
166+ return __real_rename(oldPath, newPath);
167+ }
168+ const int renameError = g_renameErrors.front();
169+ g_renameErrors.erase(g_renameErrors.begin());
170+ errno = renameError;
171+ return -1;
172+}
173+ 
174+namespace {
175+ 
176+class AscCompileExporterTest : public testing::Test {
177+protected:
178+ void SetUp() override
179+ {
180+ const char* temporary = std::getenv("TMPDIR");
181+ std::string pathTemplate = JoinTestPath(
182+ temporary == nullptr || temporary[0] == '\0' ? "/tmp" : temporary, "asc_compile_exporter_ut_XXXXXX");
183+ std::vector<char> buffer(pathTemplate.begin(), pathTemplate.end());
184+ buffer.push_back('\0');
185+ char* created = mkdtemp(buffer.data());
186+ ASSERT_NE(created, nullptr);
187+ root_ = created;
188+ }
189+ 
190+ void TearDown() override
191+ {
192+ g_renameErrors.clear();
193+ RemoveTestTree(root_);
194+ }
195+ 
196+ BuildCollectedBundleRequest Request() const
197+ {
198+ CreateTestDirectories(JoinTestPath(root_, "output"));
199+ BuildCollectedBundleRequest request;
200+ request.manifestSearchRoot = JoinTestPath(root_, "collection");
201+ request.outputPath = JoinTestPath(root_, "output/bundle.so");
202+ request.makeExecutable = "/usr/bin/make";
203+ request.cxxCompiler = "/usr/bin/c++";
204+ request.outputKind = BundleOutputKind::kSharedObject;
205+ request.keepTemp = false;
206+ request.jobs = 1U;
207+ return request;
208+ }
209+ 
210+ void CreateManifest(
211+ const std::string& unit, const std::string& name, const std::string& baseDir = "resources") const
212+ {
213+ WriteFile(
214+ JoinTestPath(unit, name + "_manifest.json"),
215+ "{\"base_dir\":\"" + baseDir + "\",\"name\":\"" + name + "\"}");
216+ }
217+ 
218+ static void ExpectCompileFailure(const BuildCollectedBundleRequest& request)
219+ {
220+ EXPECT_FALSE(ManifestBundleCompiler(request).Compile());
221+ }
222+ 
223+ std::string root_;
224+};
225+ 
226+TEST_F(AscCompileExporterTest, ReportsWorkspaceAndBuildFailures)
227+{
228+ const std::string unit = JoinTestPath(root_, "collection/Failures");
229+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
230+ CreateManifest(unit, "Failures");
231+ 
232+ {
233+ ScopedEnvironment temporary("TMPDIR", "/dev/null");
234+ ExpectCompileFailure(Request());
235+ }
236+ 
237+ BuildCollectedBundleRequest buildFailure = Request();
238+ buildFailure.makeExecutable = "/path/that/does/not/exist";
239+ ExpectCompileFailure(buildFailure);
240+}
241+ 
242+TEST_F(AscCompileExporterTest, ReplacesTrustedOutputSymlinkWithoutFollowingIt)
243+{
244+ const std::string unit = JoinTestPath(root_, "collection/SymlinkOutput");
245+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
246+ CreateManifest(unit, "SymlinkOutput");
247+ 
248+ const BuildCollectedBundleRequest request = Request();
249+ const std::string victim = JoinTestPath(root_, "victim.so");
250+ WriteFile(victim, "victim");
251+ ASSERT_EQ(symlink(victim.c_str(), request.outputPath.c_str()), 0);
252+ 
253+ ASSERT_TRUE(ManifestBundleCompiler(request).Compile());
254+ EXPECT_TRUE(IsTestRegularFile(request.outputPath));
255+ EXPECT_EQ(ReadFile(victim), "victim");
256+}
257+ 
258+TEST_F(AscCompileExporterTest, ReportsDirectPublishRenameFailureWithoutReplacingOutput)
259+{
260+ const std::string unit = JoinTestPath(root_, "collection/PublishFailure");
261+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
262+ CreateManifest(unit, "PublishFailure");
263+ const BuildCollectedBundleRequest request = Request();
264+ WriteFile(request.outputPath, "previous bundle");
265+ 
266+ g_renameErrors = {EACCES};
267+ ExpectCompileFailure(request);
268+ EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
269+}
270+ 
271+TEST_F(AscCompileExporterTest, PublishesBundleAfterCrossFileSystemRenameFailure)
272+{
273+ const std::string unit = JoinTestPath(root_, "collection/CrossFileSystem");
274+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
275+ CreateManifest(unit, "CrossFileSystem");
276+ const BuildCollectedBundleRequest request = Request();
277+ WriteFile(request.outputPath, "previous bundle");
278+ 
279+ g_renameErrors = {EXDEV};
280+ ASSERT_TRUE(ManifestBundleCompiler(request).Compile());
281+ EXPECT_TRUE(IsTestRegularFile(request.outputPath));
282+ EXPECT_NE(ReadFile(request.outputPath), "previous bundle");
283+}
284+ 
285+TEST_F(AscCompileExporterTest, ReportsCrossFileSystemWorkspaceFailure)
286+{
287+ const std::string unit = JoinTestPath(root_, "collection/CrossFileSystemWorkspaceFailure");
288+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
289+ CreateManifest(unit, "CrossFileSystemWorkspaceFailure");
290+ BuildCollectedBundleRequest request = Request();
291+ request.outputPath = "/proc/asc_compile_exporter_bundle.so";
292+ 
293+ g_renameErrors = {EXDEV};
294+ ExpectCompileFailure(request);
295+}
296+ 
297+TEST_F(AscCompileExporterTest, ReportsCrossFileSystemFinalRenameFailureWithoutReplacingOutput)
298+{
299+ const std::string unit = JoinTestPath(root_, "collection/CrossFileSystemRenameFailure");
300+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
301+ CreateManifest(unit, "CrossFileSystemRenameFailure");
302+ const BuildCollectedBundleRequest request = Request();
303+ WriteFile(request.outputPath, "previous bundle");
304+ 
305+ g_renameErrors = {EXDEV, EACCES};
306+ ExpectCompileFailure(request);
307+ EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
308+}
309+ 
310+TEST_F(AscCompileExporterTest, RejectsMissingManifestWithoutReplacingOutput)
311+{
312+ CreateTestDirectories(JoinTestPath(root_, "collection"));
313+ const BuildCollectedBundleRequest request = Request();
314+ WriteFile(request.outputPath, "previous bundle");
315+ 
316+ ExpectCompileFailure(request);
317+ EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
318+}
319+ 
320+TEST_F(AscCompileExporterTest, RejectsMalformedJsonWithoutReplacingOutput)
321+{
322+ const std::string unit = JoinTestPath(root_, "collection/Malformed");
323+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
324+ WriteFile(JoinTestPath(unit, "Malformed_manifest.json"), "{\"base_dir\":\"resources\"");
325+ const BuildCollectedBundleRequest request = Request();
326+ WriteFile(request.outputPath, "previous bundle");
327+ 
328+ ExpectCompileFailure(request);
329+ EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
330+}
331+ 
332+TEST_F(AscCompileExporterTest, RejectsBaseDirectoryOutsideManifestDirectory)
333+{
334+ const std::string unit = JoinTestPath(root_, "collection/Traversal");
335+ WriteFile(JoinTestPath(root_, "collection/outside/kernel.cpp"), "source");
336+ CreateManifest(unit, "Traversal", "../outside");
337+ 
338+ ExpectCompileFailure(Request());
339+}
340+ 
341+TEST_F(AscCompileExporterTest, RejectsSymlinkInResourceDirectory)
342+{
343+ const std::string unit = JoinTestPath(root_, "collection/Symlink");
344+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
345+ const std::string outside = JoinTestPath(root_, "outside.cpp");
346+ WriteFile(outside, "outside");
347+ ASSERT_EQ(symlink(outside.c_str(), JoinTestPath(unit, "resources/link.cpp").c_str()), 0);
348+ CreateManifest(unit, "Symlink");
349+ 
350+ ExpectCompileFailure(Request());
351+}
352+ 
353+TEST_F(AscCompileExporterTest, RejectsOversizedResourceWithoutReplacingOutput)
354+{
355+ const std::string unit = JoinTestPath(root_, "collection/Large");
356+ const std::string resource = JoinTestPath(unit, "resources/kernel.cpp");
357+ WriteFile(resource, "");
358+ ASSERT_EQ(truncate(resource.c_str(), 1U * 1024U * 1024U + 1U), 0);
359+ CreateManifest(unit, "Large");
360+ const BuildCollectedBundleRequest request = Request();
361+ WriteFile(request.outputPath, "previous bundle");
362+ 
363+ ExpectCompileFailure(request);
364+ EXPECT_EQ(ReadFile(request.outputPath), "previous bundle");
365+}
366+ 
367+TEST_F(AscCompileExporterTest, BuildsLoadableBundleAndCleansTemporaryWorkspace)
368+{
369+ ScopedEnvironment temporary("TMPDIR", root_.c_str());
370+ ScopedEnvironment cannHome("ASCEND_HOME_PATH", nullptr);
371+ const std::string first = JoinTestPath(root_, "collection/nested/First");
372+ const std::string second = JoinTestPath(root_, "collection/Second");
373+ WriteFile(JoinTestPath(first, "resources/kernel.cpp"), "first source");
374+ WriteFile(JoinTestPath(first, "resources/include/nested.h"), "nested header");
375+ WriteFile(JoinTestPath(first, "resources/empty.bin"), "");
376+ WriteFile(JoinTestPath(second, "resources/kernel.cpp"), "second source");
377+ CreateManifest(first, "First");
378+ CreateManifest(second, "Second");
379+ 
380+ const BuildCollectedBundleRequest request = Request();
381+ WriteFile(request.outputPath, "previous bundle");
382+ ASSERT_TRUE(ManifestBundleCompiler(request).Compile());
383+ 
384+ ASSERT_TRUE(IsTestRegularFile(request.outputPath));
385+ EXPECT_NE(ReadFile(request.outputPath), "previous bundle");
386+ const std::vector<std::string> workEntries = ListTestDirectory(root_);
387+ for (size_t index = 0U; index < workEntries.size(); ++index) {
388+ EXPECT_NE(workEntries[index].find(".manifest-generator-"), 0U);
389+ }
390+ 
391+ void* handle = dlopen(request.outputPath.c_str(), RTLD_NOW | RTLD_LOCAL);
392+ const char* loadError = handle == nullptr ? dlerror() : nullptr;
393+ ASSERT_NE(handle, nullptr) << (loadError == nullptr ? "dlopen failed" : loadError);
394+ auto getter = reinterpret_cast<AscendcGetCompileResourceBundleFn>(dlsym(handle, "AscendcGetCompileResourceBundle"));
395+ ASSERT_NE(getter, nullptr);
396+ const AcCompileResourceBundleHeader* header = getter();
397+ ASSERT_NE(header, nullptr);
398+ EXPECT_EQ(header->magic, AC_COMPILE_RESOURCE_MAGIC);
399+ EXPECT_EQ(header->abiVersion, AC_COMPILE_RESOURCE_ABI_VERSION);
400+ EXPECT_EQ(header->structSize, sizeof(AcCompileResourceBundle));
401+ EXPECT_EQ(header->flags, 0U);
402+ 
403+ const auto* bundle = reinterpret_cast<const AcCompileResourceBundle*>(header);
404+ ASSERT_EQ(bundle->manifestCount, 2U);
405+ EXPECT_EQ(bundle->extensions, nullptr);
406+ EXPECT_EQ(bundle->extensionCount, 0U);
407+ 
408+ uint64_t totalFiles = 0U;
409+ bool foundNestedHeader = false;
410+ bool foundEmptyFile = false;
411+ bool foundFirstSource = false;
412+ for (uint64_t manifestIndex = 0U; manifestIndex < bundle->manifestCount; ++manifestIndex) {
413+ const AcCompileResourceManifest& manifest = bundle->manifests[manifestIndex];
414+ totalFiles += manifest.fileCount;
415+ for (uint64_t fileIndex = 0U; fileIndex < manifest.fileCount; ++fileIndex) {
416+ const AcCompileResourceFile& file = manifest.files[fileIndex];
417+ const std::string path = ToString(file.filePath);
418+ foundNestedHeader = foundNestedHeader || path == "resources/include/nested.h";
419+ foundEmptyFile = foundEmptyFile || path == "resources/empty.bin";
420+ foundFirstSource = foundFirstSource ||
421+ (path == "resources/kernel.cpp" &&
422+ std::string(reinterpret_cast<const char*>(file.data), file.size) == "first source");
423+ }
424+ }
425+ EXPECT_EQ(totalFiles, 3U);
426+ EXPECT_TRUE(foundNestedHeader);
427+ EXPECT_FALSE(foundEmptyFile);
428+ EXPECT_TRUE(foundFirstSource);
429+ EXPECT_EQ(dlclose(handle), 0);
430+}
431+ 
432+TEST_F(AscCompileExporterTest, BuildsRelocatableObjectThatCanBeLinkedIntoSharedLibrary)
433+{
434+ const std::string unit = JoinTestPath(root_, "collection/Relocatable");
435+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "relocatable source");
436+ CreateManifest(unit, "Relocatable");
437+ 
438+ BuildCollectedBundleRequest request = Request();
439+ request.outputPath = JoinTestPath(root_, "output/bundle.o");
440+ request.outputKind = BundleOutputKind::kRelocatableObject;
441+ g_renameErrors = {EXDEV};
442+ ASSERT_TRUE(ManifestBundleCompiler(request).Compile());
443+ ASSERT_TRUE(IsTestRegularFile(request.outputPath));
444+ 
445+ uint16_t elfType = ET_NONE;
446+ ASSERT_TRUE(ReadElfType(request.outputPath, elfType));
447+ EXPECT_EQ(elfType, ET_REL);
448+ 
449+ const std::string linkedBundle = JoinTestPath(root_, "output/linked-bundle.so");
450+ ASSERT_TRUE(ProcessRunner::Run({request.cxxCompiler, "-shared", request.outputPath, "-o", linkedBundle}, {}));
451+ 
452+ void* handle = dlopen(linkedBundle.c_str(), RTLD_NOW | RTLD_LOCAL);
453+ const char* loadError = handle == nullptr ? dlerror() : nullptr;
454+ ASSERT_NE(handle, nullptr) << (loadError == nullptr ? "dlopen failed" : loadError);
455+ auto getter = reinterpret_cast<AscendcGetCompileResourceBundleFn>(dlsym(handle, "AscendcGetCompileResourceBundle"));
456+ ASSERT_NE(getter, nullptr);
457+ const auto* bundle = reinterpret_cast<const AcCompileResourceBundle*>(getter());
458+ ASSERT_NE(bundle, nullptr);
459+ ASSERT_EQ(bundle->manifestCount, 1U);
460+ ASSERT_EQ(bundle->manifests[0].fileCount, 1U);
461+ EXPECT_EQ(ToString(bundle->manifests[0].files[0].filePath), "resources/kernel.cpp");
462+ EXPECT_EQ(
463+ std::string(
464+ reinterpret_cast<const char*>(bundle->manifests[0].files[0].data), bundle->manifests[0].files[0].size),
465+ "relocatable source");
466+ EXPECT_EQ(dlclose(handle), 0);
467+}
468+ 
469+TEST_F(AscCompileExporterTest, KeepsTemporaryWorkspaceWhenRequested)
470+{
471+ ScopedEnvironment temporary("TMPDIR", root_.c_str());
472+ const std::string unit = JoinTestPath(root_, "collection/Keep");
473+ WriteFile(JoinTestPath(unit, "resources/kernel.cpp"), "source");
474+ CreateManifest(unit, "Keep");
475+ 
476+ BuildCollectedBundleRequest request = Request();
477+ request.keepTemp = true;
478+ ASSERT_TRUE(ManifestBundleCompiler(request).Compile());
479+ 
480+ const std::vector<std::string> workEntries = ListTestDirectory(root_);
481+ size_t workspaceCount = 0U;
482+ for (size_t index = 0U; index < workEntries.size(); ++index) {
483+ workspaceCount += workEntries[index].find(".manifest-generator-") == 0U ? 1U : 0U;
484+ }
485+ EXPECT_EQ(workspaceCount, 1U);
486+ EXPECT_TRUE(IsTestRegularFile(request.outputPath));
487+}
488+ 
489+} // namespace
@@ -0,0 +1,269 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include <sstream>
14+#include <string>
15+#include <vector>
16+ 
17+#include "file_utils.h"
18+#include "test_support.h"
19+ 
20+#define main AscCompileExporterProgramMain
21+#include "asc_compile_exporter.cpp"
22+#undef main
23+ 
24+namespace {
25+ 
26+using asc_compile_exporter_test::ModuleTest;
27+using asc_compile_exporter_test::WriteTestFile;
28+using ascendc::FileUtils;
29+using ascendc::manifest_generator::BuildCollectedBundleRequest;
30+using ascendc::manifest_generator::BundleOutputKind;
31+ 
32+class CerrCapture final {
33+public:
34+ CerrCapture() : previous_(std::cerr.rdbuf(stream_.rdbuf())) {}
35+ 
36+ ~CerrCapture() { std::cerr.rdbuf(previous_); }
37+ 
38+ std::string Content() const { return stream_.str(); }
39+ 
40+ CerrCapture(const CerrCapture&) = delete;
41+ CerrCapture& operator=(const CerrCapture&) = delete;
42+ 
43+private:
44+ std::ostringstream stream_;
45+ std::streambuf* previous_;
46+};
47+ 
48+bool ParseCommand(std::vector<std::string> arguments, BuildCollectedBundleRequest& request)
49+{
50+ std::vector<char*> argv;
51+ argv.reserve(arguments.size());
52+ for (std::string& argument : arguments) {
53+ argv.push_back(const_cast<char*>(argument.c_str()));
54+ }
55+ return ParseArguments(static_cast<int>(argv.size()), argv.data(), request);
56+}
57+ 
58+int RunCommand(std::vector<std::string> arguments)
59+{
60+ std::vector<char*> argv;
61+ argv.reserve(arguments.size());
62+ for (std::string& argument : arguments) {
63+ argv.push_back(const_cast<char*>(argument.c_str()));
64+ }
65+ return ::Run(static_cast<int>(argv.size()), argv.data());
66+}
67+ 
68+int RunProgramCommand(std::vector<std::string> arguments)
69+{
70+ std::vector<char*> argv;
71+ argv.reserve(arguments.size());
72+ for (std::string& argument : arguments) {
73+ argv.push_back(const_cast<char*>(argument.c_str()));
74+ }
75+ return AscCompileExporterProgramMain(static_cast<int>(argv.size()), argv.data());
76+}
77+ 
78+TEST(CompileExporterCliUnitTest, ParseJobsAcceptsOnlyPositiveUint32Values)
79+{
80+ uint32_t jobs = 99U;
81+ EXPECT_TRUE(ParseJobs("1", jobs));
82+ EXPECT_EQ(jobs, 1U);
83+ EXPECT_TRUE(ParseJobs("4294967295", jobs));
84+ EXPECT_EQ(jobs, 4294967295U);
85+ 
86+ EXPECT_FALSE(ParseJobs("0", jobs));
87+ EXPECT_FALSE(ParseJobs("-1", jobs));
88+ EXPECT_FALSE(ParseJobs("4jobs", jobs));
89+ EXPECT_FALSE(ParseJobs("4294967296", jobs));
90+ EXPECT_FALSE(ParseJobs("", jobs));
91+ EXPECT_FALSE(ParseJobs(std::string(128U, '9'), jobs));
92+ EXPECT_EQ(jobs, 4294967295U);
93+}
94+ 
95+TEST(CompileExporterCliUnitTest, ResolveJobCountUsesCpuLimitAndDefault)
96+{
97+ const uint32_t reportedCpuCount = static_cast<uint32_t>(std::thread::hardware_concurrency());
98+ const uint32_t cpuCount = reportedCpuCount == 0U ? 1U : reportedCpuCount;
99+ EXPECT_EQ(ResolveJobCount(UINT32_MAX), cpuCount);
100+ EXPECT_EQ(ResolveJobCount(0U), cpuCount < 2U ? 1U : cpuCount / 2U);
101+}
102+ 
103+TEST_F(ModuleTest, CliParsesKeywordArgumentsInAnyOrder)
104+{
105+ const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
106+ const std::string output = FileUtils::JoinPath(root_, "output/bundle.so");
107+ ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));
108+ 
109+ BuildCollectedBundleRequest request;
110+ ASSERT_TRUE(ParseCommand(
111+ {"asc_compile_exporter", "--cxx", "/usr/bin/c++", "--save-temp-files", "--output", output, "--jobs", "3",
112+ "--input-dir", manifestRoot, "--make", "/usr/bin/make"},
113+ request));
114+ 
115+ std::string resolvedManifestRoot;
116+ ASSERT_TRUE(FileUtils::ResolveDirectory(manifestRoot, resolvedManifestRoot));
117+ std::string resolvedOutputParent;
118+ ASSERT_TRUE(FileUtils::ResolveDirectory(FileUtils::ParentPath(output), resolvedOutputParent));
119+ EXPECT_EQ(request.manifestSearchRoot, resolvedManifestRoot);
120+ EXPECT_EQ(request.outputPath, FileUtils::JoinPath(resolvedOutputParent, "bundle.so"));
121+ EXPECT_EQ(request.outputKind, BundleOutputKind::kSharedObject);
122+ EXPECT_EQ(request.makeExecutable, "/usr/bin/make");
123+ EXPECT_EQ(request.cxxCompiler, "/usr/bin/c++");
124+ EXPECT_TRUE(request.keepTemp);
125+ EXPECT_EQ(request.jobs, ResolveJobCount(3U));
126+}
127+ 
128+TEST_F(ModuleTest, CliUsesDefaultValues)
129+{
130+ const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
131+ const std::string output = FileUtils::JoinPath(root_, "output/bundle.o");
132+ ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));
133+ BuildCollectedBundleRequest request;
134+ ASSERT_TRUE(ParseCommand(
135+ {"asc_compile_exporter", "--input-dir", manifestRoot, "--output", output, "--make", "/usr/bin/make", "--cxx",
136+ "/usr/bin/c++"},
137+ request));
138+ std::string resolvedOutputParent;
139+ ASSERT_TRUE(FileUtils::ResolveDirectory(FileUtils::ParentPath(output), resolvedOutputParent));
140+ EXPECT_EQ(request.outputPath, FileUtils::JoinPath(resolvedOutputParent, "bundle.o"));
141+ EXPECT_EQ(request.outputKind, BundleOutputKind::kRelocatableObject);
142+ EXPECT_FALSE(request.keepTemp);
143+ EXPECT_EQ(request.jobs, ResolveJobCount(0U));
144+}
145+ 
146+TEST_F(ModuleTest, CliRejectsUnknownPositionalAndMissingArguments)
147+{
148+ CerrCapture capture;
149+ BuildCollectedBundleRequest request;
150+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter"}, request));
151+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "collection"}, request));
152+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--unknown"}, request));
153+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--cxx"}, request));
154+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--cxx", "--unknown"}, request));
155+ 
156+ const std::string output = capture.Content();
157+ EXPECT_NE(output.find("Missing required argument: --input-dir"), std::string::npos);
158+ EXPECT_NE(output.find("Unable to parse argument: collection"), std::string::npos);
159+ EXPECT_NE(output.find("Unable to parse argument: --unknown"), std::string::npos);
160+ EXPECT_NE(output.find("Missing value for argument: --cxx"), std::string::npos);
161+ EXPECT_NE(output.find("Missing value for argument --cxx: --unknown"), std::string::npos);
162+}
163+ 
164+TEST_F(ModuleTest, CliRejectsDuplicateArguments)
165+{
166+ CerrCapture capture;
167+ const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
168+ ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));
169+ BuildCollectedBundleRequest request;
170+ EXPECT_FALSE(
171+ ParseCommand({"asc_compile_exporter", "--input-dir", manifestRoot, "--input-dir", manifestRoot}, request));
172+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--save-temp-files", "--save-temp-files"}, request));
173+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--jobs", "1", "--jobs", "2"}, request));
174+ 
175+ const std::string output = capture.Content();
176+ EXPECT_NE(output.find("Duplicate argument: --input-dir"), std::string::npos);
177+ EXPECT_NE(output.find("Duplicate argument: --save-temp-files"), std::string::npos);
178+ EXPECT_NE(output.find("Duplicate argument: --jobs"), std::string::npos);
179+}
180+ 
181+TEST_F(ModuleTest, CliValidatesArgumentsWhenRead)
182+{
183+ CerrCapture capture;
184+ BuildCollectedBundleRequest request;
185+ const std::string missingManifestRoot = FileUtils::JoinPath(root_, "missing");
186+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--input-dir", missingManifestRoot}, request));
187+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--output", "bundle.so"}, request));
188+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--output", FileUtils::JoinPath(root_, "output/")}, request));
189+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--output", "/dev/null/bundle.so"}, request));
190+ const std::string outputDirectory = FileUtils::JoinPath(root_, "real-output");
191+ const std::string outputDirectoryLink = FileUtils::JoinPath(root_, "output-link");
192+ ASSERT_TRUE(FileUtils::CreateDirectories(outputDirectory));
193+ ASSERT_EQ(symlink(outputDirectory.c_str(), outputDirectoryLink.c_str()), 0);
194+ EXPECT_FALSE(ParseCommand(
195+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(outputDirectoryLink, "bundle.so")}, request));
196+ const std::string unsupportedOutputParent = FileUtils::JoinPath(root_, "unsupported-output");
197+ EXPECT_FALSE(ParseCommand(
198+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(unsupportedOutputParent, "bundle")}, request));
199+ EXPECT_FALSE(ParseCommand(
200+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(unsupportedOutputParent, "bundle.a")}, request));
201+ EXPECT_FALSE(ParseCommand(
202+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(unsupportedOutputParent, "bundle.obj")}, request));
203+ EXPECT_FALSE(ParseCommand(
204+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(unsupportedOutputParent, "bundle.so.1")}, request));
205+ EXPECT_FALSE(ParseCommand(
206+ {"asc_compile_exporter", "--output", FileUtils::JoinPath(unsupportedOutputParent, "bundle.SO")}, request));
207+ EXPECT_FALSE(FileUtils::IsDirectory(unsupportedOutputParent));
208+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--make", ""}, request));
209+ EXPECT_FALSE(ParseCommand({"asc_compile_exporter", "--jobs", "0"}, request));
210+ 
211+ const std::string output = capture.Content();
212+ EXPECT_NE(output.find(missingManifestRoot), std::string::npos);
213+ EXPECT_NE(output.find("Invalid value for argument --output: bundle.so"), std::string::npos);
214+ EXPECT_NE(output.find(FileUtils::JoinPath(root_, "output/")), std::string::npos);
215+ EXPECT_NE(output.find("/dev/null/bundle.so"), std::string::npos);
216+ EXPECT_NE(output.find(FileUtils::JoinPath(outputDirectoryLink, "bundle.so")), std::string::npos);
217+ EXPECT_NE(output.find("Unsupported output suffix for argument --output (expected .so or .o)"), std::string::npos);
218+ EXPECT_NE(output.find("Invalid value for argument: --make"), std::string::npos);
219+ EXPECT_NE(output.find("Invalid value for argument --jobs: 0"), std::string::npos);
220+}
221+ 
222+TEST_F(ModuleTest, RunPassesValidatedArgumentsToCompiler)
223+{
224+ const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
225+ const std::string output = FileUtils::JoinPath(root_, "output/bundle.so");
226+ ASSERT_TRUE(FileUtils::CreateDirectories(manifestRoot));
227+ EXPECT_EQ(
228+ RunCommand(
229+ {"asc_compile_exporter", "--input-dir", manifestRoot, "--output", output, "--make", "/usr/bin/make",
230+ "--cxx", "/usr/bin/c++"}),
231+ 1);
232+ EXPECT_TRUE(FileUtils::IsDirectory(FileUtils::ParentPath(output)));
233+}
234+ 
235+TEST_F(ModuleTest, RunBuildsBundleWithKeywordArguments)
236+{
237+ const std::string manifestRoot = FileUtils::JoinPath(root_, "collection");
238+ const std::string unit = FileUtils::JoinPath(manifestRoot, "Unit");
239+ const std::string output = FileUtils::JoinPath(root_, "output/bundle.so");
240+ const std::string objectOutput = FileUtils::JoinPath(root_, "output/bundle.o");
241+ WriteTestFile(FileUtils::JoinPath(unit, "resources/kernel.cpp"), "source");
242+ CreateManifest(unit, "Unit");
243+ 
244+ EXPECT_EQ(
245+ RunCommand(
246+ {"asc_compile_exporter", "--output", output, "--make", "/usr/bin/make", "--input-dir", manifestRoot,
247+ "--cxx", "/usr/bin/c++", "--jobs", "1"}),
248+ 0);
249+ EXPECT_TRUE(FileUtils::IsRegularFile(output));
250+ 
251+ EXPECT_EQ(
252+ RunCommand(
253+ {"asc_compile_exporter", "--output", objectOutput, "--make", "/usr/bin/make", "--input-dir", manifestRoot,
254+ "--cxx", "/usr/bin/c++", "--jobs", "1"}),
255+ 0);
256+ EXPECT_TRUE(FileUtils::IsRegularFile(objectOutput));
257+}
258+ 
259+TEST(CompileExporterCliUnitTest, ProgramMainReturnsRunStatusAndPrintsArgument)
260+{
261+ CerrCapture capture;
262+ EXPECT_EQ(RunProgramCommand({"asc_compile_exporter", "--unknown"}), 1);
263+ EXPECT_NE(capture.Content().find("Unable to parse argument: --unknown"), std::string::npos);
264+ EXPECT_NE(capture.Content().find("Usage:"), std::string::npos);
265+ EXPECT_NE(capture.Content().find("Required arguments:"), std::string::npos);
266+ EXPECT_NE(capture.Content().find("Optional arguments:"), std::string::npos);
267+}
268+ 
269+} // namespace
@@ -0,0 +1,29 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include "ascendc_tool_log.h"
14+ 
15+namespace ascendc {
16+namespace {
17+ 
18+TEST(AscendcToolLogTest, UnitTestLoggingMacrosCompile)
19+{
20+ EXPECT_NO_THROW({
21+ ASCENDLOGE("error value=%d", 1);
22+ ASCENDLOGW("warning value=%d", 2);
23+ ASCENDLOGI("info value=%d", 3);
24+ ASCENDLOGD("debug value=%d", 4);
25+ });
26+}
27+ 
28+} // namespace
29+} // namespace ascendc
@@ -0,0 +1,70 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include "build_workspace.h"
14+#include "file_utils.h"
15+#include "test_support.h"
16+ 
17+namespace ascendc {
18+namespace manifest_generator {
19+namespace {
20+ 
21+using asc_compile_exporter_test::LstatPathExists;
22+using asc_compile_exporter_test::ModuleTest;
23+using asc_compile_exporter_test::ScopedEnvironment;
24+using asc_compile_exporter_test::WriteTestFile;
25+ 
26+TEST_F(ModuleTest, BuildWorkspaceHonorsCleanupPolicy)
27+{
28+ const std::string parent = FileUtils::JoinPath(root_, "sessions");
29+ std::string removedSession;
30+ {
31+ BuildWorkspace workspace;
32+ ASSERT_TRUE(workspace.Create(parent, false));
33+ removedSession = workspace.Session();
34+ EXPECT_TRUE(FileUtils::IsDirectory(removedSession));
35+ EXPECT_FALSE(workspace.Create(parent, false));
36+ EXPECT_EQ(workspace.Session(), removedSession);
37+ }
38+ EXPECT_FALSE(LstatPathExists(removedSession));
39+ 
40+ std::string keptSession;
41+ {
42+ BuildWorkspace workspace;
43+ ASSERT_TRUE(workspace.Create(parent, true));
44+ keptSession = workspace.Session();
45+ }
46+ EXPECT_TRUE(FileUtils::IsDirectory(keptSession));
47+ 
48+ const std::string regularFile = FileUtils::JoinPath(root_, "not-a-directory");
49+ WriteTestFile(regularFile, "file");
50+ BuildWorkspace failedWorkspace;
51+ EXPECT_FALSE(failedWorkspace.Create(regularFile, false));
52+ EXPECT_FALSE(failedWorkspace.Create("/proc", false));
53+}
54+ 
55+TEST_F(ModuleTest, BuildWorkspaceUsesTmpDir)
56+{
57+ ScopedEnvironment temporary("TMPDIR", root_.c_str());
58+ std::string session;
59+ {
60+ BuildWorkspace workspace;
61+ ASSERT_TRUE(workspace.Create("", false));
62+ session = workspace.Session();
63+ EXPECT_TRUE(FileUtils::IsPathWithin(session, root_));
64+ }
65+ EXPECT_FALSE(LstatPathExists(session));
66+}
67+ 
68+} // namespace
69+} // namespace manifest_generator
70+} // namespace ascendc
@@ -0,0 +1,106 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include "bundle_source_generator.h"
14+#include "file_utils.h"
15+#include "test_support.h"
16+ 
17+namespace ascendc {
18+namespace manifest_generator {
19+namespace {
20+ 
21+using asc_compile_exporter_test::ModuleTest;
22+using asc_compile_exporter_test::ReadTestFile;
23+ 
24+TEST_F(ModuleTest, SourceGeneratorEmitsBundleManifestAbiAndMakefile)
25+{
26+ const std::string work = FileUtils::JoinPath(root_, "work");
27+ ASSERT_TRUE(FileUtils::CreateDirectories(work));
28+ const std::vector<ManifestUnit> units = {
29+ {"{\"base_dir\":\"resources\"}", {{"kernel.bin", "resources/kernel.bin", {0U, 127U, 255U}}}}};
30+ 
31+ ASSERT_TRUE(BundleSourceGenerator(work, units, BundleOutputKind::kSharedObject).Generate());
32+ 
33+ const std::string abiHeader = ReadTestFile(FileUtils::JoinPath(work, "ascendc_manifest_abi.h"));
34+ const std::string bundleSource = ReadTestFile(FileUtils::JoinPath(work, "compile_resource_bundle.cpp"));
35+ const std::string manifestSource = ReadTestFile(FileUtils::JoinPath(work, "manifest_0.cpp"));
36+ const std::string makefile = ReadTestFile(FileUtils::JoinPath(work, "Makefile"));
37+ EXPECT_NE(abiHeader.find("AC_COMPILE_RESOURCE_MAGIC"), std::string::npos);
38+ EXPECT_NE(abiHeader.find("typedef struct AcCompileResourceBundle"), std::string::npos);
39+ EXPECT_NE(abiHeader.find("AscendcGetCompileResourceBundle(void)"), std::string::npos);
40+ EXPECT_NE(bundleSource.find("#include \"ascendc_manifest_abi.h\""), std::string::npos);
41+ EXPECT_NE(bundleSource.find("GetManifest0()"), std::string::npos);
42+ EXPECT_NE(bundleSource.find("1ULL, nullptr, 0ULL"), std::string::npos);
43+ EXPECT_NE(manifestSource.find("#include \"ascendc_manifest_abi.h\""), std::string::npos);
44+ EXPECT_NE(manifestSource.find("0, 127, 255"), std::string::npos);
45+ EXPECT_NE(manifestSource.find("g_manifest_0_file_0_path_bytes"), std::string::npos);
46+ EXPECT_NE(manifestSource.find("g_manifest_0_file_0_path ="), std::string::npos);
47+ EXPECT_NE(manifestSource.find("20ULL"), std::string::npos);
48+ EXPECT_NE(makefile.find("manifest_0.cpp"), std::string::npos);
49+ EXPECT_EQ(makefile.find("ASCENDC_BUNDLE_INCLUDE_DIR"), std::string::npos);
50+ EXPECT_NE(makefile.find("-std=c++11"), std::string::npos);
51+ EXPECT_NE(makefile.find("-shared $(OBJECTS)"), std::string::npos);
52+ EXPECT_NE(makefile.find("-Wl,-z,relro,-z,now,-z,noexecstack"), std::string::npos);
53+ EXPECT_TRUE(BundleSourceGenerator(work, units, BundleOutputKind::kSharedObject).Generate());
54+}
55+ 
56+TEST_F(ModuleTest, SourceGeneratorEmitsRelocatableObjectMakefile)
57+{
58+ const std::string work = FileUtils::JoinPath(root_, "relocatable-object-work");
59+ ASSERT_TRUE(FileUtils::CreateDirectories(work));
60+ const std::vector<ManifestUnit> units = {{"{\"base_dir\":\"resources\"}", {}}};
61+ 
62+ ASSERT_TRUE(BundleSourceGenerator(work, units, BundleOutputKind::kRelocatableObject).Generate());
63+ 
64+ const std::string makefile = ReadTestFile(FileUtils::JoinPath(work, "Makefile"));
65+ EXPECT_NE(makefile.find("-r $(OBJECTS)"), std::string::npos);
66+ EXPECT_EQ(makefile.find("-shared"), std::string::npos);
67+ EXPECT_EQ(makefile.find("-Wl,-z,relro,-z,now,-z,noexecstack"), std::string::npos);
68+ EXPECT_NE(makefile.find("-std=c++11 -fPIC -fvisibility=hidden"), std::string::npos);
69+}
70+ 
71+TEST_F(ModuleTest, SourceGeneratorEmitsManifestWithoutResourceFiles)
72+{
73+ const std::string work = FileUtils::JoinPath(root_, "empty-resource-work");
74+ ASSERT_TRUE(FileUtils::CreateDirectories(work));
75+ const std::vector<ManifestUnit> units = {{"{\"base_dir\":\"resources\"}", {}}};
76+ 
77+ ASSERT_TRUE(BundleSourceGenerator(work, units, BundleOutputKind::kSharedObject).Generate());
78+ 
79+ const std::string manifestSource = ReadTestFile(FileUtils::JoinPath(work, "manifest_0.cpp"));
80+ EXPECT_NE(manifestSource.find("nullptr, 0ULL"), std::string::npos);
81+ EXPECT_EQ(manifestSource.find("AcCompileResourceFile g_manifest_0_files"), std::string::npos);
82+}
83+ 
84+TEST_F(ModuleTest, SourceGeneratorReportsFailureAtEachGenerationStage)
85+{
86+ const std::vector<ManifestUnit> units = {{"{\"base_dir\":\"resources\"}", {}}};
87+ 
88+ const std::string bundleWork = FileUtils::JoinPath(root_, "bundle-failure-work");
89+ ASSERT_TRUE(FileUtils::CreateDirectories(bundleWork));
90+ ASSERT_TRUE(FileUtils::CreateDirectories(FileUtils::JoinPath(bundleWork, "compile_resource_bundle.cpp")));
91+ EXPECT_FALSE(BundleSourceGenerator(bundleWork, units, BundleOutputKind::kSharedObject).Generate());
92+ 
93+ const std::string manifestWork = FileUtils::JoinPath(root_, "manifest-failure-work");
94+ ASSERT_TRUE(FileUtils::CreateDirectories(manifestWork));
95+ ASSERT_TRUE(FileUtils::CreateDirectories(FileUtils::JoinPath(manifestWork, "manifest_0.cpp")));
96+ EXPECT_FALSE(BundleSourceGenerator(manifestWork, units, BundleOutputKind::kSharedObject).Generate());
97+ 
98+ const std::string makefileWork = FileUtils::JoinPath(root_, "makefile-failure-work");
99+ ASSERT_TRUE(FileUtils::CreateDirectories(makefileWork));
100+ ASSERT_TRUE(FileUtils::CreateDirectories(FileUtils::JoinPath(makefileWork, "Makefile")));
101+ EXPECT_FALSE(BundleSourceGenerator(makefileWork, units, BundleOutputKind::kSharedObject).Generate());
102+}
103+ 
104+} // namespace
105+} // namespace manifest_generator
106+} // namespace ascendc
@@ -0,0 +1,135 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include <string>
14+#include <vector>
15+ 
16+#include <sys/stat.h>
17+#include <unistd.h>
18+ 
19+#include "collected_manifest_repository.h"
20+#include "file_utils.h"
21+#include "test_support.h"
22+ 
23+namespace ascendc {
24+namespace manifest_generator {
25+namespace {
26+ 
27+constexpr size_t TEST_MAX_DIRECTORY_DEPTH = 64U;
28+ 
29+using asc_compile_exporter_test::ModuleTest;
30+using asc_compile_exporter_test::WriteTestFile;
31+ 
32+TEST_F(ModuleTest, RepositorySortsManifestsAndResourcesAndSkipsEmptyFiles)
33+{
34+ const std::string first = FileUtils::JoinPath(root_, "collection/z/First");
35+ const std::string second = FileUtils::JoinPath(root_, "collection/a/Second");
36+ WriteTestFile(FileUtils::JoinPath(first, "resources/z.cpp"), "z");
37+ WriteTestFile(FileUtils::JoinPath(first, "resources/a.cpp"), "a");
38+ WriteTestFile(FileUtils::JoinPath(first, "resources/empty.cpp"), "");
39+ WriteTestFile(FileUtils::JoinPath(second, "resources/kernel.cpp"), "second");
40+ CreateManifest(first, "First");
41+ CreateManifest(second, "Second");
42+ WriteTestFile(FileUtils::JoinPath(root_, "collection/ignored.json"), "not a manifest");
43+ 
44+ std::vector<ManifestUnit> units;
45+ ASSERT_TRUE(CollectedManifestRepository(FileUtils::JoinPath(root_, "collection")).Load(units));
46+ 
47+ ASSERT_EQ(units.size(), 2U);
48+ EXPECT_NE(units[0].json.find("Second"), std::string::npos);
49+ EXPECT_NE(units[1].json.find("First"), std::string::npos);
50+ ASSERT_EQ(units[1].files.size(), 2U);
51+ EXPECT_EQ(units[1].files[0].fileName, "a.cpp");
52+ EXPECT_EQ(units[1].files[0].filePath, "resources/a.cpp");
53+ EXPECT_EQ(units[1].files[1].filePath, "resources/z.cpp");
54+ EXPECT_EQ(std::string(units[1].files[0].data.begin(), units[1].files[0].data.end()), "a");
55+}
56+ 
57+TEST_F(ModuleTest, RepositoryReportsManifestJsonAndBaseDirErrors)
58+{
59+ const std::string malformed = FileUtils::JoinPath(root_, "malformed/Unit");
60+ WriteTestFile(FileUtils::JoinPath(malformed, "resources/kernel.cpp"), "source");
61+ WriteTestFile(FileUtils::JoinPath(malformed, "Unit_manifest.json"), "{\"base_dir\":");
62+ std::vector<ManifestUnit> units;
63+ EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "malformed")).Load(units));
64+ 
65+ const std::string missing = FileUtils::JoinPath(root_, "missing-base/Unit");
66+ WriteTestFile(FileUtils::JoinPath(missing, "resources/kernel.cpp"), "source");
67+ WriteTestFile(FileUtils::JoinPath(missing, "Unit_manifest.json"), "{}");
68+ EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "missing-base")).Load(units));
69+ 
70+ const std::string wrongType = FileUtils::JoinPath(root_, "wrong-type/Unit");
71+ WriteTestFile(FileUtils::JoinPath(wrongType, "resources/kernel.cpp"), "source");
72+ WriteTestFile(FileUtils::JoinPath(wrongType, "Unit_manifest.json"), "{\"base_dir\":7}");
73+ EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "wrong-type")).Load(units));
74+}
75+ 
76+TEST_F(ModuleTest, RepositorySkipsSymlinkedManifestsAndAcceptsAbsoluteBaseDir)
77+{
78+ const std::string collection = FileUtils::JoinPath(root_, "collection-absolute");
79+ const std::string unit = FileUtils::JoinPath(collection, "Unit");
80+ const std::string resources = FileUtils::JoinPath(unit, "resources");
81+ WriteTestFile(FileUtils::JoinPath(resources, "kernel.cpp"), "source");
82+ WriteTestFile(FileUtils::JoinPath(unit, "Unit_manifest.json"), "{\"base_dir\":\"" + resources + "\"}");
83+ 
84+ const std::string ignoredManifest = FileUtils::JoinPath(root_, "ignored_manifest.json");
85+ WriteTestFile(ignoredManifest, "{\"base_dir\":\"resources\"}");
86+ ASSERT_EQ(symlink(ignoredManifest.c_str(), FileUtils::JoinPath(collection, "Link_manifest.json").c_str()), 0);
87+ 
88+ std::vector<ManifestUnit> units;
89+ ASSERT_TRUE(CollectedManifestRepository(collection).Load(units));
90+ ASSERT_EQ(units.size(), 1U);
91+ ASSERT_EQ(units[0].files.size(), 1U);
92+ EXPECT_EQ(units[0].files[0].filePath, "resources/kernel.cpp");
93+}
94+ 
95+TEST_F(ModuleTest, RepositoryRejectsNonRegularResource)
96+{
97+ const std::string unit = FileUtils::JoinPath(root_, "collection-special/Unit");
98+ const std::string resources = FileUtils::JoinPath(unit, "resources");
99+ ASSERT_TRUE(FileUtils::CreateDirectories(resources));
100+ ASSERT_EQ(mkfifo(FileUtils::JoinPath(resources, "resource.pipe").c_str(), 0600), 0);
101+ CreateManifest(unit, "Unit");
102+ 
103+ std::vector<ManifestUnit> units;
104+ EXPECT_FALSE(CollectedManifestRepository(FileUtils::JoinPath(root_, "collection-special")).Load(units));
105+}
106+ 
107+TEST_F(ModuleTest, RepositoryEnforcesDirectoryDepthLimit)
108+{
109+ const std::string allowedCollection = FileUtils::JoinPath(root_, "collection-depth-allowed");
110+ std::string allowedDirectory = allowedCollection;
111+ for (size_t depth = 0U; depth < TEST_MAX_DIRECTORY_DEPTH; ++depth) {
112+ allowedDirectory = FileUtils::JoinPath(allowedDirectory, "level-" + std::to_string(depth));
113+ }
114+ WriteTestFile(FileUtils::JoinPath(allowedDirectory, "Deep_manifest.json"), "{\"base_dir\":\".\"}");
115+ std::vector<ManifestUnit> units;
116+ EXPECT_TRUE(CollectedManifestRepository(allowedCollection).Load(units));
117+ 
118+ const std::string rejectedCollection = FileUtils::JoinPath(root_, "collection-depth-rejected");
119+ std::string rejectedDirectory = rejectedCollection;
120+ for (size_t depth = 0U; depth <= TEST_MAX_DIRECTORY_DEPTH; ++depth) {
121+ rejectedDirectory = FileUtils::JoinPath(rejectedDirectory, "level-" + std::to_string(depth));
122+ }
123+ ASSERT_TRUE(FileUtils::CreateDirectories(rejectedDirectory));
124+ EXPECT_FALSE(CollectedManifestRepository(rejectedCollection).Load(units));
125+}
126+ 
127+TEST_F(ModuleTest, RepositoryReportsDirectoryOpenFailure)
128+{
129+ std::vector<ManifestUnit> units;
130+ EXPECT_FALSE(CollectedManifestRepository("/dev/null").Load(units));
131+}
132+ 
133+} // namespace
134+} // namespace manifest_generator
135+} // namespace ascendc
@@ -0,0 +1,159 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include <fstream>
14+#include <string>
15+#include <vector>
16+ 
17+#include <unistd.h>
18+ 
19+#include "file_utils.h"
20+#include "test_support.h"
21+ 
22+namespace ascendc {
23+namespace {
24+ 
25+using asc_compile_exporter_test::LstatPathExists;
26+using asc_compile_exporter_test::ModuleTest;
27+using asc_compile_exporter_test::ReadTestFile;
28+using asc_compile_exporter_test::WriteTestFile;
29+ 
30+TEST_F(ModuleTest, FileUtilsBuildsAndComparesPaths)
31+{
32+ EXPECT_EQ(FileUtils::JoinPath("", "right"), "right");
33+ EXPECT_EQ(FileUtils::JoinPath("left", ""), "left");
34+ EXPECT_EQ(FileUtils::JoinPath("/left", "right"), "/left/right");
35+ EXPECT_EQ(FileUtils::ParentPath("/one/two.txt"), "/one");
36+ EXPECT_EQ(FileUtils::ParentPath("single"), "");
37+ EXPECT_EQ(FileUtils::FileName("/one/two.txt"), "two.txt");
38+ 
39+ std::string absolute;
40+ ASSERT_TRUE(FileUtils::MakeAbsolutePath("relative/path", absolute));
41+ EXPECT_FALSE(absolute.empty());
42+ EXPECT_EQ(absolute[0], '/');
43+ 
44+ EXPECT_TRUE(FileUtils::IsPathWithin("/root/child", "/root"));
45+ EXPECT_TRUE(FileUtils::IsPathWithin("/root", "/root"));
46+ EXPECT_TRUE(FileUtils::IsPathWithin("/root/nested/../child", "/root"));
47+ EXPECT_TRUE(FileUtils::IsPathWithin("/child", "/"));
48+ EXPECT_FALSE(FileUtils::IsPathWithin("/rooted", "/root"));
49+ EXPECT_FALSE(FileUtils::IsPathWithin("/root/../outside", "/root"));
50+ EXPECT_FALSE(FileUtils::IsPathWithin("relative", "/"));
51+ EXPECT_FALSE(FileUtils::IsPathWithin("", "/"));
52+}
53+ 
54+TEST_F(ModuleTest, FileUtilsInspectsAndResolvesPaths)
55+{
56+ const std::string directory = FileUtils::JoinPath(root_, "directory/nested");
57+ const std::string regular = FileUtils::JoinPath(directory, "regular.bin");
58+ const std::string link = FileUtils::JoinPath(root_, "directory-link");
59+ ASSERT_TRUE(FileUtils::CreateDirectories(directory));
60+ WriteTestFile(regular, "data");
61+ ASSERT_EQ(symlink(directory.c_str(), link.c_str()), 0);
62+ 
63+ EXPECT_TRUE(FileUtils::PathExists(regular));
64+ EXPECT_TRUE(FileUtils::IsDirectory(directory));
65+ EXPECT_TRUE(FileUtils::IsRegularFile(regular));
66+ EXPECT_TRUE(FileUtils::IsSymlink(link));
67+ EXPECT_FALSE(FileUtils::IsSymlink(regular));
68+ EXPECT_FALSE(FileUtils::PathExists(FileUtils::JoinPath(root_, "missing")));
69+ 
70+ std::string canonical;
71+ ASSERT_TRUE(FileUtils::ResolveCanonicalPath(directory, canonical));
72+ std::string resolved;
73+ EXPECT_TRUE(FileUtils::ResolveDirectory(directory, resolved));
74+ EXPECT_EQ(resolved, canonical);
75+ EXPECT_TRUE(FileUtils::ResolveSubdirectory(directory, root_, resolved));
76+ EXPECT_EQ(resolved, canonical);
77+ EXPECT_FALSE(FileUtils::ResolveCanonicalPath(FileUtils::JoinPath(root_, "missing"), resolved));
78+ EXPECT_TRUE(resolved.empty());
79+ EXPECT_FALSE(FileUtils::ResolveDirectory(regular, resolved));
80+ EXPECT_FALSE(FileUtils::ResolveDirectory(link, resolved));
81+ EXPECT_FALSE(FileUtils::ResolveDirectory(FileUtils::JoinPath(root_, "missing"), resolved));
82+ EXPECT_FALSE(FileUtils::ResolveSubdirectory("/tmp", root_, resolved));
83+}
84+ 
85+TEST_F(ModuleTest, FileUtilsCreatesAndRemovesDirectoryTrees)
86+{
87+ EXPECT_TRUE(FileUtils::CreateDirectories(""));
88+ EXPECT_TRUE(FileUtils::CreateDirectories("/"));
89+ const std::string tree = FileUtils::JoinPath(root_, "tree");
90+ const std::string nested = FileUtils::JoinPath(tree, "nested");
91+ ASSERT_TRUE(FileUtils::CreateDirectories(nested));
92+ EXPECT_TRUE(FileUtils::CreateDirectories(nested));
93+ WriteTestFile(FileUtils::JoinPath(nested, "leaf.txt"), "leaf");
94+ 
95+ const std::string blockingFile = FileUtils::JoinPath(root_, "blocking-file");
96+ WriteTestFile(blockingFile, "block");
97+ EXPECT_FALSE(FileUtils::CreateDirectories(FileUtils::JoinPath(blockingFile, "child")));
98+ 
99+ const std::string link = FileUtils::JoinPath(root_, "tree-link");
100+ ASSERT_EQ(symlink(tree.c_str(), link.c_str()), 0);
101+ EXPECT_TRUE(FileUtils::RemoveAll(link));
102+ EXPECT_TRUE(FileUtils::IsDirectory(tree));
103+ EXPECT_TRUE(FileUtils::RemoveAll(tree));
104+ EXPECT_FALSE(LstatPathExists(tree));
105+ EXPECT_TRUE(FileUtils::RemoveAll(FileUtils::JoinPath(root_, "missing")));
106+ EXPECT_TRUE(FileUtils::RemoveAll("/dev/null/child"));
107+ EXPECT_FALSE(FileUtils::RemoveAll(FileUtils::JoinPath(root_, std::string(5000U, 'x'))));
108+}
109+ 
110+TEST_F(ModuleTest, FileUtilsReadsRegularFilesWithinSizeLimit)
111+{
112+ const std::string binary = FileUtils::JoinPath(root_, "binary/data.bin");
113+ const std::string content("\0\x7f\xff", 3U);
114+ WriteTestFile(binary, content);
115+ 
116+ std::vector<uint8_t> data;
117+ ASSERT_TRUE(FileUtils::ReadRegularFile(binary, content.size(), data));
118+ EXPECT_EQ(std::string(data.begin(), data.end()), content);
119+ EXPECT_FALSE(FileUtils::ReadRegularFile(binary, content.size() - 1U, data));
120+ EXPECT_TRUE(data.empty());
121+ EXPECT_FALSE(FileUtils::ReadRegularFile(root_, 100U, data));
122+ EXPECT_FALSE(FileUtils::ReadRegularFile(FileUtils::JoinPath(root_, "missing"), 100U, data));
123+ 
124+ const std::string empty = FileUtils::JoinPath(root_, "binary/empty.bin");
125+ WriteTestFile(empty, "");
126+ EXPECT_TRUE(FileUtils::ReadRegularFile(empty, 0U, data));
127+ EXPECT_TRUE(data.empty());
128+ 
129+ const std::string link = FileUtils::JoinPath(root_, "binary-link");
130+ ASSERT_EQ(symlink(binary.c_str(), link.c_str()), 0);
131+ EXPECT_FALSE(FileUtils::ReadRegularFile(link, 100U, data));
132+}
133+ 
134+TEST_F(ModuleTest, FileUtilsFinalizesStreamsAndCopiesFiles)
135+{
136+ const std::string generated = FileUtils::JoinPath(root_, "generated.cpp");
137+ std::ofstream output(generated.c_str(), std::ios::trunc);
138+ ASSERT_TRUE(output.is_open());
139+ output << "generated";
140+ EXPECT_TRUE(FileUtils::FinalizeOutput(output));
141+ EXPECT_EQ(ReadTestFile(generated), "generated");
142+ 
143+ std::ofstream failedOutput;
144+ failedOutput.setstate(std::ios::badbit);
145+ EXPECT_FALSE(FileUtils::FinalizeOutput(failedOutput));
146+ 
147+ const std::string source = FileUtils::JoinPath(root_, "source.bin");
148+ const std::string destination = FileUtils::JoinPath(root_, "destination.bin");
149+ WriteTestFile(source, "copy data");
150+ EXPECT_TRUE(FileUtils::CopyFile(source, destination));
151+ EXPECT_EQ(ReadTestFile(destination), "copy data");
152+ EXPECT_FALSE(FileUtils::CopyFile(source, destination));
153+ EXPECT_FALSE(FileUtils::CopyFile(FileUtils::JoinPath(root_, "missing"), FileUtils::JoinPath(root_, "copy")));
154+ EXPECT_FALSE(FileUtils::CopyFile(root_, FileUtils::JoinPath(root_, "directory-copy")));
155+ EXPECT_FALSE(FileUtils::CopyFile(source, FileUtils::JoinPath(root_, "missing-parent/copy")));
156+}
157+ 
158+} // namespace
159+} // namespace ascendc
@@ -0,0 +1,33 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include "manifest_bundle_types.h"
14+ 
15+namespace ascendc {
16+namespace manifest_generator {
17+namespace {
18+ 
19+TEST(ManifestBundleTypesTest, StoresManifestAndResourceFields)
20+{
21+ const ResourceFile resource = {"kernel.cpp", "resources/kernel.cpp", {1U, 2U, 3U}};
22+ const ManifestUnit manifest = {"{\"base_dir\":\"resources\"}", {resource}};
23+ 
24+ EXPECT_EQ(resource.fileName, "kernel.cpp");
25+ EXPECT_EQ(resource.filePath, "resources/kernel.cpp");
26+ EXPECT_EQ(resource.data.size(), 3U);
27+ EXPECT_EQ(manifest.files.size(), 1U);
28+ EXPECT_EQ(manifest.files[0].fileName, resource.fileName);
29+}
30+ 
31+} // namespace
32+} // namespace manifest_generator
33+} // namespace ascendc
@@ -0,0 +1,112 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+ 
13+#include <cerrno>
14+#include <chrono>
15+#include <cstdlib>
16+ 
17+#include <signal.h>
18+#include <sys/wait.h>
19+ 
20+#include "file_utils.h"
21+#include "process_runner.h"
22+#include "test_support.h"
23+ 
24+namespace {
25+ 
26+bool g_failFork = false;
27+bool g_failNextWaitpid = false;
28+ 
29+} // namespace
30+ 
31+extern "C" pid_t __real_fork(void);
32+extern "C" pid_t __real_waitpid(pid_t pid, int* status, int options);
33+ 
34+extern "C" pid_t __wrap_fork(void)
35+{
36+ if (g_failFork) {
37+ errno = EAGAIN;
38+ return -1;
39+ }
40+ return __real_fork();
41+}
42+ 
43+extern "C" pid_t __wrap_waitpid(pid_t pid, int* status, int options)
44+{
45+ if (g_failNextWaitpid) {
46+ g_failNextWaitpid = false;
47+ errno = EIO;
48+ return -1;
49+ }
50+ return __real_waitpid(pid, status, options);
51+}
52+ 
53+namespace ascendc {
54+namespace {
55+ 
56+using asc_compile_exporter_test::ModuleTest;
57+using asc_compile_exporter_test::ReadTestFile;
58+using asc_compile_exporter_test::ScopedEnvironment;
59+ 
60+TEST_F(ModuleTest, ProcessRunnerValidatesArgumentsAndPropagatesEnvironment)
61+{
62+ EXPECT_FALSE(ProcessRunner::Run({}, {}));
63+ EXPECT_FALSE(ProcessRunner::Run({"/bin/true"}, {}, std::chrono::milliseconds(0U)));
64+ EXPECT_FALSE(ProcessRunner::Run({"/bin/sh", "-c", "exit 7"}, {}));
65+ 
66+ const std::string output = FileUtils::JoinPath(root_, "environment.txt");
67+ ScopedEnvironment makeFlags("MAKEFLAGS", "must-not-leak");
68+ ASSERT_TRUE(ProcessRunner::Run(
69+ {"/bin/sh", "-c",
70+ "printf '%s|%s' \"$ASC_COMPILE_EXPORTER_UNIT_VALUE\" \"${MAKEFLAGS-unset}\" > "
71+ "\"$ASC_COMPILE_EXPORTER_UNIT_OUTPUT\""},
72+ {{"ASC_COMPILE_EXPORTER_UNIT_VALUE", "visible"}, {"ASC_COMPILE_EXPORTER_UNIT_OUTPUT", output}}));
73+ EXPECT_EQ(ReadTestFile(output), "visible|unset");
74+}
75+ 
76+TEST_F(ModuleTest, ProcessRunnerReportsExecAndSignalFailures)
77+{
78+ g_failFork = true;
79+ EXPECT_FALSE(ProcessRunner::Run({"/bin/true"}, {}));
80+ g_failFork = false;
81+ EXPECT_FALSE(ProcessRunner::Run({FileUtils::JoinPath(root_, "missing-command")}, {}));
82+ EXPECT_FALSE(ProcessRunner::Run({"/bin/sh", "-c", "kill -TERM $$"}, {}));
83+}
84+ 
85+TEST_F(ModuleTest, ProcessRunnerTimesOutAndReapsProcessGroup)
86+{
87+ const std::string pidFile = FileUtils::JoinPath(root_, "timed-out.pid");
88+ const std::string terminationFile = FileUtils::JoinPath(root_, "termination.txt");
89+ EXPECT_FALSE(ProcessRunner::Run(
90+ {"/bin/sh", "-c",
91+ "trap 'printf terminated > \"$ASC_COMPILE_EXPORTER_TERMINATION_FILE\"; exit 0' TERM; "
92+ "printf '%s' \"$$\" > \"$ASC_COMPILE_EXPORTER_PID_FILE\"; sleep 10"},
93+ {{"ASC_COMPILE_EXPORTER_PID_FILE", pidFile}, {"ASC_COMPILE_EXPORTER_TERMINATION_FILE", terminationFile}},
94+ std::chrono::milliseconds(100U)));
95+ 
96+ EXPECT_EQ(ReadTestFile(terminationFile), "terminated");
97+ const pid_t child = static_cast<pid_t>(std::strtol(ReadTestFile(pidFile).c_str(), nullptr, 10));
98+ ASSERT_GT(child, 0);
99+ errno = 0;
100+ EXPECT_EQ(kill(child, 0), -1);
101+ EXPECT_EQ(errno, ESRCH);
102+}
103+ 
104+TEST_F(ModuleTest, ProcessRunnerReportsWaitFailureAndReapsChild)
105+{
106+ g_failNextWaitpid = true;
107+ EXPECT_FALSE(ProcessRunner::Run({"/bin/true"}, {}, std::chrono::seconds(1U)));
108+ EXPECT_FALSE(g_failNextWaitpid);
109+}
110+ 
111+} // namespace
112+} // namespace ascendc
@@ -0,0 +1,129 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9+ */
10+ 
11+#ifndef ASC_COMPILE_EXPORTER_TEST_SUPPORT_H
12+#define ASC_COMPILE_EXPORTER_TEST_SUPPORT_H
13+ 
14+#include <gtest/gtest.h>
15+ 
16+#include <cerrno>
17+#include <cstdlib>
18+#include <cstring>
19+#include <fstream>
20+#include <iterator>
21+#include <stdexcept>
22+#include <string>
23+#include <vector>
24+ 
25+#include <sys/stat.h>
26+#include <unistd.h>
27+ 
28+#include "file_utils.h"
29+ 
30+namespace asc_compile_exporter_test {
31+ 
32+inline std::runtime_error SystemError(const std::string& operation)
33+{
34+ return std::runtime_error(operation + ": " + std::strerror(errno));
35+}
36+ 
37+inline void WriteTestFile(const std::string& path, const std::string& content)
38+{
39+ if (!ascendc::FileUtils::CreateDirectories(ascendc::FileUtils::ParentPath(path))) {
40+ throw std::runtime_error("failed to create test directory for: " + path);
41+ }
42+ std::ofstream output(path.c_str(), std::ios::binary | std::ios::trunc);
43+ if (!output.is_open()) {
44+ throw std::runtime_error("failed to create unit test file: " + path);
45+ }
46+ output.write(content.data(), static_cast<std::streamsize>(content.size()));
47+ output.close();
48+ if (!output) {
49+ throw std::runtime_error("failed to write unit test file: " + path);
50+ }
51+}
52+ 
53+inline std::string ReadTestFile(const std::string& path)
54+{
55+ std::ifstream input(path.c_str(), std::ios::binary);
56+ return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
57+}
58+ 
59+inline bool LstatPathExists(const std::string& path)
60+{
61+ struct stat info {};
62+ return lstat(path.c_str(), &info) == 0;
63+}
64+ 
65+class ScopedEnvironment final {
66+public:
67+ ScopedEnvironment(const char* name, const char* value) : name_(name)
68+ {
69+ const char* current = std::getenv(name);
70+ existed_ = current != nullptr;
71+ if (current != nullptr) {
72+ oldValue_ = current;
73+ }
74+ if (value == nullptr) {
75+ if (unsetenv(name_.c_str()) != 0) {
76+ throw SystemError("failed to unset test environment variable");
77+ }
78+ } else if (setenv(name_.c_str(), value, 1) != 0) {
79+ throw SystemError("failed to set test environment variable");
80+ }
81+ }
82+ 
83+ ~ScopedEnvironment()
84+ {
85+ if (existed_) {
86+ (void)setenv(name_.c_str(), oldValue_.c_str(), 1);
87+ } else {
88+ (void)unsetenv(name_.c_str());
89+ }
90+ }
91+ 
92+ ScopedEnvironment(const ScopedEnvironment&) = delete;
93+ ScopedEnvironment& operator=(const ScopedEnvironment&) = delete;
94+ 
95+private:
96+ std::string name_;
97+ std::string oldValue_;
98+ bool existed_ = false;
99+};
100+ 
101+class ModuleTest : public testing::Test {
102+protected:
103+ void SetUp() override
104+ {
105+ const char* temporary = std::getenv("TMPDIR");
106+ const std::string parent = temporary == nullptr || temporary[0] == '\0' ? "/tmp" : temporary;
107+ std::string pathTemplate = ascendc::FileUtils::JoinPath(parent, "asc_compile_exporter_module_ut_XXXXXX");
108+ std::vector<char> buffer(pathTemplate.begin(), pathTemplate.end());
109+ buffer.push_back('\0');
110+ char* created = mkdtemp(buffer.data());
111+ ASSERT_NE(created, nullptr);
112+ root_ = created;
113+ }
114+ 
115+ void TearDown() override { (void)ascendc::FileUtils::RemoveAll(root_); }
116+ 
117+ void CreateManifest(const std::string& unit, const std::string& name) const
118+ {
119+ WriteTestFile(
120+ ascendc::FileUtils::JoinPath(unit, name + "_manifest.json"),
121+ "{\"base_dir\":\"resources\",\"name\":\"" + name + "\"}");
122+ }
123+ 
124+ std::string root_;
125+};
126+ 
127+} // namespace asc_compile_exporter_test
128+ 
129+#endif // ASC_COMPILE_EXPORTER_TEST_SUPPORT_H
@@ -0,0 +1,73 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_MANIFEST_ABI_H
12+#define ASCENDC_MANIFEST_ABI_H
13+ 
14+#include <stdint.h>
15+ 
16+#ifdef __cplusplus
17+extern "C" {
18+#endif
19+ 
20+#define AC_COMPILE_RESOURCE_MAGIC 0x41435253u
21+#define AC_COMPILE_RESOURCE_ABI_VERSION 1u
22+#define AC_COMPILE_RESOURCE_EXTENSION_REQUIRED 0x1ULL
23+ 
24+typedef struct AcString {
25+ const char* data;
26+ uint64_t size;
27+} AcString;
28+ 
29+typedef struct AcCompileResourceExtension {
30+ uint32_t type;
31+ uint32_t version;
32+ uint64_t flags;
33+ const void* data;
34+ uint64_t dataSize;
35+} AcCompileResourceExtension;
36+ 
37+typedef struct AcCompileResourceFile {
38+ AcString fileName;
39+ AcString filePath;
40+ const uint8_t* data;
41+ uint64_t size;
42+} AcCompileResourceFile;
43+ 
44+typedef struct AcCompileResourceManifest {
45+ AcString json;
46+ const AcCompileResourceFile* files;
47+ uint64_t fileCount;
48+} AcCompileResourceManifest;
49+ 
50+typedef struct AcCompileResourceBundleHeader {
51+ uint32_t magic;
52+ uint32_t abiVersion;
53+ uint32_t structSize;
54+ uint32_t flags;
55+} AcCompileResourceBundleHeader;
56+ 
57+typedef struct AcCompileResourceBundle {
58+ AcCompileResourceBundleHeader header;
59+ const AcCompileResourceManifest* manifests;
60+ uint64_t manifestCount;
61+ const AcCompileResourceExtension* extensions;
62+ uint64_t extensionCount;
63+} AcCompileResourceBundle;
64+ 
65+typedef const AcCompileResourceBundleHeader* (*AscendcGetCompileResourceBundleFn)(void);
66+ 
67+const AcCompileResourceBundleHeader* AscendcGetCompileResourceBundle(void);
68+ 
69+#ifdef __cplusplus
70+}
71+#endif
72+ 
73+#endif // ASCENDC_MANIFEST_ABI_H
@@ -11,6 +11,7 @@ add_subdirectory(elf_tool)
11add_subdirectory(asc_pack_kernel)11add_subdirectory(asc_pack_kernel)
12add_subdirectory(asc_rt)12add_subdirectory(asc_rt)
13add_subdirectory(opbuild)13add_subdirectory(opbuild)
14+add_subdirectory(asc_compile_exporter)
14if(BUILD_OPEN_PROJECT AND NOT DEVICE_MODE)15if(BUILD_OPEN_PROJECT AND NOT DEVICE_MODE)
15 add_subdirectory(asc_opc)16 add_subdirectory(asc_opc)
16 add_subdirectory(asc_op_compile_base)17 add_subdirectory(asc_op_compile_base)
@@ -0,0 +1,96 @@
1+# ----------------------------------------------------------------------------------------------------------
2+# Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for details. You may not use this file except in compliance with the License.
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+# See LICENSE in the root of the software repository for the full text of the License.
9+# ----------------------------------------------------------------------------------------------------------
10+ 
11+cmake_minimum_required(VERSION 3.16.0)
12+project(asc_compile_exporter)
13+ 
14+set(ASC_COMPILE_EXPORTER_PUBLIC_INTERFACE intf_pub)
15+if(TARGET intf_pub_base)
16+ set(ASC_COMPILE_EXPORTER_PUBLIC_INTERFACE intf_pub_base)
17+endif()
18+ 
19+set(ASC_COMPILE_EXPORTER_GENERATED_DIR
20+ "${CMAKE_BINARY_DIR}/generated/asc_compile_exporter"
21+ CACHE INTERNAL "Generated headers for asc_compile_exporter"
22+)
23+set(ASCENDC_MANIFEST_ABI_HEADER
24+ "${CMAKE_CURRENT_SOURCE_DIR}/../../aclrtc/specialization/ascendc_manifest_abi.h"
25+)
26+set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${ASCENDC_MANIFEST_ABI_HEADER}")
27+file(READ "${ASCENDC_MANIFEST_ABI_HEADER}" ASCENDC_MANIFEST_ABI_HEADER_CONTENT)
28+file(MAKE_DIRECTORY "${ASC_COMPILE_EXPORTER_GENERATED_DIR}")
29+configure_file(
30+ manifest_abi_header_content.h.in
31+ "${ASC_COMPILE_EXPORTER_GENERATED_DIR}/manifest_abi_header_content.h"
32+ @ONLY
33+)
34+ 
35+add_executable(asc_compile_exporter
36+ asc_compile_exporter.cpp
37+ build_workspace.cpp
38+ bundle_source_generator.cpp
39+ collected_manifest_repository.cpp
40+ manifest_bundle_compiler.cpp
41+ ../common/file_utils.cpp
42+ ../common/process_runner.cpp
43+)
44+ 
45+set_target_properties(asc_compile_exporter PROPERTIES
46+ CXX_STANDARD 11
47+ CXX_STANDARD_REQUIRED ON
48+ CXX_EXTENSIONS OFF
49+)
50+ 
51+target_compile_options(asc_compile_exporter PRIVATE -Wextra -Wfloat-equal -fno-common -fpie -Werror)
52+ 
53+if(BUILD_OPEN_PROJECT)
54+ target_include_directories(asc_compile_exporter PRIVATE
55+ ${CMAKE_CURRENT_SOURCE_DIR}
56+ ${CMAKE_CURRENT_SOURCE_DIR}/../common
57+ ${ASC_COMPILE_EXPORTER_GENERATED_DIR}
58+ )
59+else()
60+ target_include_directories(asc_compile_exporter PRIVATE
61+ ${CMAKE_CURRENT_SOURCE_DIR}
62+ ${ASC_COMPILE_EXPORTER_GENERATED_DIR}
63+ ${TOP_DIR}/abl/libc_sec/include
64+ ${TOP_DIR}/abl/mmpa/inc
65+ ${TOP_DIR}/abl/slog/inc/toolchain
66+ ${TOP_DIR}/asc/asc-devkit/tools/build/common
67+ )
68+endif()
69+ 
70+target_link_libraries(asc_compile_exporter PRIVATE
71+ Boost::filesystem
72+ $<BUILD_INTERFACE:${ASC_COMPILE_EXPORTER_PUBLIC_INTERFACE}>
73+ $<BUILD_INTERFACE:mmpa_headers>
74+ $<BUILD_INTERFACE:c_sec_headers>
75+ metadef_headers
76+ metadef
77+ dl
78+ unified_dlog
79+ mmpa
80+ error_manager
81+ json
82+)
83+ 
84+target_link_options(asc_compile_exporter PRIVATE
85+ -pie
86+ $<$<CONFIG:Release>:-s>
87+)
88+ 
89+install(TARGETS asc_compile_exporter
90+ RUNTIME DESTINATION tools/asc_compile_exporter ${INSTALL_OPTIONAL}
91+ COMPONENT asc-devkit
92+ PERMISSIONS
93+ OWNER_READ OWNER_EXECUTE
94+ GROUP_READ GROUP_EXECUTE
95+ WORLD_READ WORLD_EXECUTE
96+)
@@ -0,0 +1,255 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
Y
YYANXI_ZHAO8月13日

copyright 2026

likedislike
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <algorithm>
12+#include <cerrno>
13+#include <climits>
14+#include <cstdint>
15+#include <cstdlib>
16+#include <exception>
17+#include <iostream>
18+#include <string>
19+#include <thread>
20+#include <utility>
21+ 
22+#include "ascendc_tool_log.h"
23+#include "file_utils.h"
24+#include "manifest_bundle_compiler.h"
25+ 
26+namespace {
27+ 
28+void PrintUsage(const char* program)
29+{
30+ std::cerr << "Usage:\n " << program
Y
YYANXI_ZHAO8月13日

后续建议增加-h选项

likedislike
31+ << " --input-dir <path> --output <path> --make <path> --cxx <path>"
32+ " [--save-temp-files] [--jobs <N>]\n\n"
33+ "Required arguments:\n"
34+ " --input-dir <path> Directory recursively searched for *_manifest.json files.\n"
35+ " --output <path> Output path ending in .so or .o, including its parent directory.\n"
36+ " --make <path> Path to the make executable used to build generated sources.\n"
37+ " --cxx <path> Path to the C++ compiler used by the generated Makefile.\n"
38+ "\nOptional arguments:\n"
39+ " --save-temp-files Preserve the private build directory under TMPDIR or /tmp.\n"
40+ " --jobs <N> Positive make job count, capped at the detected CPU count;\n"
41+ " defaults to half the CPU count, with a minimum of one.\n";
42+}
43+ 
44+bool ReportArgumentError(const char* reason, const std::string& argument)
45+{
46+ std::cerr << reason << ": " << argument << '\n';
47+ return false;
48+}
49+ 
50+bool ParseJobs(const std::string& text, uint32_t& jobs)
51+{
52+ if (text.empty()) {
53+ return false;
54+ }
55+ errno = 0;
56+ char* end = nullptr;
57+ const unsigned long parsed = std::strtoul(text.c_str(), &end, 10);
58+ if (errno == ERANGE || end == text.c_str() || *end != '\0' || parsed == 0UL || parsed > UINT32_MAX) {
59+ return false;
60+ }
61+ jobs = static_cast<uint32_t>(parsed);
62+ return true;
63+}
64+ 
65+uint32_t ResolveJobCount(uint32_t requested) noexcept
66+{
67+ const uint32_t reportedCpuCount = static_cast<uint32_t>(std::thread::hardware_concurrency());
68+ const uint32_t cpuCount = reportedCpuCount == 0U ? 1U : reportedCpuCount;
69+ if (requested != 0U) {
70+ return std::min(requested, cpuCount);
71+ }
72+ return cpuCount < 2U ? 1U : cpuCount / 2U;
73+}
74+ 
75+bool ReadOptionValue(int argc, char** argv, int& index, const std::string& option, bool& specified, std::string& value)
76+{
77+ if (specified) {
78+ return ReportArgumentError("Duplicate argument", option);
79+ }
80+ if (index + 1 >= argc) {
81+ return ReportArgumentError("Missing value for argument", option);
82+ }
83+ const std::string candidate = argv[index + 1];
84+ if (candidate.empty()) {
85+ return ReportArgumentError("Invalid value for argument", option);
86+ }
87+ if (candidate.compare(0U, 2U, "--") == 0) {
88+ std::cerr << "Missing value for argument " << option << ": " << candidate << '\n';
89+ return false;
90+ }
91+ value = candidate;
92+ specified = true;
93+ ++index;
94+ return true;
95+}
96+ 
97+bool PrepareOutputPath(
98+ const std::string& argument, std::string& output, ascendc::manifest_generator::BundleOutputKind& outputKind)
99+{
100+ const std::string outputParent = ascendc::FileUtils::ParentPath(argument);
101+ const std::string outputName = ascendc::FileUtils::FileName(argument);
102+ if (outputParent.empty() || outputName.empty() || outputName == "." || outputName == "..") {
103+ return ReportArgumentError("Invalid value for argument --output", argument);
104+ }
105+ if (outputName.size() >= 3U && outputName.compare(outputName.size() - 3U, 3U, ".so") == 0) {
106+ outputKind = ascendc::manifest_generator::BundleOutputKind::kSharedObject;
107+ } else if (outputName.size() >= 2U && outputName.compare(outputName.size() - 2U, 2U, ".o") == 0) {
108+ outputKind = ascendc::manifest_generator::BundleOutputKind::kRelocatableObject;
109+ } else {
110+ return ReportArgumentError("Unsupported output suffix for argument --output (expected .so or .o)", argument);
111+ }
112+ std::string absoluteOutputParent;
113+ if (!ascendc::FileUtils::MakeAbsolutePath(outputParent, absoluteOutputParent) ||
114+ !ascendc::FileUtils::CreateDirectories(absoluteOutputParent)) {
115+ return ReportArgumentError("Invalid value for argument --output", argument);
116+ }
117+ std::string resolvedOutputParent;
118+ if (!ascendc::FileUtils::ResolveDirectory(absoluteOutputParent, resolvedOutputParent)) {
119+ return ReportArgumentError("Invalid value for argument --output", argument);
120+ }
121+ output = ascendc::FileUtils::JoinPath(resolvedOutputParent, outputName);
122+ return true;
123+}
124+ 
125+struct ArgumentState {
126+ ascendc::manifest_generator::BuildCollectedBundleRequest request;
127+ bool inputDirSpecified{false};
128+ bool outputSpecified{false};
129+ bool makeSpecified{false};
130+ bool cxxSpecified{false};
131+ bool saveTempFilesSpecified{false};
132+ bool jobsSpecified{false};
133+};
134+ 
135+bool ParseInputDirectory(int argc, char** argv, int& index, const std::string& argument, ArgumentState& state)
136+{
137+ std::string value;
138+ if (!ReadOptionValue(argc, argv, index, argument, state.inputDirSpecified, value)) {
139+ return false;
140+ }
141+ if (!ascendc::FileUtils::ResolveDirectory(value, state.request.manifestSearchRoot)) {
142+ return ReportArgumentError("Invalid value for argument --input-dir", value);
143+ }
144+ return true;
145+}
146+ 
147+bool ParseOutput(int argc, char** argv, int& index, const std::string& argument, ArgumentState& state)
148+{
149+ std::string value;
150+ return ReadOptionValue(argc, argv, index, argument, state.outputSpecified, value) &&
151+ PrepareOutputPath(value, state.request.outputPath, state.request.outputKind);
152+}
153+ 
154+bool ParseJobCount(int argc, char** argv, int& index, const std::string& argument, ArgumentState& state)
155+{
156+ std::string value;
157+ if (!ReadOptionValue(argc, argv, index, argument, state.jobsSpecified, value)) {
158+ return false;
159+ }
160+ uint32_t jobs = 0U;
161+ if (!ParseJobs(value, jobs)) {
162+ return ReportArgumentError("Invalid value for argument --jobs", value);
163+ }
164+ state.request.jobs = ResolveJobCount(jobs);
165+ return true;
166+}
167+ 
168+bool ParseArgument(int argc, char** argv, int& index, ArgumentState& state)
169+{
170+ const std::string argument = argv[index];
171+ if (argument == "--input-dir") {
172+ return ParseInputDirectory(argc, argv, index, argument, state);
173+ }
174+ if (argument == "--output") {
175+ return ParseOutput(argc, argv, index, argument, state);
176+ }
177+ if (argument == "--make") {
178+ return ReadOptionValue(argc, argv, index, argument, state.makeSpecified, state.request.makeExecutable);
179+ }
180+ if (argument == "--cxx") {
181+ return ReadOptionValue(argc, argv, index, argument, state.cxxSpecified, state.request.cxxCompiler);
182+ }
183+ if (argument == "--save-temp-files") {
184+ if (state.saveTempFilesSpecified) {
185+ return ReportArgumentError("Duplicate argument", argument);
186+ }
187+ state.saveTempFilesSpecified = true;
188+ state.request.keepTemp = true;
189+ return true;
190+ }
191+ if (argument == "--jobs") {
192+ return ParseJobCount(argc, argv, index, argument, state);
193+ }
194+ return ReportArgumentError("Unable to parse argument", argument);
195+}
196+ 
197+bool ValidateRequiredArguments(const ArgumentState& state)
198+{
199+ bool complete = true;
200+ if (!state.inputDirSpecified) {
201+ complete = ReportArgumentError("Missing required argument", "--input-dir");
202+ }
203+ if (!state.outputSpecified) {
204+ complete = ReportArgumentError("Missing required argument", "--output");
205+ }
206+ if (!state.makeSpecified) {
207+ complete = ReportArgumentError("Missing required argument", "--make");
208+ }
209+ if (!state.cxxSpecified) {
210+ complete = ReportArgumentError("Missing required argument", "--cxx");
211+ }
212+ return complete;
213+}
214+ 
215+bool ParseArguments(int argc, char** argv, ascendc::manifest_generator::BuildCollectedBundleRequest& request)
216+{
217+ ArgumentState state;
218+ state.request.jobs = ResolveJobCount(0U);
219+ for (int index = 1; index < argc; ++index) {
220+ if (!ParseArgument(argc, argv, index, state)) {
221+ return false;
222+ }
223+ }
224+ if (!ValidateRequiredArguments(state)) {
225+ return false;
226+ }
227+ request = std::move(state.request);
228+ return true;
229+}
230+ 
231+int Run(int argc, char** argv)
232+{
233+ ascendc::manifest_generator::BuildCollectedBundleRequest request;
234+ if (!ParseArguments(argc, argv, request)) {
235+ PrintUsage(argv[0]);
236+ return 1;
237+ }
238+ ascendc::manifest_generator::ManifestBundleCompiler compiler(std::move(request));
239+ if (!compiler.Compile()) {
240+ return 1;
241+ }
242+ return 0;
243+}
244+ 
245+} // namespace
246+ 
247+int main(int argc, char** argv)
248+{
249+ try {
250+ return Run(argc, argv);
251+ } catch (const std::exception& error) {
252+ ASCENDLOGE("asc_compile_exporter failed unexpectedly: %s", error.what());
253+ return 1;
254+ }
255+}
@@ -0,0 +1,76 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "build_workspace.h"
12+ 
13+#include <cstdlib>
14+ 
15+#include <boost/filesystem.hpp>
16+#include <boost/system/error_code.hpp>
17+ 
18+#include "ascendc_tool_log.h"
19+#include "file_utils.h"
20+ 
21+namespace ascendc {
22+namespace manifest_generator {
23+ 
24+bool BuildWorkspace::Create(const std::string& requestedParent, bool keepTemp)
25+{
26+ if (!session_.empty()) {
27+ ASCENDLOGE("Build workspace is already initialized: %s", session_.c_str());
28+ return false;
29+ }
30+ const char* temporary = std::getenv("TMPDIR");
31+ const std::string requested =
32+ requestedParent.empty() ? (temporary == nullptr || temporary[0] == '\0' ? "/tmp" : temporary) : requestedParent;
33+ std::string absolute;
34+ if (!FileUtils::MakeAbsolutePath(requested, absolute)) {
35+ ASCENDLOGE("Failed to resolve bundle work directory: %s", requested.c_str());
36+ return false;
37+ }
38+ if (!FileUtils::CreateDirectories(absolute)) {
39+ ASCENDLOGE("Failed to create bundle work directory: %s", absolute.c_str());
40+ return false;
41+ }
42+ std::string parent;
43+ if (!FileUtils::ResolveDirectory(absolute, parent)) {
44+ ASCENDLOGE("Bundle work directory is unavailable: %s", absolute.c_str());
45+ return false;
46+ }
47+ boost::system::error_code error;
48+ const boost::filesystem::path candidate =
49+ boost::filesystem::path(parent) / boost::filesystem::unique_path(".manifest-generator-%%%%%%%%", error);
50+ if (error || !boost::filesystem::create_directory(candidate, error)) {
51+ ASCENDLOGE(
52+ "Failed to create private bundle work directory: parent=%s error=%s", parent.c_str(),
53+ error ? error.message().c_str() : "path already exists");
54+ return false;
55+ }
56+ ASCENDLOGD("Created private bundle work directory %s", candidate.c_str());
57+ session_ = candidate.string();
58+ keepTemp_ = keepTemp;
59+ return true;
60+}
61+ 
62+BuildWorkspace::~BuildWorkspace()
63+{
64+ if (!session_.empty() && !keepTemp_) {
65+ if (FileUtils::RemoveAll(session_)) {
66+ ASCENDLOGD("Removed private bundle work directory %s", session_.c_str());
67+ } else {
68+ ASCENDLOGW("Failed to completely remove private bundle work directory %s", session_.c_str());
69+ }
70+ }
71+}
72+ 
73+const std::string& BuildWorkspace::Session() const noexcept { return session_; }
74+ 
75+} // namespace manifest_generator
76+} // namespace ascendc
@@ -0,0 +1,39 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_BUILD_WORKSPACE_H
12+#define ASCENDC_BUILD_WORKSPACE_H
13+ 
14+#include <string>
15+ 
16+namespace ascendc {
17+namespace manifest_generator {
18+ 
19+class BuildWorkspace final {
20+public:
21+ BuildWorkspace() = default;
22+ 
23+ bool Create(const std::string& requestedParent, bool keepTemp);
24+ 
25+ BuildWorkspace(const BuildWorkspace&) = delete;
26+ BuildWorkspace& operator=(const BuildWorkspace&) = delete;
27+ ~BuildWorkspace();
28+ 
29+ const std::string& Session() const noexcept;
30+ 
31+private:
32+ std::string session_;
33+ bool keepTemp_ = false;
34+};
35+ 
36+} // namespace manifest_generator
37+} // namespace ascendc
38+ 
39+#endif // ASCENDC_BUILD_WORKSPACE_H
@@ -0,0 +1,201 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "bundle_source_generator.h"
12+ 
13+#include <fstream>
14+#include <ostream>
15+#include <utility>
16+ 
17+#include "ascendc_tool_log.h"
18+#include "file_utils.h"
19+#include "manifest_abi_header_content.h"
20+ 
21+namespace ascendc {
22+namespace manifest_generator {
23+namespace {
24+ 
25+bool OpenGeneratedOutput(const std::string& path, std::ofstream& output)
26+{
27+ output.open(path.c_str(), std::ios::trunc);
28+ if (!output.is_open()) {
29+ ASCENDLOGE("Failed to create generated output: %s", path.c_str());
30+ return false;
31+ }
32+ return true;
33+}
34+ 
35+bool FinishGeneratedOutput(std::ofstream& output, const std::string& path)
36+{
37+ if (!FileUtils::FinalizeOutput(output)) {
38+ ASCENDLOGE("Failed to finalize generated output: %s", path.c_str());
39+ return false;
40+ }
41+ return true;
42+}
43+ 
44+} // namespace
45+ 
46+BundleSourceGenerator::BundleSourceGenerator(
47+ std::string workDir, const std::vector<ManifestUnit>& units, BundleOutputKind outputKind) noexcept
48+ : workDir_(std::move(workDir)), units_(units), outputKind_(outputKind)
49+{}
50+ 
51+bool BundleSourceGenerator::Generate() const
52+{
53+ ASCENDLOGD("Generating bundle sources in %s", workDir_.c_str());
54+ const std::string abiHeader = FileUtils::JoinPath(workDir_, "ascendc_manifest_abi.h");
55+ ASCENDLOGD("Generating bundle ABI header %s", abiHeader.c_str());
56+ if (!EmitAbiHeader(abiHeader)) {
57+ return false;
58+ }
59+ const std::string bundleSource = FileUtils::JoinPath(workDir_, "compile_resource_bundle.cpp");
60+ ASCENDLOGD("Generating bundle source %s", bundleSource.c_str());
61+ if (!EmitBundleSource(bundleSource)) {
62+ return false;
63+ }
64+ for (size_t unitIndex = 0; unitIndex < units_.size(); ++unitIndex) {
65+ const std::string manifestSource =
66+ FileUtils::JoinPath(workDir_, "manifest_" + std::to_string(unitIndex) + ".cpp");
67+ ASCENDLOGD("Generating manifest source %s", manifestSource.c_str());
68+ if (!EmitManifestSource(manifestSource, unitIndex, units_[unitIndex])) {
69+ return false;
70+ }
71+ }
72+ const std::string makefile = FileUtils::JoinPath(workDir_, "Makefile");
73+ ASCENDLOGD("Generating build file %s", makefile.c_str());
74+ if (!EmitMakefile(makefile)) {
75+ return false;
76+ }
77+ ASCENDLOGI("Generated bundle sources for %zu manifest(s)", units_.size());
78+ return true;
79+}
80+ 
81+bool BundleSourceGenerator::EmitAbiHeader(const std::string& path)
82+{
83+ std::ofstream output;
84+ if (!OpenGeneratedOutput(path, output)) {
85+ return false;
86+ }
87+ output << kManifestAbiHeaderContent;
88+ return FinishGeneratedOutput(output, path);
89+}
90+ 
91+void BundleSourceGenerator::EmitByteArray(
92+ std::ostream& output, const std::string& name, const uint8_t* data, size_t size, bool nulTerminate)
93+{
94+ output << "static const uint8_t " << name << "[] = {";
95+ for (size_t index = 0; index < size; ++index) {
96+ if (index % 16U == 0U) {
97+ output << "\n ";
98+ }
99+ output << static_cast<unsigned int>(data[index]) << ", ";
100+ }
101+ if (nulTerminate) {
102+ output << "0";
103+ }
104+ output << "\n};\n";
105+}
106+ 
107+void BundleSourceGenerator::EmitString(std::ostream& output, const std::string& name, const std::string& value)
108+{
109+ EmitByteArray(output, name + "_bytes", reinterpret_cast<const uint8_t*>(value.data()), value.size(), true);
110+ output << "static const AcString " << name << " = { reinterpret_cast<const char *>(" << name << "_bytes), "
111+ << value.size() << "ULL };\n";
112+}
113+ 
114+bool BundleSourceGenerator::EmitManifestSource(const std::string& path, size_t unitIndex, const ManifestUnit& unit)
115+{
116+ std::ofstream output;
117+ if (!OpenGeneratedOutput(path, output)) {
118+ return false;
119+ }
120+ const std::string prefix = "g_manifest_" + std::to_string(unitIndex);
121+ output << "#include \"ascendc_manifest_abi.h\"\n\nnamespace {\n";
122+ EmitString(output, prefix + "_json", unit.json);
123+ for (size_t fileIndex = 0; fileIndex < unit.files.size(); ++fileIndex) {
124+ const ResourceFile& file = unit.files[fileIndex];
125+ const std::string filePrefix = prefix + "_file_" + std::to_string(fileIndex);
126+ EmitString(output, filePrefix + "_name", file.fileName);
127+ EmitString(output, filePrefix + "_path", file.filePath);
128+ EmitByteArray(output, filePrefix + "_data", file.data.data(), file.data.size(), false);
129+ }
130+ if (!unit.files.empty()) {
131+ output << "static const AcCompileResourceFile " << prefix << "_files[] = {\n";
132+ for (size_t fileIndex = 0; fileIndex < unit.files.size(); ++fileIndex) {
133+ const std::string filePrefix = prefix + "_file_" + std::to_string(fileIndex);
134+ output << " { " << filePrefix << "_name, " << filePrefix << "_path, " << filePrefix << "_data, "
135+ << unit.files[fileIndex].data.size() << "ULL },\n";
136+ }
137+ output << "};\n";
138+ }
139+ output << "} // namespace\n\nnamespace ascendc {\nnamespace resource {\nnamespace generated {\n"
140+ << "AcCompileResourceManifest GetManifest" << unitIndex << "()\n{\n"
141+ << " return { " << prefix << "_json, " << (unit.files.empty() ? "nullptr" : prefix + "_files") << ", "
142+ << unit.files.size()
143+ << "ULL };\n}\n} // namespace generated\n"
144+ "} // namespace resource\n} // namespace ascendc\n";
145+ return FinishGeneratedOutput(output, path);
146+}
147+ 
148+bool BundleSourceGenerator::EmitBundleSource(const std::string& path) const
149+{
150+ std::ofstream output;
151+ if (!OpenGeneratedOutput(path, output)) {
152+ return false;
153+ }
154+ output << "#include \"ascendc_manifest_abi.h\"\n\n"
155+ "namespace ascendc {\nnamespace resource {\nnamespace generated {\n";
156+ for (size_t unitIndex = 0; unitIndex < units_.size(); ++unitIndex) {
157+ output << "AcCompileResourceManifest GetManifest" << unitIndex << "();\n";
158+ }
159+ output << "} // namespace generated\n} // namespace resource\n} // namespace ascendc\n\nnamespace {\n";
160+ output << "static const AcCompileResourceManifest g_manifests[] = {\n";
161+ for (size_t unitIndex = 0; unitIndex < units_.size(); ++unitIndex) {
162+ output << " ascendc::resource::generated::GetManifest" << unitIndex << "(),\n";
163+ }
164+ output << "};\nstatic const AcCompileResourceBundle g_bundle = {\n"
165+ << " { AC_COMPILE_RESOURCE_MAGIC, AC_COMPILE_RESOURCE_ABI_VERSION, "
166+ "sizeof(AcCompileResourceBundle), 0U },\n"
167+ " g_manifests, "
168+ << units_.size() << "ULL, nullptr, 0ULL\n};\n} // namespace\n\n"
169+ << "extern \"C\" __attribute__((visibility(\"default\")))\n"
170+ "const AcCompileResourceBundleHeader *AscendcGetCompileResourceBundle(void)\n"
171+ "{\n return &g_bundle.header;\n}\n";
172+ return FinishGeneratedOutput(output, path);
173+}
174+ 
175+bool BundleSourceGenerator::EmitMakefile(const std::string& path) const
176+{
177+ std::ofstream output;
178+ if (!OpenGeneratedOutput(path, output)) {
179+ return false;
180+ }
181+ output << "SOURCES := compile_resource_bundle.cpp";
182+ for (size_t unitIndex = 0; unitIndex < units_.size(); ++unitIndex) {
183+ output << " manifest_" << unitIndex << ".cpp";
184+ }
185+ output << "\nOBJECTS := $(SOURCES:.cpp=.o)\n\n"
186+ ".PHONY: all\n"
187+ "all: $(OBJECTS)\n";
188+ if (outputKind_ == BundleOutputKind::kSharedObject) {
189+ output << "\t\"$${ASCENDC_BUNDLE_CXX}\" -shared $(OBJECTS) "
190+ "-Wl,-z,relro,-z,now,-z,noexecstack "
191+ "-o \"$${ASCENDC_BUNDLE_OUTPUT}\"\n\n";
192+ } else {
193+ output << "\t\"$${ASCENDC_BUNDLE_CXX}\" -r $(OBJECTS) -o \"$${ASCENDC_BUNDLE_OUTPUT}\"\n\n";
194+ }
195+ output << "%.o: %.cpp\n"
196+ "\t\"$${ASCENDC_BUNDLE_CXX}\" -std=c++11 -fPIC -fvisibility=hidden -O2 -c $< -o $@\n";
197+ return FinishGeneratedOutput(output, path);
198+}
199+ 
200+} // namespace manifest_generator
201+} // namespace ascendc
@@ -0,0 +1,49 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_BUNDLE_SOURCE_GENERATOR_H
12+#define ASCENDC_BUNDLE_SOURCE_GENERATOR_H
13+ 
14+#include <cstddef>
15+#include <cstdint>
16+#include <iosfwd>
17+#include <string>
18+#include <vector>
19+ 
20+#include "manifest_bundle_types.h"
21+ 
22+namespace ascendc {
23+namespace manifest_generator {
24+ 
25+class BundleSourceGenerator final {
26+public:
27+ BundleSourceGenerator(
28+ std::string workDir, const std::vector<ManifestUnit>& units, BundleOutputKind outputKind) noexcept;
29+ 
30+ bool Generate() const;
31+ 
32+private:
33+ static bool EmitAbiHeader(const std::string& path);
34+ static void EmitByteArray(
35+ std::ostream& output, const std::string& name, const uint8_t* data, size_t size, bool nulTerminate);
36+ static void EmitString(std::ostream& output, const std::string& name, const std::string& value);
37+ static bool EmitManifestSource(const std::string& path, size_t unitIndex, const ManifestUnit& unit);
38+ bool EmitBundleSource(const std::string& path) const;
39+ bool EmitMakefile(const std::string& path) const;
40+ 
41+ std::string workDir_;
42+ const std::vector<ManifestUnit>& units_;
43+ BundleOutputKind outputKind_;
44+};
45+ 
46+} // namespace manifest_generator
47+} // namespace ascendc
48+ 
49+#endif // ASCENDC_BUNDLE_SOURCE_GENERATOR_H
@@ -0,0 +1,291 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "collected_manifest_repository.h"
12+ 
13+#include <algorithm>
14+#include <cstdint>
15+#include <cstring>
16+#include <utility>
17+ 
18+#include <boost/filesystem.hpp>
19+#include <boost/system/error_code.hpp>
20+ 
21+#include "ascendc_tool_log.h"
22+#include "file_utils.h"
23+#include "nlohmann/json.hpp"
24+ 
25+using Json = nlohmann::json;
26+ 
27+namespace ascendc {
28+namespace manifest_generator {
29+namespace {
30+ 
31+namespace fs = boost::filesystem;
32+ 
33+constexpr uint32_t MAX_MANIFEST_BYTES = 8U * 1024U * 1024U;
34+constexpr uint32_t MAX_RESOURCE_FILE_BYTES = 1U * 1024U * 1024U;
35+constexpr uint32_t MAX_BUNDLE_BYTES = 256U * 1024U * 1024U;
36+constexpr size_t MAX_MANIFEST_COUNT = 4096U;
37+constexpr size_t MAX_FILES_PER_MANIFEST = 4096U;
38+constexpr size_t MAX_FILES_PER_BUNDLE = 65536U;
39+constexpr size_t MAX_DIRECTORY_DEPTH = 64U;
40+ 
41+class ResourceBudget final {
42+public:
43+ bool AddResource(uint32_t size, const std::string& field)
44+ {
45+ if (fileCount_ >= MAX_FILES_PER_BUNDLE) {
46+ ASCENDLOGE("Bundle exceeds resource file count limit of %zu", MAX_FILES_PER_BUNDLE);
47+ return false;
48+ }
49+ if (!AddPayload(size, field)) {
50+ return false;
51+ }
52+ ++fileCount_;
53+ return true;
54+ }
55+ 
56+ bool AddPayload(uint32_t size, const std::string& field)
57+ {
58+ if (size > MAX_BUNDLE_BYTES - totalBytes_) {
59+ ASCENDLOGE("Bundle exceeds raw payload limit while adding %s", field.c_str());
60+ return false;
61+ }
62+ totalBytes_ += size;
63+ return true;
64+ }
65+ 
66+private:
67+ uint32_t totalBytes_{0U};
68+ size_t fileCount_{0U};
69+};
70+ 
71+bool ReadBaseDir(const std::string& text, const std::string& manifestPath, std::string& baseDir)
72+{
73+ const Json manifest = Json::parse(text, nullptr, false);
74+ if (manifest.is_discarded()) {
75+ ASCENDLOGE("Manifest is not valid JSON: %s", manifestPath.c_str());
76+ return false;
77+ }
78+ 
79+ const Json::const_iterator baseDirValue = manifest.find("base_dir");
80+ if (baseDirValue == manifest.end() || !baseDirValue->is_string()) {
81+ ASCENDLOGE("Manifest base_dir is unavailable: %s", manifestPath.c_str());
82+ return false;
83+ }
84+ baseDir = baseDirValue->get<std::string>();
85+ return true;
86+}
87+ 
88+bool IsManifestFile(const fs::path& path)
89+{
90+ constexpr const char* suffix = "_manifest.json";
91+ const std::string name = path.filename().string();
92+ return name.size() > std::strlen(suffix) &&
93+ name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), suffix) == 0;
94+}
95+ 
96+bool AdvanceIterator(fs::recursive_directory_iterator& iterator, const std::string& root)
97+{
98+ boost::system::error_code error;
99+ iterator.increment(error);
100+ if (!error) {
101+ return true;
102+ }
103+ ASCENDLOGE("Failed to enumerate %s: %s", root.c_str(), error.message().c_str());
104+ return false;
105+}
106+ 
107+bool DiscoverManifestFiles(const std::string& manifestSearchRoot, std::vector<std::string>& manifests)
108+{
109+ boost::system::error_code error;
110+ fs::recursive_directory_iterator iterator(fs::path(manifestSearchRoot), fs::directory_options::none, error);
111+ const fs::recursive_directory_iterator end;
112+ if (error) {
113+ ASCENDLOGE("Failed to enumerate %s: %s", manifestSearchRoot.c_str(), error.message().c_str());
114+ return false;
115+ }
116+ while (iterator != end) {
117+ const fs::path path = iterator->path();
118+ const fs::file_status status = iterator->symlink_status(error);
119+ if (error) {
120+ ASCENDLOGE("Failed to inspect collection entry %s: %s", path.c_str(), error.message().c_str());
121+ return false;
122+ }
123+ if (fs::is_symlink(status)) {
124+ iterator.disable_recursion_pending();
125+ } else if (fs::is_directory(status)) {
126+ if (static_cast<size_t>(iterator.depth()) >= MAX_DIRECTORY_DEPTH) {
127+ ASCENDLOGE("Collection exceeds directory depth limit of %zu: %s", MAX_DIRECTORY_DEPTH, path.c_str());
128+ return false;
129+ }
130+ } else if (fs::is_regular_file(status) && IsManifestFile(path)) {
131+ if (manifests.size() >= MAX_MANIFEST_COUNT) {
132+ ASCENDLOGE("Collection exceeds manifest count limit of %zu", MAX_MANIFEST_COUNT);
133+ return false;
134+ }
135+ manifests.push_back(path.string());
136+ }
137+ if (!AdvanceIterator(iterator, manifestSearchRoot)) {
138+ return false;
139+ }
140+ }
141+ std::sort(manifests.begin(), manifests.end());
142+ if (manifests.empty()) {
143+ ASCENDLOGE("Collected resources contain no manifest units");
144+ return false;
145+ }
146+ return true;
147+}
148+ 
149+bool LoadResourceFile(
150+ const fs::path& source, const std::string& manifestRoot, const std::string& manifestPath, ResourceBudget& budget,
151+ ManifestUnit& unit)
152+{
153+ std::vector<uint8_t> data;
154+ if (!FileUtils::ReadRegularFile(source.string(), MAX_RESOURCE_FILE_BYTES, data)) {
155+ ASCENDLOGE("Failed to read permitted manifest resource: %s", source.c_str());
156+ return false;
157+ }
158+ if (data.empty()) {
159+ ASCENDLOGD("Skipping empty manifest resource %s", source.c_str());
160+ return true;
161+ }
162+ if (unit.files.size() >= MAX_FILES_PER_MANIFEST) {
163+ ASCENDLOGE("Manifest exceeds resource file count limit: %s", manifestPath.c_str());
164+ return false;
165+ }
166+ if (!budget.AddResource(static_cast<uint32_t>(data.size()), "manifest resource " + source.string())) {
167+ return false;
168+ }
169+ boost::system::error_code error;
170+ const fs::path relative = fs::relative(source, fs::path(manifestRoot), error);
171+ if (error || relative.empty()) {
172+ ASCENDLOGE(
173+ "Failed to make manifest resource path relative: path=%s root=%s error=%s", source.c_str(),
174+ manifestRoot.c_str(), error ? error.message().c_str() : "empty relative path");
175+ return false;
176+ }
177+ ResourceFile file;
178+ file.fileName = source.filename().string();
179+ file.filePath = relative.generic_string();
180+ file.data = std::move(data);
181+ unit.files.push_back(std::move(file));
182+ return true;
183+}
184+ 
185+bool LoadResourceFiles(
186+ const std::string& directory, const std::string& manifestRoot, const std::string& manifestPath,
187+ ResourceBudget& budget, ManifestUnit& unit)
188+{
189+ boost::system::error_code error;
190+ fs::recursive_directory_iterator iterator(fs::path(directory), fs::directory_options::none, error);
191+ const fs::recursive_directory_iterator end;
192+ if (error) {
193+ ASCENDLOGE("Failed to enumerate manifest base_dir %s: %s", directory.c_str(), error.message().c_str());
194+ return false;
195+ }
196+ while (iterator != end) {
197+ const fs::path source = iterator->path();
198+ const fs::file_status status = iterator->symlink_status(error);
199+ if (error) {
200+ ASCENDLOGE("Failed to inspect manifest resource %s: %s", source.c_str(), error.message().c_str());
201+ return false;
202+ }
203+ if (fs::is_symlink(status)) {
204+ ASCENDLOGE("Manifest base_dir contains a symlink: %s", source.c_str());
205+ return false;
206+ }
207+ if (fs::is_directory(status)) {
208+ if (static_cast<size_t>(iterator.depth()) >= MAX_DIRECTORY_DEPTH) {
209+ ASCENDLOGE(
210+ "Manifest resources exceed directory depth limit of %zu: %s", MAX_DIRECTORY_DEPTH, source.c_str());
211+ return false;
212+ }
213+ } else if (!fs::is_regular_file(status)) {
214+ ASCENDLOGE("Manifest base_dir contains a non-regular file: %s", source.c_str());
215+ return false;
216+ } else if (!LoadResourceFile(source, manifestRoot, manifestPath, budget, unit)) {
217+ return false;
218+ }
219+ if (!AdvanceIterator(iterator, directory)) {
220+ return false;
221+ }
222+ }
223+ return true;
224+}
225+ 
226+bool LoadUnit(const std::string& manifestPath, ResourceBudget& budget, ManifestUnit& unit)
227+{
228+ ASCENDLOGD("Loading manifest %s", manifestPath.c_str());
229+ std::vector<uint8_t> manifestBytes;
230+ if (!FileUtils::ReadRegularFile(manifestPath, MAX_MANIFEST_BYTES, manifestBytes)) {
231+ ASCENDLOGE("Failed to read permitted manifest: %s", manifestPath.c_str());
232+ return false;
233+ }
234+ if (!budget.AddPayload(static_cast<uint32_t>(manifestBytes.size()), "manifest " + manifestPath)) {
235+ return false;
236+ }
237+ unit.json.assign(manifestBytes.begin(), manifestBytes.end());
238+ std::string baseDir;
239+ if (!ReadBaseDir(unit.json, manifestPath, baseDir)) {
240+ return false;
241+ }
242+ 
243+ const std::string manifestRoot = FileUtils::ParentPath(manifestPath);
244+ const fs::path baseDirPath(baseDir);
245+ const std::string resourceInput = baseDirPath.is_absolute() ? baseDir : FileUtils::JoinPath(manifestRoot, baseDir);
246+ std::string resourceRoot;
247+ if (!FileUtils::ResolveSubdirectory(resourceInput, manifestRoot, resourceRoot)) {
248+ ASCENDLOGE(
249+ "Manifest base_dir must be a directory below the manifest root and must not be a symlink: %s",
250+ resourceInput.c_str());
251+ return false;
252+ }
253+ if (!LoadResourceFiles(resourceRoot, manifestRoot, manifestPath, budget, unit)) {
254+ return false;
255+ }
256+ std::sort(unit.files.begin(), unit.files.end(), [](const ResourceFile& left, const ResourceFile& right) {
257+ return left.filePath < right.filePath;
258+ });
259+ ASCENDLOGD("Loaded manifest %s with %zu resource file(s)", manifestPath.c_str(), unit.files.size());
260+ return true;
261+}
262+ 
263+} // namespace
264+ 
265+CollectedManifestRepository::CollectedManifestRepository(std::string manifestSearchRoot) noexcept
266+ : manifestSearchRoot_(std::move(manifestSearchRoot))
267+{}
268+ 
269+bool CollectedManifestRepository::Load(std::vector<ManifestUnit>& units) const
270+{
271+ ASCENDLOGD("Discovering manifests under %s", manifestSearchRoot_.c_str());
272+ ResourceBudget budget;
273+ std::vector<std::string> manifests;
274+ if (!DiscoverManifestFiles(manifestSearchRoot_, manifests)) {
275+ return false;
276+ }
277+ ASCENDLOGI("Discovered %zu manifest(s)", manifests.size());
278+ units.clear();
279+ units.reserve(manifests.size());
280+ for (const std::string& manifest : manifests) {
281+ ManifestUnit unit;
282+ if (!LoadUnit(manifest, budget, unit)) {
283+ return false;
284+ }
285+ units.push_back(std::move(unit));
286+ }
287+ return true;
288+}
289+ 
290+} // namespace manifest_generator
291+} // namespace ascendc
@@ -0,0 +1,35 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_COLLECTED_MANIFEST_REPOSITORY_H
12+#define ASCENDC_COLLECTED_MANIFEST_REPOSITORY_H
13+ 
14+#include <string>
15+#include <vector>
16+ 
17+#include "manifest_bundle_types.h"
18+ 
19+namespace ascendc {
20+namespace manifest_generator {
21+ 
22+class CollectedManifestRepository final {
23+public:
24+ explicit CollectedManifestRepository(std::string manifestSearchRoot) noexcept;
25+ 
26+ bool Load(std::vector<ManifestUnit>& units) const;
27+ 
28+private:
29+ std::string manifestSearchRoot_;
30+};
31+ 
32+} // namespace manifest_generator
33+} // namespace ascendc
34+ 
35+#endif // ASCENDC_COLLECTED_MANIFEST_REPOSITORY_H
@@ -0,0 +1,22 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#ifndef ASCENDC_MANIFEST_ABI_HEADER_CONTENT_H
12+#define ASCENDC_MANIFEST_ABI_HEADER_CONTENT_H
13+ 
14+namespace ascendc {
15+namespace manifest_generator {
16+ 
17+constexpr char kManifestAbiHeaderContent[] = R"ASCENDC_ABI(@ASCENDC_MANIFEST_ABI_HEADER_CONTENT@)ASCENDC_ABI";
18+ 
19+} // namespace manifest_generator
20+} // namespace ascendc
21+ 
22+#endif // ASCENDC_MANIFEST_ABI_HEADER_CONTENT_H
@@ -0,0 +1,135 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "manifest_bundle_compiler.h"
12+ 
13+#include <utility>
14+#include <vector>
15+ 
16+#include <boost/filesystem.hpp>
17+#include <boost/system/error_code.hpp>
18+ 
19+#include "ascendc_tool_log.h"
20+#include "build_workspace.h"
21+#include "bundle_source_generator.h"
22+#include "collected_manifest_repository.h"
23+#include "file_utils.h"
24+#include "manifest_bundle_types.h"
25+#include "process_runner.h"
26+ 
27+namespace ascendc {
28+namespace manifest_generator {
29+ 
30+ManifestBundleCompiler::ManifestBundleCompiler(BuildCollectedBundleRequest request) noexcept
31+ : request_(std::move(request))
32+{}
33+ 
34+bool ManifestBundleCompiler::BuildBundle(const std::string& workDir, const std::string& output) const
35+{
36+ ASCENDLOGD(
37+ "Building bundle with make=%s compiler=%s jobs=%u", request_.makeExecutable.c_str(),
38+ request_.cxxCompiler.c_str(), request_.jobs);
39+ if (!ProcessRunner::Run(
40+ {request_.makeExecutable, "-C", workDir, "-f", "Makefile", "-B", "-j" + std::to_string(request_.jobs)},
41+ {{"ASCENDC_BUNDLE_CXX", request_.cxxCompiler}, {"ASCENDC_BUNDLE_OUTPUT", output}})) {
42+ return false;
43+ }
44+ ASCENDLOGI("Built staged compile resource bundle %s", output.c_str());
45+ return true;
46+}
47+ 
48+bool ManifestBundleCompiler::PublishBundle(const std::string& staged) const
49+{
50+ // Prefer rename because replacing the output is atomic when both paths are on the same filesystem.
51+ boost::system::error_code error;
52+ boost::filesystem::rename(boost::filesystem::path(staged), boost::filesystem::path(request_.outputPath), error);
53+ if (!error) {
54+ ASCENDLOGI("Published compile resource bundle %s", request_.outputPath.c_str());
55+ return true;
56+ }
57+ if (error != boost::system::errc::cross_device_link) {
58+ ASCENDLOGE("Failed to publish bundle output: %s", error.message().c_str());
59+ return false;
60+ }
61+ 
62+ // rename cannot cross filesystems. Copy into the output filesystem before renaming again.
63+ if (!PublishBundleAcrossFileSystems(staged)) {
64+ return false;
65+ }
66+ ASCENDLOGI("Published compile resource bundle %s", request_.outputPath.c_str());
67+ return true;
68+}
69+ 
70+bool ManifestBundleCompiler::PublishBundleAcrossFileSystems(const std::string& staged) const
71+{
72+ ASCENDLOGD("Copying staged bundle across filesystems: %s -> %s", staged.c_str(), request_.outputPath.c_str());
73+ // A private workspace under the output parent places the copied file on the output filesystem.
74+ BuildWorkspace publishWorkspace;
75+ if (!publishWorkspace.Create(FileUtils::ParentPath(request_.outputPath), false)) {
76+ return false;
77+ }
78+ const std::string copied = FileUtils::JoinPath(publishWorkspace.Session(), FileUtils::FileName(staged));
79+ if (!FileUtils::CopyFile(staged, copied)) {
80+ ASCENDLOGE("Failed to copy staged bundle across filesystems: %s", staged.c_str());
81+ return false;
82+ }
83+ 
84+ boost::system::error_code error;
85+ boost::filesystem::rename(boost::filesystem::path(copied), boost::filesystem::path(request_.outputPath), error);
86+ if (error) {
87+ ASCENDLOGE("Failed to publish copied bundle output: %s", error.message().c_str());
88+ return false;
89+ }
90+ return true;
91+}
92+ 
93+bool ManifestBundleCompiler::Compile() const
94+{
95+ ASCENDLOGI(
96+ "Starting bundle generation: manifest_root=%s output=%s keep_temp=%s", request_.manifestSearchRoot.c_str(),
97+ request_.outputPath.c_str(), request_.keepTemp ? "true" : "false");
98+ 
99+ ASCENDLOGI("Using %u parallel bundle build job(s)", request_.jobs);
100+ 
101+ // Load and validate the complete collection before allocating a private build workspace.
102+ std::vector<ManifestUnit> units;
103+ if (!CollectedManifestRepository(request_.manifestSearchRoot).Load(units)) {
104+ return false;
105+ }
106+ BuildWorkspace workspace;
107+ if (!workspace.Create(std::string(), request_.keepTemp)) {
108+ return false;
109+ }
110+ if (request_.keepTemp) {
111+ ASCENDLOGI("Keeping temporary bundle directory %s", workspace.Session().c_str());
112+ }
113+ 
114+ // Generate isolated translation units and compile them into a staged output.
115+ if (!BundleSourceGenerator(workspace.Session(), units, request_.outputKind).Generate()) {
116+ return false;
117+ }
118+ const std::string stagedName = request_.outputKind == BundleOutputKind::kSharedObject ?
119+ "compile_resource_bundle.so" :
120+ "compile_resource_bundle_relocatable.o";
121+ const std::string stagedOutput = FileUtils::JoinPath(workspace.Session(), stagedName);
122+ if (!BuildBundle(workspace.Session(), stagedOutput)) {
123+ return false;
124+ }
125+ 
126+ // Publish only after a successful build so failures never replace an existing output bundle.
127+ if (!PublishBundle(stagedOutput)) {
128+ return false;
129+ }
130+ ASCENDLOGI("Bundle generation completed successfully: %s", request_.outputPath.c_str());
131+ return true;
132+}
133+ 
134+} // namespace manifest_generator
135+} // namespace ascendc