diff options
-rw-r--r-- | Help/command/cmake_file_api.rst | 78 | ||||
-rw-r--r-- | Help/manual/cmake-commands.7.rst | 1 | ||||
-rw-r--r-- | Help/manual/cmake-file-api.7.rst | 6 | ||||
-rw-r--r-- | Help/release/dev/file-api-query-command.rst | 6 | ||||
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmCommands.cxx | 3 | ||||
-rw-r--r-- | Source/cmFileAPI.cxx | 42 | ||||
-rw-r--r-- | Source/cmFileAPI.h | 33 | ||||
-rw-r--r-- | Source/cmFileAPICommand.cxx | 170 | ||||
-rw-r--r-- | Source/cmFileAPICommand.h | 13 | ||||
-rw-r--r-- | Source/cmake.h | 4 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/ProjectQueryBad-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/ProjectQueryBad-stderr.txt | 36 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/ProjectQueryBad.cmake | 42 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/ProjectQueryGood-check.cmake | 11 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/ProjectQueryGood.cmake | 8 | ||||
-rw-r--r-- | Tests/RunCMake/FileAPI/RunCMakeTest.cmake | 2 |
17 files changed, 447 insertions, 11 deletions
diff --git a/Help/command/cmake_file_api.rst b/Help/command/cmake_file_api.rst new file mode 100644 index 0000000..e7ee7e7 --- /dev/null +++ b/Help/command/cmake_file_api.rst @@ -0,0 +1,78 @@ +cmake_file_api +-------------- + +.. versionadded:: 3.27 + +Enables interacting with the :manual:`CMake file API <cmake-file-api(7)>`. + +.. signature:: + cmake_file_api(QUERY ...) + + The ``QUERY`` subcommand adds a file API query for the current CMake + invocation. + + .. code-block:: cmake + + cmake_file_api( + QUERY + API_VERSION <version> + [CODEMODEL <versions>...] + [CACHE <versions>...] + [CMAKEFILES <versions>...] + [TOOLCHAINS <versions>...] + ) + + The ``API_VERSION`` must always be given. Currently, the only supported + value for ``<version>`` is 1. See :ref:`file-api v1` for details of the + reply content and location. + + Each of the optional keywords ``CODEMODEL``, ``CACHE``, ``CMAKEFILES`` and + ``TOOLCHAINS`` correspond to one of the object kinds that can be requested + by the project. The ``configureLog`` object kind cannot be set with this + command, since it must be set before CMake starts reading the top level + ``CMakeLists.txt`` file. + + For each of the optional keywords, the ``<versions>`` list must contain one + or more version values of the form ``major`` or ``major.minor``, where + ``major`` and ``minor`` are integers. Projects should list the versions they + accept in their preferred order, as only the first supported value from the + list will be selected. The command will ignore versions with a ``major`` + version higher than any major version it supports for that object kind. + It will raise an error if it encounters an invalid version number, or if none + of the requested versions is supported. + + For each type of object kind requested, a query equivalent to a shared, + stateless query will be added internally. No query file will be created in + the file system. The reply *will* be written to the file system at + generation time. + + It is not an error to add a query for the same thing more than once, whether + from query files or from multiple calls to ``cmake_file_api(QUERY)``. + The final set of queries will be a merged combination of all queries + specified on disk and queries submitted by the project. + +Example +^^^^^^^ + +A project may want to use replies from the file API at build time to implement +some form of verification task. Instead of relying on something outside of +CMake to create a query file, the project can use ``cmake_file_api(QUERY)`` +to request the required information for the current run. It can then create +a custom command to run at build time, knowing that the requested information +should always be available. + +.. code-block:: cmake + + cmake_file_api( + QUERY + API_VERSION 1 + CODEMODEL 2.3 + TOOLCHAINS 1 + ) + + add_custom_target(verify_project + COMMAND ${CMAKE_COMMAND} + -D BUILD_DIR=${CMAKE_BINARY_DIR} + -D CONFIG=$<CONFIG> + -P ${CMAKE_CURRENT_SOURCE_DIR}/verify_project.cmake + ) diff --git a/Help/manual/cmake-commands.7.rst b/Help/manual/cmake-commands.7.rst index 0f35632..bd678b7 100644 --- a/Help/manual/cmake-commands.7.rst +++ b/Help/manual/cmake-commands.7.rst @@ -87,6 +87,7 @@ These commands are available only in CMake projects. /command/add_test /command/aux_source_directory /command/build_command + /command/cmake_file_api /command/create_test_sourcelist /command/define_property /command/enable_language diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst index 0bdb419..5f16a8b 100644 --- a/Help/manual/cmake-file-api.7.rst +++ b/Help/manual/cmake-file-api.7.rst @@ -23,6 +23,12 @@ of files within the API directory. API file layout versioning is orthogonal to the versioning of `Object Kinds`_ used in replies. This version of CMake supports only one API version, `API v1`_. +.. versionadded:: 3.27 + Projects may also submit queries for the current run using the + :command:`cmake_file_api` command. + +.. _`file-api v1`: + API v1 ====== diff --git a/Help/release/dev/file-api-query-command.rst b/Help/release/dev/file-api-query-command.rst new file mode 100644 index 0000000..66ae7d9 --- /dev/null +++ b/Help/release/dev/file-api-query-command.rst @@ -0,0 +1,6 @@ +file-api-query-command +---------------------- + +* The :command:`cmake_file_api` command was added, enabling projects to + add :manual:`CMake file API <cmake-file-api(7)>` queries for the current + CMake run. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index bcaf890..b01e1e7 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -232,6 +232,8 @@ add_library( cmFileAPIConfigureLog.h cmFileAPICMakeFiles.cxx cmFileAPICMakeFiles.h + cmFileAPICommand.cxx + cmFileAPICommand.h cmFileAPIToolchains.cxx cmFileAPIToolchains.h cmFileCopier.cxx diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx index 27f2156..ae83b2e 100644 --- a/Source/cmCommands.cxx +++ b/Source/cmCommands.cxx @@ -97,6 +97,7 @@ # include "cmExportCommand.h" # include "cmExportLibraryDependenciesCommand.h" # include "cmFLTKWrapUICommand.h" +# include "cmFileAPICommand.h" # include "cmIncludeExternalMSProjectCommand.h" # include "cmInstallProgramsCommand.h" # include "cmLinkLibrariesCommand.h" @@ -293,6 +294,7 @@ void GetProjectCommands(cmState* state) state->AddBuiltinCommand("qt_wrap_ui", cmQTWrapUICommand); state->AddBuiltinCommand("remove_definitions", cmRemoveDefinitionsCommand); state->AddBuiltinCommand("source_group", cmSourceGroupCommand); + state->AddBuiltinCommand("cmake_file_api", cmFileAPICommand); state->AddDisallowedCommand( "export_library_dependencies", cmExportLibraryDependenciesCommand, @@ -333,6 +335,7 @@ void GetProjectCommandsInScriptMode(cmState* state) CM_UNEXPECTED_PROJECT_COMMAND("add_test"); CM_UNEXPECTED_PROJECT_COMMAND("aux_source_directory"); CM_UNEXPECTED_PROJECT_COMMAND("build_command"); + CM_UNEXPECTED_PROJECT_COMMAND("cmake_file_api"); CM_UNEXPECTED_PROJECT_COMMAND("create_test_sourcelist"); CM_UNEXPECTED_PROJECT_COMMAND("define_property"); CM_UNEXPECTED_PROJECT_COMMAND("enable_language"); diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index 8b98916..8abb5a8 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -978,3 +978,45 @@ Json::Value cmFileAPI::ReportCapabilities() return capabilities; } + +bool cmFileAPI::AddProjectQuery(cmFileAPI::ObjectKind kind, + unsigned majorVersion, unsigned minorVersion) +{ + switch (kind) { + case ObjectKind::CodeModel: + if (majorVersion != 2 || minorVersion > CodeModelV2Minor) { + return false; + } + break; + case ObjectKind::Cache: + if (majorVersion != 2 || minorVersion > CacheV2Minor) { + return false; + } + break; + case ObjectKind::CMakeFiles: + if (majorVersion != 1 || minorVersion > CMakeFilesV1Minor) { + return false; + } + break; + case ObjectKind::Toolchains: + if (majorVersion != 1 || minorVersion > ToolchainsV1Minor) { + return false; + } + break; + // These cannot be requested by the project + case ObjectKind::ConfigureLog: + case ObjectKind::InternalTest: + return false; + } + + Object query; + query.Kind = kind; + query.Version = majorVersion; + if (std::find(this->TopQuery.Known.begin(), this->TopQuery.Known.end(), + query) == this->TopQuery.Known.end()) { + this->TopQuery.Known.emplace_back(query); + this->QueryExists = true; + } + + return true; +} diff --git a/Source/cmFileAPI.h b/Source/cmFileAPI.h index 6d7678f..1c13d7b 100644 --- a/Source/cmFileAPI.h +++ b/Source/cmFileAPI.h @@ -41,6 +41,20 @@ public: /** Report file-api capabilities for cmake -E capabilities. */ static Json::Value ReportCapabilities(); + // Keep in sync with ObjectKindName. + enum class ObjectKind + { + CodeModel, + ConfigureLog, + Cache, + CMakeFiles, + Toolchains, + InternalTest + }; + + bool AddProjectQuery(ObjectKind kind, unsigned majorVersion, + unsigned minorVersion); + private: cmake* CMakeInstance; @@ -53,17 +67,6 @@ private: static std::vector<std::string> LoadDir(std::string const& dir); void RemoveOldReplyFiles(); - // Keep in sync with ObjectKindName. - enum class ObjectKind - { - CodeModel, - ConfigureLog, - Cache, - CMakeFiles, - Toolchains, - InternalTest - }; - /** Identify one object kind and major version. */ struct Object { @@ -76,6 +79,14 @@ private: } return l.Version < r.Version; } + friend bool operator==(Object const& l, Object const& r) + { + return l.Kind == r.Kind && l.Version == r.Version; + } + friend bool operator!=(Object const& l, Object const& r) + { + return !(l == r); + } }; /** Represent content of a query directory. */ diff --git a/Source/cmFileAPICommand.cxx b/Source/cmFileAPICommand.cxx new file mode 100644 index 0000000..d051c9c --- /dev/null +++ b/Source/cmFileAPICommand.cxx @@ -0,0 +1,170 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying +file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmFileAPICommand.h" + +#include <algorithm> +#include <array> +#include <cctype> +#include <cstdlib> + +#include <cm/string_view> +#include <cmext/string_view> + +#include "cmArgumentParser.h" +#include "cmArgumentParserTypes.h" +#include "cmExecutionStatus.h" +#include "cmFileAPI.h" +#include "cmMakefile.h" +#include "cmRange.h" +#include "cmStringAlgorithms.h" +#include "cmSubcommandTable.h" +#include "cmake.h" + +namespace { + +bool isCharDigit(char ch) +{ + return std::isdigit(static_cast<unsigned char>(ch)); +} + +std::string processObjectKindVersions(cmFileAPI& fileApi, + cmFileAPI::ObjectKind objectKind, + cm::string_view keyword, + const std::vector<std::string>& versions) +{ + // The "versions" vector is empty only when the keyword was not present. + // It is an error to provide the keyword with no versions after it, and that + // is enforced by the argument parser before we get here. + if (versions.empty()) { + return {}; + } + + // The first supported version listed is what we use + for (const std::string& ver : versions) { + const char* vStart = ver.c_str(); + int majorVersion = std::atoi(vStart); + int minorVersion = 0; + std::string::size_type pos = ver.find('.'); + if (pos != std::string::npos) { + vStart += pos + 1; + minorVersion = std::atoi(vStart); + } + if (majorVersion < 1 || minorVersion < 0) { + return cmStrCat("Given a malformed version \"", ver, "\" for ", keyword, + "."); + } + if (fileApi.AddProjectQuery(objectKind, + static_cast<unsigned>(majorVersion), + static_cast<unsigned>(minorVersion))) { + return {}; + } + } + return cmStrCat("None of the specified ", keyword, + " versions is supported by this version of CMake."); +} + +bool handleQueryCommand(std::vector<std::string> const& args, + cmExecutionStatus& status) +{ + if (args.empty()) { + status.SetError("QUERY subcommand called without required arguments."); + return false; + } + + struct Arguments : public ArgumentParser::ParseResult + { + ArgumentParser::NonEmpty<std::string> ApiVersion; + ArgumentParser::NonEmpty<std::vector<std::string>> CodeModelVersions; + ArgumentParser::NonEmpty<std::vector<std::string>> CacheVersions; + ArgumentParser::NonEmpty<std::vector<std::string>> CMakeFilesVersions; + ArgumentParser::NonEmpty<std::vector<std::string>> ToolchainsVersions; + }; + + static auto const parser = + cmArgumentParser<Arguments>{} + .Bind("API_VERSION"_s, &Arguments::ApiVersion) + .Bind("CODEMODEL"_s, &Arguments::CodeModelVersions) + .Bind("CACHE"_s, &Arguments::CacheVersions) + .Bind("CMAKEFILES"_s, &Arguments::CMakeFilesVersions) + .Bind("TOOLCHAINS"_s, &Arguments::ToolchainsVersions); + + std::vector<std::string> unparsedArguments; + Arguments const arguments = + parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments); + + if (arguments.MaybeReportError(status.GetMakefile())) { + return true; + } + if (!unparsedArguments.empty()) { + status.SetError("QUERY subcommand given unknown argument \"" + + unparsedArguments.front() + "\"."); + return false; + } + + if (!std::all_of(arguments.ApiVersion.begin(), arguments.ApiVersion.end(), + isCharDigit)) { + status.SetError("QUERY subcommand given a non-integer API_VERSION."); + return false; + } + const int apiVersion = std::atoi(arguments.ApiVersion.c_str()); + if (apiVersion != 1) { + status.SetError( + cmStrCat("QUERY subcommand given an unsupported API_VERSION \"", + arguments.ApiVersion, + "\" (the only currently supported version is 1).")); + return false; + } + + cmMakefile& mf = status.GetMakefile(); + cmake* cmi = mf.GetCMakeInstance(); + cmFileAPI* fileApi = cmi->GetFileAPI(); + + // We want to check all keywords and report all errors, not just the first. + // Record each result rather than short-circuiting on the first error. + + // NOTE: Double braces are needed here for compilers that don't implement the + // CWG 1270 revision to C++11. + std::array<std::string, 4> errors{ + { processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::CodeModel, + "CODEMODEL"_s, arguments.CodeModelVersions), + processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::Cache, + "CACHE"_s, arguments.CacheVersions), + processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::CMakeFiles, + "CMAKEFILES"_s, arguments.CMakeFilesVersions), + processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::Toolchains, + "TOOLCHAINS"_s, arguments.ToolchainsVersions) } + }; + + if (!std::all_of(errors.begin(), errors.end(), + [](const std::string& s) -> bool { return s.empty(); })) { + std::string message("QUERY subcommand was given invalid arguments:"); + for (const std::string& s : errors) { + if (!s.empty()) { + message = cmStrCat(message, "\n ", s); + } + } + status.SetError(message); + return false; + } + + return true; +} + +} + +bool cmFileAPICommand(std::vector<std::string> const& args, + cmExecutionStatus& status) +{ + if (args.empty()) { + status.SetError("must be called with arguments."); + return false; + } + + // clang-format off + static cmSubcommandTable const subcommand{ + { "QUERY"_s, handleQueryCommand } + }; + // clang-format on + + return subcommand(args[0], args, status); +} diff --git a/Source/cmFileAPICommand.h b/Source/cmFileAPICommand.h new file mode 100644 index 0000000..28a4571 --- /dev/null +++ b/Source/cmFileAPICommand.h @@ -0,0 +1,13 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying +file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <string> +#include <vector> + +class cmExecutionStatus; + +bool cmFileAPICommand(std::vector<std::string> const& args, + cmExecutionStatus& status); diff --git a/Source/cmake.h b/Source/cmake.h index 2f5ea24..156d061 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -635,6 +635,10 @@ public: void UnwatchUnusedCli(const std::string& var); void WatchUnusedCli(const std::string& var); +#if !defined(CMAKE_BOOTSTRAP) + cmFileAPI* GetFileAPI() const { return this->FileAPI.get(); } +#endif + cmState* GetState() const { return this->State.get(); } void SetCurrentSnapshot(cmStateSnapshot const& snapshot) { diff --git a/Tests/RunCMake/FileAPI/ProjectQueryBad-result.txt b/Tests/RunCMake/FileAPI/ProjectQueryBad-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/FileAPI/ProjectQueryBad-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/FileAPI/ProjectQueryBad-stderr.txt b/Tests/RunCMake/FileAPI/ProjectQueryBad-stderr.txt new file mode 100644 index 0000000..84eff98 --- /dev/null +++ b/Tests/RunCMake/FileAPI/ProjectQueryBad-stderr.txt @@ -0,0 +1,36 @@ +Non-query check +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api does not recognize sub-command NOT_A_QUERY +.* +Invalid API version checks +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api QUERY subcommand given an unsupported API_VERSION "2" \(the + only currently supported version is 1\)\. +.* +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api QUERY subcommand given a non-integer API_VERSION\. +.* +Invalid version numbers check +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api QUERY subcommand was given invalid arguments: + + Given a malformed version "nope" for CODEMODEL\. + Given a malformed version "-2" for CACHE\. + Given a malformed version "\.8" for CMAKEFILES\. + Given a malformed version "0\.1" for TOOLCHAINS\. +.* +Requested versions too high check +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api QUERY subcommand was given invalid arguments: + + None of the specified CODEMODEL versions is supported by this version of CMake\. + None of the specified CACHE versions is supported by this version of CMake\. + None of the specified CMAKEFILES versions is supported by this version of CMake\. + None of the specified TOOLCHAINS versions is supported by this version of CMake\. +.* +Requested versions too low check +CMake Error at ProjectQueryBad\.cmake:[0-9]+ \(cmake_file_api\): + cmake_file_api QUERY subcommand was given invalid arguments: + + None of the specified CODEMODEL versions is supported by this version of CMake\. + None of the specified CACHE versions is supported by this version of CMake\. diff --git a/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake b/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake new file mode 100644 index 0000000..3a06105 --- /dev/null +++ b/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake @@ -0,0 +1,42 @@ +# All of these should fail. Execution does continue though, so we should see +# the error output from each one. There is no observable effect of the command +# during the configure phase, so it isn't critical to end processing on the +# first failure. Allowing execution to proceed may allow the project to see +# other potential errors before ultimately halting. That behavior is generally +# desirable, and the multiple failing calls here will confirm that we retain +# that behavior. + +message(NOTICE "Non-query check") +cmake_file_api(NOT_A_QUERY) + +message(NOTICE "Invalid API version checks") +cmake_file_api(QUERY API_VERSION 2) +cmake_file_api(QUERY API_VERSION nah) + +message(NOTICE "Invalid version numbers check") +cmake_file_api( + QUERY + API_VERSION 1 + CODEMODEL nope + CACHE -2 + CMAKEFILES .8 + TOOLCHAINS 2 0.1 +) + +message(NOTICE "Requested versions too high check") +cmake_file_api( + QUERY + API_VERSION 1 + CODEMODEL 3 + CACHE 3 + CMAKEFILES 2 + TOOLCHAINS 1.1 +) + +message(NOTICE "Requested versions too low check") +cmake_file_api( + QUERY + API_VERSION 1 + CODEMODEL 1 + CACHE 1 +) diff --git a/Tests/RunCMake/FileAPI/ProjectQueryGood-check.cmake b/Tests/RunCMake/FileAPI/ProjectQueryGood-check.cmake new file mode 100644 index 0000000..46d3f77 --- /dev/null +++ b/Tests/RunCMake/FileAPI/ProjectQueryGood-check.cmake @@ -0,0 +1,11 @@ +set(expect + reply + reply/cache-v2-[0-9a-f]+.json + reply/cmakeFiles-v1-[0-9a-f]+.json + reply/codemodel-v2-[0-9a-f]+.json + .*reply/index-[0-9.T-]+.json + .*reply/toolchains-v1-[0-9a-f]+.json +) + +# Only need to check for existence. Other tests check the reply contents. +check_api("^${expect}$") diff --git a/Tests/RunCMake/FileAPI/ProjectQueryGood.cmake b/Tests/RunCMake/FileAPI/ProjectQueryGood.cmake new file mode 100644 index 0000000..da0f3ce --- /dev/null +++ b/Tests/RunCMake/FileAPI/ProjectQueryGood.cmake @@ -0,0 +1,8 @@ +cmake_file_api( + QUERY + API_VERSION 1 + CODEMODEL 3 2.1 + CACHE 3.2 2 + CMAKEFILES 3 2 1.0 + TOOLCHAINS 3 2 1 +) diff --git a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake index c768d18..81926af 100644 --- a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake +++ b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake @@ -52,6 +52,8 @@ run_cmake(ClientStateless) run_cmake(MixedStateless) run_cmake(DuplicateStateless) run_cmake(ClientStateful) +run_cmake(ProjectQueryGood) +run_cmake(ProjectQueryBad) function(run_object object) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${object}-build) |