You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/07/15 15:36:58 UTC

(tooling-trusted-releases) branch arm updated: Add suggestion to avoid double-prefixing files in dist

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

alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/arm by this push:
     new b65a7ab6 Add suggestion to avoid double-prefixing files in dist
b65a7ab6 is described below

commit b65a7ab666f1c413483f17486e92a6d6a4ad9b0a
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Wed Jul 15 16:36:35 2026 +0100

    Add suggestion to avoid double-prefixing files in dist
---
 atr/render.py                   |  3 +++
 atr/storage/datatypes.py        |  1 +
 atr/storage/readers/releases.py |  2 ++
 atr/tasks/checks/paths.py       | 58 +++++++++++++++++++++++++++++++++++++++--
 4 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/atr/render.py b/atr/render.py
index 6be66210..4c70439b 100644
--- a/atr/render.py
+++ b/atr/render.py
@@ -324,6 +324,7 @@ def render_checks_summary(
         and (not info.release_level_concerns)
         and (not info.release_level_exceptions)
         and (not info.release_level_blockers)
+        and (not info.release_level_suggestions)
     ):
         return None
 
@@ -339,6 +340,8 @@ def render_checks_summary(
         release_problems_by_checker.setdefault(result.checker, []).append(result)
     for result in info.release_level_blockers:
         release_problems_by_checker.setdefault(result.checker, []).append(result)
+    for result in info.release_level_suggestions:
+        release_problems_by_checker.setdefault(result.checker, []).append(result)
 
     stats_by_checker = {stat.checker: stat for stat in info.checker_stats}
     all_checkers = sorted(set(release_problems_by_checker) | {stat.checker for stat in info.checker_stats})
diff --git a/atr/storage/datatypes.py b/atr/storage/datatypes.py
index 52ada271..b42cfabc 100644
--- a/atr/storage/datatypes.py
+++ b/atr/storage/datatypes.py
@@ -78,6 +78,7 @@ class PathInfo(schema.Strict):
     release_level_blockers: list[sql.CheckResult] = schema.factory(list)
     release_level_concerns: list[sql.CheckResult] = schema.factory(list)
     release_level_exceptions: list[sql.CheckResult] = schema.factory(list)
+    release_level_suggestions: list[sql.CheckResult] = schema.factory(list)
     suggestions: dict[safe.RelPath, list[sql.CheckResult]] = schema.factory(dict)
 
 
diff --git a/atr/storage/readers/releases.py b/atr/storage/readers/releases.py
index 30a796fa..b3be32c2 100644
--- a/atr/storage/readers/releases.py
+++ b/atr/storage/readers/releases.py
@@ -185,3 +185,5 @@ class GeneralPublic:
                 continue
             if path := suggestion.safe_primary_rel_path:
                 cs.info.suggestions.setdefault(path, []).append(suggestion)
+            else:
+                cs.info.release_level_suggestions.append(suggestion)
diff --git a/atr/tasks/checks/paths.py b/atr/tasks/checks/paths.py
index 96080f73..89312c85 100644
--- a/atr/tasks/checks/paths.py
+++ b/atr/tasks/checks/paths.py
@@ -25,6 +25,7 @@ import aiofiles.os
 import atr.analysis as analysis
 import atr.attestable as attestable
 import atr.classify as classify
+import atr.construct as construct
 import atr.db as db
 import atr.log as log
 import atr.models.results as results
@@ -41,9 +42,9 @@ _ALLOWED_TOP_LEVEL: Final = frozenset(
 )
 _DOC_TREE_MAX_FILES: Final = 512
 # Release policy fields which this check relies on - used for result caching
-INPUT_POLICY_KEYS: Final[list[str]] = ["binary_artifact_paths", "source_artifact_paths"]
+INPUT_POLICY_KEYS: Final[list[str]] = ["binary_artifact_paths", "source_artifact_paths", "download_path_suffix"]
 INPUT_EXTRA_ARGS: Final[list[str]] = ["is_podling", "all_files"]
-CHECK_VERSION: Final[str] = "5"
+CHECK_VERSION: Final[str] = "6"
 
 
 async def check(args: checks.FunctionArguments) -> results.Results | None:
@@ -115,6 +116,7 @@ async def check(args: checks.FunctionArguments) -> results.Results | None:
 
     await _check_source_artifact_present(args, recorder_problems, relative_paths, base_path)
     await _check_documentation_tree(recorder_problems, relative_paths)
+    await _check_download_suffix_duplication(args, recorder_suggestions, relative_paths)
 
     return None
 
@@ -170,6 +172,58 @@ async def _check_documentation_tree(
     )
 
 
+async def _check_download_suffix_duplication(
+    args: checks.FunctionArguments,
+    recorder_suggestions: checks.Recorder,
+    relative_paths: list[safe.RelPath],
+) -> None:
+    # A file whose first path component repeats a component of the policy's resolved download path
+    # suffix doubles that component in the published path - the suffix maven-3/3.10.0-rc-1 plus a
+    # 3.10.0-rc-1/... rel path lands at .../3.10.0-rc-1/3.10.0-rc-1/... Warn so the redundant top
+    # directory (or the {{VERSION}} in the template) gets dropped before the release ships
+    async with db.session() as data:
+        release = await data.release(
+            key=str(sql.release_key(args.project_key, args.version_key)),
+            _committee=True,
+            _project_release_policy=True,
+        ).demand(RuntimeError(f"Release {args.project_key} {args.version_key} not found"))
+
+    committee = release.committee
+    suffix = construct.resolve_download_path_suffix(
+        template=release.project.policy_download_path_suffix,
+        project_key=release.project.key,
+        version=str(release.version),
+        is_top_level=(committee is not None) and (release.project.key == committee.key),
+    )
+    if suffix is None:
+        return
+
+    suffix_components = frozenset(suffix.as_path().parts)
+    clashes: dict[str, int] = {}
+    for relative_path in relative_paths:
+        parts = relative_path.as_path().parts
+        if parts and (parts[0] in suffix_components):
+            clashes[parts[0]] = clashes.get(parts[0], 0) + 1
+
+    for component in sorted(clashes):
+        await recorder_suggestions.suggestion(
+            f"{clashes[component]} file(s) start with '{component}/', which the download path suffix"
+            " already contains. Consider removing this prefix.",
+            {
+                "component": component,
+                "suffix": str(suffix),
+                "count": clashes[component],
+                "details": (
+                    f"The published path would repeat '{component}': the suffix '{suffix}' plus a "
+                    f"'{component}/...' file lands at '.../{component}/{component}/...'. Consider dropping the "
+                    f"top-level '{component}/' directory from the release, or remove that component from the download "
+                    "path suffix when publishing."
+                ),
+            },
+            primary_rel_path=None,
+        )
+
+
 async def _check_metadata_rules(
     _base_path: safe.StatePath,
     relative_path: safe.RelPath,


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