File size: 11,267 Bytes
b6a9d87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#!/usr/bin/env python3

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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
#
# http://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.

"""Cross-platform ``pyproject.toml`` manifest alignment check.

The repo ships four platform manifests (dGPU, Orin, Spark, Thor). They
are not a ``uv`` workspace, so PEP 621 has no native way to share pins
across them and a missed mirror only surfaces at install time on the
unsynced platform. This script is the CI lint that catches the gap.

It walks the four manifests, normalises the ``[project.dependencies]``
lists, and flags two classes of cross-manifest drift:

  * **presence** — a package present in some manifests but absent from
    others.
  * **version** — same package, different version constraints.

CUDA / wheel-index-tied dependencies (``torch``, ``torchvision``,
``triton``, ``flash-attn``, ``torchcodec``, ``tensorrt-*``,
``deepspeed``, ``nvidia-*``) are intentionally divergent per platform
and are auto-skipped by name pattern. Other intentional drifts are
documented in ``tools/manifest_alignment.toml``.

Usage::

    python tools/check_manifest_alignment.py            # lint
    python tools/check_manifest_alignment.py --report   # also print allow-listed drifts

Exit code 0 if no unannotated drift, 1 otherwise.
"""

from __future__ import annotations

import argparse
from collections import defaultdict
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
import re
import sys
from typing import cast


try:
    # Python 3.11+
    import tomllib as _toml
except ModuleNotFoundError:  # pragma: no cover — supports running under 3.10
    import tomli as _toml  # type: ignore[import-not-found, no-redef]


REPO_ROOT = Path(__file__).resolve().parents[1]

MANIFESTS: dict[str, Path] = {
    "main": REPO_ROOT / "pyproject.toml",
    "orin": REPO_ROOT / "scripts" / "deployment" / "orin" / "pyproject.toml",
    "spark": REPO_ROOT / "scripts" / "deployment" / "spark" / "pyproject.toml",
    "thor": REPO_ROOT / "scripts" / "deployment" / "thor" / "pyproject.toml",
}

# CUDA / wheel-index-tied packages — intentionally divergent per platform.
# Matched by exact name OR prefix (the `*` suffix marks a prefix rule).
_HW_TIED_PATTERNS: tuple[str, ...] = (
    "torch",
    "torchvision",
    "torchcodec",
    "triton",
    "flash-attn",
    "deepspeed",
    "tensorrt*",
    "nvidia-*",
)

ALLOWLIST_PATH = REPO_ROOT / "tools" / "manifest_alignment.toml"


# ---------------------------------------------------------------------------
# Parsing
# ---------------------------------------------------------------------------


# Split a PEP 508 dependency line into name / extras / spec / marker.
# Extras are dropped for the comparison axis; the raw spec is kept so
# the report can quote the exact line a reviewer needs to edit.
_REQ_SPLIT_RE = re.compile(
    r"""^
    (?P<name>[A-Za-z0-9._-]+)            # canonical project name
    (?P<extras>\[[^\]]+\])?              # optional extras
    \s*
    (?P<spec>[^;]*)                      # version specifier(s); may be empty
    (;\s*(?P<marker>.+))?                # optional environment marker
    $""",
    re.VERBOSE,
)


@dataclass(frozen=True)
class Pin:
    """One dependency from a manifest's ``[project.dependencies]``.
    ``marker`` is preserved so platform-conditional pins on the same
    package do not falsely diff."""

    name: str  # canonical PEP 503 name (lowercased, dashes)
    spec: str  # version specifier
    marker: str | None
    raw: str  # original PEP 508 line, surfaced in reports


def _canonical(name: str) -> str:
    """Apply PEP 503 name normalisation: lowercase + collapse separators."""
    return re.sub(r"[-_.]+", "-", name).lower()


def _is_hw_tied(name: str) -> bool:
    """True for CUDA / wheel-index-tied packages that are auto-skipped
    (intentionally divergent per platform; no allowlist entry needed)."""
    name_lc = name.lower()
    for pat in _HW_TIED_PATTERNS:
        if pat.endswith("*"):
            if name_lc.startswith(pat[:-1]):
                return True
        elif name_lc == pat:
            return True
    return False


def _parse_dep(raw: str) -> Pin | None:
    """Parse a PEP 508 dependency string; return ``None`` on a malformed
    or empty entry."""
    raw = raw.strip()
    if not raw:
        return None
    m = _REQ_SPLIT_RE.match(raw)
    if not m:
        return None
    name = _canonical(m.group("name"))
    spec = (m.group("spec") or "").strip()
    marker = m.group("marker")
    return Pin(name=name, spec=spec, marker=marker.strip() if marker else None, raw=raw)


def parse_manifest(path: Path) -> dict[str, list[Pin]]:
    """``{canonical_name: [Pin, ...]}`` for one manifest. The list is
    per-name because PEP 508 lets the same package appear multiple
    times under different environment markers (e.g. main's
    ``torchcodec`` split on ``platform_machine``)."""
    data = _toml.loads(path.read_text(encoding="utf-8"))
    deps_raw = cast(list[str], data.get("project", {}).get("dependencies", []) or [])
    out: dict[str, list[Pin]] = defaultdict(list)
    for raw in deps_raw:
        pin = _parse_dep(raw)
        if pin is None:
            continue
        out[pin.name].append(pin)
    return dict(out)


