Browse Source

ConfigureLog: Log try_compile and try_run checks

Add configure log events for `try_compile` and `try_run` results.

Issue: #23200
Matthew Woehlke 2 years ago
parent
commit
048a02d5bb

+ 134 - 3
Help/manual/cmake-configure-log.7.rst

@@ -43,10 +43,10 @@ step finished normally, ends with a ``...`` document marker line:
     minor: 0
   events:
     -
-      kind: "..."
+      kind: "try_compile"
       # (other fields omitted)
     -
-      kind: "..."
+      kind: "try_compile"
       # (other fields omitted)
   ...
 
@@ -99,4 +99,135 @@ The keys common to all events are:
   locations at which the event occurred.  Each node is a string
   specifying one location formatted as ``<file>:<line> (<function>)``.
 
-Additional mapping keys are specific to each event kind.
+Additional mapping keys are specific to each event kind,
+described below.
+
+.. _`try_compile event`:
+
+Event Kind ``try_compile``
+--------------------------
+
+The :command:`try_compile` command logs ``try_compile`` events.
+
+A ``try_compile`` event is a YAML mapping:
+
+.. code-block:: yaml
+
+  kind: "try_compile"
+  backtrace:
+    - "CMakeLists.txt:123 (try_compile)"
+  directories:
+    source: "/path/to/.../TryCompile-01234"
+    binary: "/path/to/.../TryCompile-01234"
+  buildResult:
+    variable: "COMPILE_RESULT"
+    cached: true
+    stdout: |
+      # ...
+    exitCode: 0
+
+The keys specific to ``try_compile`` mappings are:
+
+``directories``
+  A mapping describing the directories associated with the
+  compilation attempt.  It has the following keys:
+
+  ``source``
+    String specifying the source directory of the
+    :command:`try_compile` project.
+
+  ``binary``
+    String specifying the binary directory of the
+    :command:`try_compile` project.
+    For non-project invocations, this is often the same as
+    the source directory.
+
+``buildResult``
+  A mapping describing the result of compiling the test code.
+  It has the following keys:
+
+  ``variable``
+    A string specifying the name of the CMake variable
+    storing the result of trying to build the test project.
+
+  ``cached``
+    A boolean indicating whether the above result ``variable``
+    is stored in the CMake cache.
+
+  ``stdout``
+    A YAML literal block scalar containing the output from building
+    the test project, represented using our `Text Block Encoding`_.
+    This contains build output from both stdout and stderr.
+
+  ``exitCode``
+    An integer specifying the build tool exit code from trying
+    to build the test project.
+
+Event Kind ``try_run``
+----------------------
+
+The :command:`try_run` command logs ``try_run`` events.
+
+A ``try_run`` event is a YAML mapping:
+
+.. code-block:: yaml
+
+  kind: "try_run"
+  backtrace:
+    - "CMakeLists.txt:456 (try_run)"
+  directories:
+    source: "/path/to/.../TryCompile-56789"
+    binary: "/path/to/.../TryCompile-56789"
+  buildResult:
+    variable: "COMPILE_RESULT"
+    cached: true
+    stdout: |
+      # ...
+    exitCode: 0
+  runResult:
+    variable: "RUN_RESULT"
+    cached: true
+    stdout: |
+      # ...
+    stderr: |
+      # ...
+    exitCode: 0
+
+The keys specific to ``try_run`` mappings include those
+documented by the `try_compile event`_, plus:
+
+``runResult``
+  A mapping describing the result of running the test code.
+  It has the following keys:
+
+  ``variable``
+    A string specifying the name of the CMake variable
+    storing the result of trying to run the test executable.
+
+  ``cached``
+    A boolean indicating whether the above result ``variable``
+    is stored in the CMake cache.
+
+  ``stdout``
+    An optional key that is present when the test project built successfully.
+    Its value is a YAML literal block scalar containing output from running
+    the test executable, represented using our `Text Block Encoding`_.
+
+    If ``RUN_OUTPUT_VARIABLE`` was used, stdout and stderr are captured
+    together, so this will contain both.  Otherwise, this will contain
+    only the stdout output.
+
+  ``stderr``
+    An optional key that is present when the test project built successfully
+    and the ``RUN_OUTPUT_VARIABLE`` option was not used.
+    Its value is a YAML literal block scalar containing output from running
+    the test executable, represented using our `Text Block Encoding`_.
+
+    If ``RUN_OUTPUT_VARIABLE`` was used, stdout and stderr are captured
+    together in the ``stdout`` key, and this key will not be present.
+    Otherwise, this will contain the stderr output.
+
+  ``exitCode``
+    An optional key that is present when the test project built successfully.
+    Its value is an integer specifying the exit code, or a string containing
+    an error message, from trying to run the test executable.

