Skip to content

fix: validate HF wheel licenses before upload - #2386

Open
charlesbluca wants to merge 1 commit into
NVIDIA:mainfrom
charlesbluca:fix-hf-nightly-validator
Open

fix: validate HF wheel licenses before upload#2386
charlesbluca wants to merge 1 commit into
NVIDIA:mainfrom
charlesbluca:fix-hf-nightly-validator

Conversation

@charlesbluca

Copy link
Copy Markdown
Collaborator

Description

Hugging Face package builds now validate Apache-2.0 wheel metadata before any PyPI upload. The shared builder inspects every wheel's single .dist-info/METADATA, prefers the PEP 639 License-Expression header, falls back to the legacy License header, and requires the configured Apache classifier. Failures include the wheel filename and both observed license fields.

The previous OCR-only workflow check ran after upload and read only the legacy License header. Hatchling emits License-Expression: Apache-2.0, so a valid OCR wheel could be reported as invalid only after publication. Moving validation into the shared builder protects OCR, page-elements, and table-structure before _twine_upload; OCR-specific version, dependency, package-content, extension-ABI, and platform-tag checks remain in the workflow.

Current setuptools rejects combining a PEP 639 license expression with the required Apache Trove classifier. The metadata patch therefore keeps modern SPDX expressions for hatchling packages and uses the equivalent legacy text form for setuptools packages. Both forms are enforced by the shared validator and continue to bundle the Apache license file.

OCRv2 2.0.2 was already fully published and must not be rerun.

Validation

  • uv run --with pytest python -m pytest ci/tests/test_huggingface_release_workflow.py -q — 34 passed.
  • uvx pre-commit run --files ci/scripts/nightly_build_publish.py ci/tests/test_huggingface_release_workflow.py .github/workflows/huggingface-nightly.yml — all hooks passed.
  • git diff --check — passed.
  • No-upload Python 3.12 production builds from the current Hugging Face repositories:
    • nemotron-page-elements-v3 3.0.2.dev20260721152500, py3-none-any: License='Apache-2.0', no License-Expression, Apache classifier present; validator and twine check passed.
    • nemotron-table-structure-v1 1.0.1.dev20260721152500, py3-none-any: License='Apache-2.0', no License-Expression, Apache classifier present; validator and twine check passed.
    • nemotron-ocr 2.0.2.dev20260721152500, CPython 3.12 manylinux_2_34_x86_64, built in nvidia/cuda:13.0.0-devel-ubuntu24.04 with the workflow build, runtime-pin, auditwheel, and required-member flags: License-Expression='Apache-2.0', no legacy License, Apache classifier present; validator and twine check passed. Twine reported only the package's existing missing long-description warnings.
  • Downloaded published nemotron-ocr 2.0.2 CPython 3.12 manylinux_2_34_aarch64: the same production validator passed; independent inspection found License-Expression='Apache-2.0', no legacy License, and the Apache classifier. twine check passed with the same existing long-description warnings.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@copy-pr-bot

copy-pr-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Charles Blackmon-Luca <20627856+charlesbluca@users.noreply.github.com>
@charlesbluca
charlesbluca force-pushed the fix-hf-nightly-validator branch from 2d1aabd to 7df9f7a Compare July 21, 2026 15:46
@charlesbluca
charlesbluca marked this pull request as ready for review July 21, 2026 15:47
@charlesbluca
charlesbluca requested review from a team as code owners July 21, 2026 15:47
@charlesbluca
charlesbluca requested a review from edknv July 21, 2026 15:47
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves Hugging Face wheel license validation from an OCR-specific post-build check in the workflow YAML into the shared nightly_build_publish.py builder, ensuring license metadata is inspected before any PyPI upload for all packages (OCR, page-elements, table-structure). It also adds a setuptools compatibility path that emits the inline-table license = {text = "Apache-2.0"} form instead of the PEP 639 string form to avoid a conflict with the required Trove classifier.

  • New _validate_wheel_license_metadata: Reads every wheel's .dist-info/METADATA, prefers License-Expression (PEP 639), falls back to the legacy License header, and enforces the expected text and Trove classifier before _twine_upload is reached.
  • _patch_pyproject_license update: Detects setuptools backends via pyproject.toml's build-system.build-backend and uses the {text = "…"} inline-table form so setuptools accepts the classifier without error.
  • 34 new/updated tests cover both the happy-path (modern and legacy headers, every-wheel iteration) and failure paths (wrong license, missing classifier, missing METADATA).

Confidence Score: 5/5

Safe to merge — moves existing validation earlier in the pipeline, adds no new network calls or secret handling, and is backed by 34 passing tests including production no-upload runs against all three target packages.

The change is narrowly scoped to CI tooling: it deletes dead workflow validation code, adds a pre-upload wheel inspector, and adjusts the setuptools license form. The new function faithfully mirrors the style of the adjacent _validate_required_wheel_members, the PEP 639 fallback logic is straightforward, and every branch is covered by a dedicated test. No application logic, API contracts, or data paths are touched.

No files require special attention. The structural test in test_huggingface_release_workflow.py that checks source-code ordering via str.index() is slightly brittle (an existing pattern in the file), but it correctly guards the pre-upload invariant.

Important Files Changed

Filename Overview
ci/scripts/nightly_build_publish.py Adds _validate_wheel_license_metadata (pre-upload, every wheel) and updates _patch_pyproject_license to detect setuptools backends and emit legacy {text = …} form; logic is correct and mirrors the adjacent _validate_required_wheel_members pattern.
.github/workflows/huggingface-nightly.yml Removes the OCR-only post-build license check now handled by the shared builder; OCR-specific version, dependency, and extension-ABI checks remain untouched.
ci/tests/test_huggingface_release_workflow.py Adds 7 new test functions covering the full matrix of license header combinations, every-wheel iteration, classifier absence, and ordering of validation before upload; SPDX header present, all tests are unit-level with no external calls.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant W as Workflow YAML
    participant B as nightly_build_publish main
    participant V as _validate_wheel_license_metadata
    participant T as _twine_upload

    W->>B: run with --license-text Apache-2.0
    B->>B: _patch_pyproject_license (setuptools uses legacy form)
    B->>B: "_build produces dist/*.whl"
    B->>B: _validate_required_wheel_members
    B->>V: _validate_wheel_license_metadata(dist_dir)
    loop "every *.whl"
        V->>V: open ZipFile, read .dist-info/METADATA
        V->>V: prefer License-Expression, fallback to License header
        V->>V: check Trove classifier present
    end
    alt any failure
        V-->>B: raise RuntimeError, abort before upload
    else all pass
        V-->>B: return
        B->>T: _twine_upload if args.upload
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant W as Workflow YAML
    participant B as nightly_build_publish main
    participant V as _validate_wheel_license_metadata
    participant T as _twine_upload

    W->>B: run with --license-text Apache-2.0
    B->>B: _patch_pyproject_license (setuptools uses legacy form)
    B->>B: "_build produces dist/*.whl"
    B->>B: _validate_required_wheel_members
    B->>V: _validate_wheel_license_metadata(dist_dir)
    loop "every *.whl"
        V->>V: open ZipFile, read .dist-info/METADATA
        V->>V: prefer License-Expression, fallback to License header
        V->>V: check Trove classifier present
    end
    alt any failure
        V-->>B: raise RuntimeError, abort before upload
    else all pass
        V-->>B: return
        B->>T: _twine_upload if args.upload
    end
Loading

Reviews (1): Last reviewed commit: "fix: validate HF wheel licenses before u..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant