Hi gcc-patches mailing list,
Richard Earnshaw via Sourceware Forge 
<[email protected]> has requested that the 
following forgejo pull request
be published on the mailing list.

Created on: 2026-05-29 15:13:48+00:00
Latest update: 2026-07-03 13:58:05+00:00
Changes: 6 changed files, 6927 additions, 11 deletions
Head revision: rearnsha/gcc-TEST ref MAINT-yaml commit 
8455cd53d3b1a8e7c1b64ac8c64b864521221c6b
Base revision: gcc/gcc-TEST ref trunk commit 
395e3d8131c189cd58e8c8061cdc77d1c44e3822 r17-2112-g395e3d8131c189
Merge base: 395e3d8131c189cd58e8c8061cdc77d1c44e3822
Full diff url: https://forge.sourceware.org/gcc/gcc-TEST/pulls/163.diff
Discussion:  https://forge.sourceware.org/gcc/gcc-TEST/pulls/163
Requested Reviewers: rdfm

convert MAINTAINERS data to YAML.

For the forge to be able to notify maintainers efficiently when a
merge request is submitted, I need to be able to automate assigning
reviewers as well as just labels.  The information needed for this is
largely in the MAINTAINERS file, but not in a format that is readily
machine readable: it can be scraped, but it's a bit of an ad-hoc
process and not something I'd want to maintain in the long term.

To mitigate that, and to make it possible to augment this information
with additional details, I've been experimenting with converting the
raw data into YAML and then having a script build the official
MAINTAINERS file form that.  The results can be seen in the following
patch.  The results so far are /almost/ identical with only a limited
set of differences, some of which can probably be resolved later on.

- the free-form entries saying All XXX maintainers have been expanded
  into explicit lists; I really don't think it helps much to have yet
  another level of indirection to deal with here and in particular it
  complicates regenerating the MAINTAINERS file as the template rules
  would need to be handled specifically.

- I've found some small sort-order differences; these could be easily
  rectified, but I think the sort order I'm using here
  (case-independent sort) is preferable to a case-dependent one: Lac
  should be sorted before LaD.  It would be better, I think to tweak
  check-MAINTAINERS.py to prefer this new order, but that file becomes
  redundant if this code goes in.

- I might have over-merged the entries for Feng Wang.  That's easily
  fixable in the MAINTAINERS.yml data, but is an example of the kind
  of issue that I've faced with creating the machine-readable data
  from the raw files.

Finally, the name for Naveen Gowda has changed in the DCO list.  The
email address used appears elsewhere in the MAINTAINERS file with a
slightly different name.  This could be fixed with a small extension
to the YAML data (the DCO entry could take an optional name), if it
really matters.

One additional field that I've added is a marker for what I consider
to be inactive accounts.  The forge is likely to be more aggressive in
emailing developers and I don't want it to be forever mailing people
who have long ceased to contribute to the project.  For now I've
scraped the GCC commit logs and posts to [email protected] and
[email protected]; anybody who has not contributed to one of those
sources in the last two years has been marked as inactive.  The
generator script has an option (-a) to build a version of the
MAINTAINERS file with inactive users removed: we have a lot of retired
devs!

Changes since V1:
- Added a schema and additional checks to validate the yaml data
- Added a simple script to create a Write-After entry
- Split the edits to the base conversion of the original
  MAINTAINERS data into separate commits.  This is just to
  make things easier in the case I need to re-run the conversion
  and will be collapsed before the final commit

Changes since V2:
- Improve python formatting, and general cleanups
- Incorporate recent changes from the existing MAINTAINERS data

Unless there are some new objections, I think this is now ready to be merged.
---

Thanks for taking the time to contribute to GCC!

Please be advised that https://forge.sourceware.org/ is currently a trial
that is being used by the GCC community to experiment with a new workflow
based on pull requests.

Pull requests sent here may be forgotten or ignored. Patches that you want to
propose for inclusion in GCC should use the existing email-based workflow,
see https://gcc.gnu.org/contribute.html


Changed files:
- A: MAINTAINERS.yml
- A: contrib/add-write-after.py
- A: contrib/gen-MAINTAINERS.py
- A: contrib/maintainer_utils.py
- M: .editorconfig
- M: MAINTAINERS


Richard Earnshaw (8):
  editorconfig: Add rules for yaml/yml files.
  MAINTAINERS: scripts to generate the file from yaml data
  MAINTAINERS.yml: Correct Martin Liška's name
  MAINTAINERS.yml: Restore James Norris' email address
  MAINTAINERS.yml: Handle libgrust explicitly
  MAINTAINERS.yml: Add libcpp to all C and C++ maintainer's roles
  MAINTAINERS: generate from MAINTAINERS.yml
  MAINTAINERS: Add a script to create a new entry in the mainainers data

 .editorconfig               |    6 +
 MAINTAINERS                 |   27 +-
 MAINTAINERS.yml             | 6148 +++++++++++++++++++++++++++++++++++
 contrib/add-write-after.py  |  150 +
 contrib/gen-MAINTAINERS.py  |  319 ++
 contrib/maintainer_utils.py |  288 ++
 6 files changed, 6927 insertions(+), 11 deletions(-)
 create mode 100644 MAINTAINERS.yml
 create mode 100755 contrib/add-write-after.py
 create mode 100755 contrib/gen-MAINTAINERS.py
 create mode 100755 contrib/maintainer_utils.py

Range-diff against v2:
1:  44a93c99c840 = 1:  2e1dcddc702b editorconfig: Add rules for yaml/yml files.
2:  68bc1749d8be ! 2:  48e835e9eef2 MAINTAINERS: scripts to generate the file 
from yaml data
    @@ MAINTAINERS.yml (new)
     +  roles:
     +  - WriteAfter
     +  account: pheeck
    ++- sn: Kaushik
    ++  cn: Abhishek Kaushik
    ++  email:
    ++  - [email protected]
    ++  - [email protected]
    ++  roles:
    ++  - WriteAfter
    ++  account: abhkau01
     +- sn: Keating
     +  cn: Geoffrey Keating
     +  email:
    @@ MAINTAINERS.yml (new)
     +- sn: Rozenfeld
     +  cn: Eugene Rozenfeld
     +  email:
    -+  - [email protected]
    ++  - [email protected]
     +  - [email protected]
    ++  - [email protected]
     +  roles:
     +  - Maintainer: AutoFDO
     +  - WriteAfter
    @@ MAINTAINERS.yml (new)
     +  cn: Martin Uecker
     +  email:
     +  - [email protected]
    ++  - [email protected]
     +  roles:
     +  - WriteAfter
     +  - DCO: [email protected]
    @@ MAINTAINERS.yml (new)
     +  - Maintainer: LoongArch port
     +  - WriteAfter: [email protected]
     +  account: paulhua
    -+  inactive: true
     +- sn: Xu
     +  cn: Li Xu
     +  email:
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[cn]:{w[cn]}} <{u[email]}>",
     +        'widths': [('cn', 47)],
    -+        'filter': {'role': "Global", 'order': ['sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Global",
    ++            'order': ['sn', 'cn', 'email'],
    ++        },
     +        'posttext': """
     +Note that while global reviewers can approve changes to any part of
     +the compiler or associated libraries, they still need approval for
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[subsystem]:{w[subsystem]}} {u[cn]:{w[cn]}} 
<{u[email]}>",
     +        'widths': [('subsystem', 23), ('cn', 23)],
    -+        'filter': {'role': "Maintainer", 'subclass': "CPU",
    -+                   'order': ['subsystem', 'sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Maintainer",
    ++            'subclass': "CPU",
    ++            'order': ['subsystem', 'sn', 'cn', 'email'],
    ++        },
     +    },
     +    'os': {
     +        'preamble': """
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[subsystem]:{w[subsystem]}} {u[cn]:{w[cn]}} 
<{u[email]}>",
     +        'widths': [('subsystem', 23), ('cn', 23)],
    -+        'filter': {'role': "Maintainer", 'subclass': "OS",
    -+                   'order': ['subsystem', 'sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Maintainer",
    ++            'subclass': "OS",
    ++            'order': ['subsystem', 'sn', 'cn', 'email'],
    ++        },
     +    },
     +    'lang': {
     +        'preamble': """
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[subsystem]:{w[subsystem]}} {u[cn]:{w[cn]}} 
<{u[email]}>",
     +        'widths': [('subsystem', 23), ('cn', 23)],
    -+        'filter': {'role': "Maintainer", 'subclass': "Lang",
    -+                   'order': ['subsystem', 'sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Maintainer",
    ++            'subclass': "Lang",
    ++            'order': ['subsystem', 'sn', 'cn', 'email'],
    ++        },
     +    },
     +    'various': {
     +        'preamble': """
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[subsystem]:{w[subsystem]}}  {u[cn]:{w[cn]}} 
<{u[email]}>",
     +        'widths': [('subsystem', 22), ('cn', 23)],
    -+        'filter': {'role': "Maintainer", 'subclass': "Subsystem",
    -+                   'order': ['subsystem', 'sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Maintainer",
    ++            'subclass': "Subsystem",
    ++            'order': ['subsystem', 'sn', 'cn', 'email'],
    ++        },
     +        'posttext': """
     +Note that individuals who maintain parts of the compiler need approval to
     +check in changes outside of the parts of the compiler they maintain.""",
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[subsystem]:{w[subsystem]}}  {u[cn]:{w[cn]}} 
<{u[email]}>",
     +        'widths': [('subsystem', 22), ('cn', 23)],
    -+        'filter': {'role': "Reviewer",
    -+                   'order': ['subsystem', 'sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "Reviewer",
    ++            'order': ['subsystem', 'sn', 'cn', 'email'],
    ++        },
     +        'posttext': """
     +Note that while reviewers can approve changes to parts of the compiler
     +that they maintain, they still need approval for their own patches
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[cn]:{w[cn]}} {u[account]:{w[account]}} 
<{u[email]}>",
     +        'widths': [('cn', 31), ('account', 15)],
    -+        'filter': {'role': "WriteAfter",
    -+                   'order': ['sn', 'cn', 'account']},
    ++        'filter': {
    ++            'role': "WriteAfter",
    ++            'order': ['sn', 'cn', 'account'],
    ++        },
     +    },
     +    'bz': {
     +        'preamble': """
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[cn]:{w[cn]}} <{u[email]}>",
     +        'widths': [('cn', 47)],
    -+        'filter': {'role': "BZ",
    -+                   'order': ['sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "BZ",
    ++            'order': ['sn', 'cn', 'email'],
    ++        },
     +    },
     +    'dco': {
     +        'preamble': """
    @@ contrib/gen-MAINTAINERS.py (new)
     +""",
     +        'template': "{u[cn]:{w[cn]}} <{u[email]}>",
     +        'widths': [('cn', 47)],
    -+        'filter': {'role': "DCO",
    -+                   'order': ['sn', 'cn', 'email']},
    ++        'filter': {
    ++            'role': "DCO",
    ++            'order': ['sn', 'cn', 'email'],
    ++        },
     +    },
     +}
     +
    ++
     +def debug(msg):
     +    global verbose
     +    if verbose:
     +        print(msg, file=sys.stderr)
     +    return
     +
    -+def mylower(txt):
    ++
    ++def uni2alower(txt):
    ++    """Translate unicode text into its closest lower-case ASCII."""
     +    return unidecode.unidecode(txt).lower()
     +
    ++
     +def format_output(outfile, data):
     +    global active_only
     +    all_users = data['users']
     +    if active_only:
    -+        all_users = [u for u in filter(lambda x: not x.get('inactive', 
False),
    -+                                       data['users'])]
    ++        all_users = [
    ++            u
    ++            for u in filter(
    ++                lambda x: not x.get('inactive', False), data['users']
    ++            )
    ++        ]
     +    for section in boilerplate.values():
     +        if 'preamble' in section:
     +            print(section['preamble'], file=outfile)
    @@ contrib/gen-MAINTAINERS.py (new)
     +            for u in all_users:
     +                for r in filter(lambda x: role in x, u['roles']):
     +                    if subclass:
    -+                        sc = [s for s in filter(lambda x: x['name'] == 
r[role],
    -+                                                data['subsystems'])]
    ++                        sc = [
    ++                            s
    ++                            for s in filter(
    ++                                lambda x: x['name'] == r[role],
    ++                                data['subsystems'],
    ++                            )
    ++                        ]
     +                        if len(sc) > 1:
     +                            verbose(f"multiple matches for {r[role]}")
     +                        if not 'class' in sc[0] or sc[0]['class'] != 
