Merged
refactor(build): consolidate common build infrastructure #5247
refactor(build): consolidate common build infrastructure #5247
Merged
XuebinYangAdd on Aug 21
Total: 36 files changed+1675-1371
@@ -19,8 +19,10 @@ file(GLOB ACLRTC_SRC_FILES
19 ${ASCENDC_DIR}/tools/aclrtc/specialization/*.cpp19 ${ASCENDC_DIR}/tools/aclrtc/specialization/*.cpp
20)20)
21 21 
22-set(ACLRTC_RESOURCE_REGISTRY_DEPENDENCY_SRC_FILES22+set(ACLRTC_COMMON_DEPENDENCY_SRC_FILES
23 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp23 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp
24+ ${ASCENDC_DIR}/tools/build/common/process_executor.cpp
25+ ${ASCENDC_DIR}/tools/build/common/directory_cleanup_guard.cpp
24)26)
25 27 
26file(GLOB TEST_ACLRTC_SRC_FILES28file(GLOB TEST_ACLRTC_SRC_FILES
@@ -31,7 +33,7 @@ file(GLOB TEST_ACLRTC_SRC_FILES
31 33 
32add_executable(ascendc_ut_aclrtc34add_executable(ascendc_ut_aclrtc
33 ${ACLRTC_SRC_FILES}35 ${ACLRTC_SRC_FILES}
34- ${ACLRTC_RESOURCE_REGISTRY_DEPENDENCY_SRC_FILES}36+ ${ACLRTC_COMMON_DEPENDENCY_SRC_FILES}
35 ${TEST_ACLRTC_SRC_FILES}37 ${TEST_ACLRTC_SRC_FILES}
36)38)
37 39 
@@ -33,10 +33,8 @@ using ascendc::aclrtc::CompilationCommand;
33using ascendc::aclrtc::CompilationCommandExecutionLimits;33using ascendc::aclrtc::CompilationCommandExecutionLimits;
34using ascendc::aclrtc::CompilationCommandKind;34using ascendc::aclrtc::CompilationCommandKind;
35using ascendc::aclrtc::CompilationPlanExecutor;35using ascendc::aclrtc::CompilationPlanExecutor;
36-using ascendc::aclrtc::CompilationProcessResult;
37-using ascendc::aclrtc::CompilationProcessTermination;
38-using ascendc::aclrtc::CompilationVariant;
39using ascendc::aclrtc::KernelCompilationPlan;36using ascendc::aclrtc::KernelCompilationPlan;
37+using ascendc::aclrtc::KernelCompilationVariant;
40using ascendc::aclrtc::KernelSpecializationDiagnostics;38using ascendc::aclrtc::KernelSpecializationDiagnostics;
41using namespace mockcpp;39using namespace mockcpp;
42 40 
@@ -46,28 +44,36 @@ int FailCommandOutputPoll(pollfd*, nfds_t, int)
46 return -1;44 return -1;
47}45}
48 46 
49-int FailCommandLeaderInspection(idtype_t, id_t, siginfo_t*, int)47+pid_t FailCommandWait(pid_t, int*, int)
50{48{
51 errno = ECHILD;49 errno = ECHILD;
52 return -1;50 return -1;
53}51}
54 52 
53+uint32_t COMMAND_OUTPUT_READ_CALL_COUNT = 0U;
54+ 
55+ssize_t FailFinalCommandOutputRead(int, void*, size_t)
56+{
57+ ++COMMAND_OUTPUT_READ_CALL_COUNT;
58+ errno = COMMAND_OUTPUT_READ_CALL_COUNT == 3U ? EIO : EAGAIN;
59+ return -1;
60+}
61+ 
62+pid_t ReapCommandProcess(pid_t processId, int* waitStatus, int)
63+{
64+ pid_t waitResult;
65+ do {
66+ waitResult = wait4(processId, waitStatus, 0, nullptr);
67+ } while (waitResult < 0 && errno == EINTR);
68+ return waitResult;
69+}
70+ 
55static_assert(71static_assert(
56- std::is_same<std::underlying_type_t<CompilationVariant>, uint32_t>::value,72+ std::is_same<std::underlying_type_t<KernelCompilationVariant>, uint32_t>::value,
57- "CompilationVariant must have a stable width");73+ "KernelCompilationVariant must have a stable width");
58static_assert(74static_assert(
59 std::is_same<std::underlying_type_t<CompilationCommandKind>, uint32_t>::value,75 std::is_same<std::underlying_type_t<CompilationCommandKind>, uint32_t>::value,
60 "CompilationCommandKind must have a stable width");76 "CompilationCommandKind must have a stable width");
61-static_assert(
62- std::is_same<std::underlying_type_t<CompilationProcessTermination>, uint32_t>::value,
63- "CompilationProcessTermination must have a stable width");
64-static_assert(
65- std::is_same<decltype(CompilationProcessResult::terminationCode), int32_t>::value,
66- "terminationCode must have a stable width");
67-static_assert(
68- std::is_same<decltype(CompilationCommandExecutionLimits::capturedOutputByteLimit), uint64_t>::value,
69- "capturedOutputByteLimit must have a stable width");
70- 
71CompilationCommand CreateCommand(77CompilationCommand CreateCommand(
72 CompilationCommandKind commandKind, const char* executable, std::vector<std::string> arguments)78 CompilationCommandKind commandKind, const char* executable, std::vector<std::string> arguments)
73{79{
@@ -108,7 +114,6 @@ TEST(CompilationPlanExecutorTest, ProductionLimitsMatchReviewedPerCommandTimeout
108 EXPECT_EQ(limits.compileCommandTimeout, 5min);114 EXPECT_EQ(limits.compileCommandTimeout, 5min);
109 EXPECT_EQ(limits.objectCopyAndLinkCommandTimeout, 1min);115 EXPECT_EQ(limits.objectCopyAndLinkCommandTimeout, 1min);
110 EXPECT_EQ(limits.terminationGracePeriod, 5s);116 EXPECT_EQ(limits.terminationGracePeriod, 5s);
111- EXPECT_EQ(limits.capturedOutputByteLimit, 4U * 1024U * 1024U);
112}117}
113 118 
114TEST(CompilationPlanExecutorTest, ExecutesCommandsWithoutShellParsing)119TEST(CompilationPlanExecutorTest, ExecutesCommandsWithoutShellParsing)
@@ -167,7 +172,6 @@ TEST(CompilationPlanExecutorTest, UsesInjectedShortTimeoutInUnitTests)
167 limits.compileCommandTimeout = 20ms;172 limits.compileCommandTimeout = 20ms;
168 limits.objectCopyAndLinkCommandTimeout = 20ms;173 limits.objectCopyAndLinkCommandTimeout = 20ms;
169 limits.terminationGracePeriod = 20ms;174 limits.terminationGracePeriod = 20ms;
170- limits.capturedOutputByteLimit = 1024U;
171 CompilationPlanExecutor executor(diagnostics, limits);175 CompilationPlanExecutor executor(diagnostics, limits);
172 KernelCompilationPlan plan;176 KernelCompilationPlan plan;
173 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"10"}));177 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"10"}));
@@ -180,7 +184,7 @@ TEST(CompilationPlanExecutorTest, UsesInjectedShortTimeoutInUnitTests)
180TEST(CompilationPlanExecutorTest, EnforcesTimeoutWhileCommandContinuouslyProducesOutput)184TEST(CompilationPlanExecutorTest, EnforcesTimeoutWhileCommandContinuouslyProducesOutput)
181{185{
182 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_continuous_output");186 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_continuous_output");
183- CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms, 1024U};187+ CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms};
184 CompilationPlanExecutor executor(diagnostics, limits);188 CompilationPlanExecutor executor(diagnostics, limits);
185 KernelCompilationPlan plan;189 KernelCompilationPlan plan;
186 plan.compilationCommands.push_back(CreateCommand(190 plan.compilationCommands.push_back(CreateCommand(
@@ -194,7 +198,7 @@ TEST(CompilationPlanExecutorTest, EnforcesTimeoutWhileCommandContinuouslyProduce
194TEST(CompilationPlanExecutorTest, WaitsWithoutBusyPollingAfterCommandOutputCloses)198TEST(CompilationPlanExecutorTest, WaitsWithoutBusyPollingAfterCommandOutputCloses)
195{199{
196 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_closed_output");200 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_closed_output");
197- CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};201+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms};
198 CompilationPlanExecutor executor(diagnostics, limits);202 CompilationPlanExecutor executor(diagnostics, limits);
199 KernelCompilationPlan plan;203 KernelCompilationPlan plan;
200 plan.compilationCommands.push_back(204 plan.compilationCommands.push_back(
@@ -211,10 +215,10 @@ TEST(CompilationPlanExecutorTest, WaitsWithoutBusyPollingAfterCommandOutputClose
211 EXPECT_LT(consumedCpuNanoseconds, 100000000LL);215 EXPECT_LT(consumedCpuNanoseconds, 100000000LL);
212}216}
213 217 
214-TEST(CompilationPlanExecutorTest, WaitsForOutputPipeClosureAfterCommandLeaderExits)218+TEST(CompilationPlanExecutorTest, CompletesWhenSynchronousCommandLeaderExits)
215{219{
216 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_leader_exit");220 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_leader_exit");
217- CompilationCommandExecutionLimits limits{500ms, 500ms, 20ms, 1024U};221+ CompilationCommandExecutionLimits limits{500ms, 500ms, 20ms};
218 CompilationPlanExecutor executor(diagnostics, limits);222 CompilationPlanExecutor executor(diagnostics, limits);
219 KernelCompilationPlan plan;223 KernelCompilationPlan plan;
220 plan.compilationCommands.push_back(224 plan.compilationCommands.push_back(
@@ -222,22 +226,21 @@ TEST(CompilationPlanExecutorTest, WaitsForOutputPipeClosureAfterCommandLeaderExi
222 226 
223 const auto startedAt = std::chrono::steady_clock::now();227 const auto startedAt = std::chrono::steady_clock::now();
224 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);228 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
225- EXPECT_GE(std::chrono::steady_clock::now() - startedAt, 200ms);229+ EXPECT_LT(std::chrono::steady_clock::now() - startedAt, 200ms);
226}230}
227 231 
228-TEST(CompilationPlanExecutorTest, TimeoutTerminatesDescendantsAfterCommandLeaderExits)232+TEST(CompilationPlanExecutorTest, TimeoutTerminatesActiveProcessGroup)
229{233{
230 static uint64_t sequence = 0U;234 static uint64_t sequence = 0U;
231 const fs::path descendantMarkerPath =235 const fs::path descendantMarkerPath =
232 fs::temp_directory_path() /236 fs::temp_directory_path() /
233 ("aclrtc_descendant_marker_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));237 ("aclrtc_descendant_marker_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
234 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_process_group");238 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_process_group");
235- CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms, 1024U};239+ CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms};
236 CompilationPlanExecutor executor(diagnostics, limits);240 CompilationPlanExecutor executor(diagnostics, limits);
237 KernelCompilationPlan plan;241 KernelCompilationPlan plan;
238 const std::string commandScript =242 const std::string commandScript =
239- "(trap '' TERM; count=0; while [ $count -lt 100 ]; do printf x >> '" + descendantMarkerPath.string() +243+ "(trap '' TERM; while true; do printf x >> '" + descendantMarkerPath.string() + "'; sleep 0.01; done) & wait";
240- "'; count=$((count + 1)); sleep 0.01; done; rm -f '" + descendantMarkerPath.string() + "') & exec /bin/true";
241 plan.compilationCommands.push_back(244 plan.compilationCommands.push_back(
242 CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", commandScript}));245 CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", commandScript}));
243 246 
@@ -254,7 +257,7 @@ TEST(CompilationPlanExecutorTest, TimeoutTerminatesDescendantsAfterCommandLeader
254 fs::remove(descendantMarkerPath, ignoredError);257 fs::remove(descendantMarkerPath, ignoredError);
255}258}
256 259 
257-TEST(CompilationPlanExecutorTest, RecordsSpawnFailureAsInfrastructureFailure)260+TEST(CompilationPlanExecutorTest, RecordsSpawnFailureAsExecutorFailure)
258{261{
259 ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;262 ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;
260 const std::string specializationSessionId = "executor_spawn_failure";263 const std::string specializationSessionId = "executor_spawn_failure";
@@ -265,27 +268,25 @@ TEST(CompilationPlanExecutorTest, RecordsSpawnFailureAsInfrastructureFailure)
265 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);268 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
266 269 
267 const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");270 const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");
268- EXPECT_NE(compilationLog.find("termination=infrastructure_failure"), std::string::npos);271+ EXPECT_NE(compilationLog.find("outcome=executor_failure"), std::string::npos);
269 EXPECT_EQ(compilationLog.find("termination=exited code=0"), std::string::npos);272 EXPECT_EQ(compilationLog.find("termination=exited code=0"), std::string::npos);
270}273}
271 274 
272-TEST(CompilationPlanExecutorTest, BoundsCapturedProcessOutput)275+TEST(CompilationPlanExecutorTest, MirrorsCompleteProcessOutputWhenDiagnosticsAreEnabled)
273{276{
274 ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;277 ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;
275 const std::string specializationSessionId = "executor_output_limit";278 const std::string specializationSessionId = "executor_output_limit";
276 KernelSpecializationDiagnostics diagnostics(fs::current_path(), specializationSessionId);279 KernelSpecializationDiagnostics diagnostics(fs::current_path(), specializationSessionId);
277- CompilationCommandExecutionLimits limits = CompilationCommandExecutionLimits::ProductionDefaults();280+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
278- limits.capturedOutputByteLimit = 8U;
279- CompilationPlanExecutor executor(diagnostics, limits);
280 KernelCompilationPlan plan;281 KernelCompilationPlan plan;
281 plan.compilationCommands.push_back(282 plan.compilationCommands.push_back(
282 CreateCommand(CompilationCommandKind::Compile, "/usr/bin/printf", {"0123456789abcdef"}));283 CreateCommand(CompilationCommandKind::Compile, "/usr/bin/printf", {"0123456789abcdef"}));
283 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);284 EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
284 285 
285 const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");286 const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");
286- EXPECT_NE(compilationLog.find("output_truncated=true"), std::string::npos);287+ EXPECT_NE(compilationLog.find("[test-command] output:\n"), std::string::npos);
287- EXPECT_NE(compilationLog.find("01234567"), std::string::npos);288+ EXPECT_NE(compilationLog.find("0123456789abcdef"), std::string::npos);
288- EXPECT_EQ(compilationLog.find("89abcdef"), std::string::npos);289+ EXPECT_NE(compilationLog.find("\n[test-command] outcome=exited code=0"), std::string::npos);
289}290}
290 291 
291TEST(CompilationPlanExecutorTest, ReportsToolProcessTerminatedBySignal)292TEST(CompilationPlanExecutorTest, ReportsToolProcessTerminatedBySignal)
@@ -311,40 +312,57 @@ TEST(CompilationPlanExecutorTest, ReportsPipeCreationFailureWhenFileDescriptorsA
311 struct rlimit exhaustedFileDescriptorLimit = originalFileDescriptorLimit;312 struct rlimit exhaustedFileDescriptorLimit = originalFileDescriptorLimit;
312 exhaustedFileDescriptorLimit.rlim_cur = 0U;313 exhaustedFileDescriptorLimit.rlim_cur = 0U;
313 ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &exhaustedFileDescriptorLimit), 0);314 ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &exhaustedFileDescriptorLimit), 0);
314- const aclError executionResult = executor.ExecuteCompilationPlan(plan);315+ const aclError planExecutionStatus = executor.ExecuteCompilationPlan(plan);
315 const int restoreLimitResult = setrlimit(RLIMIT_NOFILE, &originalFileDescriptorLimit);316 const int restoreLimitResult = setrlimit(RLIMIT_NOFILE, &originalFileDescriptorLimit);
316 317 
317 ASSERT_EQ(restoreLimitResult, 0);318 ASSERT_EQ(restoreLimitResult, 0);
318- EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);319+ EXPECT_EQ(planExecutionStatus, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
319}320}
320 321 
321TEST(CompilationPlanExecutorTest, ReportsCommandOutputPollFailure)322TEST(CompilationPlanExecutorTest, ReportsCommandOutputPollFailure)
322{323{
323 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_poll_failure");324 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_poll_failure");
324- CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};325+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms};
325 CompilationPlanExecutor executor(diagnostics, limits);326 CompilationPlanExecutor executor(diagnostics, limits);
326 KernelCompilationPlan plan;327 KernelCompilationPlan plan;
327 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));328 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));
328 MOCKER(poll, int (*)(pollfd*, nfds_t, int)).expects(once()).will(invoke(FailCommandOutputPoll));329 MOCKER(poll, int (*)(pollfd*, nfds_t, int)).expects(once()).will(invoke(FailCommandOutputPoll));
329 330 
330- const aclError executionResult = executor.ExecuteCompilationPlan(plan);331+ const aclError planExecutionStatus = executor.ExecuteCompilationPlan(plan);
331 332 
332 GlobalMockObject::verify();333 GlobalMockObject::verify();
333- EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);334+ EXPECT_EQ(planExecutionStatus, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
334}335}
335 336 
336-TEST(CompilationPlanExecutorTest, ReportsCommandLeaderInspectionFailure)337+TEST(CompilationPlanExecutorTest, ReportsCommandWaitFailure)
337{338{
338 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_waitid_failure");339 KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_waitid_failure");
339- CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};340+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms};
340 CompilationPlanExecutor executor(diagnostics, limits);341 CompilationPlanExecutor executor(diagnostics, limits);
341 KernelCompilationPlan plan;342 KernelCompilationPlan plan;
342 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));343 plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));
343- MOCKER(waitid, int (*)(idtype_t, id_t, siginfo_t*, int)).expects(once()).will(invoke(FailCommandLeaderInspection));344+ MOCKER(waitpid, pid_t(*)(pid_t, int*, int)).expects(once()).will(invoke(FailCommandWait));
344 345 
345- const aclError executionResult = executor.ExecuteCompilationPlan(plan);346+ const aclError planExecutionStatus = executor.ExecuteCompilationPlan(plan);
346 347 
347 GlobalMockObject::verify();348 GlobalMockObject::verify();
348- EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);349+ EXPECT_EQ(planExecutionStatus, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
349}350}
351+ 
352+TEST(CompilationPlanExecutorTest, ReportsFinalCommandOutputReadFailure)
353+{
354+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_final_output_read_failure");
355+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
356+ KernelCompilationPlan plan;
357+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/true", {}));
358+ COMMAND_OUTPUT_READ_CALL_COUNT = 0U;
359+ MOCKER(read, ssize_t(*)(int, void*, size_t)).expects(exactly(4)).will(invoke(FailFinalCommandOutputRead));
360+ MOCKER(waitpid, pid_t(*)(pid_t, int*, int)).expects(once()).will(invoke(ReapCommandProcess));
361+ 
362+ const aclError planExecutionStatus = executor.ExecuteCompilationPlan(plan);
363+ 
364+ GlobalMockObject::verify();
365+ EXPECT_EQ(planExecutionStatus, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
366+}
367+ 
350} // namespace368} // namespace
@@ -26,9 +26,9 @@ namespace {
26namespace fs = boost::filesystem;26namespace fs = boost::filesystem;
27using Json = nlohmann::json;27using Json = nlohmann::json;
28using ascendc::aclrtc::CompilationCommandKind;28using ascendc::aclrtc::CompilationCommandKind;
29-using ascendc::aclrtc::CompilationVariant;
30using ascendc::aclrtc::KernelCompilationPlan;29using ascendc::aclrtc::KernelCompilationPlan;
31using ascendc::aclrtc::KernelCompilationPlanBuilder;30using ascendc::aclrtc::KernelCompilationPlanBuilder;
31+using ascendc::aclrtc::KernelCompilationVariant;
32using ascendc::aclrtc::NormalizedKernelSpecializationRequest;32using ascendc::aclrtc::NormalizedKernelSpecializationRequest;
33 33 
34class ScopedEnvironmentVariable final {34class ScopedEnvironmentVariable final {
@@ -300,7 +300,7 @@ TEST(KernelCompilationPlanBuilderTest, PreservesExplicitStagesAndManifestCommand
300 const void* argumentAddresses[] = {tilingBytes};300 const void* argumentAddresses[] = {tilingBytes};
301 const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};301 const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
302 NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);302 NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
303- request.compilationVariant = CompilationVariant::BasicWithSuperKernel;303+ request.compilationVariant = KernelCompilationVariant::BasicWithSuperKernel;
304 request.compilerOptions.superKernelOptions = {"-g"};304 request.compilerOptions.superKernelOptions = {"-g"};
305 305 
306 KernelCompilationPlan plan;306 KernelCompilationPlan plan;
@@ -335,7 +335,7 @@ TEST(KernelCompilationPlanBuilderTest, SelectsAllRequestedObjectsInManifestOrder
335 const void* argumentAddresses[] = {tilingBytes};335 const void* argumentAddresses[] = {tilingBytes};
336 const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};336 const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
337 NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);337 NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
338- request.compilationVariant = CompilationVariant::BasicWithSuperKernel;338+ request.compilationVariant = KernelCompilationVariant::BasicWithSuperKernel;
339 339 
340 Json manifest = CreateManifest();340 Json manifest = CreateManifest();
341 Json secondBasicObject = manifest["kernels"][1]["objects"][0];341 Json secondBasicObject = manifest["kernels"][1]["objects"][0];
@@ -398,7 +398,7 @@ TEST(KernelCompilationPlanBuilderTest, RequiresOnlyEnvironmentUsedBySelectedObje
398 .BuildCompilationPlan(plan),398 .BuildCompilationPlan(plan),
399 0);399 0);
400 400 
401- request.compilationVariant = CompilationVariant::BasicWithSuperKernel;401+ request.compilationVariant = KernelCompilationVariant::BasicWithSuperKernel;
402 EXPECT_EQ(402 EXPECT_EQ(
403 KernelCompilationPlanBuilder(403 KernelCompilationPlanBuilder(
404 request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())404 request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
@@ -673,7 +673,7 @@ TEST(KernelCompilationPlanBuilderTest, RequiresBasicAndRequestedSuperKernelObjec
673 .BuildCompilationPlan(plan),673 .BuildCompilationPlan(plan),
674 ascendc::aclrtc::ACLRTC_ERROR_FAILURE);674 ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
675 675 
676- request.compilationVariant = CompilationVariant::BasicWithSuperKernel;676+ request.compilationVariant = KernelCompilationVariant::BasicWithSuperKernel;
677 Json manifestWithoutSuperKernel = CreateManifest();677 Json manifestWithoutSuperKernel = CreateManifest();
678 manifestWithoutSuperKernel["kernels"][1]["objects"].erase(1);678 manifestWithoutSuperKernel["kernels"][1]["objects"].erase(1);
679 EXPECT_EQ(679 EXPECT_EQ(
@@ -227,7 +227,7 @@ TEST(KernelCompilationWorkspaceTest, RemovesWorktreeBeforeOutputPublicationWhenN
227{227{
228 const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_prepublish_cleanup");228 const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_prepublish_cleanup");
229 KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RemoveAfterCompilation);229 KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RemoveAfterCompilation);
230- EXPECT_EQ(workspace.RemoveWorktreeBeforePublishingElfIfNeeded(), 0);230+ EXPECT_EQ(workspace.CleanupWorktreeBeforeElfPublication(), 0);
231 EXPECT_FALSE(fs::exists(worktreePath));231 EXPECT_FALSE(fs::exists(worktreePath));
232}232}
233 233 
@@ -266,11 +266,11 @@ TEST(KernelCompilationWorkspaceTest, PreservesSourceWhenTemporaryPatchFilenameIs
266TEST(KernelCompilationWorkspaceTest, PrepublicationCleanupNoOpsForEmptyAndRetainedWorktrees)266TEST(KernelCompilationWorkspaceTest, PrepublicationCleanupNoOpsForEmptyAndRetainedWorktrees)
267{267{
268 KernelCompilationWorkspace emptyWorkspace({}, WorktreeRetentionPolicy::RemoveAfterCompilation);268 KernelCompilationWorkspace emptyWorkspace({}, WorktreeRetentionPolicy::RemoveAfterCompilation);
269- EXPECT_EQ(emptyWorkspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_SUCCESS);269+ EXPECT_EQ(emptyWorkspace.CleanupWorktreeBeforeElfPublication(), ascendc::aclrtc::ACLRTC_SUCCESS);
270 270 
271 const fs::path retainedWorktreePath = CreateWorkspacePath("aclrtc_workspace_retained_prepublish");271 const fs::path retainedWorktreePath = CreateWorkspacePath("aclrtc_workspace_retained_prepublish");
272 KernelCompilationWorkspace retainedWorkspace(retainedWorktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);272 KernelCompilationWorkspace retainedWorkspace(retainedWorktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
273- EXPECT_EQ(retainedWorkspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_SUCCESS);273+ EXPECT_EQ(retainedWorkspace.CleanupWorktreeBeforeElfPublication(), ascendc::aclrtc::ACLRTC_SUCCESS);
274 EXPECT_TRUE(fs::is_directory(retainedWorktreePath));274 EXPECT_TRUE(fs::is_directory(retainedWorktreePath));
275 275 
276 boost::system::error_code ignoredError;276 boost::system::error_code ignoredError;
@@ -280,7 +280,7 @@ TEST(KernelCompilationWorkspaceTest, PrepublicationCleanupNoOpsForEmptyAndRetain
280TEST(KernelCompilationWorkspaceTest, ReportsWorktreeRemovalFailure)280TEST(KernelCompilationWorkspaceTest, ReportsWorktreeRemovalFailure)
281{281{
282 KernelCompilationWorkspace workspace("/proc/self/status", WorktreeRetentionPolicy::RemoveAfterCompilation);282 KernelCompilationWorkspace workspace("/proc/self/status", WorktreeRetentionPolicy::RemoveAfterCompilation);
283- EXPECT_EQ(workspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);283+ EXPECT_EQ(workspace.CleanupWorktreeBeforeElfPublication(), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
284 EXPECT_EQ(workspace.GetWorktreePath(), "/proc/self/status");284 EXPECT_EQ(workspace.GetWorktreePath(), "/proc/self/status");
285}285}
286} // namespace286} // namespace
@@ -70,7 +70,7 @@ TEST(KernelSpecializationRequestTest, NormalizesBasicRequestAndCopiesOptions)
70 EXPECT_EQ(normalizedRequest.resourceId, RESOURCE_ID);70 EXPECT_EQ(normalizedRequest.resourceId, RESOURCE_ID);
71 EXPECT_EQ(normalizedRequest.kernelName, "add_custom_100000");71 EXPECT_EQ(normalizedRequest.kernelName, "add_custom_100000");
72 EXPECT_EQ(normalizedRequest.outputElfPath, "/tmp/kernel.elf");72 EXPECT_EQ(normalizedRequest.outputElfPath, "/tmp/kernel.elf");
73- EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::Basic);73+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::KernelCompilationVariant::Basic);
74 EXPECT_EQ(normalizedRequest.compilerOptions.basicOptions, (std::vector<std::string>{"-g", "-DVALUE=1"}));74 EXPECT_EQ(normalizedRequest.compilerOptions.basicOptions, (std::vector<std::string>{"-g", "-DVALUE=1"}));
75 EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());75 EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());
76}76}
@@ -96,7 +96,7 @@ TEST(KernelSpecializationRequestTest, EnablesSuperKernelAndRemovesControlOption)
96 96 
97 ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;97 ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
98 EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);98 EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
99- EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::BasicWithSuperKernel);99+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::KernelCompilationVariant::BasicWithSuperKernel);
100 EXPECT_EQ(normalizedRequest.compilerOptions.superKernelOptions, (std::vector<std::string>{"-g", "-DDCCI_OFF"}));100 EXPECT_EQ(normalizedRequest.compilerOptions.superKernelOptions, (std::vector<std::string>{"-g", "-DDCCI_OFF"}));
101}101}
102 102 
@@ -109,7 +109,7 @@ TEST(KernelSpecializationRequestTest, IgnoresSuperKernelOptionsWithoutEnable)
109 109 
110 ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;110 ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
111 EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);111 EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
112- EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::Basic);112+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::KernelCompilationVariant::Basic);
113 EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());113 EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());
114}114}
115 115 
@@ -9,6 +9,7 @@
9 */9 */
10 10 
11#include "specialization/kernel_specialization_diagnostics.h"11#include "specialization/kernel_specialization_diagnostics.h"
12+#include "process_executor.h"
12 13 
13#include <gtest/gtest.h>14#include <gtest/gtest.h>
14#include <mockcpp/mockcpp.hpp>15#include <mockcpp/mockcpp.hpp>
@@ -29,11 +30,12 @@
29 30 
30namespace {31namespace {
31namespace fs = boost::filesystem;32namespace fs = boost::filesystem;
33+using ascendc::ProcessExecutorResult;
34+using ascendc::ProcessOutcome;
32using ascendc::aclrtc::CompilationCommand;35using ascendc::aclrtc::CompilationCommand;
33using ascendc::aclrtc::CompilationCommandKind;36using ascendc::aclrtc::CompilationCommandKind;
34-using ascendc::aclrtc::CompilationProcessResult;37+using ascendc::aclrtc::KernelElfPublicationStatus;
35using ascendc::aclrtc::KernelSpecializationDiagnostics;38using ascendc::aclrtc::KernelSpecializationDiagnostics;
36-using ascendc::aclrtc::OutputPublicationStatus;
37using namespace mockcpp;39using namespace mockcpp;
38 40 
39int RejectReplayScriptPermissionChange(const char*, mode_t)41int RejectReplayScriptPermissionChange(const char*, mode_t)
@@ -112,10 +114,9 @@ TEST(KernelSpecializationDiagnosticsTest, SavesOnlyReviewedDiagnosticFiles)
112 "/bin/sh",114 "/bin/sh",
113 {"-c", "test \"$PWD\" = \"$1\"", "replay-working-directory-check", testRoot.string()}};115 {"-c", "test \"$PWD\" = \"$1\"", "replay-working-directory-check", testRoot.string()}};
114 diagnostics.AppendCommandToReplayScript(workingDirectoryCheck);116 diagnostics.AppendCommandToReplayScript(workingDirectoryCheck);
115- CompilationProcessResult processResult;117+ ProcessExecutorResult executorResult;
116- processResult.capturedOutput = "compiler output\n";118+ diagnostics.AppendCommandExecutionResultToLog(command, executorResult);
117- diagnostics.AppendCommandResultToCompilationLog(command, processResult);119+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", KernelElfPublicationStatus::Published);
118- diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::Published);
119 }120 }
120 121 
121 const fs::path replayScriptPath = testRoot / "aclrtc_replay.sh";122 const fs::path replayScriptPath = testRoot / "aclrtc_replay.sh";
@@ -152,9 +153,9 @@ TEST(KernelSpecializationDiagnosticsTest, DisablesDiagnosticsWhenReplayScriptCan
152 153 
153 const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};154 const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};
154 diagnostics.AppendCommandToReplayScript(command);155 diagnostics.AppendCommandToReplayScript(command);
155- diagnostics.AppendCommandResultToCompilationLog(command, CompilationProcessResult{});156+ diagnostics.AppendCommandExecutionResultToLog(command, ProcessExecutorResult{});
156 diagnostics.LogCommandFailureRecoveryHint();157 diagnostics.LogCommandFailureRecoveryHint();
157- diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::NotPublished);158+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", KernelElfPublicationStatus::NotPublished);
158 159 
159 EXPECT_FALSE(fs::exists(missingWorktreePath));160 EXPECT_FALSE(fs::exists(missingWorktreePath));
160}161}
@@ -257,21 +258,21 @@ TEST(KernelSpecializationDiagnosticsTest, RecordsTerminationKindsAndUnpublishedR
257 KernelSpecializationDiagnostics diagnostics(testRoot, "session_states");258 KernelSpecializationDiagnostics diagnostics(testRoot, "session_states");
258 const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};259 const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};
259 260 
260- CompilationProcessResult processResult;261+ ProcessExecutorResult executorResult;
261- processResult.termination = ascendc::aclrtc::CompilationProcessTermination::Signaled;262+ executorResult.outcome = ProcessOutcome::Signaled;
262- processResult.terminationCode = SIGTERM;263+ executorResult.terminationCode = SIGTERM;
263- diagnostics.AppendCommandResultToCompilationLog(command, processResult);264+ diagnostics.AppendCommandExecutionResultToLog(command, executorResult);
264- processResult.termination = ascendc::aclrtc::CompilationProcessTermination::TimedOut;265+ executorResult.outcome = ProcessOutcome::TimedOut;
265- diagnostics.AppendCommandResultToCompilationLog(command, processResult);266+ diagnostics.AppendCommandExecutionResultToLog(command, executorResult);
266- processResult.termination = static_cast<ascendc::aclrtc::CompilationProcessTermination>(UINT32_MAX);267+ executorResult.outcome = static_cast<ProcessOutcome>(UINT32_MAX);
267- diagnostics.AppendCommandResultToCompilationLog(command, processResult);268+ diagnostics.AppendCommandExecutionResultToLog(command, executorResult);
268 diagnostics.LogCommandFailureRecoveryHint();269 diagnostics.LogCommandFailureRecoveryHint();
269- diagnostics.WriteSpecializationResult("/tmp/not_published.elf", OutputPublicationStatus::NotPublished);270+ diagnostics.WriteSpecializationResult("/tmp/not_published.elf", KernelElfPublicationStatus::NotPublished);
270 271 
271 const std::string compilationLog = ReadTextFile(testRoot / "aclrtc_compile.log");272 const std::string compilationLog = ReadTextFile(testRoot / "aclrtc_compile.log");
272- EXPECT_NE(compilationLog.find("termination=signaled"), std::string::npos);273+ EXPECT_NE(compilationLog.find("outcome=signaled"), std::string::npos);
273- EXPECT_NE(compilationLog.find("termination=timed_out"), std::string::npos);274+ EXPECT_NE(compilationLog.find("outcome=timed_out"), std::string::npos);
274- EXPECT_NE(compilationLog.find("termination=unknown"), std::string::npos);275+ EXPECT_NE(compilationLog.find("outcome=unknown"), std::string::npos);
275 const nlohmann::json result = nlohmann::json::parse(ReadTextFile(testRoot / "aclrtc_result.json"));276 const nlohmann::json result = nlohmann::json::parse(ReadTextFile(testRoot / "aclrtc_result.json"));
276 EXPECT_FALSE(result.at("output_published"));277 EXPECT_FALSE(result.at("output_published"));
277 278 
@@ -292,8 +293,8 @@ TEST(KernelSpecializationDiagnosticsTest, IgnoresDiagnosticWriteFailuresAfterWor
292 CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {"argument"}};293 CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {"argument"}};
293 diagnostics.WriteManifestSnapshot(nlohmann::json{{"schema_version", "1.0"}});294 diagnostics.WriteManifestSnapshot(nlohmann::json{{"schema_version", "1.0"}});
294 diagnostics.AppendCommandToReplayScript(command);295 diagnostics.AppendCommandToReplayScript(command);
295- diagnostics.AppendCommandResultToCompilationLog(command, CompilationProcessResult{});296+ diagnostics.AppendCommandExecutionResultToLog(command, ProcessExecutorResult{});
296- diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::NotPublished);297+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", KernelElfPublicationStatus::NotPublished);
297 298 
298 EXPECT_FALSE(fs::exists(testRoot));299 EXPECT_FALSE(fs::exists(testRoot));
299}300}
@@ -31,6 +31,7 @@
31namespace {31namespace {
32namespace fs = boost::filesystem;32namespace fs = boost::filesystem;
33using Json = nlohmann::json;33using Json = nlohmann::json;
34+using ascendc::DirectoryCleanupGuard;
34using ascendc::aclrtc::KernelCompilationWorkspace;35using ascendc::aclrtc::KernelCompilationWorkspace;
35using ascendc::aclrtc::KernelSpecializationSession;36using ascendc::aclrtc::KernelSpecializationSession;
36using ascendc::aclrtc::MaterializedKernelCompilationResource;37using ascendc::aclrtc::MaterializedKernelCompilationResource;
@@ -193,14 +194,13 @@ public:
193 std::vector<ResourceFileData> resourceFiles = {{"payload.bin", "payload.bin", {0x01U, 0x02U}}})194 std::vector<ResourceFileData> resourceFiles = {{"payload.bin", "payload.bin", {0x01U, 0x02U}}})
194 : registry_(ResourceRegistry::Instance()),195 : registry_(ResourceRegistry::Instance()),
195 resourceId_(std::move(resourceId)),196 resourceId_(std::move(resourceId)),
196- previousTemporaryRoot_(registry_.temporaryRoot_),197+ previousOwnedMaterializationRoot_(std::move(registry_.ownedMaterializationRoot_)),
197 previousAutomaticLoadAttempted_(registry_.automaticLoadAttempted_),198 previousAutomaticLoadAttempted_(registry_.automaticLoadAttempted_),
198- previousKeepTemporaryRoot_(registry_.keepTemporaryRoot_),199+ previousRetainMaterializedDirectories_(registry_.retainMaterializedDirectories_),
199 previousAutomaticLoadStatus_(registry_.automaticLoadStatus_)200 previousAutomaticLoadStatus_(registry_.automaticLoadStatus_)
200 {201 {
201- registry_.temporaryRoot_.clear();
202 registry_.automaticLoadAttempted_ = true;202 registry_.automaticLoadAttempted_ = true;
203- registry_.keepTemporaryRoot_ = false;203+ registry_.retainMaterializedDirectories_ = false;
204 registry_.automaticLoadStatus_ = ResourceStatus::Success;204 registry_.automaticLoadStatus_ = ResourceStatus::Success;
205 205 
206 std::unique_ptr<ResourceEntry> resourceEntry(new ResourceEntry());206 std::unique_ptr<ResourceEntry> resourceEntry(new ResourceEntry());
@@ -216,11 +216,9 @@ public:
216 if (inserted_) {216 if (inserted_) {
217 registry_.externalResources_.erase(resourceId_);217 registry_.externalResources_.erase(resourceId_);
218 }218 }
219- boost::system::error_code ignoredError;219+ registry_.ownedMaterializationRoot_ = std::move(previousOwnedMaterializationRoot_);
220- fs::remove_all(registry_.temporaryRoot_, ignoredError);
221- registry_.temporaryRoot_ = previousTemporaryRoot_;
222 registry_.automaticLoadAttempted_ = previousAutomaticLoadAttempted_;220 registry_.automaticLoadAttempted_ = previousAutomaticLoadAttempted_;
223- registry_.keepTemporaryRoot_ = previousKeepTemporaryRoot_;221+ registry_.retainMaterializedDirectories_ = previousRetainMaterializedDirectories_;
224 registry_.automaticLoadStatus_ = previousAutomaticLoadStatus_;222 registry_.automaticLoadStatus_ = previousAutomaticLoadStatus_;
225 }223 }
226 224 
@@ -231,9 +229,9 @@ public:
231private:229private:
232 ResourceRegistry& registry_;230 ResourceRegistry& registry_;
233 std::string resourceId_;231 std::string resourceId_;
234- std::string previousTemporaryRoot_;232+ DirectoryCleanupGuard previousOwnedMaterializationRoot_;
235 bool previousAutomaticLoadAttempted_{false};233 bool previousAutomaticLoadAttempted_{false};
236- bool previousKeepTemporaryRoot_{false};234+ bool previousRetainMaterializedDirectories_{false};
237 ResourceStatus previousAutomaticLoadStatus_{ResourceStatus::Success};235 ResourceStatus previousAutomaticLoadStatus_{ResourceStatus::Success};
238 bool inserted_{false};236 bool inserted_{false};
239};237};
@@ -367,13 +365,13 @@ TEST(KernelSpecializationSessionTest, LoadsAndCopiesFreshMaterializedPathsFromRe
367 registeredResource.SetAutomaticLoadStatus(ResourceStatus::NotFound);365 registeredResource.SetAutomaticLoadStatus(ResourceStatus::NotFound);
368 MaterializedKernelCompilationResource rejectedResource;366 MaterializedKernelCompilationResource rejectedResource;
369 EXPECT_EQ(367 EXPECT_EQ(
370- KernelSpecializationSession::PrepareCompilationResource(resourceId, rejectedResource),368+ KernelSpecializationSession::LoadAndMaterializeCompilationResource(resourceId, rejectedResource),
371 ascendc::aclrtc::ACLRTC_ERROR_FAILURE);369 ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
372 370 
373 registeredResource.SetAutomaticLoadStatus(ResourceStatus::Success);371 registeredResource.SetAutomaticLoadStatus(ResourceStatus::Success);
374 MaterializedKernelCompilationResource firstResource;372 MaterializedKernelCompilationResource firstResource;
375 ASSERT_EQ(373 ASSERT_EQ(
376- KernelSpecializationSession::PrepareCompilationResource(resourceId, firstResource),374+ KernelSpecializationSession::LoadAndMaterializeCompilationResource(resourceId, firstResource),
377 ascendc::aclrtc::ACLRTC_SUCCESS);375 ascendc::aclrtc::ACLRTC_SUCCESS);
378 EXPECT_EQ(firstResource.manifest, manifest);376 EXPECT_EQ(firstResource.manifest, manifest);
379 EXPECT_EQ(firstResource.externalSourceDirectoryPath, sourceDirectoryPath);377 EXPECT_EQ(firstResource.externalSourceDirectoryPath, sourceDirectoryPath);
@@ -381,7 +379,7 @@ TEST(KernelSpecializationSessionTest, LoadsAndCopiesFreshMaterializedPathsFromRe
381 379 
382 MaterializedKernelCompilationResource secondResource;380 MaterializedKernelCompilationResource secondResource;
383 ASSERT_EQ(381 ASSERT_EQ(
384- KernelSpecializationSession::PrepareCompilationResource(resourceId, secondResource),382+ KernelSpecializationSession::LoadAndMaterializeCompilationResource(resourceId, secondResource),
385 ascendc::aclrtc::ACLRTC_SUCCESS);383 ascendc::aclrtc::ACLRTC_SUCCESS);
386 EXPECT_NE(firstResource.ownedResourceDirectoryPath, secondResource.ownedResourceDirectoryPath);384 EXPECT_NE(firstResource.ownedResourceDirectoryPath, secondResource.ownedResourceDirectoryPath);
387 EXPECT_TRUE(fs::is_regular_file(secondResource.ownedResourceDirectoryPath / "payload.bin"));385 EXPECT_TRUE(fs::is_regular_file(secondResource.ownedResourceDirectoryPath / "payload.bin"));
@@ -1202,8 +1202,8 @@ TEST_F(ResourceRegistryTest, LookupReportsTemporaryRootAndMaterializationFailure
1202 EXPECT_EQ(registry_->Lookup("bad-temp", output), ResourceStatus::IoError);1202 EXPECT_EQ(registry_->Lookup("bad-temp", output), ResourceStatus::IoError);
1203 EXPECT_TRUE(output.resourceDir.empty());1203 EXPECT_TRUE(output.resourceDir.empty());
1204 1204 
1205- registry_->temporaryRoot_ = Path("forced-materialize").string();1205+ ASSERT_TRUE(registry_->ownedMaterializationRoot_.RegisterDirectoryForCleanup(Path("forced-materialize").string()));
1206- WriteFile(fs::path(registry_->temporaryRoot_) / "external", "blocking file");1206+ WriteFile(fs::path(registry_->ownedMaterializationRoot_.GetDirectoryPath()) / "external", "blocking file");
1207 output.resourceDir = "stale";1207 output.resourceDir = "stale";
1208 EXPECT_EQ(registry_->Lookup("bad-temp", output), ResourceStatus::IoError);1208 EXPECT_EQ(registry_->Lookup("bad-temp", output), ResourceStatus::IoError);
1209 EXPECT_TRUE(output.resourceDir.empty());1209 EXPECT_TRUE(output.resourceDir.empty());
@@ -1216,7 +1216,7 @@ TEST_F(ResourceRegistryTest, DestructorHonorsSaveKernelMetaAtTemporaryRootCreati
1216 registry_->externalResources_.emplace("keep-temp", MakeEntry(ResourceSourceType::External));1216 registry_->externalResources_.emplace("keep-temp", MakeEntry(ResourceSourceType::External));
1217 ResourceData output;1217 ResourceData output;
1218 EXPECT_EQ(registry_->Lookup("keep-temp", output), ResourceStatus::Success);1218 EXPECT_EQ(registry_->Lookup("keep-temp", output), ResourceStatus::Success);
1219- const std::string retainedRoot = registry_->temporaryRoot_;1219+ const std::string retainedRoot = registry_->ownedMaterializationRoot_.GetDirectoryPath();
1220 ASSERT_TRUE(fs::is_directory(retainedRoot));1220 ASSERT_TRUE(fs::is_directory(retainedRoot));
1221 registry_.reset();1221 registry_.reset();
1222 EXPECT_TRUE(fs::is_directory(retainedRoot));1222 EXPECT_TRUE(fs::is_directory(retainedRoot));
@@ -1225,7 +1225,7 @@ TEST_F(ResourceRegistryTest, DestructorHonorsSaveKernelMetaAtTemporaryRootCreati
1225 SetEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", "true");1225 SetEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", "true");
1226 registry_->externalResources_.emplace("remove-temp", MakeEntry(ResourceSourceType::External));1226 registry_->externalResources_.emplace("remove-temp", MakeEntry(ResourceSourceType::External));
1227 EXPECT_EQ(registry_->Lookup("remove-temp", output), ResourceStatus::Success);1227 EXPECT_EQ(registry_->Lookup("remove-temp", output), ResourceStatus::Success);
1228- const std::string removedRoot = registry_->temporaryRoot_;1228+ const std::string removedRoot = registry_->ownedMaterializationRoot_.GetDirectoryPath();
1229 ASSERT_TRUE(fs::is_directory(removedRoot));1229 ASSERT_TRUE(fs::is_directory(removedRoot));
1230 registry_.reset();1230 registry_.reset();
1231 EXPECT_FALSE(fs::exists(removedRoot));1231 EXPECT_FALSE(fs::exists(removedRoot));
@@ -1241,8 +1241,8 @@ TEST_F(ResourceRegistryTest, SaveKernelMetaTrimsWhitespaceAndKeepsFailedMaterial
1241 ResourceData output;1241 ResourceData output;
1242 EXPECT_EQ(registry_->Lookup("failed-materialization", output), ResourceStatus::IoError);1242 EXPECT_EQ(registry_->Lookup("failed-materialization", output), ResourceStatus::IoError);
1243 EXPECT_TRUE(output.resourceDir.empty());1243 EXPECT_TRUE(output.resourceDir.empty());
1244- EXPECT_TRUE(registry_->keepTemporaryRoot_);1244+ EXPECT_TRUE(registry_->retainMaterializedDirectories_);
1245- const fs::path retainedRoot(registry_->temporaryRoot_);1245+ const fs::path retainedRoot(registry_->ownedMaterializationRoot_.GetDirectoryPath());
1246 const fs::path categoryParent = retainedRoot / "external";1246 const fs::path categoryParent = retainedRoot / "external";
1247 std::vector<fs::path> retainedCategories;1247 std::vector<fs::path> retainedCategories;
1248 for (fs::directory_iterator current(categoryParent), end; current != end; ++current) {1248 for (fs::directory_iterator current(categoryParent), end; current != end; ++current) {
@@ -202,21 +202,21 @@ add_executable(ascendc_ut_asc_compile_exporter
202 ${ASCENDC_DIR}/tests/main.cpp202 ${ASCENDC_DIR}/tests/main.cpp
203 asc_compile_exporter/test_asc_compile_exporter_unit.cpp203 asc_compile_exporter/test_asc_compile_exporter_unit.cpp
204 asc_compile_exporter/test_asc_compile_exporter.cpp204 asc_compile_exporter/test_asc_compile_exporter.cpp
205- asc_compile_exporter/test_build_workspace.cpp
206 asc_compile_exporter/test_bundle_source_generator.cpp205 asc_compile_exporter/test_bundle_source_generator.cpp
207 asc_compile_exporter/test_collected_manifest_repository.cpp206 asc_compile_exporter/test_collected_manifest_repository.cpp
208 asc_compile_exporter/test_file_utils.cpp207 asc_compile_exporter/test_file_utils.cpp
209 asc_compile_exporter/test_manifest_bundle_types.cpp208 asc_compile_exporter/test_manifest_bundle_types.cpp
210- asc_compile_exporter/test_process_runner.cpp209+ common/test_process_executor.cpp
211 asc_compile_exporter/test_resource_manifest_validator.cpp210 asc_compile_exporter/test_resource_manifest_validator.cpp
211+ common/test_directory_cleanup_guard.cpp
212 asc_compile_exporter/test_ascendc_tool_log.cpp212 asc_compile_exporter/test_ascendc_tool_log.cpp
213- ${ASCENDC_DIR}/tools/build/asc_compile_exporter/build_workspace.cpp
214 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp213 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/bundle_source_generator.cpp
215 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp214 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/collected_manifest_repository.cpp
216 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp215 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/manifest_bundle_compiler.cpp
217 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/resource_manifest_validator.cpp216 ${ASCENDC_DIR}/tools/build/asc_compile_exporter/resource_manifest_validator.cpp
218 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp217 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp
219- ${ASCENDC_DIR}/tools/build/common/process_runner.cpp218+ ${ASCENDC_DIR}/tools/build/common/process_executor.cpp
219+ ${ASCENDC_DIR}/tools/build/common/directory_cleanup_guard.cpp
220)220)
221 221 
222function(configure_asc_compile_exporter_test target_name)222function(configure_asc_compile_exporter_test target_name)
@@ -235,9 +235,7 @@ function(configure_asc_compile_exporter_test target_name)
235 )235 )
236 236 
237 target_link_options(${target_name} PRIVATE237 target_link_options(${target_name} PRIVATE
238- -Wl,--wrap=fork
239 -Wl,--wrap=rename238 -Wl,--wrap=rename
240- -Wl,--wrap=waitpid
241 )239 )
242 240 
243 target_link_libraries(${target_name} PRIVATE241 target_link_libraries(${target_name} PRIVATE
@@ -15,6 +15,7 @@
15#include <unistd.h>15#include <unistd.h>
16 16 
17#include <cerrno>17#include <cerrno>
18+#include <chrono>
18#include <cstdlib>19#include <cstdlib>
19#include <cstring>20#include <cstring>
20#include <fstream>21#include <fstream>
@@ -28,13 +29,14 @@
28 29 
29#include "ascendc_manifest_abi.h"30#include "ascendc_manifest_abi.h"
30#include "manifest_bundle_compiler.h"31#include "manifest_bundle_compiler.h"
31-#include "process_runner.h"32+#include "process_executor.h"
32#include "test_support.h"33#include "test_support.h"
33 34 
34namespace {35namespace {
35 36 
36using asc_compile_exporter_test::ScopedEnvironment;37using asc_compile_exporter_test::ScopedEnvironment;
37-using ascendc::ProcessRunner;38+using ascendc::ProcessExecutor;
39+using ascendc::ProcessExecutorRequest;
38using ascendc::manifest_generator::BuildCollectedBundleRequest;40using ascendc::manifest_generator::BuildCollectedBundleRequest;
39using ascendc::manifest_generator::BundleOutputKind;41using ascendc::manifest_generator::BundleOutputKind;
40using ascendc::manifest_generator::ManifestBundleCompiler;42using ascendc::manifest_generator::ManifestBundleCompiler;
@@ -371,6 +373,7 @@ TEST_F(AscCompileExporterTest, BuildsLoadableBundleAndCleansTemporaryWorkspace)
371{373{
372 ScopedEnvironment temporary("TMPDIR", root_.c_str());374 ScopedEnvironment temporary("TMPDIR", root_.c_str());
373 ScopedEnvironment cannHome("ASCEND_HOME_PATH", nullptr);375 ScopedEnvironment cannHome("ASCEND_HOME_PATH", nullptr);
376+ ScopedEnvironment inheritedMakeFlags("MAKEFLAGS", "--definitely-invalid-option");
374 const std::string first = JoinTestPath(root_, "collection/nested/First");377 const std::string first = JoinTestPath(root_, "collection/nested/First");
375 const std::string second = JoinTestPath(root_, "collection/Second");378 const std::string second = JoinTestPath(root_, "collection/Second");
376 WriteFile(JoinTestPath(first, "resources/kernel.cpp"), "first source");379 WriteFile(JoinTestPath(first, "resources/kernel.cpp"), "first source");
@@ -450,7 +453,11 @@ TEST_F(AscCompileExporterTest, BuildsRelocatableObjectThatCanBeLinkedIntoSharedL
450 EXPECT_EQ(elfType, ET_REL);453 EXPECT_EQ(elfType, ET_REL);
451 454 
452 const std::string linkedBundle = JoinTestPath(root_, "output/linked-bundle.so");455 const std::string linkedBundle = JoinTestPath(root_, "output/linked-bundle.so");
453- ASSERT_TRUE(ProcessRunner::Run({request.cxxCompiler, "-shared", request.outputPath, "-o", linkedBundle}, {}));456+ ProcessExecutorRequest linkRequest;
457+ linkRequest.arguments = {request.cxxCompiler, "-shared", request.outputPath, "-o", linkedBundle};
458+ linkRequest.executionTimeout = std::chrono::minutes(5);
459+ linkRequest.terminationGracePeriod = std::chrono::seconds(1);
460+ ASSERT_TRUE(ProcessExecutor::Execute(linkRequest).HasSuccessfulExit());
454 461 
455 void* handle = dlopen(linkedBundle.c_str(), RTLD_NOW | RTLD_LOCAL);462 void* handle = dlopen(linkedBundle.c_str(), RTLD_NOW | RTLD_LOCAL);
456 const char* loadError = handle == nullptr ? dlerror() : nullptr;463 const char* loadError = handle == nullptr ? dlerror() : nullptr;
@@ -1,70 +0,0 @@
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
@@ -1,112 +0,0 @@
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,172 @@
1+/**
2+ * Copyright (c) 2026 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 "file_utils.h"
14+#include "directory_cleanup_guard.h"
15+ 
16+#include <cstdlib>
17+#include <string>
18+#include <sys/stat.h>
19+#include <utility>
20+#include <vector>
21+ 
22+namespace ascendc {
23+namespace {
24+ 
25+bool PathExistsWithoutFollowingSymlinks(const std::string& path)
26+{
27+ struct stat pathStatus {};
28+ return lstat(path.c_str(), &pathStatus) == 0;
29+}
30+ 
31+class DirectoryCleanupGuardTest : public testing::Test {
32+protected:
33+ void SetUp() override
34+ {
35+ const char* configuredTemporaryDirectory = std::getenv("TMPDIR");
36+ const std::string parentDirectoryPath =
37+ configuredTemporaryDirectory == nullptr || configuredTemporaryDirectory[0] == '\0' ?
38+ "/tmp" :
39+ configuredTemporaryDirectory;
40+ std::string rootDirectoryPattern =
41+ FileUtils::JoinPath(parentDirectoryPath, "directory_cleanup_guard_ut_XXXXXX");
42+ std::vector<char> writablePattern(rootDirectoryPattern.begin(), rootDirectoryPattern.end());
43+ writablePattern.push_back('\0');
44+ char* createdDirectoryPath = mkdtemp(writablePattern.data());
45+ ASSERT_NE(createdDirectoryPath, nullptr);
46+ rootDirectoryPath_ = createdDirectoryPath;
47+ }
48+ 
49+ void TearDown() override { (void)FileUtils::RemoveAll(rootDirectoryPath_); }
50+ 
51+ std::string rootDirectoryPath_;
52+};
53+ 
54+TEST_F(DirectoryCleanupGuardTest, CreatesUniqueDirectoryAndRemovesItOnDestruction)
55+{
56+ const std::string parentPath = FileUtils::JoinPath(rootDirectoryPath_, "temporary-directories");
57+ ASSERT_TRUE(FileUtils::CreateDirectories(parentPath));
58+ std::string removedDirectoryPath;
59+ {
60+ DirectoryCleanupGuard directory;
61+ ASSERT_TRUE(directory.CreateUniqueSubdirectory(parentPath, "test"));
62+ removedDirectoryPath = directory.GetDirectoryPath();
63+ EXPECT_TRUE(FileUtils::IsDirectory(removedDirectoryPath));
64+ EXPECT_TRUE(FileUtils::IsPathWithin(removedDirectoryPath, parentPath));
65+ struct stat directoryStatus {};
66+ ASSERT_EQ(stat(removedDirectoryPath.c_str(), &directoryStatus), 0);
67+ constexpr mode_t permissionBits = S_IRWXU | S_IRWXG | S_IRWXO;
68+ EXPECT_EQ(directoryStatus.st_mode & permissionBits, S_IRWXU);
69+ EXPECT_FALSE(directory.CreateUniqueSubdirectory(parentPath, "second"));
70+ }
71+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(removedDirectoryPath));
72+}
73+ 
74+TEST_F(DirectoryCleanupGuardTest, PreservesCreatedOrRegisteredDirectory)
75+{
76+ const std::string createdParentPath = FileUtils::JoinPath(rootDirectoryPath_, "retained-created");
77+ ASSERT_TRUE(FileUtils::CreateDirectories(createdParentPath));
78+ std::string retainedCreatedPath;
79+ {
80+ DirectoryCleanupGuard directory;
81+ ASSERT_TRUE(directory.CreateUniqueSubdirectory(createdParentPath, "created"));
82+ retainedCreatedPath = directory.GetDirectoryPath();
83+ directory.PreserveDirectory();
84+ }
85+ EXPECT_TRUE(FileUtils::IsDirectory(retainedCreatedPath));
86+ 
87+ const std::string retainedRegisteredPath = FileUtils::JoinPath(rootDirectoryPath_, "retained-registered");
88+ ASSERT_TRUE(FileUtils::CreateDirectories(retainedRegisteredPath));
89+ {
90+ DirectoryCleanupGuard directory;
91+ ASSERT_TRUE(directory.RegisterDirectoryForCleanup(retainedRegisteredPath));
92+ directory.PreserveDirectory();
93+ }
94+ EXPECT_TRUE(FileUtils::IsDirectory(retainedRegisteredPath));
95+}
96+ 
97+TEST_F(DirectoryCleanupGuardTest, MovesDirectoryCleanupResponsibilityExactlyOnce)
98+{
99+ const std::string parentPath = FileUtils::JoinPath(rootDirectoryPath_, "moved");
100+ ASSERT_TRUE(FileUtils::CreateDirectories(parentPath));
101+ std::string movedDirectoryPath;
102+ {
103+ DirectoryCleanupGuard original;
104+ ASSERT_TRUE(original.CreateUniqueSubdirectory(parentPath, "original"));
105+ movedDirectoryPath = original.GetDirectoryPath();
106+ 
107+ DirectoryCleanupGuard moved(std::move(original));
108+ EXPECT_TRUE(original.GetDirectoryPath().empty());
109+ EXPECT_EQ(moved.GetDirectoryPath(), movedDirectoryPath);
110+ 
111+ DirectoryCleanupGuard assigned;
112+ assigned = std::move(moved);
113+ EXPECT_TRUE(moved.GetDirectoryPath().empty());
114+ EXPECT_EQ(assigned.GetDirectoryPath(), movedDirectoryPath);
115+ }
116+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(movedDirectoryPath));
117+}
118+ 
119+TEST_F(DirectoryCleanupGuardTest, RemovesRegisteredDirectoryOnRequest)
120+{
121+ const std::string registeredPath = FileUtils::JoinPath(rootDirectoryPath_, "registered");
122+ ASSERT_TRUE(FileUtils::CreateDirectories(registeredPath));
123+ DirectoryCleanupGuard directory;
124+ ASSERT_TRUE(directory.RegisterDirectoryForCleanup(registeredPath));
125+ EXPECT_TRUE(directory.RemoveDirectory());
126+ EXPECT_TRUE(directory.GetDirectoryPath().empty());
127+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(registeredPath));
128+ EXPECT_TRUE(directory.RemoveDirectory());
129+}
130+ 
131+TEST_F(DirectoryCleanupGuardTest, MoveAssignmentRemovesPreviouslyRegisteredDirectoryAndEmptiesSource)
132+{
133+ const std::string sourceParentPath = FileUtils::JoinPath(rootDirectoryPath_, "move-source");
134+ const std::string destinationParentPath = FileUtils::JoinPath(rootDirectoryPath_, "move-destination");
135+ ASSERT_TRUE(FileUtils::CreateDirectories(sourceParentPath));
136+ ASSERT_TRUE(FileUtils::CreateDirectories(destinationParentPath));
137+ std::string sourceDirectoryPath;
138+ std::string destinationDirectoryPath;
139+ {
140+ DirectoryCleanupGuard source;
141+ ASSERT_TRUE(source.CreateUniqueSubdirectory(sourceParentPath, "source"));
142+ sourceDirectoryPath = source.GetDirectoryPath();
143+ 
144+ DirectoryCleanupGuard destination;
145+ ASSERT_TRUE(destination.CreateUniqueSubdirectory(destinationParentPath, "destination"));
146+ destinationDirectoryPath = destination.GetDirectoryPath();
147+ destination = std::move(source);
148+ 
149+ EXPECT_EQ(destination.GetDirectoryPath(), sourceDirectoryPath);
150+ EXPECT_TRUE(source.GetDirectoryPath().empty());
151+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(destinationDirectoryPath));
152+ }
153+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(sourceDirectoryPath));
154+ EXPECT_FALSE(PathExistsWithoutFollowingSymlinks(destinationDirectoryPath));
155+}
156+ 
157+TEST_F(DirectoryCleanupGuardTest, RejectsInvalidCreationInputsAndReportsRemovalFailure)
158+{
159+ DirectoryCleanupGuard directory;
160+ EXPECT_FALSE(directory.CreateUniqueSubdirectory(FileUtils::JoinPath(rootDirectoryPath_, "missing"), "test"));
161+ EXPECT_FALSE(directory.CreateUniqueSubdirectory(rootDirectoryPath_, "nested/test"));
162+ EXPECT_FALSE(directory.CreateUniqueSubdirectory(rootDirectoryPath_, "invalid%prefix"));
163+ EXPECT_FALSE(directory.RegisterDirectoryForCleanup(std::string()));
164+ 
165+ ASSERT_TRUE(directory.RegisterDirectoryForCleanup("/proc/self/status"));
166+ EXPECT_FALSE(directory.RemoveDirectory());
167+ EXPECT_EQ(directory.GetDirectoryPath(), "/proc/self/status");
168+ directory.PreserveDirectory();
169+}
170+ 
171+} // namespace
172+} // namespace ascendc
@@ -0,0 +1,305 @@
1+/**
2+ * Copyright (c) 2026 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 "process_executor.h"
14+ 
15+#include <boost/filesystem.hpp>
16+#include <boost/system/error_code.hpp>
17+#include <chrono>
18+#include <csignal>
19+#include <cstdlib>
20+#include <fstream>
21+#include <string>
22+#include <thread>
23+#include <type_traits>
24+#include <utility>
25+#include <unistd.h>
26+#include <vector>
27+ 
28+namespace ascendc {
29+namespace {
30+namespace fs = boost::filesystem;
31+using namespace std::chrono_literals;
32+ 
33+static_assert(
34+ std::is_same<std::underlying_type_t<ProcessOutcome>, uint32_t>::value, "ProcessOutcome must have a stable width");
35+static_assert(
36+ std::is_same<decltype(ProcessExecutorResult::terminationCode), int32_t>::value,
37+ "terminationCode must have a stable width");
38+ 
39+ProcessExecutorRequest CreateProcessRequest(std::vector<std::string> arguments)
40+{
41+ ProcessExecutorRequest request;
42+ request.arguments = std::move(arguments);
43+ request.executionTimeout = 1s;
44+ request.terminationGracePeriod = 20ms;
45+ return request;
46+}
47+ 
48+std::string ReadTextFile(const fs::path& filePath)
49+{
50+ std::ifstream inputStream(filePath.string());
51+ return {std::istreambuf_iterator<char>(inputStream), std::istreambuf_iterator<char>()};
52+}
53+ 
54+class TemporarilyClosedStandardOutputStreams final {
55+public:
56+ ~TemporarilyClosedStandardOutputStreams() { (void)Restore(); }
57+ 
58+ bool Close()
59+ {
60+ savedStandardOutput_ = dup(STDOUT_FILENO);
61+ savedStandardError_ = dup(STDERR_FILENO);
62+ if (savedStandardOutput_ < 0 || savedStandardError_ < 0) {
63+ return false;
64+ }
65+ const bool standardOutputClosed = close(STDOUT_FILENO) == 0;
66+ const bool standardErrorClosed = close(STDERR_FILENO) == 0;
67+ return standardOutputClosed && standardErrorClosed;
68+ }
69+ 
70+ bool Restore()
71+ {
72+ bool restored = true;
73+ if (savedStandardOutput_ >= 0) {
74+ restored = dup2(savedStandardOutput_, STDOUT_FILENO) == STDOUT_FILENO && restored;
75+ restored = close(savedStandardOutput_) == 0 && restored;
76+ savedStandardOutput_ = -1;
77+ }
78+ if (savedStandardError_ >= 0) {
79+ restored = dup2(savedStandardError_, STDERR_FILENO) == STDERR_FILENO && restored;
80+ restored = close(savedStandardError_) == 0 && restored;
81+ savedStandardError_ = -1;
82+ }
83+ return restored;
84+ }
85+ 
86+private:
87+ int savedStandardOutput_{-1};
88+ int savedStandardError_{-1};
89+};
90+ 
91+TEST(ProcessExecutorTest, RejectsInvalidRequestsBeforeSpawning)
92+{
93+ ProcessExecutorRequest emptyArgumentsRequest;
94+ emptyArgumentsRequest.executionTimeout = 1s;
95+ EXPECT_EQ(ProcessExecutor::Execute(emptyArgumentsRequest).outcome, ProcessOutcome::ExecutorFailure);
96+ 
97+ ProcessExecutorRequest zeroTimeoutRequest = CreateProcessRequest({"/bin/true"});
98+ zeroTimeoutRequest.executionTimeout = 0ms;
99+ EXPECT_EQ(ProcessExecutor::Execute(zeroTimeoutRequest).outcome, ProcessOutcome::ExecutorFailure);
100+ 
101+ ProcessExecutorRequest negativeGracePeriodRequest = CreateProcessRequest({"/bin/true"});
102+ negativeGracePeriodRequest.terminationGracePeriod = -1ms;
103+ EXPECT_EQ(ProcessExecutor::Execute(negativeGracePeriodRequest).outcome, ProcessOutcome::ExecutorFailure);
104+ 
105+ ProcessExecutorRequest embeddedNullRequest = CreateProcessRequest({std::string("/bin/true\0invalid", 17U)});
106+ EXPECT_EQ(ProcessExecutor::Execute(embeddedNullRequest).outcome, ProcessOutcome::ExecutorFailure);
107+ 
108+ ProcessExecutorRequest invalidOverrideNameRequest = CreateProcessRequest({"/bin/true"});
109+ invalidOverrideNameRequest.environmentOverrides.emplace_back("INVALID=NAME", "value");
110+ EXPECT_EQ(ProcessExecutor::Execute(invalidOverrideNameRequest).outcome, ProcessOutcome::ExecutorFailure);
111+ 
112+ ProcessExecutorRequest embeddedNullOverrideValueRequest = CreateProcessRequest({"/bin/true"});
113+ embeddedNullOverrideValueRequest.environmentOverrides.emplace_back("VALID_NAME", std::string("value\0suffix", 12U));
114+ EXPECT_EQ(ProcessExecutor::Execute(embeddedNullOverrideValueRequest).outcome, ProcessOutcome::ExecutorFailure);
115+ 
116+ ProcessExecutorRequest invalidRemovalNameRequest = CreateProcessRequest({"/bin/true"});
117+ invalidRemovalNameRequest.removedEnvironmentVariables.emplace_back("INVALID=NAME");
118+ EXPECT_EQ(ProcessExecutor::Execute(invalidRemovalNameRequest).outcome, ProcessOutcome::ExecutorFailure);
119+}
120+ 
121+TEST(ProcessExecutorTest, ExecutesArgumentsWithoutShellParsing)
122+{
123+ const fs::path unexpectedFilePath =
124+ fs::temp_directory_path() / ("process_executor_shell_" + std::to_string(getpid()));
125+ boost::system::error_code ignoredError;
126+ fs::remove(unexpectedFilePath, ignoredError);
127+ 
128+ const fs::path outputLogPath = fs::temp_directory_path() / fs::unique_path("process_executor_output_%%%%-%%%%");
129+ ProcessExecutorRequest request =
130+ CreateProcessRequest({"/usr/bin/printf", "$(touch " + unexpectedFilePath.string() + ")"});
131+ request.mirroredOutputLogFilePath = outputLogPath.string();
132+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
133+ 
134+ EXPECT_TRUE(result.HasSuccessfulExit());
135+ EXPECT_EQ(ReadTextFile(outputLogPath), "$(touch " + unexpectedFilePath.string() + ")");
136+ EXPECT_FALSE(fs::exists(unexpectedFilePath));
137+ fs::remove(outputLogPath, ignoredError);
138+}
139+ 
140+TEST(ProcessExecutorTest, AppliesEnvironmentOverridesAndRemovals)
141+{
142+ ASSERT_EQ(setenv("PROCESS_EXECUTOR_REMOVE_ME", "inherited", 1), 0);
143+ const fs::path outputLogPath = fs::temp_directory_path() / fs::unique_path("process_executor_env_%%%%-%%%%");
144+ ProcessExecutorRequest request = CreateProcessRequest(
145+ {"/bin/sh", "-c", "printf '%s|%s' \"$PROCESS_EXECUTOR_VALUE\" \"${PROCESS_EXECUTOR_REMOVE_ME-unset}\""});
146+ request.environmentOverrides.emplace_back("PROCESS_EXECUTOR_VALUE", "visible");
147+ request.removedEnvironmentVariables.emplace_back("PROCESS_EXECUTOR_REMOVE_ME");
148+ request.mirroredOutputLogFilePath = outputLogPath.string();
149+ 
150+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
151+ EXPECT_TRUE(result.HasSuccessfulExit());
152+ EXPECT_EQ(ReadTextFile(outputLogPath), "visible|unset");
153+ EXPECT_EQ(unsetenv("PROCESS_EXECUTOR_REMOVE_ME"), 0);
154+ boost::system::error_code ignoredError;
155+ fs::remove(outputLogPath, ignoredError);
156+}
157+ 
158+TEST(ProcessExecutorTest, EnvironmentOverrideTakesPrecedenceOverRemoval)
159+{
160+ const fs::path outputLogPath = fs::temp_directory_path() / fs::unique_path("process_executor_precedence_%%%%-%%%%");
161+ ProcessExecutorRequest request =
162+ CreateProcessRequest({"/bin/sh", "-c", "printf '%s' \"$PROCESS_EXECUTOR_PRECEDENCE\""});
163+ request.removedEnvironmentVariables.emplace_back("PROCESS_EXECUTOR_PRECEDENCE");
164+ request.environmentOverrides.emplace_back("PROCESS_EXECUTOR_PRECEDENCE", "overridden");
165+ request.mirroredOutputLogFilePath = outputLogPath.string();
166+ 
167+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
168+ EXPECT_TRUE(result.HasSuccessfulExit());
169+ EXPECT_EQ(ReadTextFile(outputLogPath), "overridden");
170+ boost::system::error_code ignoredError;
171+ fs::remove(outputLogPath, ignoredError);
172+}
173+ 
174+TEST(ProcessExecutorTest, ReportsExitSignalAndSpawnFailureSeparately)
175+{
176+ const ProcessExecutorResult nonzeroExit =
177+ ProcessExecutor::Execute(CreateProcessRequest({"/bin/sh", "-c", "exit 7"}));
178+ EXPECT_EQ(nonzeroExit.outcome, ProcessOutcome::Exited);
179+ EXPECT_EQ(nonzeroExit.terminationCode, 7);
180+ 
181+ const ProcessExecutorResult signalExit =
182+ ProcessExecutor::Execute(CreateProcessRequest({"/bin/sh", "-c", "kill -TERM $$"}));
183+ EXPECT_EQ(signalExit.outcome, ProcessOutcome::Signaled);
184+ EXPECT_EQ(signalExit.terminationCode, SIGTERM);
185+ 
186+ const ProcessExecutorResult spawnFailure =
187+ ProcessExecutor::Execute(CreateProcessRequest({"/path/that/does/not/exist"}));
188+ EXPECT_EQ(spawnFailure.outcome, ProcessOutcome::ExecutorFailure);
189+}
190+ 
191+TEST(ProcessExecutorTest, RedirectsChildOutputWhenParentStandardOutputStreamsAreClosed)
192+{
193+ const fs::path outputLogPath =
194+ fs::temp_directory_path() / fs::unique_path("process_executor_closed_stdio_%%%%-%%%%");
195+ ProcessExecutorRequest request = CreateProcessRequest({"/bin/sh", "-c", "printf stdout; printf stderr >&2"});
196+ request.mirroredOutputLogFilePath = outputLogPath.string();
197+ TemporarilyClosedStandardOutputStreams standardOutputStreams;
198+ const bool streamsClosed = standardOutputStreams.Close();
199+ ProcessExecutorResult result;
200+ if (streamsClosed) {
201+ result = ProcessExecutor::Execute(request);
202+ }
203+ const bool streamsRestored = standardOutputStreams.Restore();
204+ 
205+ ASSERT_TRUE(streamsClosed);
206+ ASSERT_TRUE(streamsRestored);
207+ EXPECT_TRUE(result.HasSuccessfulExit());
208+ const std::string mirroredOutput = ReadTextFile(outputLogPath);
209+ EXPECT_NE(mirroredOutput.find("stdout"), std::string::npos);
210+ EXPECT_NE(mirroredOutput.find("stderr"), std::string::npos);
211+ boost::system::error_code ignoredError;
212+ fs::remove(outputLogPath, ignoredError);
213+}
214+ 
215+TEST(ProcessExecutorTest, MirrorsStandardOutputAndStandardErrorToOneLogFile)
216+{
217+ const fs::path outputLogPath = fs::temp_directory_path() / fs::unique_path("process_executor_streams_%%%%-%%%%");
218+ ProcessExecutorRequest request =
219+ CreateProcessRequest({"/bin/sh", "-c", "printf 'stdout-line\\n'; printf 'stderr-line\\n' >&2"});
220+ request.mirroredOutputLogFilePath = outputLogPath.string();
221+ 
222+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
223+ EXPECT_TRUE(result.HasSuccessfulExit());
224+ const std::string mirroredOutput = ReadTextFile(outputLogPath);
225+ EXPECT_NE(mirroredOutput.find("stdout-line\n"), std::string::npos);
226+ EXPECT_NE(mirroredOutput.find("stderr-line\n"), std::string::npos);
227+ boost::system::error_code ignoredError;
228+ fs::remove(outputLogPath, ignoredError);
229+}
230+ 
231+TEST(ProcessExecutorTest, DrainsProcessOutputLargerThanPipeCapacity)
232+{
233+ constexpr uintmax_t outputByteCount = 200000U;
234+ const fs::path outputLogPath = fs::temp_directory_path() / fs::unique_path("process_executor_large_%%%%-%%%%");
235+ ProcessExecutorRequest request = CreateProcessRequest({"/bin/sh", "-c", "head -c 200000 /dev/zero | tr '\\0' x"});
236+ request.mirroredOutputLogFilePath = outputLogPath.string();
237+ 
238+ EXPECT_TRUE(ProcessExecutor::Execute(request).HasSuccessfulExit());
239+ EXPECT_EQ(fs::file_size(outputLogPath), outputByteCount);
240+ boost::system::error_code ignoredError;
241+ fs::remove(outputLogPath, ignoredError);
242+}
243+ 
244+TEST(ProcessExecutorTest, DiagnosticLogFailureDoesNotFailCommand)
245+{
246+ ProcessExecutorRequest request = CreateProcessRequest({"/usr/bin/printf", "still-runs"});
247+ request.mirroredOutputLogFilePath = fs::temp_directory_path().string();
248+ 
249+ EXPECT_TRUE(ProcessExecutor::Execute(request).HasSuccessfulExit());
250+}
251+ 
252+TEST(ProcessExecutorTest, RejectsIncompatibleSigchldDispositionBeforeSpawning)
253+{
254+ const fs::path spawnMarkerPath = fs::temp_directory_path() / fs::unique_path("process_executor_sigchld_%%%%-%%%%");
255+ struct sigaction ignoredSigchld {};
256+ struct sigaction originalSigchld {};
257+ ignoredSigchld.sa_handler = SIG_IGN;
258+ ASSERT_EQ(sigemptyset(&ignoredSigchld.sa_mask), 0);
259+ ASSERT_EQ(sigaction(SIGCHLD, &ignoredSigchld, &originalSigchld), 0);
260+ 
261+ const ProcessExecutorResult result =
262+ ProcessExecutor::Execute(CreateProcessRequest({"/usr/bin/touch", spawnMarkerPath.string()}));
263+ 
264+ ASSERT_EQ(sigaction(SIGCHLD, &originalSigchld, nullptr), 0);
265+ EXPECT_EQ(result.outcome, ProcessOutcome::ExecutorFailure);
266+ EXPECT_FALSE(fs::exists(spawnMarkerPath));
267+ boost::system::error_code ignoredError;
268+ fs::remove(spawnMarkerPath, ignoredError);
269+}
270+ 
271+TEST(ProcessExecutorTest, CompletesWhenSynchronousCommandLeaderExits)
272+{
273+ ProcessExecutorRequest request = CreateProcessRequest({"/bin/sh", "-c", "sleep 0.3 & exec /bin/true"});
274+ 
275+ const auto startedAt = std::chrono::steady_clock::now();
276+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
277+ EXPECT_TRUE(result.HasSuccessfulExit());
278+ EXPECT_LT(std::chrono::steady_clock::now() - startedAt, 200ms);
279+}
280+ 
281+TEST(ProcessExecutorTest, TimeoutTerminatesActiveProcessGroup)
282+{
283+ static uint64_t sequence = 0U;
284+ const fs::path descendantMarkerPath =
285+ fs::temp_directory_path() /
286+ ("process_executor_descendant_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
287+ const std::string commandScript =
288+ "(trap '' TERM; while true; do printf x >> '" + descendantMarkerPath.string() + "'; sleep 0.01; done) & wait";
289+ ProcessExecutorRequest request = CreateProcessRequest({"/bin/sh", "-c", commandScript});
290+ request.executionTimeout = 20ms;
291+ 
292+ const ProcessExecutorResult result = ProcessExecutor::Execute(request);
293+ EXPECT_EQ(result.outcome, ProcessOutcome::TimedOut);
294+ 
295+ const uintmax_t markerSizeAfterTimeout =
296+ fs::exists(descendantMarkerPath) ? fs::file_size(descendantMarkerPath) : 0U;
297+ std::this_thread::sleep_for(100ms);
298+ const uintmax_t markerSizeAfterWait = fs::exists(descendantMarkerPath) ? fs::file_size(descendantMarkerPath) : 0U;
299+ EXPECT_EQ(markerSizeAfterWait, markerSizeAfterTimeout);
300+ boost::system::error_code ignoredError;
301+ fs::remove(descendantMarkerPath, ignoredError);
302+}
303+ 
304+} // namespace
305+} // namespace ascendc
@@ -14,6 +14,8 @@ add_library(acl_rtc SHARED
14 ${SRC}14 ${SRC}
15 ${SPECIALIZATION_SRC}15 ${SPECIALIZATION_SRC}
16 ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/file_utils.cpp16 ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/file_utils.cpp
17+ ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/process_executor.cpp
18+ ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/directory_cleanup_guard.cpp
17)19)
18 20 
19if(BUILD_OPEN_PROJECT)21if(BUILD_OPEN_PROJECT)
@@ -5,570 +5,29 @@
5 * Please refer to the License for details. You may not use this file except in compliance with 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,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.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.8+ * See LICENSE in the root of the software repository for the full text of the LICENSE.
9 */9 */
10 10 
11#include "compilation_plan_executor.h"11#include "compilation_plan_executor.h"
12 12 
13-#include "kernel_specialization_diagnostics.h"
14#include "ascendc_tool_log.h"13#include "ascendc_tool_log.h"
14+#include "kernel_specialization_diagnostics.h"
15+#include "process_executor.h"
15 16 
16-#include <algorithm>
17-#include <cerrno>
18-#include <csignal>
19-#include <cstring>
20#include <exception>17#include <exception>
21-#include <fcntl.h>
22#include <new>18#include <new>
23-#include <poll.h>
24-#include <spawn.h>
25-#include <sys/wait.h>
26-#include <thread>
27-#include <unistd.h>
28 19 
29namespace ascendc {20namespace ascendc {
30namespace aclrtc {21namespace aclrtc {
31-namespace {
32-using SteadyClock = std::chrono::steady_clock;
33-constexpr uint64_t MAX_PROCESS_OUTPUT_READ_BYTES_PER_WAIT_ITERATION = 64U * 1024U;
34-constexpr int64_t MAX_OUTPUT_WAIT_INTERVAL_MILLISECONDS = 50;
35-constexpr int64_t MAX_PROCESS_CHECK_INTERVAL_AFTER_OUTPUT_EOF_MILLISECONDS = 5;
36- 
37-enum class ProcessOutputDrainStatus : uint32_t {
38- NoDataAvailable,
39- EndOfFile,
40- ReadBudgetExhausted,
41- Failed,
42-};
43- 
44-class ScopedFileDescriptor final {
45-public:
46- explicit ScopedFileDescriptor(int fileDescriptor) noexcept : fileDescriptor_(fileDescriptor) {}
47- 
48- ~ScopedFileDescriptor() noexcept { Close(); }
49- 
50- ScopedFileDescriptor(const ScopedFileDescriptor&) = delete;
51- ScopedFileDescriptor& operator=(const ScopedFileDescriptor&) = delete;
52- 
53- int GetFileDescriptor() const noexcept { return fileDescriptor_; }
54- 
55- void TakeOwnership(int fileDescriptor) noexcept
56- {
57- Close();
58- fileDescriptor_ = fileDescriptor;
59- }
60- 
61- void Close() noexcept
62- {
63- if (fileDescriptor_ < 0) {
64- return;
65- }
66- const int closingFileDescriptor = fileDescriptor_;
67- fileDescriptor_ = -1;
68- if (close(closingFileDescriptor) != 0) {
69- const int closeError = errno;
70- ASCENDLOGW(
71- "Failed to close command pipe descriptor: fd=%d errno=%d message=%s", closingFileDescriptor, closeError,
72- std::strerror(closeError));
73- }
74- }
75- 
76-private:
77- int fileDescriptor_{-1};
78-};
79- 
80-void CloseFileDescriptorBestEffort(int fileDescriptor) noexcept
81-{
82- if (fileDescriptor >= 0 && close(fileDescriptor) != 0) {
83- const int closeError = errno;
84- ASCENDLOGW(
85- "Failed to close command pipe descriptor: fd=%d errno=%d message=%s", fileDescriptor, closeError,
86- std::strerror(closeError));
87- }
88-}
89- 
90-class CommandSpawnConfiguration final {
91-public:
92- CommandSpawnConfiguration() = default;
93- 
94- ~CommandSpawnConfiguration() noexcept
95- {
96- if (spawnAttributesInitialized_) {
97- const int destroyResult = posix_spawnattr_destroy(&spawnAttributes_);
98- if (destroyResult != 0) {
99- ASCENDLOGW(
100- "Failed to destroy spawn attributes: error=%d message=%s", destroyResult,
101- std::strerror(destroyResult));
102- }
@@ -15,8 +15,12 @@
15 15 
16#include <chrono>16#include <chrono>
17#include <cstddef>17#include <cstddef>
18+#include <cstdint>
18 19 
19namespace ascendc {20namespace ascendc {
21+ 
22+struct ProcessExecutorResult;
23+ 
20namespace aclrtc {24namespace aclrtc {
21 25 
22class KernelSpecializationDiagnostics;26class KernelSpecializationDiagnostics;
@@ -25,7 +29,6 @@ struct CompilationCommandExecutionLimits {
25 std::chrono::milliseconds compileCommandTimeout;29 std::chrono::milliseconds compileCommandTimeout;
26 std::chrono::milliseconds objectCopyAndLinkCommandTimeout;30 std::chrono::milliseconds objectCopyAndLinkCommandTimeout;
27 std::chrono::milliseconds terminationGracePeriod;31 std::chrono::milliseconds terminationGracePeriod;
28- uint64_t capturedOutputByteLimit;
29 32 
30 static CompilationCommandExecutionLimits ProductionDefaults() noexcept;33 static CompilationCommandExecutionLimits ProductionDefaults() noexcept;
31};34};
@@ -39,11 +42,10 @@ public:
39 42 
40private:43private:
41 aclError ExecuteCompilationCommand(44 aclError ExecuteCompilationCommand(
42- const CompilationCommand& compilationCommand, CompilationProcessResult& processResult) const;45+ const CompilationCommand& compilationCommand, ProcessExecutorResult& executorResult) const;
43- std::chrono::milliseconds GetCommandExecutionTimeout(CompilationCommandKind commandKind) const noexcept;
44 46 
45 KernelSpecializationDiagnostics& specializationDiagnostics_;47 KernelSpecializationDiagnostics& specializationDiagnostics_;
46- CompilationCommandExecutionLimits executionLimits_;48+ CompilationCommandExecutionLimits commandExecutionLimits_;
47};49};
48 50 
49} // namespace aclrtc51} // namespace aclrtc
@@ -354,7 +354,7 @@ aclError BuildManifestCommand(
354}354}
355 355 
356aclError SelectManifestObjectsForCompilation(356aclError SelectManifestObjectsForCompilation(
357- const Json& selectedKernel, CompilationVariant compilationVariant,357+ const Json& selectedKernel, KernelCompilationVariant compilationVariant,
358 std::vector<SelectedManifestObject>& selectedObjects)358 std::vector<SelectedManifestObject>& selectedObjects)
359{359{
360 bool foundBasicObject = false;360 bool foundBasicObject = false;
@@ -366,7 +366,7 @@ aclError SelectManifestObjectsForCompilation(
366 selectedObjects.push_back({&manifestObject, ManifestObjectKind::Basic});366 selectedObjects.push_back({&manifestObject, ManifestObjectKind::Basic});
367 } else if (objectType == "sk") {367 } else if (objectType == "sk") {
368 foundSuperKernelObject = true;368 foundSuperKernelObject = true;
369- if (compilationVariant == CompilationVariant::BasicWithSuperKernel) {369+ if (compilationVariant == KernelCompilationVariant::BasicWithSuperKernel) {
370 selectedObjects.push_back({&manifestObject, ManifestObjectKind::SuperKernel});370 selectedObjects.push_back({&manifestObject, ManifestObjectKind::SuperKernel});
371 }371 }
372 }372 }
@@ -375,7 +375,7 @@ aclError SelectManifestObjectsForCompilation(
375 ASCENDLOGE("Selected kernel does not contain a basic object; regenerate the JIT resource");375 ASCENDLOGE("Selected kernel does not contain a basic object; regenerate the JIT resource");
376 return ACLRTC_ERROR_FAILURE;376 return ACLRTC_ERROR_FAILURE;
377 }377 }
378- if (compilationVariant == CompilationVariant::BasicWithSuperKernel && !foundSuperKernelObject) {378+ if (compilationVariant == KernelCompilationVariant::BasicWithSuperKernel && !foundSuperKernelObject) {
379 ASCENDLOGE("Super-kernel compilation was requested but the selected kernel has no sk object; remove "379 ASCENDLOGE("Super-kernel compilation was requested but the selected kernel has no sk object; remove "
380 "--enable-super-kernel or use a JIT resource that contains an sk object");380 "--enable-super-kernel or use a JIT resource that contains an sk object");
381 return ACLRTC_ERROR_FAILURE;381 return ACLRTC_ERROR_FAILURE;
@@ -117,25 +117,20 @@ bool WritePatchedSourceFileAtomically(const fs::path& sourceFilePath, const std:
117} // namespace117} // namespace
118 118 
119KernelCompilationWorkspace::KernelCompilationWorkspace(fs::path worktreePath, WorktreeRetentionPolicy retentionPolicy)119KernelCompilationWorkspace::KernelCompilationWorkspace(fs::path worktreePath, WorktreeRetentionPolicy retentionPolicy)
120- : worktreePath_(std::move(worktreePath)), retentionPolicy_(retentionPolicy)120+ : retentionPolicy_(retentionPolicy)
121-{}
122- 
123-KernelCompilationWorkspace::~KernelCompilationWorkspace()
124{121{
125- if (retentionPolicy_ == WorktreeRetentionPolicy::RetainAfterCompilation || worktreePath_.empty()) {122+ if (!worktreePath.empty()) {
126- return;123+ (void)worktreeCleanupGuard_.RegisterDirectoryForCleanup(worktreePath.string());
127 }124 }
128- boost::system::error_code cleanupError;125+ if (retentionPolicy_ == WorktreeRetentionPolicy::RetainAfterCompilation) {
129- fs::remove_all(worktreePath_, cleanupError);126+ worktreeCleanupGuard_.PreserveDirectory();
130- if (cleanupError) {
131- ASCENDLOGW(
132- "Failed to clean resource worktree: path=%s error=%d message=%s; remove the worktree manually if it "
133- "is no longer needed",
134- worktreePath_.c_str(), cleanupError.value(), cleanupError.message().c_str());
135 }127 }
136}128}
137 129 
138-const fs::path& KernelCompilationWorkspace::GetWorktreePath() const noexcept { return worktreePath_; }130+fs::path KernelCompilationWorkspace::GetWorktreePath() const
131+{
132+ return fs::path(worktreeCleanupGuard_.GetDirectoryPath());
133+}
139 134 
140aclError KernelCompilationWorkspace::CreateOutputDirectoriesAndApplySourcePatches(135aclError KernelCompilationWorkspace::CreateOutputDirectoriesAndApplySourcePatches(
141 const KernelCompilationPlan& compilationPlan)136 const KernelCompilationPlan& compilationPlan)
@@ -178,24 +173,21 @@ aclError KernelCompilationWorkspace::CreateOutputDirectoriesAndApplySourcePatche
178 return ACLRTC_SUCCESS;173 return ACLRTC_SUCCESS;
179}174}
180 175 
181-aclError KernelCompilationWorkspace::RemoveWorktreeBeforePublishingElfIfNeeded()176+aclError KernelCompilationWorkspace::CleanupWorktreeBeforeElfPublication()
182{177{
183- if (worktreePath_.empty()) {178+ if (worktreeCleanupGuard_.GetDirectoryPath().empty()) {
184 return ACLRTC_SUCCESS;179 return ACLRTC_SUCCESS;
185 }180 }
186 if (retentionPolicy_ == WorktreeRetentionPolicy::RetainAfterCompilation) {181 if (retentionPolicy_ == WorktreeRetentionPolicy::RetainAfterCompilation) {
187 return ACLRTC_SUCCESS;182 return ACLRTC_SUCCESS;
188 }183 }
189- boost::system::error_code cleanupError;184+ const std::string worktreePath = worktreeCleanupGuard_.GetDirectoryPath();
190- fs::remove_all(worktreePath_, cleanupError);185+ if (!worktreeCleanupGuard_.RemoveDirectory()) {
191- if (cleanupError) {
192 ASCENDLOGE(186 ASCENDLOGE(
193- "Failed to clean resource worktree before publishing ELF: path=%s error=%d message=%s; ensure the "187+ "Failed to clean resource worktree before publishing ELF: path=%s; ensure the path is removable and retry",
194- "path is removable and retry",188+ worktreePath.c_str());
195- worktreePath_.c_str(), cleanupError.value(), cleanupError.message().c_str());
196 return ACLRTC_ERROR_FAILURE;189 return ACLRTC_ERROR_FAILURE;
197 }190 }
198- worktreePath_.clear();
199 return ACLRTC_SUCCESS;191 return ACLRTC_SUCCESS;
200}192}
201 193 
@@ -12,6 +12,7 @@
12#define ASCENDC_ACLRTC_KERNEL_COMPILATION_WORKSPACE_H12#define ASCENDC_ACLRTC_KERNEL_COMPILATION_WORKSPACE_H
13 13 
14#include "kernel_specialization_types.h"14#include "kernel_specialization_types.h"
15+#include "directory_cleanup_guard.h"
15 16 
16#include <boost/filesystem/path.hpp>17#include <boost/filesystem/path.hpp>
17#include <cstdint>18#include <cstdint>
@@ -27,17 +28,17 @@ enum class WorktreeRetentionPolicy : uint32_t {
27class KernelCompilationWorkspace final {28class KernelCompilationWorkspace final {
28public:29public:
29 KernelCompilationWorkspace(boost::filesystem::path worktreePath, WorktreeRetentionPolicy retentionPolicy);30 KernelCompilationWorkspace(boost::filesystem::path worktreePath, WorktreeRetentionPolicy retentionPolicy);
30- ~KernelCompilationWorkspace();31+ ~KernelCompilationWorkspace() = default;
31 32 
32 KernelCompilationWorkspace(const KernelCompilationWorkspace&) = delete;33 KernelCompilationWorkspace(const KernelCompilationWorkspace&) = delete;
33 KernelCompilationWorkspace& operator=(const KernelCompilationWorkspace&) = delete;34 KernelCompilationWorkspace& operator=(const KernelCompilationWorkspace&) = delete;
34 35 
35- const boost::filesystem::path& GetWorktreePath() const noexcept;36+ boost::filesystem::path GetWorktreePath() const;
36 aclError CreateOutputDirectoriesAndApplySourcePatches(const KernelCompilationPlan& compilationPlan);37 aclError CreateOutputDirectoriesAndApplySourcePatches(const KernelCompilationPlan& compilationPlan);
37- aclError RemoveWorktreeBeforePublishingElfIfNeeded();38+ aclError CleanupWorktreeBeforeElfPublication();
38 39 
39private:40private:
40- boost::filesystem::path worktreePath_;41+ DirectoryCleanupGuard worktreeCleanupGuard_;
41 WorktreeRetentionPolicy retentionPolicy_{WorktreeRetentionPolicy::RemoveAfterCompilation};42 WorktreeRetentionPolicy retentionPolicy_{WorktreeRetentionPolicy::RemoveAfterCompilation};
42};43};
43 44 
@@ -117,7 +117,7 @@ void RemoveTemporaryOutputFileBestEffort(const fs::path& temporaryOutputPath)
117}117}
118} // namespace118} // namespace
119 119 
120-aclError KernelSpecializationSession::PrepareCompilationResource(120+aclError KernelSpecializationSession::LoadAndMaterializeCompilationResource(
121 const std::string& resourceId, MaterializedKernelCompilationResource& compilationResource)121 const std::string& resourceId, MaterializedKernelCompilationResource& compilationResource)
122{122{
123 ResourceRegistry& resourceRegistry = ResourceRegistry::Instance();123 ResourceRegistry& resourceRegistry = ResourceRegistry::Instance();
@@ -146,20 +146,20 @@ aclError KernelSpecializationSession::PrepareCompilationResource(
146 return ACLRTC_SUCCESS;146 return ACLRTC_SUCCESS;
147}147}
148 148 
149-aclError KernelSpecializationSession::FinalizeSpecializationResult(149+aclError KernelSpecializationSession::ReportSpecializationStatus(
150 const NormalizedKernelSpecializationRequest& specializationRequest, KernelSpecializationDiagnostics& diagnostics,150 const NormalizedKernelSpecializationRequest& specializationRequest, KernelSpecializationDiagnostics& diagnostics,
151- aclError specializationResult) noexcept151+ aclError specializationStatus) noexcept
152{152{
153- const OutputPublicationStatus publicationStatus = specializationResult == ACLRTC_SUCCESS ?153+ const KernelElfPublicationStatus publicationStatus = specializationStatus == ACLRTC_SUCCESS ?
154- OutputPublicationStatus::Published :154+ KernelElfPublicationStatus::Published :
155- OutputPublicationStatus::NotPublished;155+ KernelElfPublicationStatus::NotPublished;
156 diagnostics.WriteSpecializationResult(specializationRequest.outputElfPath, publicationStatus);156 diagnostics.WriteSpecializationResult(specializationRequest.outputElfPath, publicationStatus);
157- if (specializationResult == ACLRTC_SUCCESS) {157+ if (specializationStatus == ACLRTC_SUCCESS) {
158 ASCENDLOGI(158 ASCENDLOGI(
159 "Kernel %s specialization completed: output=%s", specializationRequest.kernelName.c_str(),159 "Kernel %s specialization completed: output=%s", specializationRequest.kernelName.c_str(),
160 specializationRequest.outputElfPath.c_str());160 specializationRequest.outputElfPath.c_str());
161 }161 }
162- return specializationResult;162+ return specializationStatus;
163}163}
164 164 
165aclError KernelSpecializationSession::RunKernelSpecialization(165aclError KernelSpecializationSession::RunKernelSpecialization(
@@ -175,16 +175,16 @@ aclError KernelSpecializationSession::RunKernelSpecialization(
175 }175 }
176 176 
177 NormalizedKernelSpecializationRequest specializationRequest;177 NormalizedKernelSpecializationRequest specializationRequest;
178- aclError specializationResult =178+ aclError specializationStatus =
179 NormalizeKernelSpecializationRequest(*apiRequest, outputElfPath, specializationRequest);179 NormalizeKernelSpecializationRequest(*apiRequest, outputElfPath, specializationRequest);
180- if (specializationResult != ACLRTC_SUCCESS) {180+ if (specializationStatus != ACLRTC_SUCCESS) {
181- return specializationResult;181+ return specializationStatus;
182 }182 }
183 183 
184 MaterializedKernelCompilationResource compilationResource;184 MaterializedKernelCompilationResource compilationResource;
185- specializationResult = PrepareCompilationResource(specializationRequest.resourceId, compilationResource);185+ specializationStatus = LoadAndMaterializeCompilationResource(specializationRequest.resourceId, compilationResource);
186- if (specializationResult != ACLRTC_SUCCESS) {186+ if (specializationStatus != ACLRTC_SUCCESS) {
187- return specializationResult;187+ return specializationStatus;
188 }188 }
189 return RunSpecializationWithMaterializedResource(specializationRequest, std::move(compilationResource));189 return RunSpecializationWithMaterializedResource(specializationRequest, std::move(compilationResource));
190}190}
@@ -204,28 +204,28 @@ aclError KernelSpecializationSession::RunSpecializationWithMaterializedResource(
204 diagnostics.WriteManifestSnapshot(compilationResource.manifest);204 diagnostics.WriteManifestSnapshot(compilationResource.manifest);
205 205 
206 KernelCompilationPlan compilationPlan;206 KernelCompilationPlan compilationPlan;
207- aclError specializationResult =207+ aclError specializationStatus =
208 KernelCompilationPlanBuilder(208 KernelCompilationPlanBuilder(
209 specializationRequest, compilationResource.manifest, compilationWorkspace.GetWorktreePath(),209 specializationRequest, compilationResource.manifest, compilationWorkspace.GetWorktreePath(),
210 compilationResource.externalSourceDirectoryPath)210 compilationResource.externalSourceDirectoryPath)
211 .BuildCompilationPlan(compilationPlan);211 .BuildCompilationPlan(compilationPlan);
212- if (specializationResult != ACLRTC_SUCCESS) {212+ if (specializationStatus != ACLRTC_SUCCESS) {
213- return FinalizeSpecializationResult(specializationRequest, diagnostics, specializationResult);213+ return ReportSpecializationStatus(specializationRequest, diagnostics, specializationStatus);
214 }214 }
215- specializationResult = compilationWorkspace.CreateOutputDirectoriesAndApplySourcePatches(compilationPlan);215+ specializationStatus = compilationWorkspace.CreateOutputDirectoriesAndApplySourcePatches(compilationPlan);
216- if (specializationResult != ACLRTC_SUCCESS) {216+ if (specializationStatus != ACLRTC_SUCCESS) {
217- return FinalizeSpecializationResult(specializationRequest, diagnostics, specializationResult);217+ return ReportSpecializationStatus(specializationRequest, diagnostics, specializationStatus);
218 }218 }
219 219 
220 CompilationPlanExecutor planExecutor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());220 CompilationPlanExecutor planExecutor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
221- specializationResult = planExecutor.ExecuteCompilationPlan(compilationPlan);221+ specializationStatus = planExecutor.ExecuteCompilationPlan(compilationPlan);
222- if (specializationResult != ACLRTC_SUCCESS) {222+ if (specializationStatus != ACLRTC_SUCCESS) {
223- return FinalizeSpecializationResult(specializationRequest, diagnostics, specializationResult);223+ return ReportSpecializationStatus(specializationRequest, diagnostics, specializationStatus);
224 }224 }
225 225 
226- specializationResult = PublishKernelElf(226+ specializationStatus = PublishKernelElf(
227 compilationPlan.linkedKernelElfPath, specializationRequest.outputElfPath, compilationWorkspace);227 compilationPlan.linkedKernelElfPath, specializationRequest.outputElfPath, compilationWorkspace);
228- return FinalizeSpecializationResult(specializationRequest, diagnostics, specializationResult);228+ return ReportSpecializationStatus(specializationRequest, diagnostics, specializationStatus);
229}229}
230 230 
231aclError KernelSpecializationSession::PublishKernelElf(231aclError KernelSpecializationSession::PublishKernelElf(
@@ -274,10 +274,10 @@ aclError KernelSpecializationSession::PublishKernelElf(
274 return ACLRTC_ERROR_FAILURE;274 return ACLRTC_ERROR_FAILURE;
275 }275 }
276 276 
277- const aclError worktreeRemovalResult = compilationWorkspace.RemoveWorktreeBeforePublishingElfIfNeeded();277+ const aclError worktreeCleanupStatus = compilationWorkspace.CleanupWorktreeBeforeElfPublication();
278- if (worktreeRemovalResult != ACLRTC_SUCCESS) {278+ if (worktreeCleanupStatus != ACLRTC_SUCCESS) {
279 RemoveTemporaryOutputFileBestEffort(temporaryOutputPath);279 RemoveTemporaryOutputFileBestEffort(temporaryOutputPath);
280- return worktreeRemovalResult;280+ return worktreeCleanupStatus;
281 }281 }
282 282 
283 boost::system::error_code renameError;283 boost::system::error_code renameError;
@@ -32,16 +32,16 @@ public:
32 aclError RunKernelSpecialization(const aclrtcKernelSpecRequest* apiRequest, const char* outputElfPath);32 aclError RunKernelSpecialization(const aclrtcKernelSpecRequest* apiRequest, const char* outputElfPath);
33 33 
34private:34private:
35- static aclError PrepareCompilationResource(35+ static aclError LoadAndMaterializeCompilationResource(
36 const std::string& resourceId, MaterializedKernelCompilationResource& compilationResource);36 const std::string& resourceId, MaterializedKernelCompilationResource& compilationResource);
37 37 
38 aclError RunSpecializationWithMaterializedResource(38 aclError RunSpecializationWithMaterializedResource(
39 const NormalizedKernelSpecializationRequest& specializationRequest,39 const NormalizedKernelSpecializationRequest& specializationRequest,
40 MaterializedKernelCompilationResource compilationResource);40 MaterializedKernelCompilationResource compilationResource);
41 41 
42- static aclError FinalizeSpecializationResult(42+ static aclError ReportSpecializationStatus(
43 const NormalizedKernelSpecializationRequest& specializationRequest,43 const NormalizedKernelSpecializationRequest& specializationRequest,
44- KernelSpecializationDiagnostics& diagnostics, aclError specializationResult) noexcept;44+ KernelSpecializationDiagnostics& diagnostics, aclError specializationStatus) noexcept;
45 45 
46 aclError PublishKernelElf(46 aclError PublishKernelElf(
47 const boost::filesystem::path& linkedKernelElfPath, const boost::filesystem::path& outputElfPath,47 const boost::filesystem::path& linkedKernelElfPath, const boost::filesystem::path& outputElfPath,
@@ -11,6 +11,7 @@
11#include "kernel_specialization_diagnostics.h"11#include "kernel_specialization_diagnostics.h"
12 12 
13#include "ascendc_tool_log.h"13#include "ascendc_tool_log.h"
14+#include "process_executor.h"
14 15 
15#include <boost/filesystem.hpp>16#include <boost/filesystem.hpp>
16#include <cerrno>17#include <cerrno>
@@ -43,22 +44,6 @@ std::string QuoteArgumentForReplayScript(const std::string& argument)
43 return quotedArgument;44 return quotedArgument;
44}45}
45 46 
46-const char* GetProcessTerminationName(CompilationProcessTermination termination)
47-{
48- switch (termination) {
49- case CompilationProcessTermination::InfrastructureFailure:
50- return "infrastructure_failure";
51- case CompilationProcessTermination::Exited:
52- return "exited";
53- case CompilationProcessTermination::Signaled:
54- return "signaled";
55- case CompilationProcessTermination::TimedOut:
56- return "timed_out";
57- default:
58- return "unknown";
59- }
60-}
61- 
62bool WriteDiagnosticTextFile(const fs::path& filePath, const std::string& text, std::ios::openmode writeMode)47bool WriteDiagnosticTextFile(const fs::path& filePath, const std::string& text, std::ios::openmode writeMode)
63{48{
64 std::ofstream outputStream(filePath.string(), std::ios::out | writeMode);49 std::ofstream outputStream(filePath.string(), std::ios::out | writeMode);
@@ -175,22 +160,40 @@ void KernelSpecializationDiagnostics::AppendCommandToReplayScript(const Compilat
175 }160 }
176}161}
177 162 
178-void KernelSpecializationDiagnostics::AppendCommandResultToCompilationLog(163+std::string KernelSpecializationDiagnostics::GetCompilationLogFilePath() const
179- const CompilationCommand& compilationCommand, const CompilationProcessResult& processResult) noexcept164+{
165+ return diagnosticsEnabled_ ? (resourceWorktreePath_ / DIAGNOSTIC_COMPILE_LOG_FILE_NAME).string() : std::string();
166+}
167+ 
168+void KernelSpecializationDiagnostics::AppendCommandExecutionHeaderToLog(
169+ const CompilationCommand& compilationCommand) noexcept
170+{
171+ if (!diagnosticsEnabled_) {
172+ return;
173+ }
174+ try {
175+ AppendTextToDiagnosticFile(
176+ resourceWorktreePath_ / DIAGNOSTIC_COMPILE_LOG_FILE_NAME,
177+ "[" + compilationCommand.diagnosticLabel + "] output:\n");
178+ } catch (...) {
179+ ASCENDLOGW(
180+ "Failed to record command output header: command=%s resource_worktree=%s",
181+ compilationCommand.diagnosticLabel.c_str(), resourceWorktreePath_.c_str());
182+ }
183+}
184+ 
185+void KernelSpecializationDiagnostics::AppendCommandExecutionResultToLog(
186+ const CompilationCommand& compilationCommand, const ProcessExecutorResult& executorResult) noexcept
180{187{
181 if (!diagnosticsEnabled_) {188 if (!diagnosticsEnabled_) {
182 return;189 return;
183 }190 }
184 try {191 try {
185 std::ostringstream resultText;192 std::ostringstream resultText;
186- resultText << '[' << compilationCommand.diagnosticLabel193+ resultText << '\n'
187- << "] termination=" << GetProcessTerminationName(processResult.termination)194+ << '[' << compilationCommand.diagnosticLabel << "] outcome=" << executorResult.GetOutcomeName()
188- << " code=" << processResult.terminationCode << " elapsed_ms=" << processResult.elapsedTime.count()195+ << " code=" << executorResult.terminationCode << " elapsed_ms=" << executorResult.elapsedTime.count()
189- << " output_truncated=" << (processResult.capturedOutputTruncated ? "true" : "false") << '\n'196+ << '\n';
190- << processResult.capturedOutput;
191- if (!processResult.capturedOutput.empty() && processResult.capturedOutput.back() != '\n') {
192- resultText << '\n';
193- }
194 AppendTextToDiagnosticFile(resourceWorktreePath_ / DIAGNOSTIC_COMPILE_LOG_FILE_NAME, resultText.str());197 AppendTextToDiagnosticFile(resourceWorktreePath_ / DIAGNOSTIC_COMPILE_LOG_FILE_NAME, resultText.str());
195 } catch (...) {198 } catch (...) {
196 ASCENDLOGW(199 ASCENDLOGW(
@@ -220,7 +223,7 @@ void KernelSpecializationDiagnostics::LogCommandFailureRecoveryHint() const noex
220}223}
221 224 
222void KernelSpecializationDiagnostics::WriteSpecializationResult(225void KernelSpecializationDiagnostics::WriteSpecializationResult(
223- const fs::path& outputElfPath, OutputPublicationStatus publicationStatus) noexcept226+ const fs::path& outputElfPath, KernelElfPublicationStatus publicationStatus) noexcept
224{227{
225 if (!diagnosticsEnabled_) {228 if (!diagnosticsEnabled_) {
226 return;229 return;
@@ -230,7 +233,7 @@ void KernelSpecializationDiagnostics::WriteSpecializationResult(
230 nlohmann::json resultJson{233 nlohmann::json resultJson{
231 {"specialization_session_id", specializationSessionId_},234 {"specialization_session_id", specializationSessionId_},
232 {"output_path", outputElfPath.string()},235 {"output_path", outputElfPath.string()},
233- {"output_published", publicationStatus == OutputPublicationStatus::Published},236+ {"output_published", publicationStatus == KernelElfPublicationStatus::Published},
234 };237 };
235 const fs::path resultPath = resourceWorktreePath_ / DIAGNOSTIC_RESULT_FILE_NAME;238 const fs::path resultPath = resourceWorktreePath_ / DIAGNOSTIC_RESULT_FILE_NAME;
236 WriteDiagnosticTextFile(resultPath, resultJson.dump(diagnosticJsonIndentationWidth) + '\n', std::ios::trunc);239 WriteDiagnosticTextFile(resultPath, resultJson.dump(diagnosticJsonIndentationWidth) + '\n', std::ios::trunc);
@@ -19,9 +19,12 @@
19#include <string>19#include <string>
20 20 
21namespace ascendc {21namespace ascendc {
22+ 
23+struct ProcessExecutorResult;
24+ 
22namespace aclrtc {25namespace aclrtc {
23 26 
24-enum class OutputPublicationStatus : uint32_t {27+enum class KernelElfPublicationStatus : uint32_t {
25 NotPublished,28 NotPublished,
26 Published,29 Published,
27};30};
@@ -32,11 +35,13 @@ public:
32 35 
33 void WriteManifestSnapshot(const nlohmann::json& resourceManifest) noexcept;36 void WriteManifestSnapshot(const nlohmann::json& resourceManifest) noexcept;
34 void AppendCommandToReplayScript(const CompilationCommand& compilationCommand) noexcept;37 void AppendCommandToReplayScript(const CompilationCommand& compilationCommand) noexcept;
35- void AppendCommandResultToCompilationLog(38+ void AppendCommandExecutionHeaderToLog(const CompilationCommand& compilationCommand) noexcept;
36- const CompilationCommand& compilationCommand, const CompilationProcessResult& processResult) noexcept;39+ std::string GetCompilationLogFilePath() const;
40+ void AppendCommandExecutionResultToLog(
41+ const CompilationCommand& compilationCommand, const ProcessExecutorResult& executorResult) noexcept;
37 void LogCommandFailureRecoveryHint() const noexcept;42 void LogCommandFailureRecoveryHint() const noexcept;
38 void WriteSpecializationResult(43 void WriteSpecializationResult(
39- const boost::filesystem::path& outputElfPath, OutputPublicationStatus publicationStatus) noexcept;44+ const boost::filesystem::path& outputElfPath, KernelElfPublicationStatus publicationStatus) noexcept;
40 45 
41private:46private:
42 void AppendTextToDiagnosticFile(const boost::filesystem::path& filePath, const std::string& text) noexcept;47 void AppendTextToDiagnosticFile(const boost::filesystem::path& filePath, const std::string& text) noexcept;
@@ -198,7 +198,7 @@ aclError NormalizeSuperKernelCompilerOptions(
198 return ACLRTC_SUCCESS;198 return ACLRTC_SUCCESS;
199 }199 }
200 200 
201- normalizedRequest.compilationVariant = CompilationVariant::BasicWithSuperKernel;201+ normalizedRequest.compilationVariant = KernelCompilationVariant::BasicWithSuperKernel;
202 for (size_t optionIndex = 0U; optionIndex < copiedSuperKernelOptions.size(); ++optionIndex) {202 for (size_t optionIndex = 0U; optionIndex < copiedSuperKernelOptions.size(); ++optionIndex) {
203 std::string& superKernelOption = copiedSuperKernelOptions[optionIndex];203 std::string& superKernelOption = copiedSuperKernelOptions[optionIndex];
204 if (superKernelOption == SUPER_KERNEL_ENABLE_OPTION) {204 if (superKernelOption == SUPER_KERNEL_ENABLE_OPTION) {
@@ -13,7 +13,6 @@
13 13 
14#include "acl/acl_rt_compile.h"14#include "acl/acl_rt_compile.h"
15 15 
16-#include <chrono>
17#include <boost/filesystem/path.hpp>16#include <boost/filesystem/path.hpp>
18#include <cstdint>17#include <cstdint>
19#include <optional>18#include <optional>
@@ -31,7 +30,7 @@ constexpr aclError ACLRTC_ERROR_LINKING = 176004;
31constexpr aclError ACLRTC_ERROR_OUT_OF_MEMORY = 276001;30constexpr aclError ACLRTC_ERROR_OUT_OF_MEMORY = 276001;
32constexpr aclError ACLRTC_ERROR_FAILURE = 576000;31constexpr aclError ACLRTC_ERROR_FAILURE = 576000;
33 32 
34-enum class CompilationVariant : uint32_t {33+enum class KernelCompilationVariant : uint32_t {
35 Basic,34 Basic,
36 BasicWithSuperKernel,35 BasicWithSuperKernel,
37};36};
@@ -42,13 +41,6 @@ enum class CompilationCommandKind : uint32_t {
42 Link,41 Link,
43};42};
44 43 
45-enum class CompilationProcessTermination : uint32_t {
46- InfrastructureFailure,
47- Exited,
48- Signaled,
49- TimedOut,
50-};
51- 
52struct KernelCompilerOptions {44struct KernelCompilerOptions {
53 std::vector<std::string> basicOptions;45 std::vector<std::string> basicOptions;
54 std::vector<std::string> superKernelOptions;46 std::vector<std::string> superKernelOptions;
@@ -59,7 +51,7 @@ struct NormalizedKernelSpecializationRequest {
59 std::string resourceId;51 std::string resourceId;
60 std::string kernelName;52 std::string kernelName;
61 boost::filesystem::path outputElfPath;53 boost::filesystem::path outputElfPath;
62- CompilationVariant compilationVariant{CompilationVariant::Basic};54+ KernelCompilationVariant compilationVariant{KernelCompilationVariant::Basic};
63 KernelCompilerOptions compilerOptions;55 KernelCompilerOptions compilerOptions;
64 uint64_t kernelArgumentCount{0};56 uint64_t kernelArgumentCount{0};
65 const void* const* borrowedKernelArgumentDataPointers{nullptr};57 const void* const* borrowedKernelArgumentDataPointers{nullptr};
@@ -87,14 +79,6 @@ struct KernelCompilationPlan {
87 boost::filesystem::path linkedKernelElfPath;79 boost::filesystem::path linkedKernelElfPath;
88};80};
89 81 
90-struct CompilationProcessResult {
91- CompilationProcessTermination termination{CompilationProcessTermination::InfrastructureFailure};
92- int32_t terminationCode{0};
93- bool capturedOutputTruncated{false};
94- std::chrono::milliseconds elapsedTime{0};
95- std::string capturedOutput;
96-};
97- 
98aclError NormalizeKernelSpecializationRequest(82aclError NormalizeKernelSpecializationRequest(
99 const aclrtcKernelSpecRequest& apiRequest, const char* outputElfPath,83 const aclrtcKernelSpecRequest& apiRequest, const char* outputElfPath,
100 NormalizedKernelSpecializationRequest& normalizedRequest);84 NormalizedKernelSpecializationRequest& normalizedRequest);
@@ -113,60 +113,16 @@ const char* ResourceStatusName(ResourceStatus status) noexcept
113 return "unknown";113 return "unknown";
114}114}
115 115 
116-void CleanupPath(const std::string& path) noexcept116+ResourceStatus CreateCompileResourceMaterializationRoot(DirectoryCleanupGuard& materializationRootDirectory)
117-{
118- if (path.empty()) {
119- return;
120- }
121- if (!FileUtils::RemoveAll(path)) {
122- ASCENDLOGW("Failed to remove compile resource path: path=%s reason=recursive removal failed", path.c_str());
123- }
124-}
125- 
126-ResourceStatus CreateTemporaryRoot(std::string& temporaryRoot)
127{117{
128 const char* environment = std::getenv("TMPDIR");118 const char* environment = std::getenv("TMPDIR");
129- const fs::path configured = environment == nullptr || *environment == '\0' ? "/tmp" : environment;119+ const std::string parentDirectoryPath = environment == nullptr || *environment == '\0' ? "/tmp" : environment;
130- boost::system::error_code error;120+ if (!materializationRootDirectory.CreateUniqueSubdirectory(parentDirectoryPath, "aclrtc-resource")) {
131- const fs::path parent = fs::canonical(configured, error);121+ ASCENDLOGE("Failed to create compile resource temporary root: parent=%s", parentDirectoryPath.c_str());
132- if (error) {
133- ASCENDLOGE(
134- "Failed to resolve compile resource temporary parent: configured=%s error=%s", configured.c_str(),
135- error.message().c_str());
136 return ResourceStatus::IoError;122 return ResourceStatus::IoError;
137 }123 }
138- if (!fs::is_directory(parent, error)) {124+ ASCENDLOGI(
139- ASCENDLOGE(125+ "Created compile resource temporary root: path=%s", materializationRootDirectory.GetDirectoryPath().c_str());
140- "Compile resource temporary parent is not a directory: configured=%s resolved=%s error=%s",
141- configured.c_str(), parent.c_str(), error ? error.message().c_str() : "none");
142- return ResourceStatus::IoError;
143- }
144- const std::string pattern = (parent / "aclrtc-resource-XXXXXX").string();
145- std::vector<char> writable(pattern.begin(), pattern.end());
146- writable.push_back('\0');
147- char* created = mkdtemp(writable.data());
148- if (created == nullptr) {
149- ASCENDLOGE(
150- "Failed to create compile resource temporary root: parent=%s error=%s", parent.c_str(),
151- std::strerror(errno));
152- return ResourceStatus::IoError;
153- }
154- const fs::path canonical = fs::canonical(created, error);
155- if (error) {
156- ASCENDLOGE(
157- "Failed to resolve created compile resource temporary root: path=%s error=%s", created,
158- error.message().c_str());
159- CleanupPath(created);
160- return ResourceStatus::IoError;
161- }
162- if (canonical != fs::path(created).lexically_normal()) {
163- ASCENDLOGE(
164- "Compile resource temporary root identity mismatch: expected=%s actual=%s", created, canonical.c_str());
165- CleanupPath(created);
166- return ResourceStatus::IoError;
167- }
168- temporaryRoot = canonical.string();
169- ASCENDLOGI("Created compile resource temporary root: path=%s", temporaryRoot.c_str());
170 return ResourceStatus::Success;126 return ResourceStatus::Success;
171}127}
172 128 
@@ -428,42 +384,18 @@ ResourceStatus ParseManifestOwnership(
428 return ParseSourceFile(ownership.document, spec, manifestIndex, ownership);384 return ParseSourceFile(ownership.document, spec, manifestIndex, ownership);
429}385}
430 386 
431-ResourceStatus PrepareMaterializationRoot(387+ResourceStatus CreateUniqueResourceMaterializationDirectory(
432- const std::string& temporaryRoot, const std::string& resourceId, ResourceSourceType sourceType,388+ const std::string& materializationRootPath, const std::string& resourceId, ResourceSourceType sourceType,
433- std::string& categoryRoot, std::string& canonicalRoot)389+ DirectoryCleanupGuard& materializedResourceDirectory)
434{390{
435- boost::system::error_code error;391+ const std::string categoryParent = FileUtils::JoinPath(materializationRootPath, SourceTypeName(sourceType));
436- const fs::path randomComponent = fs::unique_path("materialize-%%%%%%%%%%%%%%%%", error);392+ if (!FileUtils::CreateDirectories(categoryParent) ||
437- if (error || randomComponent.empty() || randomComponent.has_parent_path()) {393+ !materializedResourceDirectory.CreateUniqueSubdirectory(categoryParent, "materialize")) {
438 ASCENDLOGE(394 ASCENDLOGE(
439- "Failed to generate compile resource materialization path: resource_id=%s source_type=%s error=%s",395+ "Failed to create compile resource materialization directory: resource_id=%s source_type=%s parent=%s",
440- resourceId.c_str(), SourceTypeName(sourceType), error ? error.message().c_str() : "invalid random path");396+ resourceId.c_str(), SourceTypeName(sourceType), categoryParent.c_str());
441 return ResourceStatus::IoError;397 return ResourceStatus::IoError;
442 }398 }
443- const std::string categoryParent = FileUtils::JoinPath(temporaryRoot, SourceTypeName(sourceType));
444- categoryRoot = FileUtils::JoinPath(categoryParent, randomComponent.string());
445- if (!FileUtils::CreateDirectories(categoryRoot)) {
446- ASCENDLOGE(
447- "Failed to create compile resource materialization directory: resource_id=%s source_type=%s path=%s",
448- resourceId.c_str(), SourceTypeName(sourceType), categoryRoot.c_str());
449- return ResourceStatus::IoError;
450- }
451- const fs::path canonical = fs::canonical(categoryRoot, error);
452- if (error) {
453- ASCENDLOGE(
454- "Failed to resolve compile resource materialization directory: resource_id=%s source_type=%s path=%s "
455- "error=%s",
456- resourceId.c_str(), SourceTypeName(sourceType), categoryRoot.c_str(), error.message().c_str());
457- return ResourceStatus::IoError;
458- }
459- if (canonical != fs::path(categoryRoot).lexically_normal()) {
460- ASCENDLOGE(
461- "Compile resource materialization directory identity mismatch: resource_id=%s source_type=%s expected=%s "
462- "actual=%s",
463- resourceId.c_str(), SourceTypeName(sourceType), categoryRoot.c_str(), canonical.c_str());
464- return ResourceStatus::IoError;
465- }
466- canonicalRoot = canonical.string();
467 return ResourceStatus::Success;399 return ResourceStatus::Success;
468}400}
469 401 
@@ -1267,12 +1199,7 @@ ResourceRegistry& ResourceRegistry::Instance()
1267 1199 
1268ResourceRegistry::ResourceRegistry() = default;1200ResourceRegistry::ResourceRegistry() = default;
1269 1201 
1270-ResourceRegistry::~ResourceRegistry()1202+ResourceRegistry::~ResourceRegistry() = default;
1271-{
1272- if (!keepTemporaryRoot_) {
1273- CleanupPath(temporaryRoot_);
1274- }
1275-}
1276 1203 
1277ResourceEntry* ResourceRegistry::FindResource(const std::string& resourceId) noexcept1204ResourceEntry* ResourceRegistry::FindResource(const std::string& resourceId) noexcept
1278{1205{
@@ -1408,38 +1335,42 @@ ResourceStatus ResourceRegistry::Materialize(
1408 ASCENDLOGI(1335 ASCENDLOGI(
1409 "Materializing compile resource: resource_id=%s source_type=%s so=%s files=%zu", resourceId.c_str(),1336 "Materializing compile resource: resource_id=%s source_type=%s so=%s files=%zu", resourceId.c_str(),
1410 SourceTypeName(entry.sourceType), entry.sourceSoPath.c_str(), entry.files.size());1337 SourceTypeName(entry.sourceType), entry.sourceSoPath.c_str(), entry.files.size());
1411- std::string temporaryRoot;1338+ std::string materializationRootPath;
1412 {1339 {
1413 std::lock_guard<std::mutex> lock(registryMutex_);1340 std::lock_guard<std::mutex> lock(registryMutex_);
1414- if (temporaryRoot_.empty()) {1341+ if (ownedMaterializationRoot_.GetDirectoryPath().empty()) {
1415- keepTemporaryRoot_ = IsKernelMetaSavingEnabled();1342+ retainMaterializedDirectories_ = IsKernelMetaSavingEnabled();
1416- const ResourceStatus status = CreateTemporaryRoot(temporaryRoot_);1343+ const ResourceStatus status = CreateCompileResourceMaterializationRoot(ownedMaterializationRoot_);
1417 if (status != ResourceStatus::Success) {1344 if (status != ResourceStatus::Success) {
1418 return status;1345 return status;
1419 }1346 }
1347+ if (retainMaterializedDirectories_) {
1348+ ownedMaterializationRoot_.PreserveDirectory();
1349+ }
1420 }1350 }
1421- temporaryRoot = temporaryRoot_;1351+ materializationRootPath = ownedMaterializationRoot_.GetDirectoryPath();
1422 }1352 }
1423 ASCENDLOGD(1353 ASCENDLOGD(
1424- "Compile resource temporary root is ready: resource_id=%s path=%s", resourceId.c_str(), temporaryRoot.c_str());1354+ "Compile resource temporary root is ready: resource_id=%s path=%s", resourceId.c_str(),
1425- std::string categoryRoot;1355+ materializationRootPath.c_str());
1426- std::string canonicalRoot;1356+ DirectoryCleanupGuard materializedResourceDirectory;
1427- ResourceStatus status =1357+ ResourceStatus status = CreateUniqueResourceMaterializationDirectory(
1428- PrepareMaterializationRoot(temporaryRoot, resourceId, entry.sourceType, categoryRoot, canonicalRoot);1358+ materializationRootPath, resourceId, entry.sourceType, materializedResourceDirectory);
1429 if (status == ResourceStatus::Success) {1359 if (status == ResourceStatus::Success) {
1430 ASCENDLOGD(1360 ASCENDLOGD(
1431 "Compile resource materialization directory is ready: resource_id=%s path=%s", resourceId.c_str(),1361 "Compile resource materialization directory is ready: resource_id=%s path=%s", resourceId.c_str(),
1432- canonicalRoot.c_str());1362+ materializedResourceDirectory.GetDirectoryPath().c_str());
1433- status = WriteMaterializedFiles(entry.files, canonicalRoot);1363+ status = WriteMaterializedFiles(entry.files, materializedResourceDirectory.GetDirectoryPath());
1434 }1364 }
1435 if (status != ResourceStatus::Success) {1365 if (status != ResourceStatus::Success) {
1436- if (!keepTemporaryRoot_) {1366+ if (retainMaterializedDirectories_) {
1437- CleanupPath(categoryRoot);1367+ materializedResourceDirectory.PreserveDirectory();
1438 }1368 }
1439 resource.resourceDir.clear();1369 resource.resourceDir.clear();
1440 return status;1370 return status;
1441 }1371 }
1442- resource.resourceDir = canonicalRoot;1372+ resource.resourceDir = materializedResourceDirectory.GetDirectoryPath();
1373+ materializedResourceDirectory.PreserveDirectory();
1443 ASCENDLOGI(1374 ASCENDLOGI(
1444 "Compile resource materialized: resource_id=%s source_type=%s so=%s files=%zu directory=%s", resourceId.c_str(),1375 "Compile resource materialized: resource_id=%s source_type=%s so=%s files=%zu directory=%s", resourceId.c_str(),
1445 SourceTypeName(entry.sourceType), entry.sourceSoPath.c_str(), entry.files.size(), resource.resourceDir.c_str());1376 SourceTypeName(entry.sourceType), entry.sourceSoPath.c_str(), entry.files.size(), resource.resourceDir.c_str());
@@ -21,6 +21,7 @@
21 21 
22#include "ascendc_manifest_abi.h"22#include "ascendc_manifest_abi.h"
23#include "nlohmann/json.hpp"23#include "nlohmann/json.hpp"
24+#include "directory_cleanup_guard.h"
24 25 
25namespace boost {26namespace boost {
26namespace filesystem {27namespace filesystem {
@@ -175,14 +176,14 @@ private:
175 ResourceStatus Commit(StageState& stage);176 ResourceStatus Commit(StageState& stage);
176 bool HasCommitConflict(const ResourceStore& incoming, const ResourceStore& committed) const;177 bool HasCommitConflict(const ResourceStore& incoming, const ResourceStore& committed) const;
177 178 
178- std::string temporaryRoot_;179+ DirectoryCleanupGuard ownedMaterializationRoot_;
179 ResourceStore externalResources_;180 ResourceStore externalResources_;
180 ResourceStore customResources_;181 ResourceStore customResources_;
181 ResourceStore builtInResources_;182 ResourceStore builtInResources_;
182 uint64_t resourceBytes_ = 0U;183 uint64_t resourceBytes_ = 0U;
183 uint64_t resourceFileCount_ = 0U;184 uint64_t resourceFileCount_ = 0U;
184 bool automaticLoadAttempted_ = false;185 bool automaticLoadAttempted_ = false;
185- bool keepTemporaryRoot_ = false;186+ bool retainMaterializedDirectories_ = false;
186 ResourceStatus automaticLoadStatus_ = ResourceStatus::Success;187 ResourceStatus automaticLoadStatus_ = ResourceStatus::Success;
187 std::mutex loadMutex_;188 std::mutex loadMutex_;
188 std::mutex registryMutex_;189 std::mutex registryMutex_;
@@ -34,13 +34,13 @@ configure_file(
34 34 
35add_executable(asc_compile_exporter35add_executable(asc_compile_exporter
36 asc_compile_exporter.cpp36 asc_compile_exporter.cpp
37- build_workspace.cpp
38 bundle_source_generator.cpp37 bundle_source_generator.cpp
39 collected_manifest_repository.cpp38 collected_manifest_repository.cpp
40 manifest_bundle_compiler.cpp39 manifest_bundle_compiler.cpp
41 resource_manifest_validator.cpp40 resource_manifest_validator.cpp
42 ../common/file_utils.cpp41 ../common/file_utils.cpp
43- ../common/process_runner.cpp42+ ../common/process_executor.cpp
43+ ../common/directory_cleanup_guard.cpp
44)44)
45 45 
46set_target_properties(asc_compile_exporter PROPERTIES46set_target_properties(asc_compile_exporter PROPERTIES
@@ -1,76 +0,0 @@
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
@@ -10,6 +10,9 @@
10 10 
11#include "manifest_bundle_compiler.h"11#include "manifest_bundle_compiler.h"
12 12 
13+#include <chrono>
14+#include <cstdint>
15+#include <cstdlib>
13#include <utility>16#include <utility>
14#include <vector>17#include <vector>
15 18 
@@ -17,15 +20,40 @@
17#include <boost/system/error_code.hpp>20#include <boost/system/error_code.hpp>
18 21 
19#include "ascendc_tool_log.h"22#include "ascendc_tool_log.h"
20-#include "build_workspace.h"
21#include "bundle_source_generator.h"23#include "bundle_source_generator.h"
22#include "collected_manifest_repository.h"24#include "collected_manifest_repository.h"
23#include "file_utils.h"25#include "file_utils.h"
24#include "manifest_bundle_types.h"26#include "manifest_bundle_types.h"
25-#include "process_runner.h"27+#include "process_executor.h"
28+#include "directory_cleanup_guard.h"
26 29 
27namespace ascendc {30namespace ascendc {
28namespace manifest_generator {31namespace manifest_generator {
32+namespace {
33+ 
34+constexpr uint32_t BUNDLE_BUILD_TIMEOUT_MINUTES = 30U;
35+constexpr uint32_t BUNDLE_BUILD_TERMINATION_GRACE_SECONDS = 5U;
36+ 
37+bool CreatePrivateBundleWorkspaceDirectory(
38+ const std::string& requestedParentDirectoryPath, DirectoryCleanupGuard& bundleWorkspace)
39+{
40+ const char* configuredTemporaryDirectory = std::getenv("TMPDIR");
41+ const std::string parentDirectoryPath =
42+ requestedParentDirectoryPath.empty() ?
43+ (configuredTemporaryDirectory == nullptr || configuredTemporaryDirectory[0] == '\0' ?
44+ "/tmp" :
45+ configuredTemporaryDirectory) :
46+ requestedParentDirectoryPath;
47+ std::string absoluteParentDirectoryPath;
48+ if (!FileUtils::MakeAbsolutePath(parentDirectoryPath, absoluteParentDirectoryPath) ||
49+ !FileUtils::CreateDirectories(absoluteParentDirectoryPath)) {
50+ ASCENDLOGE("Failed to prepare bundle work directory parent: path=%s", parentDirectoryPath.c_str());
51+ return false;
52+ }
53+ return bundleWorkspace.CreateUniqueSubdirectory(absoluteParentDirectoryPath, ".manifest-generator");
54+}
55+ 
56+} // namespace
29 57 
30ManifestBundleCompiler::ManifestBundleCompiler(BuildCollectedBundleRequest request) noexcept58ManifestBundleCompiler::ManifestBundleCompiler(BuildCollectedBundleRequest request) noexcept
31 : request_(std::move(request))59 : request_(std::move(request))
@@ -36,9 +64,20 @@ bool ManifestBundleCompiler::BuildBundle(const std::string& workDir, const std::
36 ASCENDLOGD(64 ASCENDLOGD(
37 "Building bundle with make=%s compiler=%s jobs=%u", request_.makeExecutable.c_str(),65 "Building bundle with make=%s compiler=%s jobs=%u", request_.makeExecutable.c_str(),
38 request_.cxxCompiler.c_str(), request_.jobs);66 request_.cxxCompiler.c_str(), request_.jobs);
39- if (!ProcessRunner::Run(67+ ProcessExecutorRequest executionRequest;
40- {request_.makeExecutable, "-C", workDir, "-f", "Makefile", "-B", "-j" + std::to_string(request_.jobs)},68+ executionRequest.arguments = {
41- {{"ASCENDC_BUNDLE_CXX", request_.cxxCompiler}, {"ASCENDC_BUNDLE_OUTPUT", output}})) {69+ request_.makeExecutable, "-C", workDir, "-f", "Makefile", "-B", "-j" + std::to_string(request_.jobs)};
70+ executionRequest.environmentOverrides = {
71+ {"ASCENDC_BUNDLE_CXX", request_.cxxCompiler}, {"ASCENDC_BUNDLE_OUTPUT", output}};
72+ executionRequest.removedEnvironmentVariables = {"MAKEFLAGS", "MFLAGS", "MAKEFILES", "GNUMAKEFLAGS"};
73+ executionRequest.executionTimeout = std::chrono::minutes(BUNDLE_BUILD_TIMEOUT_MINUTES);
74+ executionRequest.terminationGracePeriod = std::chrono::seconds(BUNDLE_BUILD_TERMINATION_GRACE_SECONDS);
75+ const ProcessExecutorResult executionResult = ProcessExecutor::Execute(executionRequest);
76+ if (!executionResult.HasSuccessfulExit()) {
77+ ASCENDLOGE(
78+ "Failed to build compile resource bundle: termination=%s code=%d elapsed_ms=%lld",
79+ executionResult.GetOutcomeName(), static_cast<int>(executionResult.terminationCode),
80+ static_cast<long long>(executionResult.elapsedTime.count()));
42 return false;81 return false;
43 }82 }
44 ASCENDLOGI("Built staged compile resource bundle %s", output.c_str());83 ASCENDLOGI("Built staged compile resource bundle %s", output.c_str());
@@ -71,11 +110,11 @@ bool ManifestBundleCompiler::PublishBundleAcrossFileSystems(const std::string& s
71{110{
72 ASCENDLOGD("Copying staged bundle across filesystems: %s -> %s", staged.c_str(), request_.outputPath.c_str());111 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.112 // A private workspace under the output parent places the copied file on the output filesystem.
74- BuildWorkspace publishWorkspace;113+ DirectoryCleanupGuard publishWorkspace;
75- if (!publishWorkspace.Create(FileUtils::ParentPath(request_.outputPath), false)) {114+ if (!CreatePrivateBundleWorkspaceDirectory(FileUtils::ParentPath(request_.outputPath), publishWorkspace)) {
76 return false;115 return false;
77 }116 }
78- const std::string copied = FileUtils::JoinPath(publishWorkspace.Session(), FileUtils::FileName(staged));117+ const std::string copied = FileUtils::JoinPath(publishWorkspace.GetDirectoryPath(), FileUtils::FileName(staged));
79 if (!FileUtils::CopyFile(staged, copied)) {118 if (!FileUtils::CopyFile(staged, copied)) {
80 ASCENDLOGE("Failed to copy staged bundle across filesystems: %s", staged.c_str());119 ASCENDLOGE("Failed to copy staged bundle across filesystems: %s", staged.c_str());
81 return false;120 return false;
@@ -103,23 +142,24 @@ bool ManifestBundleCompiler::Compile() const
103 if (!CollectedManifestRepository(request_.manifestSearchRoot).Load(units)) {142 if (!CollectedManifestRepository(request_.manifestSearchRoot).Load(units)) {
104 return false;143 return false;
105 }144 }
106- BuildWorkspace workspace;145+ DirectoryCleanupGuard buildWorkspace;
107- if (!workspace.Create(std::string(), request_.keepTemp)) {146+ if (!CreatePrivateBundleWorkspaceDirectory(std::string(), buildWorkspace)) {
108 return false;147 return false;
109 }148 }
110 if (request_.keepTemp) {149 if (request_.keepTemp) {
111- ASCENDLOGI("Keeping temporary bundle directory %s", workspace.Session().c_str());150+ buildWorkspace.PreserveDirectory();
151+ ASCENDLOGI("Keeping temporary bundle directory %s", buildWorkspace.GetDirectoryPath().c_str());
112 }152 }
113 153 
114 // Generate isolated translation units and compile them into a staged output.154 // Generate isolated translation units and compile them into a staged output.
115- if (!BundleSourceGenerator(workspace.Session(), units, request_.outputKind).Generate()) {155+ if (!BundleSourceGenerator(buildWorkspace.GetDirectoryPath(), units, request_.outputKind).Generate()) {
116 return false;156 return false;
117 }157 }
118 const std::string stagedName = request_.outputKind == BundleOutputKind::kSharedObject ?158 const std::string stagedName = request_.outputKind == BundleOutputKind::kSharedObject ?
119 "compile_resource_bundle.so" :159 "compile_resource_bundle.so" :
120 "compile_resource_bundle_relocatable.o";160 "compile_resource_bundle_relocatable.o";
121- const std::string stagedOutput = FileUtils::JoinPath(workspace.Session(), stagedName);161+ const std::string stagedOutput = FileUtils::JoinPath(buildWorkspace.GetDirectoryPath(), stagedName);
122- if (!BuildBundle(workspace.Session(), stagedOutput)) {162+ if (!BuildBundle(buildWorkspace.GetDirectoryPath(), stagedOutput)) {
123 return false;163 return false;
124 }164 }
125 165 
@@ -0,0 +1,158 @@
1+/**
2+ * Copyright (c) 2026 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 "directory_cleanup_guard.h"
12+ 
13+#include "ascendc_tool_log.h"
14+#include "file_utils.h"
15+ 
16+#include <boost/filesystem.hpp>
17+#include <boost/system/error_code.hpp>
18+#include <cerrno>
19+#include <cstring>
20+#include <sys/stat.h>
21+#include <utility>
22+ 
23+namespace ascendc {
24+namespace {
25+namespace fs = boost::filesystem;
26+ 
27+bool IsValidDirectoryNamePrefix(const std::string& directoryNamePrefix)
28+{
29+ if (directoryNamePrefix.empty() || directoryNamePrefix.find('%') != std::string::npos ||
30+ directoryNamePrefix.find('\0') != std::string::npos || directoryNamePrefix.find('\\') != std::string::npos) {
31+ return false;
32+ }
33+ const fs::path prefixPath(directoryNamePrefix);
34+ return prefixPath.is_relative() && !prefixPath.has_parent_path();
35+}
36+ 
37+} // namespace
38+ 
39+DirectoryCleanupGuard::~DirectoryCleanupGuard() noexcept
40+{
41+ if (removeDirectoryOnDestruction_) {
42+ (void)RemoveDirectory();
43+ }
44+}
45+ 
46+DirectoryCleanupGuard::DirectoryCleanupGuard(DirectoryCleanupGuard&& other) noexcept { MoveStateFrom(other); }
47+ 
48+DirectoryCleanupGuard& DirectoryCleanupGuard::operator=(DirectoryCleanupGuard&& other) noexcept
49+{
50+ if (this == &other) {
51+ return *this;
52+ }
53+ DirectoryCleanupGuard previousDirectory(std::move(*this));
54+ MoveStateFrom(other);
55+ return *this;
56+}
57+ 
58+bool DirectoryCleanupGuard::CreateUniqueSubdirectory(
59+ const std::string& parentDirectoryPath, const std::string& directoryNamePrefix)
60+{
61+ if (!directoryPath_.empty()) {
62+ ASCENDLOGE("Directory cleanup guard already manages a path: path=%s", directoryPath_.c_str());
63+ return false;
64+ }
65+ if (!IsValidDirectoryNamePrefix(directoryNamePrefix)) {
66+ ASCENDLOGE(
67+ "Temporary directory name prefix must be one path component without %% characters: prefix=%s",
68+ directoryNamePrefix.c_str());
69+ return false;
70+ }
71+ 
72+ std::string resolvedParentDirectoryPath;
73+ if (!FileUtils::ResolveDirectory(parentDirectoryPath, resolvedParentDirectoryPath)) {
74+ ASCENDLOGE("Temporary directory parent is unavailable: parent=%s", parentDirectoryPath.c_str());
75+ return false;
76+ }
77+ boost::system::error_code filesystemError;
78+ const fs::path uniqueName = fs::unique_path(directoryNamePrefix + "-%%%%%%%%%%%%%%%%", filesystemError);
79+ if (filesystemError || uniqueName.empty() || uniqueName.has_parent_path()) {
80+ ASCENDLOGE(
81+ "Failed to generate temporary directory name: parent=%s prefix=%s error=%s",
82+ resolvedParentDirectoryPath.c_str(), directoryNamePrefix.c_str(),
83+ filesystemError ? filesystemError.message().c_str() : "invalid generated name");
84+ return false;
85+ }
86+ const fs::path createdDirectoryPath = fs::path(resolvedParentDirectoryPath) / uniqueName;
87+ constexpr mode_t privateDirectoryPermissions = S_IRWXU;
88+ if (mkdir(createdDirectoryPath.c_str(), privateDirectoryPermissions) != 0) {
89+ const int createError = errno;
90+ ASCENDLOGE(
91+ "Failed to create temporary directory: path=%s errno=%d message=%s", createdDirectoryPath.c_str(),
92+ createError, std::strerror(createError));
93+ return false;
94+ }
95+ 
96+ if (chmod(createdDirectoryPath.c_str(), privateDirectoryPermissions) != 0) {
97+ const int permissionError = errno;
98+ ASCENDLOGE(
99+ "Failed to make temporary directory private: path=%s errno=%d message=%s", createdDirectoryPath.c_str(),
100+ permissionError, std::strerror(permissionError));
101+ (void)FileUtils::RemoveAll(createdDirectoryPath.string());
102+ return false;
103+ }
104+ 
105+ std::string resolvedCreatedDirectoryPath;
106+ if (!FileUtils::ResolveDirectory(createdDirectoryPath.string(), resolvedCreatedDirectoryPath) ||
107+ fs::path(resolvedCreatedDirectoryPath) != createdDirectoryPath.lexically_normal()) {
108+ ASCENDLOGE("Created temporary directory has an unexpected identity: path=%s", createdDirectoryPath.c_str());
109+ (void)FileUtils::RemoveAll(createdDirectoryPath.string());
110+ return false;
111+ }
112+ directoryPath_ = std::move(resolvedCreatedDirectoryPath);
113+ removeDirectoryOnDestruction_ = true;
114+ ASCENDLOGD("Created temporary directory: path=%s", directoryPath_.c_str());
115+ return true;
116+}
117+ 
118+bool DirectoryCleanupGuard::RegisterDirectoryForCleanup(std::string directoryPath)
119+{
120+ if (directoryPath.empty()) {
121+ ASCENDLOGE("Cannot register an empty directory path for cleanup");
122+ return false;
123+ }
124+ if (!directoryPath_.empty()) {
125+ ASCENDLOGE("Directory cleanup guard already manages a path: path=%s", directoryPath_.c_str());
126+ return false;
127+ }
128+ directoryPath_ = std::move(directoryPath);
129+ removeDirectoryOnDestruction_ = true;
130+ return true;
131+}
132+ 
133+bool DirectoryCleanupGuard::RemoveDirectory() noexcept
134+{
135+ if (directoryPath_.empty()) {
136+ return true;
137+ }
138+ if (!FileUtils::RemoveAll(directoryPath_)) {
139+ ASCENDLOGW("Failed to remove directory registered for cleanup: path=%s", directoryPath_.c_str());
140+ return false;
141+ }
142+ directoryPath_.clear();
143+ return true;
144+}
145+ 
146+void DirectoryCleanupGuard::PreserveDirectory() noexcept { removeDirectoryOnDestruction_ = false; }
147+ 
148+const std::string& DirectoryCleanupGuard::GetDirectoryPath() const noexcept { return directoryPath_; }
149+ 
150+void DirectoryCleanupGuard::MoveStateFrom(DirectoryCleanupGuard& other) noexcept
151+{
152+ directoryPath_ = std::move(other.directoryPath_);
153+ removeDirectoryOnDestruction_ = other.removeDirectoryOnDestruction_;
154+ other.directoryPath_.clear();
155+ other.removeDirectoryOnDestruction_ = false;
156+}
157+ 
158+} // namespace ascendc
Rtools/build/asc_compile_exporter/build_workspace.h→tools/build/common/directory_cleanup_guard.h+21-15
@@ -1,5 +1,5 @@
1/**1/**
2- * Copyright (c) 2025 Huawei Technologies Co., Ltd.2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of3 * 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").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.5 * Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -8,32 +8,38 @@
8 * See LICENSE in the root of the software repository for the full text of the License.8 * See LICENSE in the root of the software repository for the full text of the License.
9 */9 */
10 10 
11-#ifndef ASCENDC_BUILD_WORKSPACE_H11+#ifndef ASCENDC_COMMON_DIRECTORY_CLEANUP_GUARD_H
12-#define ASCENDC_BUILD_WORKSPACE_H12+#define ASCENDC_COMMON_DIRECTORY_CLEANUP_GUARD_H
13 13 
14#include <string>14#include <string>
15 15 
16namespace ascendc {16namespace ascendc {
17-namespace manifest_generator {
18 17 
19-class BuildWorkspace final {18+class DirectoryCleanupGuard final {
20public:19public:
21- BuildWorkspace() = default;20+ DirectoryCleanupGuard() = default;
21+ ~DirectoryCleanupGuard() noexcept;
22 22 
23- bool Create(const std::string& requestedParent, bool keepTemp);23+ DirectoryCleanupGuard(DirectoryCleanupGuard&& other) noexcept;
24+ DirectoryCleanupGuard& operator=(DirectoryCleanupGuard&& other) noexcept;
24 25 
25- BuildWorkspace(const BuildWorkspace&) = delete;26+ DirectoryCleanupGuard(const DirectoryCleanupGuard&) = delete;
26- BuildWorkspace& operator=(const BuildWorkspace&) = delete;27+ DirectoryCleanupGuard& operator=(const DirectoryCleanupGuard&) = delete;
27- ~BuildWorkspace();
28 28 
29- const std::string& Session() const noexcept;29+ bool CreateUniqueSubdirectory(const std::string& parentDirectoryPath, const std::string& directoryNamePrefix);
30+ bool RegisterDirectoryForCleanup(std::string directoryPath);
31+ bool RemoveDirectory() noexcept;
32+ void PreserveDirectory() noexcept;
33+ 
34+ const std::string& GetDirectoryPath() const noexcept;
30 35 
31private:36private:
32- std::string session_;37+ void MoveStateFrom(DirectoryCleanupGuard& other) noexcept;
33- bool keepTemp_ = false;38+ 
39+ std::string directoryPath_;
40+ bool removeDirectoryOnDestruction_{true};
34};41};
35 42 
36-} // namespace manifest_generator
37} // namespace ascendc43} // namespace ascendc
38 44 
39-#endif // ASCENDC_BUILD_WORKSPACE_H45+#endif // ASCENDC_COMMON_DIRECTORY_CLEANUP_GUARD_H
@@ -0,0 +1,620 @@
1+/**
2+ * Copyright (c) 2026 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 "process_executor.h"
12+ 
13+#include "ascendc_tool_log.h"
14+ 
15+#include <algorithm>
16+#include <array>
17+#include <cerrno>
18+#include <csignal>
19+#include <cstring>
20+#include <exception>
21+#include <fcntl.h>
22+#include <fstream>
23+#include <map>
24+#include <new>
25+#include <poll.h>
26+#include <spawn.h>
27+#include <sys/wait.h>
28+#include <thread>
29+#include <unistd.h>
30+#include <utility>
31+ 
32+namespace ascendc {
33+namespace {
34+using SteadyClock = std::chrono::steady_clock;
35+ 
36+constexpr uint64_t MAX_OUTPUT_READ_BYTES_PER_ITERATION = 64U * 1024U;
37+constexpr int64_t MAX_PROCESS_WAIT_MILLISECONDS = 5;
38+ 
39+class FileDescriptor final {
40+public:
41+ explicit FileDescriptor(int value = -1) noexcept : value_(value) {}
42+ ~FileDescriptor() noexcept { Close(); }
43+ 
44+ FileDescriptor(const FileDescriptor&) = delete;
45+ FileDescriptor& operator=(const FileDescriptor&) = delete;
46+ 
47+ int Get() const noexcept { return value_; }
48+ 
49+ void Reset(int value = -1) noexcept
50+ {
51+ Close();
52+ value_ = value;
53+ }
54+ 
55+ void Close() noexcept
56+ {
57+ if (value_ < 0) {
58+ return;
59+ }
60+ const int valueToClose = value_;
61+ value_ = -1;
62+ if (close(valueToClose) != 0) {
63+ const int closeError = errno;
64+ ASCENDLOGW(
65+ "Failed to close process file descriptor: fd=%d errno=%d message=%s", valueToClose, closeError,
66+ std::strerror(closeError));
67+ }
68+ }
69+ 
70+private:
71+ int value_;
72+};
73+ 
74+bool RelocateFileDescriptorAboveStandardStreams(FileDescriptor& fileDescriptor) noexcept
75+{
76+ if (fileDescriptor.Get() > STDERR_FILENO) {
77+ return true;
78+ }
79+ const int relocatedFileDescriptor = fcntl(fileDescriptor.Get(), F_DUPFD_CLOEXEC, STDERR_FILENO + 1);
80+ if (relocatedFileDescriptor < 0) {
81+ const int duplicateError = errno;
82+ ASCENDLOGE(
83+ "Failed to relocate process pipe file descriptor: fd=%d errno=%d message=%s", fileDescriptor.Get(),
84+ duplicateError, std::strerror(duplicateError));
85+ return false;
86+ }
87+ fileDescriptor.Reset(relocatedFileDescriptor);
88+ return true;
89+}
90+ 
91+struct PosixSpawnResources {
92+ ~PosixSpawnResources() noexcept
93+ {
94+ if (attributesInitialized_) {
95+ (void)posix_spawnattr_destroy(&attributes_);
96+ }
97+ if (fileActionsInitialized_) {
98+ (void)posix_spawn_file_actions_destroy(&fileActions_);
99+ }
100+ }
Rtools/build/common/process_runner.h→tools/build/common/process_executor.h+34-16
@@ -1,5 +1,5 @@
1/**1/**
2- * Copyright (c) 2025 Huawei Technologies Co., Ltd.2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of3 * 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").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.5 * Please refer to the License for details. You may not use this file except in compliance with the License.
@@ -8,31 +8,49 @@
8 * See LICENSE in the root of the software repository for the full text of the License.8 * See LICENSE in the root of the software repository for the full text of the License.
9 */9 */
10 10 
11-#ifndef ASCENDC_COMMON_PROCESS_RUNNER_H11+#ifndef ASCENDC_COMMON_PROCESS_EXECUTOR_H
12-#define ASCENDC_COMMON_PROCESS_RUNNER_H12+#define ASCENDC_COMMON_PROCESS_EXECUTOR_H
13 13 
14#include <chrono>14#include <chrono>
15+#include <cstdint>
15#include <string>16#include <string>
17+#include <utility>
16#include <vector>18#include <vector>
17 19 
18-#include <sys/types.h>
19- 
20namespace ascendc {20namespace ascendc {
21 21 
22-class ProcessRunner final {22+enum class ProcessOutcome : uint32_t {
23+ ExecutorFailure,
24+ Exited,
25+ Signaled,
26+ TimedOut,
27+};
28+ 
29+struct ProcessExecutorRequest {
30+ std::vector<std::string> arguments;
31+ std::vector<std::pair<std::string, std::string>> environmentOverrides;
32+ std::vector<std::string> removedEnvironmentVariables;
33+ std::string mirroredOutputLogFilePath;
34+ std::chrono::milliseconds executionTimeout{0};
35+ std::chrono::milliseconds terminationGracePeriod{0};
36+};
37+ 
38+struct ProcessExecutorResult {
39+ ProcessOutcome outcome{ProcessOutcome::ExecutorFailure};
40+ int32_t terminationCode{0};
41+ std::chrono::milliseconds elapsedTime{0};
42+ 
43+ bool HasSuccessfulExit() const noexcept;
44+ const char* GetOutcomeName() const noexcept;
45+};
46+ 
47+class ProcessExecutor final {
23public:48public:
24- ProcessRunner() = delete;49+ ProcessExecutor() = delete;
25 50 
26- static bool Run(51+ static ProcessExecutorResult Execute(const ProcessExecutorRequest& request);
27- const std::vector<std::string>& arguments, const std::vector<std::pair<std::string, std::string>>& environment);
28- static bool Run(
29- const std::vector<std::string>& arguments, const std::vector<std::pair<std::string, std::string>>& environment,
30- std::chrono::milliseconds timeout);
31- 
32-private:
33- static void TerminateAndReap(pid_t child) noexcept;
34};52};
35 53 
36} // namespace ascendc54} // namespace ascendc
37 55 
38-#endif // ASCENDC_COMMON_PROCESS_RUNNER_H56+#endif // ASCENDC_COMMON_PROCESS_EXECUTOR_H