From abbe41578dad3923565a6d56b39879cb1bd120df Mon Sep 17 00:00:00 2001 From: Daniel Tierney Date: Thu, 23 Jan 2025 13:34:16 -0500 Subject: cmake: Add SARIF diagnostics output support Closes: #26587 --- Auxiliary/vim/syntax/cmake.vim | 1 + Help/manual/cmake-variables.7.rst | 1 + Help/manual/cmake.1.rst | 10 + Help/variable/CMAKE_EXPORT_SARIF.rst | 81 +++++ Source/CMakeLists.txt | 2 + Source/cmMessenger.cxx | 7 + Source/cmMessenger.h | 12 + Source/cmSarifLog.cxx | 383 +++++++++++++++++++++ Source/cmSarifLog.h | 287 +++++++++++++++ Source/cmake.cxx | 39 +++ Source/cmake.h | 16 + Tests/RunCMake/CMakeLists.txt | 1 + Tests/RunCMake/SarifOutput/CMakeLists.txt | 3 + .../SarifOutput/DefaultSarifOutput-check.cmake | 4 + .../SarifOutput/DefaultSarifOutput-stderr.txt | 4 + .../RunCMake/SarifOutput/DefaultSarifOutput.cmake | 1 + .../SarifOutput/GenerateSarifResults-check.cmake | 4 + .../GenerateSarifResults-expected.sarif | 67 ++++ .../SarifOutput/GenerateSarifResults-stderr.txt | 9 + .../SarifOutput/GenerateSarifResults.cmake | 8 + .../SarifOutput/ProjectFatalError-check.cmake | 4 + .../SarifOutput/ProjectFatalError-expected.sarif | 47 +++ .../SarifOutput/ProjectFatalError-result.txt | 1 + .../SarifOutput/ProjectFatalError-stderr.txt | 4 + Tests/RunCMake/SarifOutput/ProjectFatalError.cmake | 1 + Tests/RunCMake/SarifOutput/RunCMakeTest.cmake | 25 ++ .../SarifOutput/SarifFileArgument-check.cmake | 4 + .../SarifOutput/SarifFileArgument-stderr.txt | 4 + Tests/RunCMake/SarifOutput/SarifFileArgument.cmake | 1 + .../SarifFileArgumentScript-check.cmake | 4 + .../SarifOutput/SarifFileArgumentScript.cmake | 2 + .../ScriptModeSarifVariable-check.cmake | 4 + .../SarifOutput/ScriptModeSarifVariable.cmake | 3 + .../ToggleExportSarifVariable-check.cmake | 5 + .../ToggleExportSarifVariable-stderr.txt | 11 + .../SarifOutput/ToggleExportSarifVariable.cmake | 6 + Tests/RunCMake/SarifOutput/check-sarif.cmake | 19 + 37 files changed, 1085 insertions(+) create mode 100644 Help/variable/CMAKE_EXPORT_SARIF.rst create mode 100644 Source/cmSarifLog.cxx create mode 100644 Source/cmSarifLog.h create mode 100644 Tests/RunCMake/SarifOutput/CMakeLists.txt create mode 100644 Tests/RunCMake/SarifOutput/DefaultSarifOutput-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/DefaultSarifOutput-stderr.txt create mode 100644 Tests/RunCMake/SarifOutput/DefaultSarifOutput.cmake create mode 100644 Tests/RunCMake/SarifOutput/GenerateSarifResults-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif create mode 100644 Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt create mode 100644 Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake create mode 100644 Tests/RunCMake/SarifOutput/ProjectFatalError-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif create mode 100644 Tests/RunCMake/SarifOutput/ProjectFatalError-result.txt create mode 100644 Tests/RunCMake/SarifOutput/ProjectFatalError-stderr.txt create mode 100644 Tests/RunCMake/SarifOutput/ProjectFatalError.cmake create mode 100644 Tests/RunCMake/SarifOutput/RunCMakeTest.cmake create mode 100644 Tests/RunCMake/SarifOutput/SarifFileArgument-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/SarifFileArgument-stderr.txt create mode 100644 Tests/RunCMake/SarifOutput/SarifFileArgument.cmake create mode 100644 Tests/RunCMake/SarifOutput/SarifFileArgumentScript-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/SarifFileArgumentScript.cmake create mode 100644 Tests/RunCMake/SarifOutput/ScriptModeSarifVariable-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/ScriptModeSarifVariable.cmake create mode 100644 Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake create mode 100644 Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt create mode 100644 Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake create mode 100644 Tests/RunCMake/SarifOutput/check-sarif.cmake diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim index a2f06c3..35b5ff6 100644 --- a/Auxiliary/vim/syntax/cmake.vim +++ b/Auxiliary/vim/syntax/cmake.vim @@ -1180,6 +1180,7 @@ syn keyword cmakeVariable contained \ CMAKE_EXE_LINKER_FLAGS \ CMAKE_EXE_LINKER_FLAGS_INIT \ CMAKE_EXPORT_COMPILE_COMMANDS + \ CMAKE_EXPORT_SARIF \ CMAKE_EXPORT_NO_PACKAGE_REGISTRY \ CMAKE_EXPORT_PACKAGE_REGISTRY \ CMAKE_EXTRA_GENERATOR diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 4b96f12..fa10157 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -203,6 +203,7 @@ Variables that Change Behavior /variable/CMAKE_EXECUTE_PROCESS_COMMAND_ERROR_IS_FATAL /variable/CMAKE_EXPORT_BUILD_DATABASE /variable/CMAKE_EXPORT_COMPILE_COMMANDS + /variable/CMAKE_EXPORT_SARIF /variable/CMAKE_EXPORT_PACKAGE_REGISTRY /variable/CMAKE_EXPORT_NO_PACKAGE_REGISTRY /variable/CMAKE_FIND_APPBUNDLE diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index 9fa145d..72ff85b 100644 --- a/Help/manual/cmake.1.rst +++ b/Help/manual/cmake.1.rst @@ -307,6 +307,16 @@ Options When this command line option is given, :variable:`CMAKE_MESSAGE_CONTEXT_SHOW` is ignored. +.. option:: --sarif-output= + + .. versionadded:: 4.0 + + Enable logging of diagnostic messages produced by CMake in the SARIF format. + + Write diagnostic messages to a SARIF file at the path specified. Projects can + also set :variable:`CMAKE_EXPORT_SARIF` to ``ON`` to enable this feature for a + build tree. + .. option:: --debug-trycompile Do not delete the files and directories created for diff --git a/Help/variable/CMAKE_EXPORT_SARIF.rst b/Help/variable/CMAKE_EXPORT_SARIF.rst new file mode 100644 index 0000000..8532377 --- /dev/null +++ b/Help/variable/CMAKE_EXPORT_SARIF.rst @@ -0,0 +1,81 @@ +CMAKE_EXPORT_SARIF +------------------ + +.. versionadded:: 4.0 + +Enable or disable CMake diagnostics output in SARIF format for a project. + +If enabled, CMake will generate a SARIF log file containing diagnostic messages +output by CMake when running in a project. By default, the log file is written +to `.cmake/sarif/cmake.sarif`, but the location can be changed by setting the +command-line option :option:`cmake --sarif-output` to the desired path. + +The Static Analysis Results Interchange Format (SARIF) is a JSON-based standard +format for static analysis tools (including build tools like CMake) to record +and communicate diagnostic messages. CMake generates a SARIF log entry for +warnings and errors produced while running CMake on a project (e.g. +:command:`message` calls). Each log entry includes the message, severity, and +location information if available. + +An example of CMake's SARIF output is: + +.. code-block:: json + + { + "version" : "2.1.0", + "$schema" : "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json", + "runs" : + [ + { + "tool" : + { + "driver" : + { + "name" : "CMake", + "rules" : + [ + { + "id" : "CMake.Warning", + "messageStrings" : + { + "default" : + { + "text" : "CMake Warning: {0}" + } + }, + "name" : "CMake Warning" + } + ] + } + }, + "results" : + [ + { + "level" : "warning", + "locations" : + [ + { + "physicalLocation" : + { + "artifactLocation" : + { + "uri" : "/home/user/development/project/CMakeLists.txt" + }, + "region" : + { + "startLine" : 5 + } + } + } + ], + "message" : + { + "text" : "An example warning" + }, + "ruleId" : "CMake.Warning", + "ruleIndex" : 0 + } + ] + } + ] + } diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f785cd9..06eb9b4 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -446,6 +446,8 @@ add_library( cmRST.h cmRuntimeDependencyArchive.cxx cmRuntimeDependencyArchive.h + cmSarifLog.cxx + cmSarifLog.h cmScriptGenerator.h cmScriptGenerator.cxx cmSourceFile.cxx diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx index c6387d7..f7a7b83 100644 --- a/Source/cmMessenger.cxx +++ b/Source/cmMessenger.cxx @@ -10,6 +10,8 @@ #if !defined(CMAKE_BOOTSTRAP) # include "cmsys/SystemInformation.hxx" + +# include "cmSarifLog.h" #endif #include @@ -218,6 +220,11 @@ void cmMessenger::DisplayMessage(MessageType t, std::string const& text, displayMessage(t, msg); +#ifndef CMAKE_BOOTSTRAP + // Add message to SARIF logs + this->SarifLog.LogMessage(t, text, backtrace); +#endif + #ifdef CMake_ENABLE_DEBUGGER if (DebuggerAdapter) { DebuggerAdapter->OnMessageOutput(t, msg.str()); diff --git a/Source/cmMessenger.h b/Source/cmMessenger.h index d9462d4..e8852e6 100644 --- a/Source/cmMessenger.h +++ b/Source/cmMessenger.h @@ -13,6 +13,10 @@ #include "cmListFileCache.h" #include "cmMessageType.h" // IWYU pragma: keep +#ifndef CMAKE_BOOTSTRAP +# include "cmSarifLog.h" +#endif + #ifdef CMake_ENABLE_DEBUGGER namespace cmDebugger { class cmDebuggerAdapter; @@ -59,6 +63,10 @@ public: return this->DeprecatedWarningsAsErrors; } +#ifndef CMAKE_BOOTSTRAP + cmSarif::ResultsLog const& GetSarifResultsLog() const { return SarifLog; } +#endif + // Print the top of a backtrace. void PrintBacktraceTitle(std::ostream& out, cmListFileBacktrace const& bt) const; @@ -76,6 +84,10 @@ private: cm::optional TopSource; +#ifndef CMAKE_BOOTSTRAP + cmSarif::ResultsLog SarifLog; +#endif + bool SuppressDevWarnings = false; bool SuppressDeprecatedWarnings = false; bool DevWarningsAsErrors = false; diff --git a/Source/cmSarifLog.cxx b/Source/cmSarifLog.cxx new file mode 100644 index 0000000..b2fbed9 --- /dev/null +++ b/Source/cmSarifLog.cxx @@ -0,0 +1,383 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmSarifLog.h" + +#include +#include + +#include + +#include +#include + +#include "cmsys/FStream.hxx" + +#include "cmListFileCache.h" +#include "cmMessageType.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmValue.h" +#include "cmVersionConfig.h" +#include "cmake.h" + +cmSarif::ResultsLog::ResultsLog() +{ + // Add the known CMake rules + this->KnownRules.emplace(RuleBuilder("CMake.AuthorWarning") + .Name("CMake Warning (dev)") + .DefaultMessage("CMake Warning (dev): {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.Warning") + .Name("CMake Warning") + .DefaultMessage("CMake Warning: {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.DeprecationWarning") + .Name("CMake Deprecation Warning") + .DefaultMessage("CMake Deprecation Warning: {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.AuthorError") + .Name("CMake Error (dev)") + .DefaultMessage("CMake Error (dev): {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.FatalError") + .Name("CMake Error") + .DefaultMessage("CMake Error: {0}") + .Build()); + this->KnownRules.emplace( + RuleBuilder("CMake.InternalError") + .Name("CMake Internal Error") + .DefaultMessage("CMake Internal Error (please report a bug): {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.DeprecationError") + .Name("CMake Deprecation Error") + .DefaultMessage("CMake Deprecation Error: {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.Message") + .Name("CMake Message") + .DefaultMessage("CMake Message: {0}") + .Build()); + this->KnownRules.emplace(RuleBuilder("CMake.Log") + .Name("CMake Log") + .DefaultMessage("CMake Log: {0}") + .Build()); +} + +void cmSarif::ResultsLog::Log(cmSarif::Result&& result) const +{ + // The rule ID is optional, but if it is present, enable metadata output for + // the rule by marking it as used + if (result.RuleId) { + std::size_t index = this->UseRule(*result.RuleId); + result.RuleIndex = index; + } + + // Add the result to the log + this->Results.emplace_back(result); +} + +void cmSarif::ResultsLog::LogMessage( + MessageType t, std::string const& text, + cmListFileBacktrace const& backtrace) const +{ + // Add metadata to the result object + // The CMake SARIF rules for messages all expect 1 string argument with the + // message text + Json::Value additionalProperties(Json::objectValue); + Json::Value args(Json::arrayValue); + args.append(text); + additionalProperties["message"]["id"] = "default"; + additionalProperties["message"]["arguments"] = args; + + // Create and log a result object + // Rule indices are assigned when writing the final JSON output. Right now, + // leave it as nullopt. The other optional fields are filled if available + this->Log(cmSarif::Result{ + text, cmSarif::SourceFileLocation::FromBacktrace(backtrace), + cmSarif::MessageSeverityLevel(t), cmSarif::MessageRuleId(t), cm::nullopt, + additionalProperties }); +} + +std::size_t cmSarif::ResultsLog::UseRule(std::string const& id) const +{ + // Check if the rule is already in the index + auto it = this->RuleToIndex.find(id); + if (it != this->RuleToIndex.end()) { + // The rule is already in use. Return the known index + return it->second; + } + + // This rule is not yet in the index, so check if it is recognized + auto itKnown = this->KnownRules.find(id); + if (itKnown == this->KnownRules.end()) { + // The rule is not known. Add an empty rule to the known rules so that it + // is included in the output + this->KnownRules.emplace(RuleBuilder(id.c_str()).Build()); + } + + // Since this is the first time the rule is used, enable it and add it to the + // index + std::size_t idx = this->EnabledRules.size(); + this->RuleToIndex[id] = idx; + this->EnabledRules.emplace_back(id); + return idx; +} + +cmSarif::ResultSeverityLevel cmSarif::MessageSeverityLevel(MessageType t) +{ + switch (t) { + case MessageType::AUTHOR_WARNING: + case MessageType::WARNING: + case MessageType::DEPRECATION_WARNING: + return ResultSeverityLevel::SARIF_WARNING; + case MessageType::AUTHOR_ERROR: + case MessageType::FATAL_ERROR: + case MessageType::INTERNAL_ERROR: + case MessageType::DEPRECATION_ERROR: + return ResultSeverityLevel::SARIF_ERROR; + case MessageType::MESSAGE: + case MessageType::LOG: + return ResultSeverityLevel::SARIF_NOTE; + default: + return ResultSeverityLevel::SARIF_NONE; + } +} + +cm::optional cmSarif::MessageRuleId(MessageType t) +{ + switch (t) { + case MessageType::AUTHOR_WARNING: + return "CMake.AuthorWarning"; + case MessageType::WARNING: + return "CMake.Warning"; + case MessageType::DEPRECATION_WARNING: + return "CMake.DeprecationWarning"; + case MessageType::AUTHOR_ERROR: + return "CMake.AuthorError"; + case MessageType::FATAL_ERROR: + return "CMake.FatalError"; + case MessageType::INTERNAL_ERROR: + return "CMake.InternalError"; + case MessageType::DEPRECATION_ERROR: + return "CMake.DeprecationError"; + case MessageType::MESSAGE: + return "CMake.Message"; + case MessageType::LOG: + return "CMake.Log"; + default: + return cm::nullopt; + } +} + +Json::Value cmSarif::Rule::GetJson() const +{ + Json::Value rule(Json::objectValue); + rule["id"] = this->Id; + + if (this->Name) { + rule["name"] = *this->Name; + } + if (this->FullDescription) { + rule["fullDescription"]["text"] = *this->FullDescription; + } + if (this->DefaultMessage) { + rule["messageStrings"]["default"]["text"] = *this->DefaultMessage; + } + + return rule; +} + +cmSarif::SourceFileLocation::SourceFileLocation( + cmListFileBacktrace const& backtrace) +{ + if (backtrace.Empty()) { + throw std::runtime_error("Empty source file location"); + } + + cmListFileContext const& lfc = backtrace.Top(); + this->Uri = lfc.FilePath; + this->Line = lfc.Line; +} + +cm::optional +cmSarif::SourceFileLocation::FromBacktrace( + cmListFileBacktrace const& backtrace) +{ + if (backtrace.Empty()) { + return cm::nullopt; + } + cmListFileContext const& lfc = backtrace.Top(); + if (lfc.Line <= 0 || lfc.FilePath.empty()) { + return cm::nullopt; + } + + return cm::make_optional(backtrace); +} + +void cmSarif::ResultsLog::WriteJson(Json::Value& root) const +{ + // Add SARIF metadata + root["version"] = "2.1.0"; + root["$schema"] = "https://schemastore.azurewebsites.net/schemas/json/" + "sarif-2.1.0-rtm.4.json"; + + // JSON object for the SARIF runs array + Json::Value runs(Json::arrayValue); + + // JSON object for the current (only) run + Json::Value currentRun(Json::objectValue); + + // Accumulate info about the reported rules + Json::Value jsonRules(Json::arrayValue); + for (auto const& ruleId : this->EnabledRules) { + jsonRules.append(KnownRules.at(ruleId).GetJson()); + } + + // Add info the driver for the current run (CMake) + Json::Value driverTool(Json::objectValue); + driverTool["name"] = "CMake"; + driverTool["version"] = CMake_VERSION; + driverTool["rules"] = jsonRules; + currentRun["tool"]["driver"] = driverTool; + + runs.append(currentRun); + + // Add all results + Json::Value jsonResults(Json::arrayValue); + for (auto const& res : this->Results) { + Json::Value jsonResult(Json::objectValue); + + if (res.Message) { + jsonResult["message"]["text"] = *(res.Message); + } + + // If the result has a level, add it to the result + if (res.Level) { + switch (*res.Level) { + case ResultSeverityLevel::SARIF_WARNING: + jsonResult["level"] = "warning"; + break; + case ResultSeverityLevel::SARIF_ERROR: + jsonResult["level"] = "error"; + break; + case ResultSeverityLevel::SARIF_NOTE: + jsonResult["level"] = "note"; + break; + case ResultSeverityLevel::SARIF_NONE: + jsonResult["level"] = "none"; + break; + } + } + + // If the result has a rule ID or index, add it to the result + if (res.RuleId) { + jsonResult["ruleId"] = *res.RuleId; + } + if (res.RuleIndex) { + jsonResult["ruleIndex"] = Json::UInt64(*res.RuleIndex); + } + + if (res.Location) { + jsonResult["locations"][0]["physicalLocation"]["artifactLocation"] + ["uri"] = (res.Location)->Uri; + jsonResult["locations"][0]["physicalLocation"]["region"]["startLine"] = + Json::Int64((res.Location)->Line); + } + + jsonResults.append(jsonResult); + } + + currentRun["results"] = jsonResults; + runs[0] = currentRun; + root["runs"] = runs; +} + +cmSarif::LogFileWriter::~LogFileWriter() +{ + // If the file has not been written yet, try to finalize it + if (!this->FileWritten) { + // Try to write and check the result + if (this->TryWrite() == WriteResult::FAILURE) { + // If the result is `FAILURE`, it means the write condition is true but + // the file still wasn't written. This is an error. + cmSystemTools::Error("Failed to write SARIF log to " + + this->FilePath.generic_string()); + } + } +} + +bool cmSarif::LogFileWriter::EnsureFileValid() +{ + // First, ensure directory exists + cm::filesystem::path dir = this->FilePath.parent_path(); + if (!cmSystemTools::FileIsDirectory(dir.generic_string())) { + if (!this->CreateDirectories || + !cmSystemTools::MakeDirectory(dir.generic_string()).IsSuccess()) { + return false; + } + } + + // Open the file for writing + cmsys::ofstream outputFile(this->FilePath.generic_string().c_str()); + if (!outputFile.good()) { + return false; + } + return true; +} + +cmSarif::LogFileWriter::WriteResult cmSarif::LogFileWriter::TryWrite() +{ + // Check that SARIF logging is enabled + if (!this->WriteCondition || !this->WriteCondition()) { + return WriteResult::SKIPPED; + } + + // Open the file + if (!this->EnsureFileValid()) { + return WriteResult::FAILURE; + } + cmsys::ofstream outputFile(this->FilePath.generic_string().c_str()); + + // The file is available, so proceed to write the log + + // Assemble the SARIF JSON from the results in the log + Json::Value root(Json::objectValue); + this->Log.WriteJson(root); + + // Serialize the JSON to the file + Json::StreamWriterBuilder builder; + std::unique_ptr writer(builder.newStreamWriter()); + + writer->write(root, &outputFile); + outputFile.close(); + + this->FileWritten = true; + return WriteResult::SUCCESS; +} + +bool cmSarif::LogFileWriter::ConfigureForCMakeRun(cmake& cm) +{ + // If an explicit SARIF output path has been provided, set and check it + cm::optional sarifFilePath = cm.GetSarifFilePath(); + if (sarifFilePath) { + this->SetPath(cm::filesystem::path(*sarifFilePath)); + if (!this->EnsureFileValid()) { + cmSystemTools::Error( + cmStrCat("Invalid SARIF output file path: ", *sarifFilePath)); + return false; + } + } + + // The write condition is checked immediately before writing the file, which + // allows projects to enable SARIF diagnostics by setting a cache variable + // and have it take effect for the current run. + this->SetWriteCondition([&cm]() { + // The command-line option can be used to set an explicit path, but in + // normal mode, the project variable `CMAKE_EXPORT_SARIF` can also enable + // SARIF logging. + return cm.GetSarifFilePath().has_value() || + (cm.GetWorkingMode() == cmake::NORMAL_MODE && + cm.GetCacheDefinition(cmSarif::PROJECT_SARIF_FILE_VARIABLE).IsOn()); + }); + + return true; +} diff --git a/Source/cmSarifLog.h b/Source/cmSarifLog.h new file mode 100644 index 0000000..aaf0fc2 --- /dev/null +++ b/Source/cmSarifLog.h @@ -0,0 +1,287 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +class cmake; +class cmListFileBacktrace; +enum class MessageType; + +/// @brief CMake support for SARIF logging +namespace cmSarif { + +constexpr char const* PROJECT_SARIF_FILE_VARIABLE = "CMAKE_EXPORT_SARIF"; + +constexpr char const* PROJECT_DEFAULT_SARIF_FILE = ".cmake/sarif/cmake.sarif"; + +/// @brief The severity level of a result in SARIF +/// +/// The SARIF specification section 3.27.10 defines four levels of severity +/// for results. +enum class ResultSeverityLevel +{ + SARIF_WARNING, + SARIF_ERROR, + SARIF_NOTE, + SARIF_NONE, +}; + +/// @brief A location in a source file logged with a SARIF result +struct SourceFileLocation +{ + std::string Uri; + long Line = 0; + + /// @brief Construct a SourceFileLocation at the top of the call stack + SourceFileLocation(cmListFileBacktrace const& backtrace); + + /// @brief Get the SourceFileLocation from the top of a call stack, if any + /// @return The location or nullopt if the call stack is empty or is missing + /// location information + static cm::optional FromBacktrace( + cmListFileBacktrace const& backtrace); +}; + +/// @brief A result defined by SARIF reported by a CMake run +/// +/// This is the data model for results in a SARIF log. Typically, a result only +/// requires either a message or a rule index. The most common properties are +/// named in this struct, but arbitrary metadata can be added to the result +/// using the additionalProperties field. +struct Result +{ + /// @brief The message text of the result (required if no rule index) + cm::optional Message; + + /// @brief The location of the result (optional) + cm::optional Location; + + /// @brief The severity level of the result (optional) + cm::optional Level; + + /// @brief The rule ID of the result (optional) + cm::optional RuleId; + + /// @brief The index of the rule in the log's rule array (optional) + cm::optional RuleIndex; + + /// @brief Additional JSON properties for the result (optional) + /// + /// The additional properties should be merged into the result object when it + /// is written to the SARIF log. + Json::Value AdditionalProperties; +}; + +/// @brief A SARIF reporting rule +/// +/// A rule in SARIF is described by a reportingDescriptor object (SARIF +/// specification section 3.49). The only property required for a rule is the +/// ID property. The ID is normally an opaque string that identifies a rule +/// applicable to a class of results. The other included properties are +/// optional but recommended for rules reported by CMake. +struct Rule +{ + /// @brief The ID of the rule. Required by SARIF + std::string Id; + + /// @brief The end-user name of the rule (optional) + cm::optional Name; + + /// @brief The extended description of the rule (optional) + cm::optional FullDescription; + + /// @brief The default message for the rule (optional) + cm::optional DefaultMessage; + + /// @brief Get the JSON representation of this rule + Json::Value GetJson() const; +}; + +/// @brief A builder for SARIF rules +/// +/// `Rule` is a data model for SARIF rules. Known rules are usually initialized +/// manually by field. Using a builder makes initialization more readable and +/// prevents issues with reordering and optional fields. +class RuleBuilder +{ +public: + /// @brief Construct a new rule builder for a rule with the given ID + RuleBuilder(char const* id) { this->NewRule.Id = id; } + + /// @brief Set the name of the rule + RuleBuilder& Name(std::string name) + { + this->NewRule.Name = std::move(name); + return *this; + } + + /// @brief Set the full description of the rule + RuleBuilder& FullDescription(std::string fullDescription) + { + this->NewRule.FullDescription = std::move(fullDescription); + return *this; + } + + /// @brief Set the default message for the rule + RuleBuilder& DefaultMessage(std::string defaultMessage) + { + this->NewRule.DefaultMessage = std::move(defaultMessage); + return *this; + } + + /// @brief Build the rule + std::pair Build() const + { + return std::make_pair(this->NewRule.Id, this->NewRule); + } + +private: + Rule NewRule; +}; + +/// @brief Get the SARIF severity level of a CMake message type +ResultSeverityLevel MessageSeverityLevel(MessageType t); + +/// @brief Get the SARIF rule ID of a CMake message type +/// @return The rule ID or nullopt if the message type is unrecognized +/// +/// The rule ID is a string assigned to SARIF results to identify the category +/// of the result. CMake maps messages to rules based on the message type. +/// CMake's rules are of the form "CMake.". +cm::optional MessageRuleId(MessageType t); + +/// @brief A log for reporting results in the SARIF format +class ResultsLog +{ +public: + ResultsLog(); + + /// @brief Log a result of this run to the SARIF output + void Log(cmSarif::Result&& result) const; + + /// @brief Log a result from a CMake message with a source file location + /// @param t The type of the message, which corresponds to the level and rule + /// of the result + /// @param text The contents of the message + /// @param backtrace The call stack where the message originated (may be + /// empty) + void LogMessage(MessageType t, std::string const& text, + cmListFileBacktrace const& backtrace) const; + + /// @brief Write this SARIF log to an empty JSON object + /// @param[out] root The JSON object to write to + void WriteJson(Json::Value& root) const; + +private: + // Private methods + + // Log that a rule was used and should be included in the output. Returns the + // index of the rule in the log + std::size_t UseRule(std::string const& id) const; + + // Private data + // All data is mutable since log results are often added in const methods + + // All results added chronologically + mutable std::vector Results; + + // Mapping of rule IDs to rule indices in the log. + // In SARIF, rule metadata is typically only included if the rule is + // referenced. The indices are unique to one log output and and vary + // depending on when the rule was first encountered. + mutable std::unordered_map RuleToIndex; + + // Rules that will be added to the log in order of appearance + mutable std::vector EnabledRules; + + // All known rules that could be included in a log + mutable std::unordered_map KnownRules; +}; + +/// @brief Writes contents of a `cmSarif::ResultsLog` to a file +/// +/// The log file writer is a helper class that writes the contents of a +/// `cmSarif::ResultsLog` upon destruction if a condition (e.g. project +/// variable is enabled) is met. +class LogFileWriter +{ +public: + /// @brief Create a new, disabled log file writer + /// + /// The returned writer will not write anything until the path generator + /// and write condition are set. If the log has not been written when the + /// object is being destroyed, the destructor will write the log if the + /// condition is met and a valid path is available. + LogFileWriter(ResultsLog const& log) + : Log(log) + { + } + + /// @brief Configure a log file writer for a CMake run + /// + /// CMake should write a SARIF log if the project variable + /// `CMAKE_EXPORT_SARIF` is `ON` or if the `--sarif-output=` command + /// line option is set. The writer will be configured to respond to these + /// conditions. + /// + /// This does not configure a default path, so one must be set once it is + /// known that we're in normal mode if none was explicitly provided. + bool ConfigureForCMakeRun(cmake& cm); + + ~LogFileWriter(); + + /// @brief Check if a valid path is set by opening the output file + /// @return True if the file can be opened for writing + bool EnsureFileValid(); + + /// @brief The possible outcomes of trying to write the log file + enum class WriteResult + { + SUCCESS, ///< File written with no issues + FAILURE, ///< Error encountered while writing the file + SKIPPED, ///< Writing was skipped due to false write condition + }; + + /// @brief Try to write the log file and return `true` if it was written + /// + /// Check the write condition and path generator to determine if the log + /// file should be written. + WriteResult TryWrite(); + + /// @brief Set a lambda to check if the log file should be written + void SetWriteCondition(std::function const& checkConditionCallback) + { + this->WriteCondition = checkConditionCallback; + } + + /// @brief Set the output file path, optionally creating parent directories + /// + /// The settings will apply when the log file is written. If the output + /// file should be checked earlier, use `CheckFileValidity`. + void SetPath(cm::filesystem::path const& path, + bool createParentDirectories = false) + { + this->FilePath = path; + this->CreateDirectories = createParentDirectories; + } + +private: + ResultsLog const& Log; + std::function WriteCondition; + cm::filesystem::path FilePath; + bool CreateDirectories = false; + bool FileWritten = false; +}; + +} // namespace cmSarif diff --git a/Source/cmake.cxx b/Source/cmake.cxx index f5265c1..a9c14f5 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,9 @@ #include "cmJSONState.h" #include "cmList.h" #include "cmMessenger.h" +#ifndef CMAKE_BOOTSTRAP +# include "cmSarifLog.h" +#endif #include "cmState.h" #include "cmStateDirectory.h" #include "cmStringAlgorithms.h" @@ -1272,6 +1276,16 @@ void cmake::SetArgs(std::vector const& args) state->SetIgnoreLinkWarningAsError(true); return true; } }, +#ifndef CMAKE_BOOTSTRAP + CommandArgument{ "--sarif-output", "No file specified for --sarif-output", + CommandArgument::Values::One, + [](std::string const& value, cmake* state) -> bool { + state->SarifFilePath = + cmSystemTools::ToNormalizedPathOnDisk(value); + state->SarifFileOutput = true; + return true; + } }, +#endif CommandArgument{ "--debugger", CommandArgument::Values::Zero, [](std::string const&, cmake* state) -> bool { #ifdef CMake_ENABLE_DEBUGGER @@ -2853,6 +2867,15 @@ int cmake::Run(std::vector const& args, bool noconfigure) return 0; } +#ifndef CMAKE_BOOTSTRAP + // Configure the SARIF log for the current run + cmSarif::LogFileWriter sarifLogFileWriter( + this->GetMessenger()->GetSarifResultsLog()); + if (!sarifLogFileWriter.ConfigureForCMakeRun(*this)) { + return -1; + } +#endif + // Log the trace format version to the desired output if (this->GetTrace()) { this->PrintTraceFormatVersion(); @@ -2879,6 +2902,17 @@ int cmake::Run(std::vector const& args, bool noconfigure) cmSystemTools::Error("Error executing cmake::LoadCache(). Aborting.\n"); return -1; } +#ifndef CMAKE_BOOTSTRAP + // If no SARIF file has been explicitly specified, use the default path + if (!this->SarifFileOutput) { + // If no output file is specified, use the default path + // Enable parent directory creation for the default path + sarifLogFileWriter.SetPath( + cm::filesystem::path(this->GetHomeOutputDirectory()) / + std::string(cmSarif::PROJECT_DEFAULT_SARIF_FILE), + true); + } +#endif } else { if (this->FreshCache) { cmSystemTools::Error("--fresh allowed only when configuring a project"); @@ -2909,6 +2943,11 @@ int cmake::Run(std::vector const& args, bool noconfigure) return this->HasScriptModeExitCode() ? this->GetScriptModeExitCode() : 0; } +#ifndef CMAKE_BOOTSTRAP + // CMake only responds to the SARIF variable in normal mode + this->MarkCliAsUsed(cmSarif::PROJECT_SARIF_FILE_VARIABLE); +#endif + // If MAKEFLAGS are given in the environment, remove the environment // variable. This will prevent try-compile from succeeding when it // should fail (if "-i" is an option). We cannot simply test diff --git a/Source/cmake.h b/Source/cmake.h index 57e6e75..d59e844 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -29,6 +29,8 @@ #include "cmValue.h" #if !defined(CMAKE_BOOTSTRAP) +# include + # include # include @@ -575,6 +577,15 @@ public: cmMessenger* GetMessenger() const { return this->Messenger.get(); } +#ifndef CMAKE_BOOTSTRAP + /// Get the SARIF file path if set manually for this run + cm::optional GetSarifFilePath() const + { + return (this->SarifFileOutput ? cm::make_optional(this->SarifFilePath) + : cm::nullopt); + } +#endif + /** * Get the state of the suppression of developer (author) warnings. * Returns false, by default, if developer warnings should be shown, true @@ -811,6 +822,11 @@ private: cmStateSnapshot CurrentSnapshot; std::unique_ptr Messenger; +#ifndef CMAKE_BOOTSTRAP + bool SarifFileOutput = false; + std::string SarifFilePath; +#endif + std::vector TraceOnlyThisSources; std::set DebugFindPkgs; diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 2e9890f..5913c80 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -650,6 +650,7 @@ add_RunCMake_test(project_injected) add_RunCMake_test(property_init) add_RunCMake_test(DependencyProviders) add_RunCMake_test(return) +add_RunCMake_test(SarifOutput) add_RunCMake_test(separate_arguments) add_RunCMake_test(set_property) add_RunCMake_test(string) diff --git a/Tests/RunCMake/SarifOutput/CMakeLists.txt b/Tests/RunCMake/SarifOutput/CMakeLists.txt new file mode 100644 index 0000000..bf2ef15 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.10) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/SarifOutput/DefaultSarifOutput-check.cmake b/Tests/RunCMake/SarifOutput/DefaultSarifOutput-check.cmake new file mode 100644 index 0000000..e89cc64 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/DefaultSarifOutput-check.cmake @@ -0,0 +1,4 @@ +# By default, no SARIF file should be generated +if (EXISTS "${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif") + message(FATAL_ERROR "SARIF file should not have been generated by default") +endif() diff --git a/Tests/RunCMake/SarifOutput/DefaultSarifOutput-stderr.txt b/Tests/RunCMake/SarifOutput/DefaultSarifOutput-stderr.txt new file mode 100644 index 0000000..cbe39e5 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/DefaultSarifOutput-stderr.txt @@ -0,0 +1,4 @@ +^CMake Warning at DefaultSarifOutput\.cmake:1 \(message\): + Example warning message +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/SarifOutput/DefaultSarifOutput.cmake b/Tests/RunCMake/SarifOutput/DefaultSarifOutput.cmake new file mode 100644 index 0000000..5681cde --- /dev/null +++ b/Tests/RunCMake/SarifOutput/DefaultSarifOutput.cmake @@ -0,0 +1 @@ +message(WARNING "Example warning message") diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-check.cmake b/Tests/RunCMake/SarifOutput/GenerateSarifResults-check.cmake new file mode 100644 index 0000000..0f83ae9 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-check.cmake @@ -0,0 +1,4 @@ +include("${CMAKE_CURRENT_LIST_DIR}/check-sarif.cmake") + +check_sarif_output("${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif" + "${CMAKE_CURRENT_LIST_DIR}/GenerateSarifResults-expected.sarif") diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif new file mode 100644 index 0000000..2a66e6b --- /dev/null +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-expected.sarif @@ -0,0 +1,67 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json", + "runs": [ + { + "results": [ + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "PATH:/GenerateSarifResults.cmake" + }, + "region": { + "startLine": 2 + } + } + } + ], + "message": { + "text": "Example warning message" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0 + }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "PATH:/GenerateSarifResults.cmake" + }, + "region": { + "startLine": 5 + } + } + } + ], + "message": { + "text": "A second example warning message" + }, + "ruleId": "CMake.Warning", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "name": "CMake", + "rules": [ + { + "id": "CMake.Warning", + "messageStrings": { + "default": { + "text": "CMake Warning: {0}" + } + }, + "name": "CMake Warning" + } + ], + "version": "" + } + } + } + ], + "version": "2.1.0" +} diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt b/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt new file mode 100644 index 0000000..5ebc455 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults-stderr.txt @@ -0,0 +1,9 @@ +^CMake Warning at GenerateSarifResults\.cmake:2 \(message\): + Example warning message +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) ++ +CMake Warning at GenerateSarifResults\.cmake:5 \(message\): + A second example warning message +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake b/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake new file mode 100644 index 0000000..b0e6cde --- /dev/null +++ b/Tests/RunCMake/SarifOutput/GenerateSarifResults.cmake @@ -0,0 +1,8 @@ +# Write some user messages to produce SARIF results +message(WARNING "Example warning message") + +# The second warning should be logged, but the rule should not be duplicated +message(WARNING "A second example warning message") + +# Status message should not be logged +message(STATUS "Example status message") diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-check.cmake b/Tests/RunCMake/SarifOutput/ProjectFatalError-check.cmake new file mode 100644 index 0000000..4cbf4f6 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-check.cmake @@ -0,0 +1,4 @@ +include("${CMAKE_CURRENT_LIST_DIR}/check-sarif.cmake") + +check_sarif_output("${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif" + "${CMAKE_CURRENT_LIST_DIR}/ProjectFatalError-expected.sarif") diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif new file mode 100644 index 0000000..f36f101 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-expected.sarif @@ -0,0 +1,47 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json", + "runs": [ + { + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "PATH:/ProjectFatalError.cmake" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "Example error" + }, + "ruleId": "CMake.FatalError", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "name": "CMake", + "rules": [ + { + "id": "CMake.FatalError", + "messageStrings": { + "default": { + "text": "CMake Error: {0}" + } + }, + "name": "CMake Error" + } + ], + "version": "" + } + } + } + ], + "version": "2.1.0" +} diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-result.txt b/Tests/RunCMake/SarifOutput/ProjectFatalError-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError-stderr.txt b/Tests/RunCMake/SarifOutput/ProjectFatalError-stderr.txt new file mode 100644 index 0000000..613bb49 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError-stderr.txt @@ -0,0 +1,4 @@ +^CMake Error at ProjectFatalError\.cmake:1 \(message\): + Example error +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/SarifOutput/ProjectFatalError.cmake b/Tests/RunCMake/SarifOutput/ProjectFatalError.cmake new file mode 100644 index 0000000..5668a05 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ProjectFatalError.cmake @@ -0,0 +1 @@ +message(FATAL_ERROR "Example error") diff --git a/Tests/RunCMake/SarifOutput/RunCMakeTest.cmake b/Tests/RunCMake/SarifOutput/RunCMakeTest.cmake new file mode 100644 index 0000000..fc6099b --- /dev/null +++ b/Tests/RunCMake/SarifOutput/RunCMakeTest.cmake @@ -0,0 +1,25 @@ +include(RunCMake) +include("${CMAKE_CURRENT_LIST_DIR}/check-sarif.cmake") + +# Default case: the SARIF file should not be generated +run_cmake(DefaultSarifOutput) + +# Ensure the expected messages are present in the SARIF output +run_cmake_with_options(GenerateSarifResults -DCMAKE_EXPORT_SARIF=ON) + +# Activate SARIF output using the `CMAKE_EXPORT_SARIF` variable +run_cmake(ToggleExportSarifVariable) + +# If CMake stops with a fatal error, it should still generate a SARIF file if +# requested (and the fatal error should be in the log) +run_cmake_with_options(ProjectFatalError -DCMAKE_EXPORT_SARIF=ON) + +# ScriptModeSarifVariable Test: Script mode must ignore the +# `CMAKE_EXPORT_SARIF`variable +run_cmake_script(ScriptModeSarifVariable -DCMAKE_EXPORT_SARIF=ON) + +# Check that the command-line option can be used to set the file output path +run_cmake_with_options(SarifFileArgument --sarif-output=test_cmake_run.sarif) + +# Test the command-line option in script mode as well +run_cmake_script(SarifFileArgumentScript --sarif-output=test_cmake_run.sarif) diff --git a/Tests/RunCMake/SarifOutput/SarifFileArgument-check.cmake b/Tests/RunCMake/SarifOutput/SarifFileArgument-check.cmake new file mode 100644 index 0000000..55d8091 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/SarifFileArgument-check.cmake @@ -0,0 +1,4 @@ +# Make sure the output exists +if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/test_cmake_run.sarif") + message(FATAL_ERROR "SARIF file not generated in the expected location") +endif() diff --git a/Tests/RunCMake/SarifOutput/SarifFileArgument-stderr.txt b/Tests/RunCMake/SarifOutput/SarifFileArgument-stderr.txt new file mode 100644 index 0000000..277a0ad --- /dev/null +++ b/Tests/RunCMake/SarifOutput/SarifFileArgument-stderr.txt @@ -0,0 +1,4 @@ +^CMake Warning at SarifFileArgument\.cmake:1 \(message\): + SARIF file test +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/SarifOutput/SarifFileArgument.cmake b/Tests/RunCMake/SarifOutput/SarifFileArgument.cmake new file mode 100644 index 0000000..597b163 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/SarifFileArgument.cmake @@ -0,0 +1 @@ +message(WARNING "SARIF file test") diff --git a/Tests/RunCMake/SarifOutput/SarifFileArgumentScript-check.cmake b/Tests/RunCMake/SarifOutput/SarifFileArgumentScript-check.cmake new file mode 100644 index 0000000..55d8091 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/SarifFileArgumentScript-check.cmake @@ -0,0 +1,4 @@ +# Make sure the output exists +if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/test_cmake_run.sarif") + message(FATAL_ERROR "SARIF file not generated in the expected location") +endif() diff --git a/Tests/RunCMake/SarifOutput/SarifFileArgumentScript.cmake b/Tests/RunCMake/SarifOutput/SarifFileArgumentScript.cmake new file mode 100644 index 0000000..6ca9263 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/SarifFileArgumentScript.cmake @@ -0,0 +1,2 @@ +# This won't appear in the SARIF log, but it gives the script something to do. +message(STATUS "SARIF file test") diff --git a/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable-check.cmake b/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable-check.cmake new file mode 100644 index 0000000..c69e4d9 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable-check.cmake @@ -0,0 +1,4 @@ +# Script mode should ignore the SARIF project variable and export nothing +if (EXISTS "${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif") + message(FATAL_ERROR "SARIF file should not have been generated in script mode") +endif() diff --git a/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable.cmake b/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable.cmake new file mode 100644 index 0000000..d2f2a65 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ScriptModeSarifVariable.cmake @@ -0,0 +1,3 @@ +# Try enabling SARIF output in script mode +# No file should be generated since script mode ignores the variable +set(CMAKE_EXPORT_SARIF ON CACHE BOOL "Export SARIF results" FORCE) diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake new file mode 100644 index 0000000..6b26efe --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-check.cmake @@ -0,0 +1,5 @@ +include("${CMAKE_CURRENT_LIST_DIR}/check-sarif.cmake") + +# This test should produce the same output as GenerateSarifResults +check_sarif_output("${RunCMake_TEST_BINARY_DIR}/.cmake/sarif/cmake.sarif" + "${CMAKE_CURRENT_LIST_DIR}/GenerateSarifResults-expected.sarif") diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt new file mode 100644 index 0000000..5cfe7b5 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable-stderr.txt @@ -0,0 +1,11 @@ +^CMake Warning at GenerateSarifResults\.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\)$ diff --git a/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake new file mode 100644 index 0000000..19c0a6e --- /dev/null +++ b/Tests/RunCMake/SarifOutput/ToggleExportSarifVariable.cmake @@ -0,0 +1,6 @@ +# 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 +set(CMAKE_EXPORT_SARIF ON CACHE BOOL "Export SARIF results" FORCE) diff --git a/Tests/RunCMake/SarifOutput/check-sarif.cmake b/Tests/RunCMake/SarifOutput/check-sarif.cmake new file mode 100644 index 0000000..a2e5279 --- /dev/null +++ b/Tests/RunCMake/SarifOutput/check-sarif.cmake @@ -0,0 +1,19 @@ +include("${CMAKE_CURRENT_LIST_DIR}/../CXXModules/check-json.cmake") + +# Check that the SARIF results from a test match the expected results +macro(check_sarif_output sarif_output_file expected_sarif_output_file) + # Make sure the output file exists before reading it + if (NOT EXISTS "${sarif_output_file}") + message(FATAL_ERROR "SARIF output file not found: ${sarif_output_file}") + endif() + file(READ "${sarif_output_file}" actual_output) + + # Make sure the expected output file exists before reading it + if (NOT EXISTS "${expected_sarif_output_file}") + message(FATAL_ERROR "Expected SARIF output file not found: ${expected_sarif_output_file}") + endif() + file(READ "${expected_sarif_output_file}" expected_output) + + # Check the actual output against the expected output + check_json("${actual_output}" "${expected_output}") +endmacro() -- cgit v0.12