From 71a4e34d9765f33003068085fc72201fecae6300 Mon Sep 17 00:00:00 2001 From: Christian Walther Date: Sat, 22 Nov 2025 08:18:39 +1100 Subject: [PATCH] fileAPI: Expose CMAKE__COMPILER_ARG1 Compiler arguments coming from CC environment variables or multi-element CMAKE__COMPILER variables set by toolchain files were previously not exposed in the file API. Among other possible problems, this caused clients to determine wrong system include paths and built-in preprocessor macros by calling the compiler without these important arguments. This is fixed by adding an optional "commandFragment" attribute to the compiler description in the `toolchains` object, containing these arguments as a command line fragment. This is already the form in which they are internally stored in the CMAKE__COMPILER_ARG1 variable, so all that is required is adding this variable to the set of exported variables, besides some logic to omit it if empty. Issue: #22568 --- Help/manual/cmake-file-api.7.rst | 13 ++- Help/manual/file_api/schema_toolchains.json | 84 ++++++++++++++++++- Source/cmFileAPI.cxx | 2 +- Source/cmFileAPIToolchains.cxx | 34 ++++---- .../CommandLine/E_capabilities-stdout.txt | 2 +- Tests/RunCMake/FileAPI/ProjectQueryBad.cmake | 2 +- Tests/RunCMake/FileAPI/RunCMakeTest.cmake | 4 +- Tests/RunCMake/FileAPI/toolchains-v1-check.py | 17 +++- Tests/RunCMake/FileAPI/toolchains-v1.cmake | 18 +++- 9 files changed, 149 insertions(+), 27 deletions(-) diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst index e892ed1832..d68b6a64c8 100644 --- a/Help/manual/cmake-file-api.7.rst +++ b/Help/manual/cmake-file-api.7.rst @@ -2109,12 +2109,13 @@ There is only one ``toolchains`` object major version, version 1. { "kind": "toolchains", - "version": { "major": 1, "minor": 0 }, + "version": { "major": 1, "minor": 1 }, "toolchains": [ { "language": "C", "compiler": { "path": "/usr/bin/cc", + "commandFragment": "--config x86_64-linux-gnu.cfg", "id": "GNU", "version": "9.3.0", "implicit": { @@ -2193,6 +2194,16 @@ The members specific to ``toolchains`` objects are: :variable:`CMAKE__COMPILER` variable is defined for the current language. Its value is a JSON string holding the path to the compiler. + ``commandFragment`` + Optional member that is present when the + :variable:`CMAKE__COMPILER` variable is a list containing multiple + elements or the :envvar:`CC` or similar environment variable contains + command line arguments after the compiler executable. + Its value is a JSON string holding the second and further elements + (mandatory arguments to the compiler) as a command line fragment. + + This field was added in toolchains version 1.1. + ``id`` Optional member that is present when the :variable:`CMAKE__COMPILER_ID` variable is defined for the current diff --git a/Help/manual/file_api/schema_toolchains.json b/Help/manual/file_api/schema_toolchains.json index 46c716225c..d82e439422 100644 --- a/Help/manual/file_api/schema_toolchains.json +++ b/Help/manual/file_api/schema_toolchains.json @@ -10,6 +10,9 @@ "oneOf": [ { "$ref": "#/definitions/toolchainsObjV1_0" + }, + { + "$ref": "#/definitions/toolchainsObjV1_1" } ], "definitions": { @@ -36,6 +39,24 @@ }, "additionalProperties": false }, + "versionV1_1": { + "type": "object", + "required": [ + "major", + "minor" + ], + "properties": { + "major": { + "type": "integer", + "const": 1 + }, + "minor": { + "type": "integer", + "const": 1 + } + }, + "additionalProperties": false + }, "language": { "type": "string", "description": "Toolchain language identifier (e.g. C, CXX)" @@ -101,7 +122,7 @@ }, "additionalProperties": false }, - "compiler": { + "compilerV1_0": { "type": "object", "properties": { "path": { @@ -122,6 +143,30 @@ }, "additionalProperties": false }, + "compilerV1_1": { + "type": "object", + "properties": { + "path": { + "$ref": "#/definitions/compilerPath" + }, + "commandFragment": { + "$ref": "#/definitions/compilerCommandFragment" + }, + "id": { + "$ref": "#/definitions/compilerId" + }, + "version": { + "$ref": "#/definitions/compilerVersion" + }, + "target": { + "$ref": "#/definitions/compilerTarget" + }, + "implicit": { + "$ref": "#/definitions/compilerImplicit" + } + }, + "additionalProperties": false + }, "toolchainsObjV1_0": { "type": "object", "properties": { @@ -145,7 +190,42 @@ "$ref": "#/definitions/language" }, "compiler": { - "$ref": "#/definitions/compiler" + "$ref": "#/definitions/compilerV1_0" + }, + "sourceFileExtensions": { + "$ref": "#/definitions/sourceFileExtensions" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "toolchainsObjV1_1": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/definitions/kind" + }, + "version": { + "$ref": "#/definitions/versionV1_1" + }, + "toolchains": { + "type": "array", + "description": "Array of toolchain configurations per language", + "items": { + "type": "object", + "required": [ + "language", + "compiler" + ], + "properties": { + "language": { + "$ref": "#/definitions/language" + }, + "compiler": { + "$ref": "#/definitions/compilerV1_1" }, "sourceFileExtensions": { "$ref": "#/definitions/sourceFileExtensions" diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx index 216b1c6c05..78575e1b90 100644 --- a/Source/cmFileAPI.cxx +++ b/Source/cmFileAPI.cxx @@ -959,7 +959,7 @@ Json::Value cmFileAPI::BuildCMakeFiles(Object object) // The "toolchains" object kind. -static unsigned int const ToolchainsV1Minor = 0; +static unsigned int const ToolchainsV1Minor = 1; void cmFileAPI::BuildClientRequestToolchains( ClientRequest& r, std::vector const& versions) diff --git a/Source/cmFileAPIToolchains.cxx b/Source/cmFileAPIToolchains.cxx index c156ca164e..498bf359ca 100644 --- a/Source/cmFileAPIToolchains.cxx +++ b/Source/cmFileAPIToolchains.cxx @@ -24,6 +24,7 @@ struct ToolchainVariable std::string ObjectKey; std::string VariableSuffix; bool IsList; + bool OmitEmpty; }; class Toolchains @@ -74,22 +75,23 @@ Json::Value Toolchains::DumpToolchains() Json::Value Toolchains::DumpToolchain(std::string const& lang) { static std::vector const CompilerVariables{ - { "path", "COMPILER", false }, - { "id", "COMPILER_ID", false }, - { "version", "COMPILER_VERSION", false }, - { "target", "COMPILER_TARGET", false }, + { "path", "COMPILER", false, false }, + { "commandFragment", "COMPILER_ARG1", false, true }, + { "id", "COMPILER_ID", false, false }, + { "version", "COMPILER_VERSION", false, false }, + { "target", "COMPILER_TARGET", false, false }, }; static std::vector const CompilerImplicitVariables{ - { "includeDirectories", "IMPLICIT_INCLUDE_DIRECTORIES", true }, - { "linkDirectories", "IMPLICIT_LINK_DIRECTORIES", true }, - { "linkFrameworkDirectories", "IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", - true }, - { "linkLibraries", "IMPLICIT_LINK_LIBRARIES", true }, + { "includeDirectories", "IMPLICIT_INCLUDE_DIRECTORIES", true, false }, + { "linkDirectories", "IMPLICIT_LINK_DIRECTORIES", true, false }, + { "linkFrameworkDirectories", "IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", true, + false }, + { "linkLibraries", "IMPLICIT_LINK_LIBRARIES", true, false }, }; static ToolchainVariable const SourceFileExtensionsVariable{ - "sourceFileExtensions", "SOURCE_FILE_EXTENSIONS", true + "sourceFileExtensions", "SOURCE_FILE_EXTENSIONS", true, false }; auto const& mf = @@ -128,15 +130,17 @@ void Toolchains::DumpToolchainVariable(cmMakefile const* mf, cmValue data = mf->GetDefinition(variableName); if (data) { cmList values(data); - Json::Value jsonArray = Json::arrayValue; - for (auto const& value : values) { - jsonArray.append(value); + if (!variable.OmitEmpty || !values.empty()) { + Json::Value jsonArray = Json::arrayValue; + for (auto const& value : values) { + jsonArray.append(value); + } + object[variable.ObjectKey] = jsonArray; } - object[variable.ObjectKey] = jsonArray; } } else { cmValue def = mf->GetDefinition(variableName); - if (def) { + if (def && (!variable.OmitEmpty || !def.IsEmpty())) { object[variable.ObjectKey] = *def; } } diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt index f22ef61296..54daeccf26 100644 --- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt +++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt @@ -1 +1 @@ -^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":9}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":1}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$ +^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":9}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":1}]},{"kind":"toolchains","version":\[{"major":1,"minor":1}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$ diff --git a/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake b/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake index 3a06105d2a..f3aa4d3f19 100644 --- a/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake +++ b/Tests/RunCMake/FileAPI/ProjectQueryBad.cmake @@ -30,7 +30,7 @@ cmake_file_api( CODEMODEL 3 CACHE 3 CMAKEFILES 2 - TOOLCHAINS 1.1 + TOOLCHAINS 1.2 ) message(NOTICE "Requested versions too low check") diff --git a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake index 4783467507..a74cec99b2 100644 --- a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake +++ b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake @@ -146,7 +146,7 @@ run_cmake(FailConfigure) function(run_object object) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${object}-build) - list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0118=NEW) + list(APPEND RunCMake_TEST_OPTIONS ${ARGN} -DCMAKE_POLICY_DEFAULT_CMP0118=NEW) run_cmake(${object}) list(POP_BACK RunCMake_TEST_OPTIONS) set(RunCMake_TEST_NO_CLEAN 1) @@ -161,3 +161,5 @@ run_object(configureLog-v1) run_object(cache-v2) run_object(cmakeFiles-v1) run_object(toolchains-v1) +run_object(toolchains-v1 -DTOOLCHAINSV1_COMPILERARGS=1) +run_object(toolchains-v1 -DTOOLCHAINSV1_COMPILERARGS=2) diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-check.py b/Tests/RunCMake/FileAPI/toolchains-v1-check.py index ac1ef69332..11a723d7ba 100644 --- a/Tests/RunCMake/FileAPI/toolchains-v1-check.py +++ b/Tests/RunCMake/FileAPI/toolchains-v1-check.py @@ -2,17 +2,20 @@ from check_index import * import os class ExpectedVar(object): - def __init__(self, name): + def __init__(self, name, omitEmpty=False): self.name = name + self.omitEmpty = omitEmpty class ExpectedList(object): - def __init__(self, name): + def __init__(self, name, omitEmpty=False): self.name = name + self.omitEmpty = omitEmpty EXPECTED_TOOLCHAIN = { "language": "CXX", "compiler": { "path": ExpectedVar("CMAKE_CXX_COMPILER"), + "commandFragment": ExpectedVar("CMAKE_CXX_COMPILER_ARG1", omitEmpty=True), "id": ExpectedVar("CMAKE_CXX_COMPILER_ID"), "version": ExpectedVar("CMAKE_CXX_COMPILER_VERSION"), "target": ExpectedVar("CMAKE_CXX_COMPILER_TARGET"), @@ -35,7 +38,7 @@ EXPECTED_TOOLCHAIN = { def check_objects(o): assert is_list(o) assert len(o) == 1 - check_index_object(o[0], "toolchains", 1, 0, check_object_toolchains) + check_index_object(o[0], "toolchains", 1, 1, check_object_toolchains) def check_object_toolchains(o): assert sorted(o.keys()) == ["kind", "toolchains", "version"] @@ -59,7 +62,8 @@ def check_object_toolchain(o, expected): key for (key, value) in expected.items() if is_string(value) or is_dict(value) or (type(value) in (ExpectedVar, ExpectedList) - and variables[value.name]["defined"])] + and variables[value.name]["defined"] + and not (value.omitEmpty and variables[value.name]["value"] == ''))] assert sorted(o.keys()) == sorted(expected_keys), "actual object {!r}, expected keys {!r}".format(o, sorted(expected_keys)) for key in expected_keys: @@ -81,6 +85,11 @@ with open(os.path.join(args.build_dir, "toolchain_variables.json")) as f: variables = json.load(f) assert is_dict(variables) +if variables.get("TOOLCHAINSV1_COMPILERARGS", 0) == 1: + del EXPECTED_TOOLCHAIN["compiler"]["commandFragment"] +elif variables.get("TOOLCHAINSV1_COMPILERARGS", 0) == 2: + EXPECTED_TOOLCHAIN["compiler"]["commandFragment"] = "--hello world --something=other" + assert is_dict(index) assert sorted(index.keys()) == ["cmake", "objects", "reply"] check_objects(index["objects"]) diff --git a/Tests/RunCMake/FileAPI/toolchains-v1.cmake b/Tests/RunCMake/FileAPI/toolchains-v1.cmake index 1ad777dd29..fe986a6f0b 100644 --- a/Tests/RunCMake/FileAPI/toolchains-v1.cmake +++ b/Tests/RunCMake/FileAPI/toolchains-v1.cmake @@ -1,7 +1,22 @@ +# If testing with a specific CMAKE_CXX_COMPILER_ARG1 value is requested, skip +# any checks that try to actually compile anything, because the compiler +# probably wouldn't understand these arguments or lack thereof. +if(DEFINED TOOLCHAINSV1_COMPILERARGS) + if(TOOLCHAINSV1_COMPILERARGS EQUAL 1) + set(CMAKE_CXX_COMPILER_ARG1 "") + elseif(TOOLCHAINSV1_COMPILERARGS EQUAL 2) + set(CMAKE_CXX_COMPILER_ARG1 "--hello world --something=other") + endif() + set(CMAKE_CXX_COMPILER_WORKS 1) + set(CMAKE_CXX_ABI_COMPILED 1) +else() + set(TOOLCHAINSV1_COMPILERARGS 0) +endif() + enable_language(CXX) set(variable_suffixes - COMPILER COMPILER_ID COMPILER_VERSION COMPILER_TARGET + COMPILER COMPILER_ARG1 COMPILER_ID COMPILER_VERSION COMPILER_TARGET IMPLICIT_INCLUDE_DIRECTORIES IMPLICIT_LINK_DIRECTORIES IMPLICIT_LINK_FRAMEWORK_DIRECTORIES IMPLICIT_LINK_LIBRARIES SOURCE_FILE_EXTENSIONS) @@ -18,6 +33,7 @@ foreach(variable_suffix ${variable_suffixes}) string(JSON json SET "${json}" "${variable}" "defined" "false") endif() endforeach() +string(JSON json SET "${json}" "TOOLCHAINSV1_COMPILERARGS" "${TOOLCHAINSV1_COMPILERARGS}") file(WRITE ${CMAKE_BINARY_DIR}/toolchain_variables.json "${json}")