mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-27 04:09:36 +03:00
The overall `cmakeBuild` snippet is written only after the native build tool returns, so interrupting `cmake --build` with Ctrl+C terminated CMake before it was recorded and lost the build's delineation. When instrumentation is active, install a scoped, async-signal-safe handler (POSIX SIGINT; Windows CTRL_C/CTRL_BREAK) that just flags the interrupt. The existing write then runs during unwind, records the interrupting signal in a new `interruptSignal` field, skips the post-build index hook, and re-raises so the exit status still reflects the signal. The handler lives in its own translation unit, keeping the platform-divergent signal code out of the main instrumentation implementation. Issue: #27859
170 lines
4.5 KiB
C
170 lines
4.5 KiB
C
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
|
|
/* Test helper to exercise instrumentation handling of an interrupted build.
|
|
|
|
Usage: InterruptBuild <delay-seconds> <command> [args...]
|
|
|
|
It runs <command> in its own process group, waits <delay-seconds>, then
|
|
delivers a user interrupt to that group only -- mimicking a user pressing
|
|
Ctrl+C on the build's foreground process group, while leaving the calling
|
|
test driver untouched. It then reports a fixed exit code so the test can
|
|
assert deterministically:
|
|
42 - the build was interrupted and then stopped (expected)
|
|
2 - usage error
|
|
3 - failed to start the build
|
|
99 - the build did not stop after the interrupt
|
|
100 + N - the build exited normally with code N (unexpected, POSIX only)
|
|
|
|
On POSIX the interrupt is SIGINT sent to the child's process group; the
|
|
instrumented `cmake` re-raises it, so the child terminates via a signal.
|
|
On Windows the interrupt is a CTRL_BREAK_EVENT sent to the child's process
|
|
group (created with CREATE_NEW_PROCESS_GROUP); `cmake`'s console handler
|
|
treats CTRL_BREAK identically to CTRL_C. Because Windows `cmake` returns
|
|
the build's (failed) exit code rather than a signal status, success is
|
|
detected as "the child exited promptly after the interrupt". The real
|
|
assertion -- that an interrupted snippet was written -- is done by the
|
|
accompanying check script. */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#if defined(_WIN32)
|
|
|
|
# include <windows.h>
|
|
|
|
# include <string.h>
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int delay;
|
|
char cmdline[32768];
|
|
size_t len = 0;
|
|
int i;
|
|
size_t n;
|
|
STARTUPINFOA si;
|
|
PROCESS_INFORMATION pi;
|
|
DWORD waitRc;
|
|
|
|
if (argc < 3) {
|
|
fprintf(stderr,
|
|
"Usage: InterruptBuild <delay-seconds> <command> [args...]\n");
|
|
return 2;
|
|
}
|
|
delay = atoi(argv[1]);
|
|
if (delay <= 0) {
|
|
delay = 1;
|
|
}
|
|
|
|
/* Rebuild a command line from argv[2..]. The inputs are controlled by the
|
|
test (a cmake path plus simple flags) and never contain embedded quotes,
|
|
so wrapping each argument in quotes is sufficient and correct. The buffer
|
|
is sized to the CreateProcess command-line limit of 32768 characters,
|
|
including the terminating null. */
|
|
for (i = 2; i < argc; ++i) {
|
|
n = strlen(argv[i]);
|
|
if (len + n + 4 >= sizeof(cmdline)) {
|
|
return 2;
|
|
}
|
|
if (i > 2) {
|
|
cmdline[len++] = ' ';
|
|
}
|
|
cmdline[len++] = '"';
|
|
memcpy(cmdline + len, argv[i], n);
|
|
len += n;
|
|
cmdline[len++] = '"';
|
|
}
|
|
cmdline[len] = '\0';
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
|
|
/* New process group so the control event reaches only the build (and its
|
|
children), not this helper or the test driver. */
|
|
if (!CreateProcessA(NULL, cmdline, NULL, NULL, FALSE,
|
|
CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi)) {
|
|
return 3;
|
|
}
|
|
|
|
Sleep((DWORD)delay * 1000);
|
|
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId);
|
|
|
|
waitRc = WaitForSingleObject(pi.hProcess, 30000);
|
|
if (waitRc != WAIT_OBJECT_0) {
|
|
TerminateProcess(pi.hProcess, 1);
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
return 99;
|
|
}
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
return 42;
|
|
}
|
|
|
|
#else
|
|
|
|
# include <signal.h>
|
|
# include <unistd.h>
|
|
|
|
# include <sys/types.h>
|
|
# include <sys/wait.h>
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int delay;
|
|
pid_t build;
|
|
pid_t killer;
|
|
int status = 0;
|
|
|
|
if (argc < 3) {
|
|
fprintf(stderr,
|
|
"Usage: InterruptBuild <delay-seconds> <command> [args...]\n");
|
|
return 2;
|
|
}
|
|
delay = atoi(argv[1]);
|
|
if (delay <= 0) {
|
|
delay = 1;
|
|
}
|
|
|
|
build = fork();
|
|
if (build < 0) {
|
|
return 3;
|
|
}
|
|
if (build == 0) {
|
|
/* Become the leader of a new process group, then run the build. The
|
|
native build tool inherits this group, so a later group signal reaches
|
|
it too. */
|
|
setpgid(0, 0);
|
|
execvp(argv[2], &argv[2]);
|
|
_exit(127);
|
|
}
|
|
|
|
/* Best-effort from the parent side to avoid a setpgid race; ignore errors.
|
|
*/
|
|
setpgid(build, build);
|
|
|
|
killer = fork();
|
|
if (killer == 0) {
|
|
sleep((unsigned int)delay);
|
|
killpg(build, SIGINT);
|
|
_exit(0);
|
|
}
|
|
|
|
waitpid(build, &status, 0);
|
|
if (killer > 0) {
|
|
kill(killer, SIGKILL);
|
|
waitpid(killer, NULL, 0);
|
|
}
|
|
|
|
if (WIFSIGNALED(status)) {
|
|
return 42;
|
|
}
|
|
if (WIFEXITED(status)) {
|
|
return 100 + WEXITSTATUS(status);
|
|
}
|
|
return 99;
|
|
}
|
|
|
|
#endif
|