+ 23 - 0
Source/cmCoreTryCompile.cxx

@@ -16,6 +16,7 @@
 #include "cmsys/FStream.hxx"
 
 #include "cmArgumentParser.h"
+#include "cmConfigureLog.h"
 #include "cmExportTryCompileFileGenerator.h"
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
@@ -1124,6 +1125,11 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode(
   }
 
   cmTryCompileResult result;
+  result.SourceDirectory = sourceDirectory;
+  result.BinaryDirectory = this->BinaryDirectory;
+  result.Variable = *arguments.CompileResultVariable;
+  result.VariableCached = !arguments.NoCache;
+  result.Output = std::move(output);
   result.ExitCode = res;
   return result;
 }
@@ -1266,3 +1272,20 @@ std::string cmCoreTryCompile::WriteSource(std::string const& filename,
   file.close();
   return filepath;
 }
+
+void cmCoreTryCompile::WriteTryCompileEventFields(
+  cmConfigureLog& log, cmTryCompileResult const& compileResult)
+{
+#ifndef CMAKE_BOOTSTRAP
+  log.BeginObject("directories"_s);
+  log.WriteValue("source"_s, compileResult.SourceDirectory);
+  log.WriteValue("binary"_s, compileResult.BinaryDirectory);
+  log.EndObject();
+  log.BeginObject("buildResult"_s);
+  log.WriteValue("variable"_s, compileResult.Variable);
+  log.WriteValue("cached"_s, compileResult.VariableCached);
+  log.WriteLiteralTextBlock("stdout"_s, compileResult.Output);
+  log.WriteValue("exitCode"_s, compileResult.ExitCode);
+  log.EndObject();
+#endif
+}

+ 11 - 0
Source/cmCoreTryCompile.h

@@ -14,12 +14,20 @@
 #include "cmArgumentParserTypes.h"
 #include "cmStateTypes.h"
 
+class cmConfigureLog;
 class cmMakefile;
 template <typename Iter>
 class cmRange;
 
 struct cmTryCompileResult
 {
+  std::string SourceDirectory;
+  std::string BinaryDirectory;
+
+  bool VariableCached = true;
+  std::string Variable;
+
+  std::string Output;
   int ExitCode = 1;
 };
 
@@ -108,6 +116,9 @@ public:
    */
   void FindOutputFile(const std::string& targetName);
 
+  static void WriteTryCompileEventFields(
+    cmConfigureLog& log, cmTryCompileResult const& compileResult);
+
   std::string BinaryDirectory;
   std::string OutputFile;
   std::string FindErrorMessage;

+ 25 - 1
Source/cmTryCompileCommand.cxx

@@ -2,6 +2,9 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmTryCompileCommand.h"
 
+#include <cm/optional>
+
+#include "cmConfigureLog.h"
 #include "cmCoreTryCompile.h"
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
@@ -13,6 +16,19 @@
 #include "cmValue.h"
 #include "cmake.h"
 
