Presets: Generate diagnostics schema from header

Modify the script that generates the CMake presets schema to extract the
list of available diagnostics from the `cmDiagnostics.h` header and
generate the relevant properties. This information is combined with a
partial set that remains in the YAML for those diagnostics whose
definitions differ from what is easily generated. While these special
cases will remain, the addition of new diagnostics should no longer
require manual updates to the YAML. (The presets version log will still
require manual updates.)
This commit is contained in:
Matthew Woehlke
2026-04-15 14:42:17 -04:00
parent ef3b00dbbb
commit d56f9718ac
5 changed files with 164 additions and 46 deletions
+3 -6
View File
@@ -6,12 +6,9 @@
``author``
.. presets-versionadded:: 12
An optional boolean. Equivalent to passing
:cmake-option:`-Werror=author` or
:cmake-option:`-Wno-error=author`
on the command line.
This may not be set to ``true``
if ``warnings.author`` is set to ``false``.
An optional boolean. Equivalent to passing :cmake-option:`-Werror=author` or
:cmake-option:`-Wno-error=author` on the command line.
This may not be set to ``true`` if ``warnings.author`` is set to ``false``.
.. _`CMakePresets.configurePresets.errors.deprecated`:
+7 -32
View File
@@ -657,21 +657,9 @@ properties:
.. include:: presets/warnings-properties.rst
properties:
# These should be in lexicographical order, except for systemVars.
author:
since: 12
type: boolean
description:
An optional boolean. Equivalent to passing -Wauthor or
-Wno-author on the command line. This may not be set
to false if errors.author is set to true.
sphinxDescription: |
An optional boolean. Equivalent to passing
:option:`-Wauthor <cmake -W>` or
:option:`-Wno-author <cmake -Wno->`
on the command line.
This may not be set to ``false``
if ``errors.author`` is set to ``true``.
# This should ONLY have diagnostics whose definitions require
# special handling (i.e. anything from preset version < 12). Others
# will be generated from cmDiagnostics.h.
deprecated:
type: boolean
description:
@@ -725,8 +713,8 @@ properties:
on the command line.
This may not be set to ``false``
if ``errors.unusedCli`` is set to ``true``.
# systemVars should be last.
systemVars:
sort: false
type: boolean
description:
An optional boolean. Setting this to true is equivalent to
@@ -746,22 +734,9 @@ properties:
.. include:: presets/errors-properties.rst
properties:
# These should be in lexicographical order.
author:
since: 12
type: boolean
description:
An optional boolean. Equivalent to passing
-Werror=author or -Wno-error=author
on the command line. This may not be set
to true if warnings.author is set to false.
sphinxDescription: |
An optional boolean. Equivalent to passing
:cmake-option:`-Werror=author` or
:cmake-option:`-Wno-error=author`
on the command line.
This may not be set to ``true``
if ``warnings.author`` is set to ``false``.
# This should ONLY have diagnostics whose definitions require
# special handling (i.e. anything from preset version < 12). Others
# will be generated from cmDiagnostics.h.
deprecated:
type: boolean
description:
+3 -6
View File
@@ -6,12 +6,9 @@
``author``
.. presets-versionadded:: 12
An optional boolean. Equivalent to passing
:option:`-Wauthor <cmake -W>` or
:option:`-Wno-author <cmake -Wno->`
on the command line.
This may not be set to ``false``
if ``errors.author`` is set to ``true``.
An optional boolean. Equivalent to passing :option:`-Wauthor <cmake -W>` or
:option:`-Wno-author <cmake -Wno->` on the command line.
This may not be set to ``false`` if ``errors.author`` is set to ``true``.
.. _`CMakePresets.configurePresets.warnings.deprecated`:
+5 -2
View File
@@ -16,11 +16,14 @@
// IWYU pragma: no_forward_declare cmDiagnostics::DiagnosticCategory
// The list of diagnostic categories along with their associated data.
// Each entry is of the form `SELECT(ACTION, <default>, <parent>, <name>)`.
// Each entry is of the form:
// `SELECT(ACTION, <default>, <parent>, <name>, <preset-version>)`
// Entries MUST appear in the order that a depth-first enumeration would
// produce.
//
// When this changes, Help/manual/presets/schema.json must also be updated.
// When changing this table, Utilities/Scripts/regenerate-presets.py must be
// run to update the JSON schema for CMake presets. If the _TABLE macro name
// changes, said script will also need to be updated with the new name.
#define CM_FOR_EACH_DIAGNOSTIC_TABLE(ACTION, SELECT) \
SELECT(ACTION, Warn, CMD_NONE, CMD_AUTHOR, 12) \
+146
View File
@@ -14,7 +14,9 @@ Usage: python3 regenerate-presets.py
"""
import json
import re
from copy import deepcopy
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Self
@@ -30,12 +32,45 @@ SCHEMA_JSON_FILENAME = 'schema.json'
WORKSPACE = Path(__file__).parent.parent.parent.absolute()
PRESETS = WORKSPACE / PRESETS_REL
DIAGNOSTICS = WORKSPACE / 'Source/cmDiagnostics.h'
RST_BANNER = f"""\
.. This file was generated by {Path(__file__).relative_to(WORKSPACE)}
from {PRESETS_REL}/{SCHEMA_YAML_FILENAME}. Do not edit.
"""
DIAGNOSTIC_TABLE_MACRO = 'CM_FOR_EACH_DIAGNOSTIC_TABLE'
CONFIGURE_PRESET_PROPERTIES_PATH = (
'properties', 'configurePresets', 'items', 'properties'
)
WARNING_DESCRIPTION = (
'An optional boolean. '
'Equivalent to passing -W{c} or -Wno-{c} on the command line. '
'This may not be set to false if errors.{c} is set to true.'
)
WARNING_SPHINX_DESCRIPTION = """
An optional boolean. Equivalent to passing :option:`-W{c} <cmake -W>` or
:option:`-Wno-{c} <cmake -Wno->` on the command line.
This may not be set to ``false`` if ``errors.{c}`` is set to ``true``.
""".strip()
ERROR_DESCRIPTION = (
'An optional boolean. '
'Equivalent to passing -Werror={c} or -Wno-error={c} on the command line. '
'This may not be set to true if warnings.{c} is set to false.'
)
ERROR_SPHINX_DESCRIPTION = """
An optional boolean. Equivalent to passing :cmake-option:`-Werror={c}` or
:cmake-option:`-Wno-error={c}` on the command line.
This may not be set to ``true`` if ``warnings.{c}`` is set to ``false``.
""".strip()
# =============================================================================
@dataclass
class Diagnostic:
name: str
since: int
# =============================================================================
class Value:
@@ -596,6 +631,105 @@ def trimDescriptions(data: Any):
return data
# -----------------------------------------------------------------------------
def getPath(data: Any, path: tuple[str]) -> Any:
if len(path) == 0:
return data
return getPath(data[path[0]], path[1:])
# -----------------------------------------------------------------------------
def diagnosticPresetName(symbol: str) -> str:
sep = False
out = ''
for c in symbol[4:]:
if c == '_':
sep = True
elif sep:
out += c
sep = False
else:
out += c.lower()
return out
# -----------------------------------------------------------------------------
def buildDiagnosticsSchema(
diagnostics: list[Diagnostic],
descriptionTemplate: str,
sphinxDescriptionTemplate: str,
) -> dict[str, dict[str, Any]]:
out = {}
for d in diagnostics:
out[d.name] = {
'since': d.since,
'type': 'boolean',
'description': descriptionTemplate.format(c=d.name),
'sphinxDescription': sphinxDescriptionTemplate.format(c=d.name),
}
return out
# -----------------------------------------------------------------------------
def mergeDiagnostics(
generated: dict[str, dict[str, Any]],
extra: dict[str, dict[str, Any]],
) -> dict[str, dict[str, Any]]:
# Get unsorted names.
tail = {}
for k in list(extra.keys()):
s = extra[k].pop('sort', True)
if not s:
tail[k] = extra.pop(k)
# Combine inputs and sort.
out = {}
combined = deepcopy(generated)
combined.update(extra)
for k in sorted(combined.keys()):
out[k] = combined[k]
# Add unsorted items and return result.
out.update(tail)
return out
# -----------------------------------------------------------------------------
def readDiagnostics(path: Path | str) -> list[Diagnostic]:
content = ''
# Extract diagnostics table from header.
with open(path, 'r') as f:
extracting = False
for line in f:
if DIAGNOSTIC_TABLE_MACRO in line:
extracting = True
continue
if extracting:
content += line.replace('\\\n', ' ')
if not line.strip().endswith('\\'):
break
out = []
while True:
m = re.match(r'\s*SELECT[(]([^)]+)[)]', content)
if m is None:
break
args = [a.strip() for a in m.group(1).split(',')]
n = diagnosticPresetName(args[3])
v = int(args[4])
if v > 1:
out.append(Diagnostic(n, v))
content = content[m.span()[1]:]
return out
# -----------------------------------------------------------------------------
def main():
# Read the schema definition.
@@ -611,6 +745,18 @@ def main():
# Remove whitespace around descriptions.
schema = trimDescriptions(schema)
# Read diagnostics and update schema.
diagnostics = readDiagnostics(DIAGNOSTICS)
configureSchema = getPath(schema, CONFIGURE_PRESET_PROPERTIES_PATH)
configureSchema['warnings']['properties'] = mergeDiagnostics(
buildDiagnosticsSchema(diagnostics, WARNING_DESCRIPTION,
WARNING_SPHINX_DESCRIPTION),
configureSchema['warnings']['properties'])
configureSchema['errors']['properties'] = mergeDiagnostics(
buildDiagnosticsSchema(diagnostics, ERROR_DESCRIPTION,
ERROR_SPHINX_DESCRIPTION),
configureSchema['errors']['properties'])
# Extract global type definitions.
global TYPES, REFS