xref: /aosp_15_r20/external/pigweed/pw_presubmit/py/pw_presubmit/module_owners.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Ensure all modules have OWNERS files."""
15
16import logging
17from pathlib import Path
18from typing import Callable
19
20from pw_presubmit.presubmit_context import (
21    PresubmitContext,
22    PresubmitFailure,
23)
24from pw_presubmit import presubmit
25
26_LOG: logging.Logger = logging.getLogger(__name__)
27
28
29def upstream_pigweed_applicability(
30    ctx: PresubmitContext,
31    path: Path,
32) -> Path | None:
33    """Return a parent of path required to have an OWNERS file, or None."""
34    parts: tuple[str, ...] = path.relative_to(ctx.root).parts
35
36    if len(parts) >= 2 and parts[0].startswith('pw_'):
37        return ctx.root / parts[0]
38    if len(parts) >= 3 and parts[0] in ('targets', 'third_party'):
39        return ctx.root / parts[0] / parts[1]
40
41    return None
42
43
44ApplicabilityFunc = Callable[[PresubmitContext, Path], Path | None]
45
46
47def presubmit_check(
48    applicability: ApplicabilityFunc = upstream_pigweed_applicability,
49) -> presubmit.Check:
50    """Create a presubmit check for the presence of OWNERS files."""
51
52    @presubmit.check(name='module_owners')
53    def check(ctx: PresubmitContext) -> None:
54        """Presubmit check that ensures all modules have OWNERS files."""
55
56        modules_to_check = set()
57
58        for path in ctx.paths:
59            result = applicability(ctx, path)
60            if result:
61                modules_to_check.add(result)
62
63        errors = 0
64        for module in sorted(modules_to_check):
65            _LOG.debug('Checking module %s', module)
66            owners_path = module / 'OWNERS'
67            if not owners_path.is_file():
68                _LOG.error('%s is missing an OWNERS file', module)
69                errors += 1
70                continue
71
72            with owners_path.open() as ins:
73                contents = [x.strip() for x in ins.read().strip().splitlines()]
74                wo_comments = [x for x in contents if not x.startswith('#')]
75                owners = [x for x in wo_comments if 'per-file' not in x]
76                if len(owners) < 1:
77                    _LOG.error('%s is too short: add owners', owners_path)
78
79        if errors:
80            raise PresubmitFailure
81
82    return check
83