From 38e0434c9ae48a5db57dd8d997442adf4e49a565 Mon Sep 17 00:00:00 2001 From: Daksh Mamodiya Date: Sun, 5 Jul 2026 10:54:50 +0200 Subject: [PATCH 1/3] Instrumentation: Write cmakeInstall snippet on interrupted install With instrumentation enabled, interrupting `cmake --install` (Ctrl+C) terminated the process before its overall `cmakeInstall` envelope snippet was written, orphaning the per-script `install` snippets. Extend the `cmake --build` interrupt handling to the install site: wrap the `cmakeInstall` command in `HandleInterrupt`, skip the post-install hook, and re-raise so the exit status reflects the signal. Add cooperative cancellation so the command unwinds -- serial-loop and parallel `queueScripts` guards stop launching further scripts, and the parallel failure aggregation is scoped to dispatched runners so un-launched scripts are not counted as failed. Force a non-zero result on interrupt (Windows cannot re-raise), and write the envelope atomically so a second Ctrl+C cannot truncate it. Issue: #27859 --- Help/manual/cmake-instrumentation.7.rst | 8 +- .../instrumentation/snippet-v1-schema.json | 2 +- .../release/dev/instrumentation-interrupt.rst | 3 + Source/cmInstallScriptHandler.cxx | 23 ++++- Source/cmInstrumentation.cxx | 19 ++-- Source/cmInstrumentationInterrupt.cxx | 52 +++++------ Source/cmakemain.cxx | 41 ++++++++- .../Instrumentation/RunCMakeTest.cmake | 89 +++++++++++++++++-- .../check-installation-interrupted.cmake | 12 +++ .../Instrumentation/check-interrupted.cmake | 35 +------- ....txt => interrupt-build-signal-result.txt} | 0 .../interrupt-install-signal-result.txt | 1 + Tests/RunCMake/Instrumentation/json.cmake | 27 ++++++ .../Instrumentation/project/CMakeLists.txt | 10 +++ .../project/installSlow1/CMakeLists.txt | 4 + .../project/installSlow2/CMakeLists.txt | 4 + .../project/installSlow3/CMakeLists.txt | 4 + ...errupt.json.in => interrupt-build.json.in} | 0 .../query/interrupt-install.json.in | 5 ++ 19 files changed, 253 insertions(+), 86 deletions(-) create mode 100644 Tests/RunCMake/Instrumentation/check-installation-interrupted.cmake rename Tests/RunCMake/Instrumentation/{interrupt-build-result.txt => interrupt-build-signal-result.txt} (100%) create mode 100644 Tests/RunCMake/Instrumentation/interrupt-install-signal-result.txt create mode 100644 Tests/RunCMake/Instrumentation/project/installSlow1/CMakeLists.txt create mode 100644 Tests/RunCMake/Instrumentation/project/installSlow2/CMakeLists.txt create mode 100644 Tests/RunCMake/Instrumentation/project/installSlow3/CMakeLists.txt rename Tests/RunCMake/Instrumentation/query/{interrupt.json.in => interrupt-build.json.in} (100%) create mode 100644 Tests/RunCMake/Instrumentation/query/interrupt-install.json.in diff --git a/Help/manual/cmake-instrumentation.7.rst b/Help/manual/cmake-instrumentation.7.rst index 3ff309067b..fb6f244b67 100644 --- a/Help/manual/cmake-instrumentation.7.rst +++ b/Help/manual/cmake-instrumentation.7.rst @@ -454,11 +454,11 @@ Snippet files have a filename with the syntax ``interruptSignal`` .. versionadded:: 4.5 - The integer signal number that interrupted the build before it completed + The integer signal number that interrupted the command before it completed (for example ``2`` for ``SIGINT`` from Ctrl+C). Only included when ``role`` - is ``cmakeBuild`` and the build was interrupted. Consumers can use the - presence of this field to distinguish an interrupted build from one that - ran to completion. + is ``cmakeBuild`` or ``cmakeInstall`` and the command was interrupted. + Consumers can use the presence of this field to distinguish an interrupted + command from one that ran to completion. Only available as of data version ``1.2``. diff --git a/Help/manual/instrumentation/snippet-v1-schema.json b/Help/manual/instrumentation/snippet-v1-schema.json index a59de52293..37731ff1a4 100644 --- a/Help/manual/instrumentation/snippet-v1-schema.json +++ b/Help/manual/instrumentation/snippet-v1-schema.json @@ -237,7 +237,7 @@ }, "interruptSignal": { "type": "integer", - "description": "The signal number that interrupted the build before it completed. Only included when role is cmakeBuild and the build was interrupted.", + "description": "The signal number that interrupted the command before it completed. Only included when role is cmakeBuild or cmakeInstall and the command was interrupted.", "minimum": 1 }, "role": { diff --git a/Help/release/dev/instrumentation-interrupt.rst b/Help/release/dev/instrumentation-interrupt.rst index 10383a372f..c930f2ced6 100644 --- a/Help/release/dev/instrumentation-interrupt.rst +++ b/Help/release/dev/instrumentation-interrupt.rst @@ -8,3 +8,6 @@ instrumentation-interrupt ``interruptSignal`` field, recording the signal that interrupted the build, so that consumers can distinguish an interrupted build from one that ran to completion. +* Similarly, :manual:`cmake-instrumentation(7)` API now records an overall + ``cmakeInstall`` snippet, marked with the same ``interruptSignal`` field, + when a :option:`cmake --install` invocation is interrupted by the user. diff --git a/Source/cmInstallScriptHandler.cxx b/Source/cmInstallScriptHandler.cxx index d1fb45fa86..9b91a08241 100644 --- a/Source/cmInstallScriptHandler.cxx +++ b/Source/cmInstallScriptHandler.cxx @@ -22,6 +22,7 @@ #include "cmCryptoHash.h" #include "cmGeneratedFileStream.h" #include "cmInstrumentation.h" +#include "cmInstrumentationInterrupt.h" #include "cmJSONState.h" #include "cmProcessOutput.h" #include "cmStringAlgorithms.h" @@ -152,6 +153,14 @@ int cmInstallScriptHandler::Install(unsigned int j, std::function queueScripts; queueScripts = [&runners, &working, &installed, &i, &loop, j, &queueScripts]() { + if (cmInstrumentationInterrupt::PendingInterruptSignal() != 0) { + // Interrupted (e.g. Ctrl+C): launch no further scripts. In-flight + // children share the process group, receive the signal, and exit on + // their own, draining the event loop; queueScripts is the single + // re-entry point for launching, so guarding it here stops all remaining + // work without killing anything. + return; + } for (auto queue = std::min(j - working, runners.size() - i); queue > 0; --queue) { ++working; @@ -173,10 +182,18 @@ int cmInstallScriptHandler::Install(unsigned int j, queueScripts(); uv_run(loop, UV_RUN_DEFAULT); + // Aggregate child results. When an interrupt stopped queueScripts, the + // runners beyond the dispatched prefix [0, i) were never started (they have + // a null process handle); those are "not run", not "failed", so exclude + // them. With no interrupt every runner was dispatched and this inspects + // them all, matching the non-interrupt behavior exactly. + std::size_t const inspect = + cmInstrumentationInterrupt::PendingInterruptSignal() != 0 ? i + : runners.size(); int result = 0; - for (auto& runner : runners) { - if (runner.Failed()) { - runner.printFailure(); + for (std::size_t k = 0; k < inspect; ++k) { + if (runners[k].Failed()) { + runners[k].printFailure(); result = 1; } } diff --git a/Source/cmInstrumentation.cxx b/Source/cmInstrumentation.cxx index 6fe151c4c7..a63c94b342 100644 --- a/Source/cmInstrumentation.cxx +++ b/Source/cmInstrumentation.cxx @@ -770,8 +770,8 @@ int cmInstrumentation::InstrumentCommand( // See SpawnBuildDaemon(); this data is currently meaningless for build. root["result"] = command_type == "build" ? Json::nullValue : ret; - // If the build was interrupted (e.g. by Ctrl+C), record the signal number - // that stopped it, so consumers can distinguish an interrupted build from + // If the command was interrupted (e.g. by Ctrl+C), record the signal number + // that stopped it, so consumers can distinguish an interrupted command from // one that ran to completion. Omitted when no interrupt occurred; only a // command wrapped by HandleInterrupt can observe a pending signal here. int sig = cmInstrumentationInterrupt::PendingInterruptSignal(); @@ -840,14 +840,15 @@ int cmInstrumentation::InstrumentCommand( } this->configureSnippetData.clear(); } - // Write the cmakeBuild envelope atomically (temp file + rename). This is - // the snippet flushed while unwinding from a user interrupt, where a - // second Ctrl+C could otherwise truncate it mid-write; the atomic write - // guarantees it is either absent or complete. Per-step snippets are never - // flushed under interrupt and are left non-atomic. + // Write the cmakeBuild/cmakeInstall envelope atomically (temp file + + // rename). This is the snippet flushed while unwinding from a user + // interrupt, where a second Ctrl+C could otherwise truncate it mid-write; + // the atomic write guarantees it is either absent or complete. Per-step + // snippets are never flushed under interrupt and are left non-atomic. + bool const atomicEnvelope = + command_type == "cmakeBuild" || command_type == "cmakeInstall"; this->WriteInstrumentationJson(latestDataVersion, root, "data", file_name, - command_type == "cmakeBuild" ? Atomic::Yes - : Atomic::No); + atomicEnvelope ? Atomic::Yes : Atomic::No); } return ret; } diff --git a/Source/cmInstrumentationInterrupt.cxx b/Source/cmInstrumentationInterrupt.cxx index 28b8a3e018..e681a48b48 100644 --- a/Source/cmInstrumentationInterrupt.cxx +++ b/Source/cmInstrumentationInterrupt.cxx @@ -26,32 +26,34 @@ #endif namespace { -// Flag shared between the interrupt handler and the build flow that writes the -// `cmakeBuild` snippet. On Windows the console control handler runs on a -// separate thread, so an atomic is required; on POSIX the handler runs in -// signal context, where only `volatile sig_atomic_t` is guaranteed safe. +// Flag shared between the interrupt handler and the command flow that writes +// the instrumentation envelope snippet. On Windows the console control +// handler runs on a separate thread, so an atomic is required; on POSIX the +// handler runs in signal context, where only `volatile sig_atomic_t` is +// guaranteed safe. #ifdef _WIN32 -std::atomic buildInterruptSignal{ 0 }; +std::atomic interruptSignal{ 0 }; BOOL WINAPI cmInstrumentationConsoleHandler(DWORD type) { if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT) { int expected = 0; - buildInterruptSignal.compare_exchange_strong(expected, SIGINT); + interruptSignal.compare_exchange_strong(expected, SIGINT); // Return TRUE so the main thread can finish writing the snippet before the - // process exits. The native build tool shares the console and receives - // the event directly, so it still terminates and unblocks our build loop. + // process exits. Child processes (native build tool, install scripts, + // tests) share the console and receive the event directly, so they still + // terminate and unblock our loop. return TRUE; } return FALSE; } #else -sig_atomic_t volatile buildInterruptSignal = 0; +sig_atomic_t volatile interruptSignal = 0; struct sigaction savedSigIntAction; extern "C" void cmInstrumentationSignalHandler(int sig) { - buildInterruptSignal = sig; + interruptSignal = sig; } #endif @@ -59,14 +61,14 @@ extern "C" void cmInstrumentationSignalHandler(int sig) // than delivered by the OS. An injected interrupt must NOT be re-raised (the // process exits normally after flushing the snippet), so the test stays a // clean-exit, leak-checkable case on every generator. -bool buildInterruptInjected = false; +bool interruptInjected = false; // Test-only seam. An undocumented, unsupported environment variable lets the -// instrumentation test suite inject a "build was interrupted" condition -// deterministically, with no real OS signal -- so the cmakeBuild interrupt -// path can be exercised on every generator and platform. The double- -// underscore name marks it internal; it is never set in normal use. Mirrors -// CTest's internal fake-hook convention. +// instrumentation test suite inject an "interrupted" condition +// deterministically, with no real OS signal -- so the instrumentation +// interrupt path can be exercised on every generator and platform. The +// double-underscore name marks it internal; it is never set in normal use. +// Mirrors CTest's internal fake-hook convention. void InjectTestInterrupt() { char const* value = std::getenv("__CMAKE_INSTRUMENTATION_TEST_INTERRUPT"); @@ -78,21 +80,21 @@ void InjectTestInterrupt() return; } #ifdef _WIN32 - buildInterruptSignal.store(sig); + interruptSignal.store(sig); #else - buildInterruptSignal = static_cast(sig); + interruptSignal = static_cast(sig); #endif - buildInterruptInjected = true; + interruptInjected = true; } // Install the interrupt handler and clear any previously recorded signal. void InstallInterruptHandler() { #ifdef _WIN32 - buildInterruptSignal.store(0); + interruptSignal.store(0); SetConsoleCtrlHandler(cmInstrumentationConsoleHandler, TRUE); #else - buildInterruptSignal = 0; + interruptSignal = 0; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = cmInstrumentationSignalHandler; @@ -118,9 +120,9 @@ void RestoreInterruptHandler() int cmInstrumentationInterrupt::PendingInterruptSignal() { #ifdef _WIN32 - return buildInterruptSignal.load(); + return interruptSignal.load(); #else - return static_cast(buildInterruptSignal); + return static_cast(interruptSignal); #endif } @@ -134,7 +136,7 @@ cmInstrumentationInterrupt::HandleInterrupt( return { callback(), false, 0, true }; } InstallInterruptHandler(); - buildInterruptInjected = false; + interruptInjected = false; // Test-only: allow the suite to inject an interrupt deterministically. InjectTestInterrupt(); int ret = callback(); @@ -142,7 +144,7 @@ cmInstrumentationInterrupt::HandleInterrupt( RestoreInterruptHandler(); // A real OS interrupt should be re-raised so the exit status reflects it; an // injected (test) interrupt should not, so the process exits cleanly. - return { ret, sig != 0, sig, !buildInterruptInjected }; + return { ret, sig != 0, sig, !interruptInjected }; } void cmInstrumentationInterrupt::RaiseInterrupt(int sig) diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index 97b1712097..9426c6cdbf 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -28,6 +28,7 @@ #include "cmGlobalGenerator.h" #include "cmInstallScriptHandler.h" #include "cmInstrumentation.h" +#include "cmInstrumentationInterrupt.h" #include "cmInstrumentationQuery.h" #include "cmList.h" #include "cmMakefile.h" @@ -958,6 +959,12 @@ int do_install(int ac, char const* const* av) ret_ = handler.Install(jobs, instrumentation); } else { for (auto const& script : handler.GetScripts()) { + if (cmInstrumentationInterrupt::PendingInterruptSignal() != 0) { + // Interrupted (e.g. Ctrl+C): launch no further scripts. The script + // currently running executes in-process and finishes on its own; we + // simply stop starting new ones. + break; + } std::vector cmd = script.command; cmake cm(cmState::Role::Script); cmSystemTools::SetMessageCallback( @@ -976,15 +983,41 @@ int do_install(int ac, char const* const* av) } } } + if (cmInstrumentationInterrupt::PendingInterruptSignal() != 0) { + // Any caught interrupt makes the install unsuccessful even if the work + // that did run happened to succeed. Windows has no signal to re-raise, + // so this is what forces a non-zero exit status there; on POSIX it also + // keeps the snippet `result` consistent with the re-raised signal. + ret_ = 1; + } return int(ret_ > 0); }; std::vector cmd; cm::append(cmd, av, av + ac); - ret = instrumentation.InstrumentCommand( - "cmakeInstall", cmd, [doInstall]() -> cmInstrumentation::CommandResult { - return { doInstall(), cm::nullopt, cm::nullopt }; - }); + // Run the install under an interrupt handler so that a user interrupt (e.g. + // Ctrl+C) still writes the overall `cmakeInstall` snippet before we exit. + cmInstrumentationInterrupt::InterruptOutcome installOutcome = + cmInstrumentationInterrupt::HandleInterrupt( + instrumentation.HasQuery(), + [&instrumentation, &cmd, &doInstall]() -> int { + return instrumentation.InstrumentCommand( + "cmakeInstall", cmd, + [&doInstall]() -> cmInstrumentation::CommandResult { + return { doInstall(), cm::nullopt, cm::nullopt }; + }); + }); + ret = installOutcome.ExitCode; + if (installOutcome.Interrupted) { + // The install was interrupted and its snippet has been written. Skip the + // post-install indexing hook (which would run callbacks and delete data). + // For a real OS interrupt, re-raise so the exit status reflects it; for a + // test-injected interrupt, exit cleanly. + if (installOutcome.ShouldRaise) { + cmInstrumentationInterrupt::RaiseInterrupt(installOutcome.Signal); + } + return ret; + } instrumentation.CollectTimingData( cmInstrumentationQuery::Hook::PostCMakeInstall); return ret; diff --git a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake index 18f800d379..95f3c19b64 100644 --- a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake +++ b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake @@ -12,6 +12,8 @@ function(instrument test) "INTERRUPT_SEAM" "INSTALL" "INSTALL_PARALLEL" + "INSTALL_SEAM" + "INSTALL_INTERRUPT" "TEST" "WORKFLOW" "COPY_QUERIES" @@ -127,6 +129,11 @@ function(instrument test) list(APPEND ARGS_CONFIGURE_ARGS "-DINTERRUPT_BUILD_SRC=${RunCMake_SOURCE_DIR}/InterruptBuild.c") endif() + if (ARGS_INSTALL_INTERRUPT) + list(APPEND ARGS_CONFIGURE_ARGS + "-DINTERRUPT_BUILD_SRC=${RunCMake_SOURCE_DIR}/InterruptBuild.c" + "-DINSTALL_INTERRUPT=ON") + endif() set(RunCMake_TEST_SOURCE_DIR ${RunCMake_SOURCE_DIR}/project) if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) set(maybe_CMAKE_BUILD_TYPE -DCMAKE_BUILD_TYPE=Debug) @@ -200,7 +207,7 @@ function(instrument test) endif() set(helper ${helper_dir}/InterruptBuild${CMAKE_EXECUTABLE_SUFFIX}) set(RunCMake_QUIET_ERROR 1) - run_cmake_command(${test}-build + run_cmake_command(${test}-signal ${helper} 3 ${CMAKE_COMMAND} --build . --config Debug) unset(RunCMake_QUIET_ERROR) @@ -221,7 +228,7 @@ function(instrument test) # and skips the hook. set(ENV{__CMAKE_INSTRUMENTATION_TEST_INTERRUPT} 2) set(RunCMake_TEST_EXPECT_RESULT 0) - run_cmake_command(${test}-build + run_cmake_command(${test}-seam ${CMAKE_COMMAND} --build . --config Debug) unset(RunCMake_TEST_EXPECT_RESULT) unset(ENV{__CMAKE_INSTRUMENTATION_TEST_INTERRUPT}) @@ -239,6 +246,63 @@ function(instrument test) if (ARGS_INSTALL) run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --prefix install --config Debug) endif() + if (ARGS_INSTALL_SEAM) + # Drive the cmakeInstall interrupt path deterministically via the test-only + # injection seam, with no OS signal, so it runs on every generator. First + # install normally so the postCMakeInstall hook runs and creates its marker + # file; remove it (and the manifest) so their absence after the injected + # install proves that install's hook was skipped and left nothing complete. + set(RunCMake_QUIET_ERROR 1) + run_cmake_command(${test}-warmup + ${CMAKE_COMMAND} --install . --prefix install --config Debug) + file(REMOVE ${v1}/postCMakeInstall.hook) + file(REMOVE ${RunCMake_TEST_BINARY_DIR}/install_manifest.txt) + + # Inject an interrupt (SIGINT == 2) via the undocumented test seam and + # install again; cmake exits with a non-zero status but writes the + # interrupted cmakeInstall snippet and skips the hook. + set(ENV{__CMAKE_INSTRUMENTATION_TEST_INTERRUPT} 2) + set(RunCMake_TEST_EXPECT_RESULT 1) + run_cmake_command(${test}-seam + ${CMAKE_COMMAND} --install . --prefix install --config Debug) + unset(RunCMake_TEST_EXPECT_RESULT) + unset(ENV{__CMAKE_INSTRUMENTATION_TEST_INTERRUPT}) + unset(RunCMake_QUIET_ERROR) + endif() + if (ARGS_INSTALL_INTERRUPT) + # Build just the interrupt helper and main so the parallel install has + # something to do. This build runs no postCMakeInstall hook (the query only + # requests that hook), so remove any stale marker; its absence after the + # interrupted install then proves that install's hook was skipped. + run_cmake_command(${test}-helper + ${CMAKE_COMMAND} --build . --config Debug --target InterruptBuild main) + file(REMOVE ${v1}/postCMakeBuild.hook) + file(REMOVE ${v1}/postCMakeInstall.hook) + file(REMOVE ${RunCMake_TEST_BINARY_DIR}/install_manifest.txt) + + # Pin InstallScripts.json newest so the parallel install path is chosen + # deterministically (the staleness heuristic can otherwise fall back to + # serial on coarse-mtime filesystems). + file(TOUCH_NOCREATE ${RunCMake_TEST_BINARY_DIR}/CMakeFiles/InstallScripts.json) + + # Run an instrumented parallel install and interrupt it after a few seconds, + # while a slow install script is still running and others are pending. + set(helper_dir ${RunCMake_TEST_BINARY_DIR}) + if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(helper_dir ${helper_dir}/Debug) + endif() + set(helper ${helper_dir}/InterruptBuild${CMAKE_EXECUTABLE_SUFFIX}) + # Merge stdout/stderr: the parallel aggregation prints one "User interrupt" + # diagnostic per in-flight script to stderr, whose presence and count are + # cosmetic and race with the re-raise that terminates the process. + set(RunCMake_TEST_OUTPUT_MERGE 1) + set(RunCMake_QUIET_ERROR 1) + run_cmake_command(${test}-signal + ${helper} 3 + ${CMAKE_COMMAND} --install . --prefix install --config Debug -j 1) + unset(RunCMake_QUIET_ERROR) + unset(RunCMake_TEST_OUTPUT_MERGE) + endif() if (ARGS_TEST) run_cmake_command(${test}-test ${CMAKE_CTEST_COMMAND} . -C Debug) endif() @@ -266,9 +330,14 @@ if (INSTRUMENTATION_INTERRUPT_REAL) # console event and does not re-broadcast it to the runner; the other Windows # make-family generators are covered by the injection seam instead. if (NOT WIN32 OR RunCMake_GENERATOR MATCHES "Ninja") - instrument(interrupt INTERRUPT + instrument(interrupt-build INTERRUPT CHECK_SCRIPT check-interrupted.cmake ) + # Interrupt a parallel `cmake --install` with a real OS signal, proving the + # cooperative cancellation stops pending install scripts and skips the hook. + instrument(interrupt-install INSTALL_INTERRUPT + CHECK_SCRIPT check-installation-interrupted.cmake + ) endif() return() endif() @@ -476,13 +545,17 @@ if (NOT Skip_COMPILE_TRACE_QUERY_Case) endif() endif() -# Test that interrupting `cmake --build` still writes the cmakeBuild snippet, -# recording the interrupting signal. This case uses the deterministic test -# seam (no OS event). The real OS-event counterpart runs in the separate -# RunCMake.InstrumentationInterrupt suite. -instrument(interrupt INTERRUPT_SEAM +# Test that interrupting `cmake --build` or `cmake --install` still writes the +# overall cmakeBuild/cmakeInstall snippet, recording the interrupting signal, +# and skips the corresponding post-command hook. These cases use the +# deterministic test seam (no OS event); the real OS-event counterparts run in +# the separate RunCMake.InstrumentationInterrupt suite. +instrument(interrupt-build INTERRUPT_SEAM CHECK_SCRIPT check-interrupted.cmake ) +instrument(interrupt-install BUILD INSTALL_SEAM + CHECK_SCRIPT check-installation-interrupted.cmake +) # Test make/ninja hooks if(RunCMake_GENERATOR STREQUAL "FASTBuild") diff --git a/Tests/RunCMake/Instrumentation/check-installation-interrupted.cmake b/Tests/RunCMake/Instrumentation/check-installation-interrupted.cmake new file mode 100644 index 0000000000..ededb9fbac --- /dev/null +++ b/Tests/RunCMake/Instrumentation/check-installation-interrupted.cmake @@ -0,0 +1,12 @@ +include(${CMAKE_CURRENT_LIST_DIR}/json.cmake) + +# After an interrupted `cmake --install`, exactly one cmakeInstall snippet should +# be present, marked with the interrupting signal, and the postCMakeInstall hook +# must have been skipped. +check_interrupted_snippet(cmakeInstall postCMakeInstall) + +# An interrupted install is incomplete, so it must not leave behind an install +# manifest that looks complete. +if (EXISTS ${RunCMake_TEST_BINARY_DIR}/install_manifest.txt) + add_error("install_manifest.txt should be absent after an interrupted install") +endif() diff --git a/Tests/RunCMake/Instrumentation/check-interrupted.cmake b/Tests/RunCMake/Instrumentation/check-interrupted.cmake index 00dceed0e9..d6cd07d80f 100644 --- a/Tests/RunCMake/Instrumentation/check-interrupted.cmake +++ b/Tests/RunCMake/Instrumentation/check-interrupted.cmake @@ -1,35 +1,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/json.cmake) # After an interrupted `cmake --build`, exactly one cmakeBuild snippet should be -# present, recording the interrupting signal. Any cmakeBuild snippet from the -# earlier (uninterrupted) helper build was collated and removed by its -# postCMakeBuild hook. -file(GLOB cmakeBuildSnippets LIST_DIRECTORIES false ${v1}/data/cmakeBuild-*.json) -list(LENGTH cmakeBuildSnippets numCmakeBuild) -if (NOT numCmakeBuild EQUAL 1) - add_error("Expected exactly one cmakeBuild snippet, found ${numCmakeBuild}: ${cmakeBuildSnippets}") -else() - read_json("${cmakeBuildSnippets}" contents) - - string(JSON interruptSignal ERROR_VARIABLE noSignal GET "${contents}" interruptSignal) - if (noSignal OR NOT interruptSignal MATCHES "^[1-9][0-9]*$") - add_error("cmakeBuild snippet is not marked interrupted:\n${contents}") - endif() - - string(JSON version_minor GET "${contents}" version minor) - if (NOT version_minor EQUAL 2) - add_error("cmakeBuild snippet version minor expected 2, got: ${version_minor}") - endif() -endif() - -# The postCMakeBuild hook must be skipped entirely on interrupt, so its callback -# must not run. The callback (hook.cmake) writes a postCMakeBuild.hook file -# whenever it runs; the helper build's copy was removed before the interrupted -# build, so its presence here would mean the hook wrongly ran on interrupt. -if (EXISTS ${v1}/postCMakeBuild.hook) - add_error("postCMakeBuild hook should be skipped on interrupt, but it ran") -endif() - -if (DEFINED RunCMake_TEST_FAILED) - set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) -endif() +# present, marked with the interrupting signal, and the postCMakeBuild hook must +# have been skipped. +check_interrupted_snippet(cmakeBuild postCMakeBuild) diff --git a/Tests/RunCMake/Instrumentation/interrupt-build-result.txt b/Tests/RunCMake/Instrumentation/interrupt-build-signal-result.txt similarity index 100% rename from Tests/RunCMake/Instrumentation/interrupt-build-result.txt rename to Tests/RunCMake/Instrumentation/interrupt-build-signal-result.txt diff --git a/Tests/RunCMake/Instrumentation/interrupt-install-signal-result.txt b/Tests/RunCMake/Instrumentation/interrupt-install-signal-result.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/Tests/RunCMake/Instrumentation/interrupt-install-signal-result.txt @@ -0,0 +1 @@ +42 diff --git a/Tests/RunCMake/Instrumentation/json.cmake b/Tests/RunCMake/Instrumentation/json.cmake index 1c59475490..2271e9971f 100644 --- a/Tests/RunCMake/Instrumentation/json.cmake +++ b/Tests/RunCMake/Instrumentation/json.cmake @@ -71,3 +71,30 @@ function(json_equals expected_file actual_file) endif() return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE) endfunction() + +# Verify the aftermath of an interrupted top-level command whose overall +# instrumentation snippet has role `role` and whose post-command hook is `hook`. +# Exactly one such snippet must be present and marked with the interrupting +# signal (any snippet from an earlier uninterrupted warm-up run was collated and +# removed by its hook). The hook must have been skipped entirely, so its +# callback (hook.cmake writes a `${hook}.hook` marker whenever it runs, and any +# earlier copy was removed before the interrupted run) must not have run. +function(check_interrupted_snippet role hook) + file(GLOB snippets LIST_DIRECTORIES false ${v1}/data/${role}-*.json) + list(LENGTH snippets num) + if (NOT num EQUAL 1) + add_error("Expected exactly one ${role} snippet, found ${num}: ${snippets}") + else() + read_json("${snippets}" contents) + string(JSON interruptSignal ERROR_VARIABLE noSignal GET "${contents}" interruptSignal) + if (noSignal OR NOT interruptSignal MATCHES "^[1-9][0-9]*$") + add_error("${role} snippet is not marked interrupted:\n${contents}") + endif() + endif() + + if (EXISTS ${v1}/${hook}.hook) + add_error("${hook} hook should be skipped on interrupt, but it ran") + endif() + + return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE) +endfunction() diff --git a/Tests/RunCMake/Instrumentation/project/CMakeLists.txt b/Tests/RunCMake/Instrumentation/project/CMakeLists.txt index 3fde043daf..17d3c76703 100644 --- a/Tests/RunCMake/Instrumentation/project/CMakeLists.txt +++ b/Tests/RunCMake/Instrumentation/project/CMakeLists.txt @@ -79,3 +79,13 @@ if (INTERRUPT_BUILD_SRC) COMMAND ${CMAKE_COMMAND} -E echo "interruptSlow: end" ) endif() + +if (INSTALL_INTERRUPT) + # Enable parallel install and add several slow install subdirectories so that + # a `cmake --install -j 1` runs long enough to be interrupted with install + # scripts still pending. + set_property(GLOBAL PROPERTY INSTALL_PARALLEL ON) + add_subdirectory(installSlow1) + add_subdirectory(installSlow2) + add_subdirectory(installSlow3) +endif() diff --git a/Tests/RunCMake/Instrumentation/project/installSlow1/CMakeLists.txt b/Tests/RunCMake/Instrumentation/project/installSlow1/CMakeLists.txt new file mode 100644 index 0000000000..d816dd7c3a --- /dev/null +++ b/Tests/RunCMake/Instrumentation/project/installSlow1/CMakeLists.txt @@ -0,0 +1,4 @@ +# A deliberately slow install step so that a parallel `cmake --install -j 1` has +# scripts still pending when a user interrupt arrives. The interrupt must stop +# the remaining install scripts from being launched. +install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E sleep 10)") diff --git a/Tests/RunCMake/Instrumentation/project/installSlow2/CMakeLists.txt b/Tests/RunCMake/Instrumentation/project/installSlow2/CMakeLists.txt new file mode 100644 index 0000000000..d816dd7c3a --- /dev/null +++ b/Tests/RunCMake/Instrumentation/project/installSlow2/CMakeLists.txt @@ -0,0 +1,4 @@ +# A deliberately slow install step so that a parallel `cmake --install -j 1` has +# scripts still pending when a user interrupt arrives. The interrupt must stop +# the remaining install scripts from being launched. +install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E sleep 10)") diff --git a/Tests/RunCMake/Instrumentation/project/installSlow3/CMakeLists.txt b/Tests/RunCMake/Instrumentation/project/installSlow3/CMakeLists.txt new file mode 100644 index 0000000000..d816dd7c3a --- /dev/null +++ b/Tests/RunCMake/Instrumentation/project/installSlow3/CMakeLists.txt @@ -0,0 +1,4 @@ +# A deliberately slow install step so that a parallel `cmake --install -j 1` has +# scripts still pending when a user interrupt arrives. The interrupt must stop +# the remaining install scripts from being launched. +install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E sleep 10)") diff --git a/Tests/RunCMake/Instrumentation/query/interrupt.json.in b/Tests/RunCMake/Instrumentation/query/interrupt-build.json.in similarity index 100% rename from Tests/RunCMake/Instrumentation/query/interrupt.json.in rename to Tests/RunCMake/Instrumentation/query/interrupt-build.json.in diff --git a/Tests/RunCMake/Instrumentation/query/interrupt-install.json.in b/Tests/RunCMake/Instrumentation/query/interrupt-install.json.in new file mode 100644 index 0000000000..83c9d0780e --- /dev/null +++ b/Tests/RunCMake/Instrumentation/query/interrupt-install.json.in @@ -0,0 +1,5 @@ +{ + "version": 1, + "hooks": ["postCMakeInstall"], + "callbacks": ["@GET_HOOK@"] +} From 1cc963776b1a939b0798e876308ac1456199f00e Mon Sep 17 00:00:00 2001 From: Daniel Tierney Date: Tue, 30 Jun 2026 09:44:21 -0400 Subject: [PATCH 2/3] SARIF: Report call stacks with results Fixes: #27763 --- Source/cmCMakeSarifLogger.cxx | 87 ++- Source/cmSarif.cxx | 58 +- Source/cmSarif.h | 58 +- .../GenerateSarifResults-Included.cmake | 1 + .../GenerateSarifResults-expected.sarif | 566 +++++++++++++++++- .../GenerateSarifResults-stderr.txt | 27 +- .../SarifOutput/GenerateSarifResults.cmake | 30 + .../ProjectFatalError-expected.sarif | 52 +- .../ToggleExportSarifVariable-check.cmake | 5 +- .../ToggleExportSarifVariable-expected.sarif | 93 +++ .../ToggleExportSarifVariable-result.txt | 1 - .../ToggleExportSarifVariable-stderr.txt | 25 +- .../ToggleExportSarifVariable.cmake | 7 +- 13 files changed, 946 insertions(+), 64 deletions(-) create mode 100644 Tests/RunCMake/SarifOutput/GenerateSarifResults-Included.cmake create mode 100644 Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif delete mode 100644 Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-result.txt diff --git a/Source/cmCMakeSarifLogger.cxx b/Source/cmCMakeSarifLogger.cxx index 8639defb73..5d594f4bd0 100644 --- a/Source/cmCMakeSarifLogger.cxx +++ b/Source/cmCMakeSarifLogger.cxx @@ -31,38 +31,73 @@ namespace { constexpr char const* CMakeSarifOutputFlag = "CMAKE_EXPORT_SARIF"; constexpr char const* DefaultSarifFile = ".cmake/sarif/cmake.sarif"; -cm::optional GetLocationFromBacktrace( - cmListFileBacktrace const& backtrace, cmake const& cm) +cmSarif::Location LocationFromContext(cmListFileContext const& lfc, + cmake const& cm) +{ + cmSarif::Location location; + location.Physical.Artifact.Uri = lfc.FilePath; + + // SARIF requests that paths are given relative to a logical base for + // relocatability. + // Use the CMake home directory as a base dir for files under it. + std::string const& cmHomeDir = cm.GetHomeDirectory(); + std::string relative = + cmSystemTools::RelativeIfUnder(cmHomeDir, location.Physical.Artifact.Uri); + if (relative != location.Physical.Artifact.Uri) { + location.Physical.Artifact.Uri = relative; + location.Physical.Artifact.UriBaseId = cmHomeDir; + } + + if (!lfc.Name.empty()) { + location.Logical.emplace_back( + cmSarif::LogicalLocation{ lfc.Name, cmSarif::LocationKind::Function }); + } + + // Add info about the region within the file depending on how specific the + // context is. Watch for deferred call and variable watch placeholders or + // a zero, which indicates the start of processing a list file. + if (lfc.Line == cmListFileContext::DeferPlaceholderLine) { + location.Message = cmSarif::Message{ "DEFERRED" }; + } else if (lfc.Line > 0 && lfc.Line != std::numeric_limits::max()) { + cmSarif::Region region; + region.StartLine = lfc.Line; + location.Physical.ArtifactRegion = region; + } + + return location; +} + +cm::optional LastLocation(cmListFileBacktrace backtrace, + cmake const& cm) { if (backtrace.Empty()) { return {}; } - cmListFileContext const& lfc = backtrace.Top(); - // Exclude frames with no real location: negative lines are deferred-call - // placeholders, and LONG_MAX is the synthetic line used by variable_watch - // callback dispatch. Neither is a meaningful source location. - if (lfc.Line < 0 || lfc.Line == std::numeric_limits::max()) { + return LocationFromContext(backtrace.Top(), cm); +} + +cm::optional StackFromBacktrace(cmListFileBacktrace bt, + cmake const& cm) +{ + if (bt.Empty()) { return {}; } - cmSarif::PhysicalLocation location; - location.Artifact.Uri = lfc.FilePath; + cmSarif::Stack stack; + for (; !bt.Empty(); bt = bt.Pop()) { + cmSarif::Location topLocation = LocationFromContext(bt.Top(), cm); - // SARIF requests that paths are given relative to a logical base. Report - // paths relative to the source dir / script working directory if possible. - location.Artifact.UriBaseId = cm.GetHomeDirectory(); - std::string relative = cmSystemTools::RelativePath( - location.Artifact.UriBaseId, location.Artifact.Uri); - if (!relative.empty()) { - location.Artifact.Uri = relative; - } + // If the location doesn't have a specific region, this entry is a + // placeholder and should not appear in the call stack. + if (!topLocation.Message && !topLocation.Physical.ArtifactRegion) { + continue; + } - if (lfc.Line != 0) { - cmSarif::Region region; - region.StartLine = lfc.Line; - location.ArtifactRegion = region; + cmSarif::StackFrame frame; + frame.Location = std::move(topLocation); + stack.Frames.emplace_back(std::move(frame)); } - return cmSarif::Location{ location }; + return stack; } cmSarif::Tool CreateCMakeTool() @@ -231,8 +266,12 @@ bool cmCMakeSarifLogger::WriteFile(std::string const& path, cmSarif::Result result; result.RuleId = ruleInfo.first; result.RuleIndex = ruleInfo.second; - result.Message = message.Text; - result.Location = GetLocationFromBacktrace(message.Backtrace, this->CM); + result.Message = cmSarif::Message{ message.Text }; + result.Location = LastLocation(message.Backtrace, this->CM); + if (cm::optional stack = + StackFromBacktrace(message.Backtrace, this->CM)) { + result.Stacks.emplace_back(std::move(*stack)); + } result.Level = SarifLevelFromMessageType(message.Type); run.Results.emplace_back(std::move(result)); diff --git a/Source/cmSarif.cxx b/Source/cmSarif.cxx index 7b711d8821..1c9b81acc5 100644 --- a/Source/cmSarif.cxx +++ b/Source/cmSarif.cxx @@ -31,6 +31,13 @@ Json::Value GetJson(ResultSeverityLevel level) } } +Json::Value GetJson(Message const& message) +{ + Json::Value obj(Json::objectValue); + obj["text"] = message.Text; + return obj; +} + Json::Value GetJson(ArtifactLocation const& artifactLocation) { Json::Value obj(Json::objectValue); @@ -58,13 +65,54 @@ Json::Value GetJson(PhysicalLocation const& physicalLocation) return obj; } +Json::Value GetJson(LogicalLocation const& logicalLocation) +{ + Json::Value obj(Json::objectValue); + obj["name"] = logicalLocation.Name; + if (!logicalLocation.Kind.empty()) { + obj["kind"] = std::string(logicalLocation.Kind); + } + return obj; +} + Json::Value GetJson(Location const& location) { Json::Value obj(Json::objectValue); obj["physicalLocation"] = cmSarif::GetJson(location.Physical); + if (!location.Logical.empty()) { + Json::Value logical(Json::arrayValue); + for (auto const& loc : location.Logical) { + logical.append(cmSarif::GetJson(loc)); + } + obj["logicalLocations"] = logical; + } + if (location.Message) { + obj["message"] = cmSarif::GetJson(*location.Message); + } + return obj; } +Json::Value GetJson(StackFrame const& frame) +{ + Json::Value frameJson(Json::objectValue); + if (frame.Location) { + frameJson["location"] = cmSarif::GetJson(*frame.Location); + } + return frameJson; +} + +Json::Value GetJson(Stack const& stack) +{ + Json::Value stackJson(Json::objectValue); + Json::Value frames(Json::arrayValue); + for (auto const& frame : stack.Frames) { + frames.append(cmSarif::GetJson(frame)); + } + stackJson["frames"] = frames; + return stackJson; +} + Json::Value GetJson(ReportingDescriptor const& reportingDescriptor) { Json::Value rd(Json::objectValue); @@ -80,7 +128,7 @@ Json::Value GetJson(Result const& result) Json::Value resultJson(Json::objectValue); if (result.Message) { - resultJson["message"]["text"] = *result.Message; + resultJson["message"] = cmSarif::GetJson(*result.Message); } if (result.Level) { @@ -98,6 +146,14 @@ Json::Value GetJson(Result const& result) resultJson["locations"][0] = cmSarif::GetJson(*result.Location); } + if (!result.Stacks.empty()) { + Json::Value stacks(Json::arrayValue); + for (auto const& stack : result.Stacks) { + stacks.append(cmSarif::GetJson(stack)); + } + resultJson["stacks"] = stacks; + } + return resultJson; } diff --git a/Source/cmSarif.h b/Source/cmSarif.h index b0883d633f..bd779e736a 100644 --- a/Source/cmSarif.h +++ b/Source/cmSarif.h @@ -5,6 +5,8 @@ #include #include +#include +#include #include @@ -25,6 +27,14 @@ enum class ResultSeverityLevel Json::Value GetJson(ResultSeverityLevel level); +/// @brief SARIF message object (§3.11) +struct Message +{ + std::string Text; +}; + +Json::Value GetJson(Message const& message); + /// @brief SARIF artifactLocation object (§3.4) struct ArtifactLocation { @@ -51,14 +61,55 @@ struct PhysicalLocation Json::Value GetJson(PhysicalLocation const& physicalLocation); +/// @brief Suggested values for the logical location `kind` property (§3.33.7) +/// +/// The SARIF-recommended terminology for identifying the type of construct at +/// the associated location. This namespace is for defining the SARIF-specified +/// vocabulary only (although the actual `kind` property can be any string). +namespace LocationKind { +cm::string_view const Function = "function"_s; +} + +/// @brief SARIF logicalLocation object (§3.33) +struct LogicalLocation +{ + std::string Name; + + /// @brief The type of construct identified by the logical location + /// + /// The value should be from the suggestions in §3.33.7 but can be any string + /// if none of the specified suggestions apply. See `cmSarif::LocationKind` + /// for the suggested values. + cm::string_view Kind; +}; + +Json::Value GetJson(LogicalLocation const& logicalLocation); + /// @brief SARIF location object (§3.28) struct Location { PhysicalLocation Physical; + std::vector Logical; + cm::optional Message; }; Json::Value GetJson(Location const& location); +struct StackFrame +{ + cm::optional Location; + std::vector Parameters; +}; + +Json::Value GetJson(StackFrame const& stackFrame); + +struct Stack +{ + std::vector Frames; +}; + +Json::Value GetJson(Stack const& stack); + /// @brief A result reported by a run of a static analysis tool /// /// This is the data model for results in a SARIF log. Typically, a result only @@ -66,11 +117,14 @@ Json::Value GetJson(Location const& location); struct Result { /// @brief The message text of the result (required if no rule index) - cm::optional Message; + cm::optional Message; /// @brief The location of the result (optional) cm::optional Location; + /// @brief Call stacks related to the result (optional) + std::vector Stacks; + /// @brief The severity level of the result (optional) cm::optional Level; @@ -86,7 +140,7 @@ Json::Value GetJson(Result const& result); /// @brief A reporting descriptor provides information about an analysis result /// /// Reporting descriptors (SARIF specification section 3.49) provide -/// information about categories of reporting items and is used to define +/// information about categories of reporting items and are used to define /// rules and taxa. struct ReportingDescriptor { diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-Included.cmake b/Tests/RunCMake/SarifOutput/GenerateSarifResults-Included.cmake new file mode 100644 index 0000000000..a999defb47 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-Included.cmake @@ -0,0 +1 @@ +message(WARNING "Warning from an included file") diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif index c1f1fb5645..b0f4c4a011 100644 --- a/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif @@ -7,6 +7,12 @@ "level": "warning", "locations": [ { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", @@ -22,12 +28,62 @@ "text": "Example warning message" }, "ruleId": "CMake.Warning", - "ruleIndex": 0 + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 2 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] }, { "level": "warning", "locations": [ { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", @@ -43,12 +99,62 @@ "text": "A second example warning message" }, "ruleId": "CMake.Warning", - "ruleIndex": 0 + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 5 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] }, { "level": "warning", "locations": [ { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", @@ -64,12 +170,62 @@ "text": "Example author warning message" }, "ruleId": "CMake.Author", - "ruleIndex": 1 + "ruleIndex": 1, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 11 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] }, { "level": "error", "locations": [ { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", @@ -85,7 +241,405 @@ "text": "Another example author warning message" }, "ruleId": "CMake.Author", - "ruleIndex": 1 + "ruleIndex": 1, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 16 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] + }, + { + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 20 + } + } + } + ], + "message": { + "text": "Warning from a nested function call" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 20 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "a" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 24 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "b" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 27 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] + }, + { + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults-Included.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "Warning from an included file" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults-Included.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 1 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 30 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] + }, + { + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 38 + } + } + } + ], + "message": { + "text": "Warning from a variable_watch callback" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 38 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "set" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 42 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] + }, + { + "level": "error", + "locations": [ + { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 46 + } + } + } + ], + "message": { + "text": "Encabulator: a deferred error" + }, + "ruleId": "CMake.FatalError", + "ruleIndex": 2, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "GenerateSarifResults.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 46 + } + } + } + }, + { + "location": { + "message": { + "text": "DEFERRED" + }, + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + } + } + } + } + ] + } + ] } ], "tool": { @@ -99,6 +653,10 @@ { "id": "CMake.Author", "name": "CMD_AUTHOR" + }, + { + "id": "CMake.FatalError", + "name": "CMake Error" } ], "version": "" diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt b/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt index 8a2d98a1c2..e586a82e3f 100644 --- a/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt @@ -18,4 +18,29 @@ CMake Error \(author\) at GenerateSarifResults\.cmake:16 \(message\): Another example author warning message Call Stack \(most recent call first\): CMakeLists\.txt:[0-9]+ \(include\) -This error is for project developers\. Use -Wno-error=author to suppress it\.$ +This error is for project developers\. Use -Wno-error=author to suppress it\. ++ +CMake Warning at GenerateSarifResults\.cmake:20 \(message\): + Warning from a nested function call +Call Stack \(most recent call first\): + GenerateSarifResults\.cmake:24 \(a\) + GenerateSarifResults\.cmake:27 \(b\) + CMakeLists\.txt:[0-9]+ \(include\) ++ +CMake Warning at GenerateSarifResults-Included\.cmake:1 \(message\): + Warning from an included file +Call Stack \(most recent call first\): + GenerateSarifResults\.cmake:30 \(include\) + CMakeLists\.txt:[0-9]+ \(include\) ++ +CMake Warning at GenerateSarifResults\.cmake:38 \(message\): + Warning from a variable_watch callback +Call Stack \(most recent call first\): + GenerateSarifResults\.cmake:[0-9]+ \(warn_callback\) + GenerateSarifResults\.cmake:42 \(set\) + CMakeLists\.txt:[0-9]+ \(include\) ++ +CMake Error at GenerateSarifResults\.cmake:46 \(message\): + Encabulator: a deferred error +Call Stack \(most recent call first\): + CMakeLists\.txt:DEFERRED$ diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake b/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake index 0b414fcda0..14ba5be64d 100644 --- a/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake @@ -14,3 +14,33 @@ message(AUTHOR_WARNING "Example author warning message") # Change it and issue another one cmake_diagnostic(SET CMD_AUTHOR SEND_ERROR) message(AUTHOR_WARNING "Another example author warning message") + +# Define and call some functions to test stack reporting +function(a) + message(WARNING "Warning from a nested function call") +endfunction() + +function(b) + a() +endfunction() + +b() + +# Include another file that generates a warning +include("${CMAKE_CURRENT_LIST_DIR}/GenerateSarifResults-Included.cmake") + +# variable_watch and deferred calls add placeholders to the CMake backtrace. +# Placeholders on the stack are prone to accidental reporting as a stack frame +# or line number. Ensure location info is reported but the stack is not +# polluted with placeholders. + +function(warn_callback variable access value file stack) + message(WARNING "Warning from a variable_watch callback") +endfunction() + +variable_watch(x warn_callback) +set(x "x_value") + +# A result from a deferred call should report the location it was deferred from +# and the list file that finished processing and executed the deferred call. +cmake_language(DEFER CALL message SEND_ERROR "Encabulator: a deferred error") diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif index 0c7df3025f..0968d5c70a 100644 --- a/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif @@ -7,6 +7,12 @@ "level": "error", "locations": [ { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], "physicalLocation": { "artifactLocation": { "uri": "ProjectFatalError.cmake", @@ -22,7 +28,51 @@ "text": "Example error" }, "ruleId": "CMake.FatalError", - "ruleIndex": 0 + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "ProjectFatalError.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 1 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] } ], "tool": { diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake index 6b26efec5c..4a23ea2f8d 100644 --- a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake @@ -1,5 +1,6 @@ include("${CMAKE_CURRENT_LIST_DIR}/check-sarif.cmake") -# This test should produce the same output as GenerateSarifResults +# The result issued before CMAKE_EXPORT_SARIF was enabled should still be +# captured in the SARIF output check_sarif_output("${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif" - "${CMAKE_CURRENT_LIST_DIR}/GenerateSarifResults-expected.sarif") + "${CMAKE_CURRENT_LIST_DIR}/ToggleExportSarifVariable-expected.sarif") diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif new file mode 100644 index 0000000000..df80678919 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif @@ -0,0 +1,93 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "results": [ + { + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "ToggleExportSarifVariable.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 2 + } + } + } + ], + "message": { + "text": "Example warning message" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0, + "stacks": [ + { + "frames": [ + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "message" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "ToggleExportSarifVariable.cmake", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 2 + } + } + } + }, + { + "location": { + "logicalLocations": [ + { + "kind": "function", + "name": "include" + } + ], + "physicalLocation": { + "artifactLocation": { + "uri": "CMakeLists.txt", + "uriBaseId": "PATH:" + }, + "region": { + "startLine": 3 + } + } + } + } + ] + } + ] + } + ], + "tool": { + "driver": { + "name": "CMake", + "rules": [ + { + "id": "CMake.Warning", + "name": "CMake Warning" + } + ], + "version": "" + } + } + } + ], + "version": "2.1.0" +} diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-result.txt b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-result.txt deleted file mode 100644 index d00491fd7e..0000000000 --- a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-result.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt index 405923bf8f..8823493b90 100644 --- a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt @@ -1,25 +1,4 @@ -^CMake Warning at GenerateSarifResults\.cmake:2 \(message\): +^CMake Warning at ToggleExportSarifVariable\.cmake:2 \(message\): Example warning message Call Stack \(most recent call first\): - ToggleExportSarifVariable\.cmake:[0-9]+ \(include\) - CMakeLists\.txt:[0-9]+ \(include\) -+ -CMake Warning at GenerateSarifResults\.cmake:5 \(message\): - A second example warning message -Call Stack \(most recent call first\): - ToggleExportSarifVariable\.cmake:[0-9]+ \(include\) - CMakeLists\.txt:[0-9]+ \(include\) -+ -CMake Warning \(author\) at GenerateSarifResults\.cmake:11 \(message\): - Example author warning message -Call Stack \(most recent call first\): - ToggleExportSarifVariable\.cmake:[0-9]+ \(include\) - CMakeLists\.txt:[0-9]+ \(include\) -This warning is for project developers\. Use -Wno-author to suppress it\. -+ -CMake Error \(author\) at GenerateSarifResults\.cmake:16 \(message\): - Another example author warning message -Call Stack \(most recent call first\): - ToggleExportSarifVariable\.cmake:[0-9]+ \(include\) - CMakeLists\.txt:[0-9]+ \(include\) -This error is for project developers\. Use -Wno-error=author to suppress it\.$ + CMakeLists\.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake index 19c0a6e2b2..df44c7dc7c 100644 --- a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake @@ -1,6 +1,3 @@ -# Generate potential SARIF results -include("${CMAKE_CURRENT_LIST_DIR}/GenerateSarifResults.cmake") - -# Enable SARIF logging at the end for the most behavior coverage -# All results should be captured regardless of when enabled +# Enable logging after generating a result to ensure it is still captured. +message(WARNING "Example warning message") set(CMAKE_EXPORT_SARIF ON CACHE BOOL "Export SARIF results" FORCE) From 9a524ded0cd36d2e25989e23ded310a4c7c05261 Mon Sep 17 00:00:00 2001 From: Daniel Tierney Date: Thu, 2 Jul 2026 12:07:29 -0400 Subject: [PATCH 3/3] SARIF: Report named base directory paths Result locations in the SARIF log are now reported relative to a named logical base defined in the run log. --- Source/cmCMakeSarifLogger.cxx | 75 ++++++++++++++----- Source/cmSarif.cxx | 11 +++ Source/cmSarif.h | 2 + .../GenerateSarifResults-expected.sarif | 64 +++++++++------- .../ProjectFatalError-expected.sarif | 14 +++- .../ToggleExportSarifVariable-expected.sarif | 14 +++- 6 files changed, 128 insertions(+), 52 deletions(-) diff --git a/Source/cmCMakeSarifLogger.cxx b/Source/cmCMakeSarifLogger.cxx index 5d594f4bd0..2d30f3e7f6 100644 --- a/Source/cmCMakeSarifLogger.cxx +++ b/Source/cmCMakeSarifLogger.cxx @@ -31,21 +31,35 @@ namespace { constexpr char const* CMakeSarifOutputFlag = "CMAKE_EXPORT_SARIF"; constexpr char const* DefaultSarifFile = ".cmake/sarif/cmake.sarif"; -cmSarif::Location LocationFromContext(cmListFileContext const& lfc, - cmake const& cm) +/// @brief Express the location of a `cmListFileContext` in SARIF +/// @param[in] uriBaseIds A list of logical base directory names and their path +/// +/// Build a SARIF location object detailing the location data available from a +/// context. More specific information like the region (line number) and +/// function call name will be included if available. +/// +/// SARIF requests that paths are given relative to a logical base for +/// relocatability. Context locations will be made relative to a logical base +/// iff they fall under one of the directories listed in the `uriBaseIds` +/// map. Bases are tried in order. +cmSarif::Location LocationFromContext( + cmListFileContext const& lfc, + std::vector> const& + uriBaseIds = {}) { cmSarif::Location location; location.Physical.Artifact.Uri = lfc.FilePath; // SARIF requests that paths are given relative to a logical base for - // relocatability. - // Use the CMake home directory as a base dir for files under it. - std::string const& cmHomeDir = cm.GetHomeDirectory(); - std::string relative = - cmSystemTools::RelativeIfUnder(cmHomeDir, location.Physical.Artifact.Uri); - if (relative != location.Physical.Artifact.Uri) { - location.Physical.Artifact.Uri = relative; - location.Physical.Artifact.UriBaseId = cmHomeDir; + // relocatability. Check if these files are under any of the bases, if + // provided. + for (auto const& baseUri : uriBaseIds) { + std::string relative = cmSystemTools::RelativeIfUnder( + std::string(baseUri.second), location.Physical.Artifact.Uri); + if (relative != location.Physical.Artifact.Uri) { + location.Physical.Artifact.Uri = relative; + location.Physical.Artifact.UriBaseId = std::string(baseUri.first); + } } if (!lfc.Name.empty()) { @@ -67,17 +81,21 @@ cmSarif::Location LocationFromContext(cmListFileContext const& lfc, return location; } -cm::optional LastLocation(cmListFileBacktrace backtrace, - cmake const& cm) +cm::optional LastLocation( + cmListFileBacktrace backtrace, + std::vector> const& + uriBaseIds = {}) { if (backtrace.Empty()) { return {}; } - return LocationFromContext(backtrace.Top(), cm); + return LocationFromContext(backtrace.Top(), uriBaseIds); } -cm::optional StackFromBacktrace(cmListFileBacktrace bt, - cmake const& cm) +cm::optional StackFromBacktrace( + cmListFileBacktrace bt, + std::vector> const& + uriBaseIds = {}) { if (bt.Empty()) { return {}; @@ -85,7 +103,7 @@ cm::optional StackFromBacktrace(cmListFileBacktrace bt, cmSarif::Stack stack; for (; !bt.Empty(); bt = bt.Pop()) { - cmSarif::Location topLocation = LocationFromContext(bt.Top(), cm); + cmSarif::Location topLocation = LocationFromContext(bt.Top(), uriBaseIds); // If the location doesn't have a specific region, this entry is a // placeholder and should not appear in the call stack. @@ -248,6 +266,27 @@ bool cmCMakeSarifLogger::WriteFile(std::string const& path, return *result.first; }; + // Make a prioritized list of base directories applicable in this context. + // This is used for normalizing the paths of related locations. + std::vector> uriBaseIds; + + std::string const& binDir = this->CM.GetHomeOutputDirectory(); + if (!binDir.empty()) { + uriBaseIds.emplace_back("CMAKE_BINARY_DIR", binDir); + } + + std::string const& homeDir = this->CM.GetHomeDirectory(); + if (!homeDir.empty()) { + uriBaseIds.emplace_back("CMAKE_SOURCE_DIR", homeDir); + } + + // Log the base directories for this run. + for (auto const& base : uriBaseIds) { + run.OriginalUriBaseIds.emplace( + std::string(base.first), + cmSarif::ArtifactLocation{ cmStrCat("file://", base.second, "/"), "" }); + } + cmMessenger const& messenger = *this->CM.GetMessenger(); for (auto const& message : messenger.GetDisplayedMessages()) { // SARIF should only emit diagnostic messages, not general messages/logs @@ -267,9 +306,9 @@ bool cmCMakeSarifLogger::WriteFile(std::string const& path, result.RuleId = ruleInfo.first; result.RuleIndex = ruleInfo.second; result.Message = cmSarif::Message{ message.Text }; - result.Location = LastLocation(message.Backtrace, this->CM); + result.Location = LastLocation(message.Backtrace, uriBaseIds); if (cm::optional stack = - StackFromBacktrace(message.Backtrace, this->CM)) { + StackFromBacktrace(message.Backtrace, uriBaseIds)) { result.Stacks.emplace_back(std::move(*stack)); } result.Level = SarifLevelFromMessageType(message.Type); diff --git a/Source/cmSarif.cxx b/Source/cmSarif.cxx index 1c9b81acc5..00affb6efc 100644 --- a/Source/cmSarif.cxx +++ b/Source/cmSarif.cxx @@ -3,6 +3,7 @@ #include "cmSarif.h" #include +#include #include #include @@ -181,11 +182,21 @@ Json::Value GetJson(Run const& run) { Json::Value runJson(Json::objectValue); runJson["tool"] = cmSarif::GetJson(run.Tool); + + if (!run.OriginalUriBaseIds.empty()) { + Json::Value uriBaseIds(Json::objectValue); + for (auto const& base : run.OriginalUriBaseIds) { + uriBaseIds[base.first] = cmSarif::GetJson(base.second); + } + runJson["originalUriBaseIds"] = uriBaseIds; + } + Json::Value results(Json::arrayValue); for (auto const& result : run.Results) { results.append(cmSarif::GetJson(result)); } runJson["results"] = results; + return runJson; } diff --git a/Source/cmSarif.h b/Source/cmSarif.h index bd779e736a..c18bad07c3 100644 --- a/Source/cmSarif.h +++ b/Source/cmSarif.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -170,6 +171,7 @@ struct Run { cmSarif::Tool Tool; std::vector Results; + std::unordered_map OriginalUriBaseIds; }; Json::Value GetJson(Run const& run); diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif index b0f4c4a011..eecdd696f8 100644 --- a/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif @@ -16,7 +16,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 2 @@ -43,7 +43,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 2 @@ -62,7 +62,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -87,7 +87,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 5 @@ -114,7 +114,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 5 @@ -133,7 +133,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -158,7 +158,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 11 @@ -185,7 +185,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 11 @@ -204,7 +204,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -229,7 +229,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 16 @@ -256,7 +256,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 16 @@ -275,7 +275,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -300,7 +300,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 20 @@ -327,7 +327,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 20 @@ -346,7 +346,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 24 @@ -365,7 +365,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 27 @@ -384,7 +384,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -409,7 +409,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults-Included.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 1 @@ -436,7 +436,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults-Included.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 1 @@ -455,7 +455,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 30 @@ -474,7 +474,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -499,7 +499,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 38 @@ -526,7 +526,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 38 @@ -545,7 +545,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 42 @@ -564,7 +564,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -589,7 +589,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 46 @@ -616,7 +616,7 @@ "physicalLocation": { "artifactLocation": { "uri": "GenerateSarifResults.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 46 @@ -632,7 +632,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" } } } @@ -642,6 +642,14 @@ ] } ], + "originalUriBaseIds": { + "CMAKE_BINARY_DIR": { + "uri": "file://PATH:/" + }, + "CMAKE_SOURCE_DIR": { + "uri": "file://PATH:/" + } + }, "tool": { "driver": { "name": "CMake", diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif index 0968d5c70a..2f9463b828 100644 --- a/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif @@ -16,7 +16,7 @@ "physicalLocation": { "artifactLocation": { "uri": "ProjectFatalError.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 1 @@ -43,7 +43,7 @@ "physicalLocation": { "artifactLocation": { "uri": "ProjectFatalError.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 1 @@ -62,7 +62,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -75,6 +75,14 @@ ] } ], + "originalUriBaseIds": { + "CMAKE_BINARY_DIR": { + "uri": "file://PATH:/" + }, + "CMAKE_SOURCE_DIR": { + "uri": "file://PATH:/" + } + }, "tool": { "driver": { "name": "CMake", diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif index df80678919..436c6d4071 100644 --- a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-expected.sarif @@ -16,7 +16,7 @@ "physicalLocation": { "artifactLocation": { "uri": "ToggleExportSarifVariable.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 2 @@ -43,7 +43,7 @@ "physicalLocation": { "artifactLocation": { "uri": "ToggleExportSarifVariable.cmake", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 2 @@ -62,7 +62,7 @@ "physicalLocation": { "artifactLocation": { "uri": "CMakeLists.txt", - "uriBaseId": "PATH:" + "uriBaseId": "CMAKE_SOURCE_DIR" }, "region": { "startLine": 3 @@ -75,6 +75,14 @@ ] } ], + "originalUriBaseIds": { + "CMAKE_BINARY_DIR": { + "uri": "file://PATH:/" + }, + "CMAKE_SOURCE_DIR": { + "uri": "file://PATH:/" + } + }, "tool": { "driver": { "name": "CMake",