subclass:
    @@ contrib/gen-MAINTAINERS.py (new)
     +                    d['w'] = w
     +                    l.append(d)
     +            kfn = itemgetter(*section['filter']['order'])
    -+            for u in sorted(l,
    -+                            key=lambda x: tuple(mylower(v) for v in 
kfn(x))):
    ++            for u in sorted(
    ++                l, key=lambda x: tuple(uni2alower(v) for v in kfn(x))
    ++            ):
     +                debug(str(u))
    -+                print (section['template'].format(u=u, w=u['w']), 
file=outfile)
    ++                print(section['template'].format(u=u, w=u['w']), 
file=outfile)
     +        if 'posttext' in section:
     +            print(section['posttext'], file=outfile)
     +    return
     +
    ++
     +def main():
     +    global opts, verbose, active_only
     +    optp = OptionParser(usage="Usage: %prog [<options>] user-data")
    -+    optp.add_option("-v", "--verbose", action="store_true",
    -+                    default=False, dest="verbose",
    -+                    help="Print some diagnostic messages")
    -+    optp.add_option("-a", "--active", action="store_true",
    -+                    default=False, dest="active_only",
    -+                    help="Omit inactive users")
    -+    optp.add_option("-o", "--output", metavar="FILE",
    -+                    default=None, dest="outfilename",
    -+                    help="Write to FILE instead of stdout")
    ++    optp.add_option(
    ++        "-v", "--verbose",
    ++        action='store_true',
    ++        default=False,
    ++        dest='verbose',
    ++        help="Print some diagnostic messages",
    ++    )
    ++    optp.add_option(
    ++        "-a", "--active",
    ++        action='store_true',
    ++        default=False,
    ++        dest='active_only',
    ++        help="Omit inactive users",
    ++    )
    ++    optp.add_option(
    ++        "-o", "--output",
    ++        metavar="FILE",
    ++        default=None,
    ++        dest='outfilename',
    ++        help="Write to FILE instead of stdout",
    ++    )
     +    opts, args = optp.parse_args()
     +    if len(args) != 1:
     +        optp.print_help()
    @@ contrib/gen-MAINTAINERS.py (new)
     +    format_output(out_file, data)
     +    return 0
     +
    ++
     +if __name__ == "__main__":
    -+    sys.exit (main())
    ++    sys.exit(main())
     
      ## contrib/maintainer_utils.py (new) ##
     @@
    @@ contrib/maintainer_utils.py (new)
     +import sys
     +import unidecode
     +import yaml
    ++
     +# Prefer the libYAML implementations if available.  Fall back to the
     +# pure python versions as a backup.
     +try:
    @@ contrib/maintainer_utils.py (new)
     +except:
     +    from yaml import Loader, Dumper
     +
    ++label_pattern = '[a-zA-Z][-+a-zA-Z0-9]*(/[a-zA-Z][-+a-zA-Z0-9]*)+'
    ++
     +maintainer_schema = {
     +    '$schema': 'https://json-schema.org/draft/2020-12/schema',
     +    'type': 'object',
    @@ contrib/maintainer_utils.py (new)
     +                                    },
     +                                    'additionalProperties': False,
     +                                    'oneOf': [
    -+                                        {'required': ["Maintainer"] },
    -+                                        {'required': ["Reviewer"] },
    ++                                        {'required': ["Maintainer"]},
    ++                                        {'required': ["Reviewer"]},
     +                                    ],
     +                                },
     +                            ],
    @@ contrib/maintainer_utils.py (new)
     +                    },
     +                    'inactive': {
     +                        'type': 'boolean',
    -+                    }
    ++                    },
     +                },
     +                'additionalProperties': False,
     +                'required': ['sn', 'cn', 'email', 'roles'],
    @@ contrib/maintainer_utils.py (new)
     +                        'items': {
     +                            'type': 'string',
     +                            'minlength': 3,
    -+                            'pattern': 
'[a-zA-Z][-+a-zA-Z0-9]*(/[a-zA-Z][-+a-zA-Z0-9]*)+',
    ++                            'pattern': label_pattern,
     +                        },
     +                    },
     +                },
    @@ contrib/maintainer_utils.py (new)
     +error_count = 0
     +warning_count = 0
     +
    -+def _fatal (msg):
    -+    print (f"fatal: {msg}", file=sys.stderr)
    -+    sys.exit (1)
     +
    -+def _error (msg):
    ++def _fatal(msg):
    ++    print(f"fatal: {msg}", file=sys.stderr)
    ++    sys.exit(1)
    ++
    ++
    ++def _error(msg):
     +    global error_count
    -+    print (f"error: {msg}", file=sys.stderr)
    ++    print(f"error: {msg}", file=sys.stderr)
     +    error_count += 1
     +
    -+def _warning (msg):
    ++
    ++def _warning(msg):
     +    global warning_count
    -+    print (f"warning: {msg}", file=sys.stderr)
    ++    print(f"warning: {msg}", file=sys.stderr)
     +    warning_count += 1
     +
    -+def _format_path (path, data):
    ++
    ++def _format_path(path, data):
     +    """Try to turn a validation error path into something more readable"""
     +    elts = [""]
     +    cur = data
     +    try:
     +        for elt in path:
    -+            if isinstance (cur, list) and isinstance (elt, int):
    ++            if isinstance(cur, list) and isinstance(elt, int):
     +                item = cur[elt]
    -+                if isinstance (item, dict) and 'cn' in item:
    ++                if isinstance(item, dict) and 'cn' in item:
     +                    elts[-1] += f"[cn = {item['cn']}]"
    -+                elif isinstance (item, dict) and 'name' in item:
    ++                elif isinstance(item, dict) and 'name' in item:
     +                    elts[-1] += f"[name = {item['name']}]"
     +                else:
     +                    elts[-1] += f"[(anon index): {elt}]"
     +                cur = item
     +            else:
     +                cur = cur[elt]
    -+                elts.append (str (elt))
    ++                elts.append(str(elt))
     +    except Exception as e:
    -+        print ('/'.join(elts))
    ++        print('/'.join(elts))
     +        raise e
     +    if elts[0] == "":
     +        elts = elts[1:]
     +    return '/'.join(elts)
     +
    ++
     +def _check_schema(data):
     +    try:
     +        schema = 
