已合并
[feat] add asc_compile_exporter #4980
sjtulxh创建于 8月10日
[feat] add asc_compile_exporter #4980
已合并
共 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 | + | ||
| 18 | find_cann_package(unified_dlog REQUIRED) | 21 | find_cann_package(unified_dlog REQUIRED) |
| 19 | find_cann_package(securec REQUIRED) | 22 | find_cann_package(securec REQUIRED) |
| 20 | find_cann_package(mmpa REQUIRED) | 23 | find_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-cmake | 20 | 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" ]; then | 81 | 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 | fi | 83 | 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" ]; then | 87 | if [ -e "$install_path/$version_dir/${arch_name}-linux/bin/asc_opc" ]; then |
| 85 | if [ ! -L "$install_path/$version_dir/compiler/bin/asc_opc" ]; then | 88 | 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" ]; then | 95 | 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 | fi | 97 | 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" ]; then | 102 | 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) | |||
| 15 | find_package(Python3 COMPONENTS Interpreter) | 15 | find_package(Python3 COMPONENTS Interpreter) |
| 16 | add_cann_third_party(gtest) | 16 | add_cann_third_party(gtest) |
| 17 | add_cann_third_party(boost) | 17 | add_cann_third_party(boost) |
| 18 | +add_cann_third_party(json) | ||
| 18 | include(${CMAKE_CURRENT_LIST_DIR}/third_party/mockcpp.cmake) | 19 | include(${CMAKE_CURRENT_LIST_DIR}/third_party/mockcpp.cmake) |
| @@ -81,6 +81,7 @@ basic_test_three_targets=( | |||
| 81 | ascendc_ut_asc_runtime | 81 | ascendc_ut_asc_runtime |
| 82 | ascendc_ut_elf_tool | 82 | ascendc_ut_elf_tool |
| 83 | ascendc_ut_pack_kernel | 83 | ascendc_ut_pack_kernel |
| 84 | + ascendc_ut_asc_compile_exporter | ||
| 84 | ascendc_ut_tpl_tiling_debug | 85 | ascendc_ut_tpl_tiling_debug |
| 85 | ascendc_ut_tpl_tiling_release | 86 | ascendc_ut_tpl_tiling_release |
| 86 | ascendc_ut_aicpu_api | 87 | ascendc_ut_aicpu_api |
| @@ -197,6 +197,64 @@ run_llt_test( | |||
| 197 | TASK_NUM 1 | 197 | 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################################# |
| 202 | add_library(ops_utest SHARED ${ASCENDC_DIR}/tests/tools/utils/opbuild/test_ops.cpp) | 260 | add_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) | |||
| 11 | add_subdirectory(asc_pack_kernel) | 11 | add_subdirectory(asc_pack_kernel) |
| 12 | add_subdirectory(asc_rt) | 12 | add_subdirectory(asc_rt) |
| 13 | add_subdirectory(opbuild) | 13 | add_subdirectory(opbuild) |
| 14 | +add_subdirectory(asc_compile_exporter) | ||
| 14 | if(BUILD_OPEN_PROJECT AND NOT DEVICE_MODE) | 15 | if(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 | |||
| 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 后续建议增加-h选项 ![]() ![]() | |||
| 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 | ||


copyright 2026