You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/08/01 14:53:10 UTC

(tooling-trusted-release) branch main updated: Add an overall check result for license header member checks

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new 1765244  Add an overall check result for license header member checks
1765244 is described below

commit 1765244b6dc06758d0f09943fd403217e7021f9c
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Fri Aug 1 15:52:41 2025 +0100

    Add an overall check result for license header member checks
---
 atr/tasks/checks/__init__.py |  5 +++++
 atr/tasks/checks/license.py  | 46 +++++++++++++++++++++++++++-----------------
 atr/tasks/checks/paths.py    | 22 ++++++++++++++++++++-
 3 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index c8a7a45..574c9ab 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -75,6 +75,7 @@ class Recorder:
         self.member_rel_path = member_rel_path
         self.afresh = afresh
         self.constructed = False
+        self.member_problems: dict[sql.CheckResultStatus, int] = {}
 
         self.project_name = project_name
         self.version_name = version_name
@@ -114,6 +115,10 @@ class Recorder:
             #     # Clear inner path only if it's specified
             #     await self.clear(primary_rel_path=primary_rel_path, member_rel_path=member_rel_path)
 
+        if member_rel_path is not None:
+            if status != sql.CheckResultStatus.SUCCESS:
+                self.member_problems[status] = self.member_problems.get(status, 0) + 1
+
         result = sql.CheckResult(
             release_name=self.release_name,
             revision_number=self.revision_number,
diff --git a/atr/tasks/checks/license.py b/atr/tasks/checks/license.py
index 47689bb..911aea1 100644
--- a/atr/tasks/checks/license.py
+++ b/atr/tasks/checks/license.py
@@ -170,20 +170,7 @@ async def headers(args: checks.FunctionArguments) -> results.Results | None:
         if ignore_file.exists():
             ignore_lines = ignore_file.read_text().splitlines()
 
-    try:
-        for result in await asyncio.to_thread(_headers_check_core_logic, str(artifact_abs_path), ignore_lines):
-            match result:
-                case ArtifactResult():
-                    await _record_artifact(recorder, result)
-                case MemberResult():
-                    await _record_member(recorder, result)
-                case MemberSkippedResult():
-                    pass
-
-    except Exception as e:
-        await recorder.exception("Error during license header check execution", {"error": str(e)})
-
-    return None
+    return await _headers_core(recorder, str(artifact_abs_path), ignore_lines)
 
 
 def headers_validate(content: bytes, _filename: str) -> tuple[bool, str | None]:
@@ -238,8 +225,8 @@ def _files_check_core_logic(artifact_path: str, is_podling: bool) -> Iterator[Re
             elif filename in {"DISCLAIMER", "DISCLAIMER-WIP"}:
                 disclaimer_found = True
 
-    yield from __license_results(license_results)
-    yield from __notice_results(notice_results)
+    yield from _license_results(license_results)
+    yield from _notice_results(notice_results)
     if is_podling and (not disclaimer_found):
         yield ArtifactResult(
             status=sql.CheckResultStatus.FAILURE,
@@ -302,7 +289,7 @@ def _files_check_core_logic_notice(archive: tarzip.Archive, member: tarzip.Membe
     return len(issues) == 0, issues, preamble
 
 
-def __license_results(
+def _license_results(
     license_results: dict[str, str | None],
 ) -> Iterator[Result]:
     """Build status messages for license file verification."""
@@ -339,7 +326,7 @@ def __license_results(
             )
 
 
-def __notice_results(
+def _notice_results(
     notice_results: dict[str, tuple[bool, list[str], str]],
 ) -> Iterator[Result]:
     """Build status messages for notice file verification."""
@@ -517,6 +504,29 @@ def _headers_check_core_logic_should_check(filepath: str) -> bool:
     return False
 
 
+async def _headers_core(recorder: checks.Recorder, artifact_abs_path: str, ignore_lines: list[str]) -> None:
+    try:
+        for result in await asyncio.to_thread(_headers_check_core_logic, str(artifact_abs_path), ignore_lines):
+            match result:
+                case ArtifactResult():
+                    await _record_artifact(recorder, result)
+                case MemberResult():
+                    await _record_member(recorder, result)
+                case MemberSkippedResult():
+                    pass
+        member_failures = recorder.member_problems.get(sql.CheckResultStatus.FAILURE, 0)
+        if member_failures > 0:
+            await recorder.failure(
+                f"Some files had invalid license headers ({member_failures} failures)",
+                None,
+            )
+
+    except Exception as e:
+        await recorder.exception("Error during license header check execution", {"error": str(e)})
+
+    return None
+
+
 async def _record_artifact(recorder: checks.Recorder, result: ArtifactResult) -> None:
     match result.status:
         case sql.CheckResultStatus.SUCCESS:
diff --git a/atr/tasks/checks/paths.py b/atr/tasks/checks/paths.py
index f202d52..76b7fcf 100644
--- a/atr/tasks/checks/paths.py
+++ b/atr/tasks/checks/paths.py
@@ -186,7 +186,9 @@ async def _check_path_process_single(
         # We should enquire as to whether such a policy should be instituted
         # We're forbidding dotfiles to catch accidental uploads of e.g. .git or .htaccess
         # Such cases are likely to be in error, and could carry security risks
-        errors.append("Dotfiles are forbidden")
+        # We allow .atr/ files, e.g. .atr/license-headers-ignore
+        if relative_path.parts[0] != ".atr":
+            errors.append("Dotfiles are forbidden")
 
     search = re.search(analysis.extension_pattern(), relative_path_str)
     ext_artifact = search.group("artifact") if search else None
@@ -204,6 +206,24 @@ async def _check_path_process_single(
         if (relative_path.parent == pathlib.Path(".")) and (relative_path.name not in allowed_top_level):
             warnings.append(f"Unknown top level file: {relative_path.name}")
 
+    await _record(
+        recorder_errors,
+        recorder_warnings,
+        recorder_success,
+        relative_path_str,
+        errors,
+        warnings,
+    )
+
+
+async def _record(
+    recorder_errors: checks.Recorder,
+    recorder_warnings: checks.Recorder,
+    recorder_success: checks.Recorder,
+    relative_path_str: str,
+    errors: list[str],
+    warnings: list[str],
+) -> None:
     for error in errors:
         await recorder_errors.failure(error, {}, primary_rel_path=relative_path_str)
     for warning in warnings:


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org