jsonschema.validators.Draft202012Validator(maintainer_schema)
     +        errors = sorted(schema.iter_errors(data), key=lambda e: e.path)
     +        for err in errors:
    -+            _error (f"{_format_path (err.path, data)}: {err.message}")
    ++            _error(f"{_format_path(err.path, data)}: {err.message}")
     +    except jsonschema.exceptions.SchemaError as e:
    -+        _fatal (str (e))
    ++        _fatal(str(e))
     +    return
     +
    ++
     +def validate(data):
     +    """Check the data against the schema and our own consistency checks"""
     +    _check_schema(data)
    @@ contrib/maintainer_utils.py (new)
     +    for u in data['users']:
     +        # Users with the 'BZ' role should not have any other roles; we
     +        # can quickly skip the additional checks if that is the case.
    -+        if (len(u['roles']) == 1
    -+            and u['roles'][0] == 'BZ'):
    ++        if len(u['roles']) == 1 and u['roles'][0] == 'BZ':
     +            continue
     +        seen_writeafter = False
     +        only_dco = True
     +        for r in u['roles']:
    -+            if isinstance (r, str):
    ++            if isinstance(r, str):
     +                if r == 'BZ':
     +                    _error(
    -+                        f"User '{u['cn']}' has 'BZ' role as well as 
others.")
    ++                        f"User '{u['cn']}' has 'BZ' role as well as 
others."
    ++                    )
     +                if r == 'WriteAfter':
     +                    seen_writeafter = True
     +                only_dco = False
    @@ contrib/maintainer_utils.py (new)
     +                need_class = False
     +                n = r.get('Reviewer')
     +            if n:
    -+                subsystems = list(filter (lambda s: s['name'] == n,
    -+                                          data['subsystems']))
    ++                subsystems = list(
    ++                    filter(lambda s: s['name'] == n, data['subsystems'])
    ++                )
     +                if len(subsystems) == 1:
    -+                    if (need_class
    -+                        and not 'class' in subsystems[0]):
    -+                        _error (f"subsystem '{n}' missing a class.")
    ++                    if need_class and not 'class' in subsystems[0]:
    ++                        _error(f"subsystem '{n}' missing a class.")
     +                elif len(subsystems) == 0:
    -+                    _error (f"No subsystem entry for '{n}'.")
    ++                    _error(f"No subsystem entry for '{n}'.")
     +                else:
    -+                    _error (f"Multiple subsystem entries for '{n}'.")
    ++                    _error(f"Multiple subsystem entries for '{n}'.")
     +        if not seen_writeafter and not only_dco:
     +            _error(f"User '{u['cn']}' lacks WriteAfter role.")
     +    if error_count:
     +        sys.exit(1)
     +    return
     +
    ++
     +def load(file):
     +    with open(file, "r", encoding="utf-8") as f:
    -+        data = yaml.load (f, Loader=Loader)
    ++        data = yaml.load(f, Loader=Loader)
     +    return data
     +
    ++
     +def main():
     +    if len(sys.argv) != 2:
     +        print(f"Usage: {sys.argv[0]} path-to-MAINTAINERS.yml")
     +        return 1
    -+    validate (load (sys.argv[1]))
    ++    validate(load(sys.argv[1]))
     +    return 0
     +
    ++
     +if __name__ == "__main__":
     +    sys.exit(main())
3:  c859279b9ca2 = 3:  bdc86f883e9b MAINTAINERS.yml: Correct Martin Liška's name
4:  0d6e37c81933 = 4:  4933bea58a24 MAINTAINERS.yml: Restore James Norris' 
email address
5:  051566a8c9b5 = 5:  390d1ffc1fe1 MAINTAINERS.yml: Handle libgrust explicitly
6:  7db00e1ea371 = 6:  8a579eb09b9d MAINTAINERS.yml: Add libcpp to all C and 
C++ maintainer's roles
7:  67cda742b456 = 7:  3bbbfbfacca6 MAINTAINERS: generate from MAINTAINERS.yml
8:  65fb98fc5dca = 8:  8455cd53d3b1 MAINTAINERS: Add a script to create a new 
entry in the mainainers data
-- 
2.54.0

Reply via email to