+namespace {
+#ifndef CMAKE_BOOTSTRAP
+void WriteTryCompileEvent(cmConfigureLog& log, cmMakefile const& mf,
+                          cmTryCompileResult const& compileResult)
+{
+  log.BeginEvent("try_compile");
+  log.WriteBacktrace(mf);
+  cmCoreTryCompile::WriteTryCompileEventFields(log, compileResult);
+  log.EndEvent();
+}
+#endif
+}
+
 bool cmTryCompileCommand(std::vector<std::string> const& args,
                          cmExecutionStatus& status)
 {
@@ -59,7 +75,15 @@ bool cmTryCompileCommand(std::vector<std::string> const& args,
   if (!arguments) {
     return true;
   }
-  tc.TryCompileCode(arguments, targetType);
+
+  if (cm::optional<cmTryCompileResult> compileResult =
+        tc.TryCompileCode(arguments, targetType)) {
+#ifndef CMAKE_BOOTSTRAP
+    if (cmConfigureLog* log = mf.GetCMakeInstance()->GetConfigureLog()) {
+      WriteTryCompileEvent(*log, mf, *compileResult);
+    }
+#endif
+  }
 
   // if They specified clean then we clean up what we can
   if (tc.SrcFileSignature) {

+ 82 - 11
Source/cmTryRunCommand.cxx

@@ -3,12 +3,15 @@
 #include "cmTryRunCommand.h"
 
 #include <cstdio>
+#include <stdexcept>
 
 #include <cm/optional>
+#include <cmext/string_view>
 
 #include "cmsys/FStream.hxx"
 
 #include "cmArgumentParserTypes.h"
+#include "cmConfigureLog.h"
 #include "cmCoreTryCompile.h"
 #include "cmDuration.h"
 #include "cmExecutionStatus.h"
@@ -23,6 +26,44 @@
 #include "cmake.h"
 
 namespace {
+struct cmTryRunResult
+{
+  bool VariableCached = true;
+  std::string Variable;
+  cm::optional<std::string> Stdout;
+  cm::optional<std::string> Stderr;
+  cm::optional<std::string> ExitCode;
+};
+
+#ifndef CMAKE_BOOTSTRAP
+void WriteTryRunEvent(cmConfigureLog& log, cmMakefile const& mf,
+                      cmTryCompileResult const& compileResult,
+                      cmTryRunResult const& runResult)
+{
+  log.BeginEvent("try_run");
+  log.WriteBacktrace(mf);
+  cmCoreTryCompile::WriteTryCompileEventFields(log, compileResult);
+
+  log.BeginObject("runResult"_s);
+  log.WriteValue("variable"_s, runResult.Variable);
+  log.WriteValue("cached"_s, runResult.VariableCached);
+  if (runResult.Stdout) {
+    log.WriteLiteralTextBlock("stdout"_s, *runResult.Stdout);
+  }
+  if (runResult.Stderr) {
+    log.WriteLiteralTextBlock("stderr"_s, *runResult.Stderr);
+  }
+  if (runResult.ExitCode) {
+    try {
+      log.WriteValue("exitCode"_s, std::stoi(*runResult.ExitCode));
+    } catch (std::invalid_argument const&) {
+      log.WriteValue("exitCode"_s, *runResult.ExitCode);
+    }
+  }
+  log.EndObject();
+  log.EndEvent();
+}
+#endif
 
 class TryRunCommandImpl : public cmCoreTryCompile
 {
@@ -96,24 +137,33 @@ bool TryRunCommandImpl::TryRunCode(std::vector<std::string> const& argv)
   }
 
   bool captureRunOutput = false;
-  bool captureRunOutputStdOut = false;
-  bool captureRunOutputStdErr = false;
   if (arguments.OutputVariable) {
     captureRunOutput = true;
   } else if (arguments.CompileOutputVariable) {
     arguments.OutputVariable = arguments.CompileOutputVariable;
   }
-  if (arguments.RunOutputStdOutVariable || arguments.RunOutputStdErrVariable) {
-    captureRunOutputStdOut = arguments.RunOutputStdOutVariable.has_value();
-    captureRunOutputStdErr = arguments.RunOutputStdErrVariable.has_value();
-  } else if (arguments.RunOutputVariable) {
-    captureRunOutput = true;
+
+  // Capture the split output for the configure log unless the caller
+  // requests combined output to be captured by a variable.
+  bool captureRunOutputStdOutErr = true;
+  if (!arguments.RunOutputStdOutVariable &&
+      !arguments.RunOutputStdErrVariable) {
+    if (arguments.RunOutputVariable) {
+      captureRunOutput = true;
+      captureRunOutputStdOutErr = false;
+    } else if (arguments.OutputVariable) {
+      captureRunOutputStdOutErr = false;
+    }
   }
 
   // do the try compile
   cm::optional<cmTryCompileResult> compileResult =
     this->TryCompileCode(arguments, cmStateEnums::EXECUTABLE);
 
+  cmTryRunResult runResult;
+  runResult.Variable = this->RunResultVariable;
+  runResult.VariableCached = !arguments.NoCache;
+
   // now try running the command if it compiled
   if (compileResult && compileResult->ExitCode == 0) {
     if (this->OutputFile.empty()) {
@@ -134,14 +184,26 @@ bool TryRunCommandImpl::TryRunCode(std::vector<std::string> const& argv)
           runArgs, *arguments.SourceDirectoryOrFile,
           *arguments.CompileResultVariable,
           captureRunOutput ? &runOutputContents : nullptr,
-          captureRunOutputStdOut ? &runOutputStdOutContents : nullptr,
-          captureRunOutputStdErr ? &runOutputStdErrContents : nullptr);
+          captureRunOutputStdOutErr ? &runOutputStdOutContents : nullptr,
+          captureRunOutputStdOutErr ? &runOutputStdErrContents : nullptr);
       } else {
         this->RunExecutable(
           runArgs, arguments.RunWorkingDirectory,
           captureRunOutput ? &runOutputContents : nullptr,
-          captureRunOutputStdOut ? &runOutputStdOutContents : nullptr,
-          captureRunOutputStdErr ? &runOutputStdErrContents : nullptr);
+          captureRunOutputStdOutErr ? &runOutputStdOutContents : nullptr,
+          captureRunOutputStdOutErr ? &runOutputStdErrContents : nullptr);
+      }
+
+      if (captureRunOutputStdOutErr) {
+        runResult.Stdout = runOutputStdOutContents;
+        runResult.Stderr = runOutputStdErrContents;
+      } else {
+        runResult.Stdout = runOutputContents;
+      }
+
+      if (cmValue ec =
+            this->Makefile->GetDefinition(this->RunResultVariable)) {
+        runResult.ExitCode = *ec;
       }
 
       // now put the output into the variables
@@ -172,6 +234,15 @@ bool TryRunCommandImpl::TryRunCode(std::vector<std::string> const& argv)
     }
   }
 
+#ifndef CMAKE_BOOTSTRAP
+  if (compileResult) {
+    cmMakefile const& mf = *(this->Makefile);
+    if (cmConfigureLog* log = mf.GetCMakeInstance()->GetConfigureLog()) {
+      WriteTryRunEvent(*log, mf, *compileResult, runResult);
+    }
+  }
+#endif
+
   // if we created a directory etc, then cleanup after ourselves
   if (!this->Makefile->GetCMakeInstance()->GetDebugTryCompile()) {
     this->CleanupFiles(this->BinaryDirectory);

+ 37 - 0
Tests/RunCMake/try_compile/Inspect-config.txt

@@ -0,0 +1,37 @@
+^
+---
+version:
+  major: 1
+  minor: 0
+events:
+  -
+    kind: "try_compile"
+    backtrace:
+      - "[^"]*/Modules/CMakeDetermineCompilerABI.cmake:[0-9]+ \(try_compile\)"
+      - "[^"]*/Modules/CMakeTestCCompiler.cmake:[0-9]+ \(CMAKE_DETERMINE_COMPILER_ABI\)"
+      - "Inspect.cmake:[0-9]+ \(enable_language\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_compile/Inspect-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_compile/Inspect-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "CMAKE_C_ABI_COMPILED"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+  -
+    kind: "try_compile"
+    backtrace:
+      - "[^"]*/Modules/CMakeDetermineCompilerABI.cmake:[0-9]+ \(try_compile\)"
+      - "[^"]*/Modules/CMakeTestCXXCompiler.cmake:[0-9]+ \(CMAKE_DETERMINE_COMPILER_ABI\)"
+      - "Inspect.cmake:[0-9]+ \(enable_language\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_compile/Inspect-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_compile/Inspect-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "CMAKE_CXX_ABI_COMPILED"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+\.\.\.$