def load_allowlist(path: Path = ALLOWLIST_PATH) -> dict[str, str]:
    """``{canonical_package_name: reason}`` for documented intentional
    drifts. A missing file yields an empty allow-list."""
    if not path.exists():
        return {}
    data = _toml.loads(path.read_text(encoding="utf-8"))
    out: dict[str, str] = {}
    for name, body in data.get("allowed_drifts", {}).items():
        reason = body.get("reason", "")
        out[_canonical(name)] = reason
    return out


# ---------------------------------------------------------------------------
# Diff
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class Drift:
    """One cross-manifest discrepancy on a dependency."""

    package: str
    kind: str  # "presence" | "version"
    detail: str


def detect_drifts(
    manifests: dict[str, dict[str, list[Pin]]],
) -> list[Drift]:
    """Emit one ``Drift`` per cross-manifest discrepancy: ``presence``
    when a package is missing from at least one manifest but present in
    another, ``version`` when the spec set differs (markers are
    preserved so platform-conditional pins do not falsely diff)."""
    all_names: set[str] = set()
    for pins in manifests.values():
        all_names.update(pins.keys())

    drifts: list[Drift] = []

    for name in sorted(all_names):
        if _is_hw_tied(name):
            continue

        # Bucket by which manifest declares the name.
        present: dict[str, list[Pin]] = {
            mname: pins[name] for mname, pins in manifests.items() if name in pins
        }
        missing: list[str] = [mname for mname in manifests if mname not in present]

        if missing and present:
            present_str = ", ".join(sorted(present.keys()))
            missing_str = ", ".join(sorted(missing))
            drifts.append(
                Drift(
                    package=name,
                    kind="presence",
                    detail=(f"present in [{present_str}], missing from [{missing_str}]"),
                )
            )
            continue

        if missing:
            # Absent everywhere — not a drift, just unused.
            continue

        # Present in all manifests — compare (spec, marker) signatures;
        # more than one distinct signature is a version drift.
        sig_per_manifest: dict[str, tuple[tuple[str, str | None], ...]] = {}
        for mname, pin_list in present.items():
            sig_per_manifest[mname] = tuple(sorted((pin.spec, pin.marker) for pin in pin_list))

        unique_sigs = set(sig_per_manifest.values())
        if len(unique_sigs) > 1:
            rows = []
            for mname in sorted(sig_per_manifest):
                spec_strs = [
                    f"{pin.spec or '(unpinned)'}" + (f" ; {pin.marker}" if pin.marker else "")
                    for pin in present[mname]
                ]
                rows.append(f"{mname}={' / '.join(spec_strs)}")
            drifts.append(
                Drift(
                    package=name,
                    kind="version",
                    detail="; ".join(rows),
                )
            )

    return drifts


# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------


def _format_drifts(drifts: Iterable[Drift], allowlist: dict[str, str]) -> str:
    lines = []
    for d in drifts:
        marker = "ALLOWED" if d.package in allowlist else "DRIFT  "
        reason = f" — allowlisted: {allowlist[d.package]}" if d.package in allowlist else ""
        lines.append(f"  [{marker}] {d.package} ({d.kind}): {d.detail}{reason}")
    return "\n".join(lines)


def main(argv: list[str] | None = None) -> int:
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument(
        "--report",
        action="store_true",
        help="also print allow-listed (annotated) drifts",
    )
    args = p.parse_args(argv)

    for name, path in MANIFESTS.items():
        if not path.exists():
            print(f"FATAL: {name} manifest missing at {path}", file=sys.stderr)
            return 2

    parsed: dict[str, dict[str, list[Pin]]] = {
        name: parse_manifest(path) for name, path in MANIFESTS.items()
    }
    allowlist = load_allowlist()

    drifts = detect_drifts(parsed)
    unannotated = [d for d in drifts if d.package not in allowlist]
    annotated = [d for d in drifts if d.package in allowlist]

    if args.report and annotated:
        print(f"Allow-listed drifts ({len(annotated)}):")
        print(_format_drifts(annotated, allowlist))
        print()

    if not unannotated:
        print(
            f"OK: {len(drifts)} drift(s) total, all allow-listed in "
            f"{ALLOWLIST_PATH.relative_to(REPO_ROOT)}."
        )
        return 0

    print(
        f"FAIL: {len(unannotated)} unannotated drift(s) across {len(MANIFESTS)} manifests:",
        file=sys.stderr,
    )
    print(_format_drifts(unannotated, allowlist), file=sys.stderr)
    print(
        "\nResolve each drift by either:\n"
        "  (a) aligning the pin / adding the missing dep across all manifests, or\n"
        f"  (b) recording an intentional divergence in {ALLOWLIST_PATH.relative_to(REPO_ROOT)} "
        "with a reason.",
        file=sys.stderr,
    )
    return 1


if __name__ == "__main__":
    sys.exit(main())