pw_presubmit: Add OWNERS check
Change-Id: I5a638c2c2819afc2677491734327727f5b41a80e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/125691
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Greg Pataky <gregpataky@google.com>
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
diff --git a/pw_presubmit/docs.rst b/pw_presubmit/docs.rst
index 5b3636a..2fcde5b 100644
--- a/pw_presubmit/docs.rst
+++ b/pw_presubmit/docs.rst
@@ -315,6 +315,16 @@
.. In case things get moved around in the previous paragraphs the enable line
.. is repeated here: inclusive-language: enable.
+OWNERS
+^^^^^^
+There's a check that requires folders matching specific patterns contain
+``OWNERS`` files. It can be included by adding
+``module_owners.presubmit_check()`` to a presubmit program. This function takes
+a callable as an argument that indicates, for a given file, where a controlling
+``OWNERS`` file should be, or returns None if no ``OWNERS`` file is necessary.
+Formatting of ``OWNERS`` files is handled similary to formatting of other
+source files and is discussed in `Code Formatting`.
+
pw_presubmit
------------
.. automodule:: pw_presubmit
diff --git a/pw_presubmit/py/BUILD.gn b/pw_presubmit/py/BUILD.gn
index 81714d5..4d6724d 100644
--- a/pw_presubmit/py/BUILD.gn
+++ b/pw_presubmit/py/BUILD.gn
@@ -34,6 +34,7 @@
"pw_presubmit/inclusive_language.py",
"pw_presubmit/install_hook.py",
"pw_presubmit/keep_sorted.py",
+ "pw_presubmit/module_owners.py",
"pw_presubmit/ninja_parser.py",
"pw_presubmit/npm_presubmit.py",
"pw_presubmit/owners_checks.py",
diff --git a/pw_presubmit/py/pw_presubmit/module_owners.py b/pw_presubmit/py/pw_presubmit/module_owners.py
new file mode 100644
index 0000000..f16d6e3
--- /dev/null
+++ b/pw_presubmit/py/pw_presubmit/module_owners.py
@@ -0,0 +1,82 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Ensure all modules have OWNERS files."""
+
+import logging
+from pathlib import Path
+from typing import Callable, Optional, Tuple
+
+from pw_presubmit import (
+ PresubmitContext,
+ PresubmitFailure,
+ presubmit,
+)
+
+_LOG: logging.Logger = logging.getLogger(__name__)
+
+
+def upstream_pigweed_applicability(
+ ctx: PresubmitContext,
+ path: Path,
+) -> Optional[Path]:
+ """Return a parent of path required to have an OWNERS file, or None."""
+ parts: Tuple[str, ...] = path.relative_to(ctx.root).parts
+
+ if len(parts) >= 2 and parts[0].startswith('pw_'):
+ return ctx.root / parts[0]
+ if len(parts) >= 3 and parts[0] in ('targets', 'third_party'):
+ return ctx.root / parts[0] / parts[1]
+
+ return None
+
+
+ApplicabilityFunc = Callable[[PresubmitContext, Path], Optional[Path]]
+
+
+def presubmit_check(
+ applicability: ApplicabilityFunc = upstream_pigweed_applicability,
+) -> presubmit.Check:
+ """Create a presubmit check for the presence of OWNERS files."""
+
+ @presubmit.check(name='module_owners')
+ def check(ctx: PresubmitContext) -> None:
+ """Presubmit check that ensures all modules have OWNERS files."""
+
+ modules_to_check = set()
+
+ for path in ctx.paths:
+ result = applicability(ctx, path)
+ if result:
+ modules_to_check.add(result)
+
+ errors = 0
+ for module in sorted(modules_to_check):
+ _LOG.debug('Checking module %s', module)
+ owners_path = module / 'OWNERS'
+ if not owners_path.is_file():
+ _LOG.error('%s is missing an OWNERS file', module)
+ errors += 1
+ continue
+
+ with owners_path.open() as ins:
+ contents = [x.strip() for x in ins.read().strip().splitlines()]
+ wo_comments = [x for x in contents if not x.startswith('#')]
+ owners = [x for x in wo_comments if 'per-file' not in x]
+ if len(owners) < 1:
+ _LOG.error('%s is too short: add owners', owners_path)
+
+ if errors:
+ raise PresubmitFailure
+
+ return check
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 5aa300d..be287d1 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -47,6 +47,7 @@
filter_paths,
inclusive_language,
keep_sorted,
+ module_owners,
npm_presubmit,
owners_checks,
plural,
@@ -891,6 +892,7 @@
gitmodules.create(),
gn_clang_build,
gn_combined_build_check,
+ module_owners.presubmit_check(),
npm_presubmit.npm_test,
pw_transfer_integration_test,
static_analysis,