+ 1 - 0
Tests/RunCMake/try_compile/SourceFromBadName-config.txt

@@ -0,0 +1 @@
+^$

+ 1 - 0
Tests/RunCMake/try_run/ConfigureLog-bad.c

@@ -0,0 +1 @@
+#error "This does not compile!"

+ 99 - 0
Tests/RunCMake/try_run/ConfigureLog-config.txt

@@ -0,0 +1,99 @@
+^
+---
+version:
+  major: 1
+  minor: 0
+events:
+  -
+    kind: "try_compile"
+    backtrace:
+      - "[^"]*/Modules/CMakeDetermineCompilerABI.cmake:[0-9]+ \(try_compile\)"
+      - "[^"]*/Modules/CMakeTestCCompiler.cmake:[0-9]+ \(CMAKE_DETERMINE_COMPILER_ABI\)"
+      - "CMakeLists.txt:[0-9]+ \(project\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "CMAKE_C_ABI_COMPILED"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+  -
+    kind: "try_run"
+    backtrace:
+      - "ConfigureLog.cmake:[0-9]+ \(try_run\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "COMPILE_RESULT"
+      cached: true
+      stdout: \|.*
+      exitCode: [1-9][0-9]*
+    runResult:
+      variable: "RUN_RESULT"
+      cached: true
+  -
+    kind: "try_run"
+    backtrace:
+      - "ConfigureLog.cmake:[0-9]+ \(try_run\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "COMPILE_RESULT"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+    runResult:
+      variable: "RUN_RESULT"
+      cached: true
+      stdout: \|
+        Output on stdout!
+      stderr: \|
+        Output, with backslash '\\\\', on stderr!
+      exitCode: 12
+  -
+    kind: "try_run"
+    backtrace:
+      - "ConfigureLog.cmake:[0-9]+ \(try_run\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "COMPILE_RESULT"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+    runResult:
+      variable: "RUN_RESULT"
+      cached: true
+      stdout: \|
+        Output, with backslash '\\\\', on stderr!
+        Output on stdout!
+      exitCode: 12
+  -
+    kind: "try_run"
+    backtrace:
+      - "ConfigureLog.cmake:[0-9]+ \(try_run\)"
+      - "CMakeLists.txt:[0-9]+ \(include\)"
+    directories:
+      source: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+      binary: "[^"]*/Tests/RunCMake/try_run/ConfigureLog-build/CMakeFiles/CMakeScratch/TryCompile-[^/]+"
+    buildResult:
+      variable: "COMPILE_RESULT"
+      cached: true
+      stdout: \|.*
+      exitCode: 0
+    runResult:
+      variable: "RUN_RESULT"
+      cached: true
+      stdout: \|
+        Output on stdout!
+      stderr: \|
+        Output, with backslash '\\\\', on stderr!
+      exitCode: 12
+\.\.\.$

