From 4e326a3cd73112306ebd663b154d0eaaf9351cae Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Thu, 13 Aug 2026 13:20:53 -0400 Subject: [PATCH] fix(preprod): Accept CFBundleVersion groups beyond the third _parse_build_number rejected period-separated build numbers with more than three groups. The CFBundleVersion spec allows extra groups and requires parsers to drop groups beyond the third, so 1.2.3.4 now packs to the same build number as 1.2.3. All groups must still be numeric; only the first three are width-checked. Mirrors sentry's parse_build_number in build_distribution_utils.py. Deploy only after the sentry backend change is live, so query-side parsing accepts the extra groups before launchpad starts producing them. --- src/launchpad/artifact_processor.py | 23 +++++++++++-------- .../unit/artifacts/test_artifact_processor.py | 12 ++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/launchpad/artifact_processor.py b/src/launchpad/artifact_processor.py index c54b726d..da73322d 100644 --- a/src/launchpad/artifact_processor.py +++ b/src/launchpad/artifact_processor.py @@ -675,11 +675,12 @@ def _parse_build_number(build: str) -> int | None: """Parse a raw build identifier (e.g. CFBundleVersion) into a sortable int. Plain integer builds (the common case, e.g. Android versionCode) pass through - unchanged. Apple's CFBundleVersion also allows up to three dot-separated - non-negative integers (e.g. "1.2.3"); those are packed into a single int by + unchanged. Apple's CFBundleVersion also allows dot-separated non-negative + integers (e.g. "1.2.3"); those are packed into a single int by zero-padding each component, which preserves correct ordering as long as no - component reaches 10**_BUILD_NUMBER_COMPONENT_WIDTH. Anything else - (non-numeric, malformed) returns None, same as today. + component reaches 10**_BUILD_NUMBER_COMPONENT_WIDTH. Per the CFBundleVersion + spec, more than three groups are accepted and groups beyond the third are + dropped. Anything else (non-numeric, malformed) returns None, same as today. Known limitation: plain-integer values are left small while packed dotted values are much larger, so if the same app_id/build_version ever has builds @@ -695,8 +696,12 @@ def _parse_build_number(build: str) -> int | None: return int(build) parts = build.split(".") - if 2 <= len(parts) <= 3 and all(p.isdigit() and len(p) <= _BUILD_NUMBER_COMPONENT_WIDTH for p in parts): - parts += ["0"] * (3 - len(parts)) - return sum(int(part) * 10 ** (_BUILD_NUMBER_COMPONENT_WIDTH * (2 - i)) for i, part in enumerate(parts)) - - return None + if not all(p.isdigit() for p in parts): + return None + # CFBundleVersion allows more than three groups; groups beyond the third + # are dropped. + parts = parts[:3] + if not (2 <= len(parts) and all(len(p) <= _BUILD_NUMBER_COMPONENT_WIDTH for p in parts)): + return None + parts += ["0"] * (3 - len(parts)) + return sum(int(part) * 10 ** (_BUILD_NUMBER_COMPONENT_WIDTH * (2 - i)) for i, part in enumerate(parts)) diff --git a/tests/unit/artifacts/test_artifact_processor.py b/tests/unit/artifacts/test_artifact_processor.py index 8e7aa42d..82cfdf8c 100644 --- a/tests/unit/artifacts/test_artifact_processor.py +++ b/tests/unit/artifacts/test_artifact_processor.py @@ -413,13 +413,21 @@ class TestParseBuildNumber: ("9999", 9999), ("0", 0), ("1", 1), - # Apple CFBundleVersion: up to three dot-separated non-negative integers + # Apple CFBundleVersion: two or three dot-separated non-negative integers ("1.2.3", 1_000_002_000_003), ("1.2", 1_000_002_000_000), + # CFBundleVersion allows more than three groups; groups beyond the + # third are dropped + ("1.2.3.4", 1_000_002_000_003), + ("1.2.3.4.5", 1_000_002_000_003), + ("1.2.3.0", 1_000_002_000_003), + # Dropped groups are not width-checked, but must still be numeric + ("1.2.3.1234567", 1_000_002_000_003), + ("1.2.3.beta", None), + ("1.2.3.", None), # Malformed or unsupported shapes fall back to None, same as before ("1.2.a", None), ("abc", None), - ("1.2.3.4", None), ("", None), # A component too wide for the padding width is refused rather than # silently corrupting the ordering of adjacent components