+ 9 - 0
Tests/RunCMake/try_run/ConfigureLog-test.c

@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+int main()
+{
+  fprintf(stderr, "Output, with backslash '\\', on stderr!\n");
+  fflush(stderr); /* make output deterministic even if stderr is buffered */
+  fprintf(stdout, "Output on stdout!\n");
+  return 12;
+}

+ 18 - 0
Tests/RunCMake/try_run/ConfigureLog.cmake

@@ -0,0 +1,18 @@
+try_run(RUN_RESULT COMPILE_RESULT
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ConfigureLog-bad.c
+  )
+
+try_run(RUN_RESULT COMPILE_RESULT
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ConfigureLog-test.c
+  )
+
+try_run(RUN_RESULT COMPILE_RESULT
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ConfigureLog-test.c
+  RUN_OUTPUT_VARIABLE RUN_OUTPUT
+  )
+
+try_run(RUN_RESULT COMPILE_RESULT
+  SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ConfigureLog-test.c
+  RUN_OUTPUT_STDOUT_VARIABLE RUN_STDOUT
+  RUN_OUTPUT_STDERR_VARIABLE RUN_STDERR
+  )

+ 1 - 0
Tests/RunCMake/try_run/RunCMakeTest.cmake

@@ -3,6 +3,7 @@ include(RunCMake)
 run_cmake(BinDirEmpty)
 run_cmake(BinDirRelative)
 run_cmake(NoOutputVariable)
+run_cmake(ConfigureLog)
 
 set(RunCMake_TEST_OPTIONS -Dtry_compile_DEFS=old_signature.cmake)
 include(${RunCMake_SOURCE_DIR}/old_and_new_signature_tests.cmake)