{"record":{"id":"52cae35c4e60a233","repo":"headroomlabs-ai/headroom","slug":"sdist-name-declares-no-license-file-entries","errorCode":null,"errorMessage":"{sdist.name} declares no License-File entries","messagePattern":"(.+?) declares no License-File entries","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"scripts/build_python_release_smoke.py","lineNumber":143,"sourceCode":"            raise SystemExit(f\"expected one sdist root directory, found {sorted(roots)}\")\n        root = roots.pop()\n\n        pkg_info_path = f\"{root}/PKG-INFO\"\n        member = archive.getmember(pkg_info_path)\n        fh = archive.extractfile(member)\n        if fh is None:\n            raise SystemExit(f\"could not read {pkg_info_path} from {sdist.name}\")\n        pkg_info = fh.read().decode(\"utf-8\")\n\n    declared = []\n    for line in pkg_info.splitlines():\n        if not line.strip():\n            break\n        if line.startswith(\"License-File:\"):\n            declared.append(line.split(\":\", 1)[1].strip())\n\n    if not declared:\n        raise SystemExit(f\"{sdist.name} declares no License-File entries\")\n\n    missing = [name for name in declared if f\"{root}/{name}\" not in names]\n    if missing:\n        raise SystemExit(\n            f\"{sdist.name} declares License-File entries missing from tarball: {missing}\"\n        )\n\n    print(f\"sdist License-File metadata OK: {declared}\")\n\n\ndef venv_python(venv_dir: Path) -> Path:\n    if os.name == \"nt\":\n        return venv_dir / \"Scripts\" / \"python.exe\"\n    return venv_dir / \"bin\" / \"python\"\n\n\ndef smoke_install_wheel(wheel: Path, python_exe: str, expected_version: str) -> None:\n    with tempfile.TemporaryDirectory(prefix=\"headroom-python-smoke-\") as tmp:","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/scripts/build_python_release_smoke.py#L125-L161","documentation":"verify_sdist_license_files() reads the PKG-INFO headers and requires at least one `License-File:` entry. Modern Python packaging (PEP 639) records which license files ship in the sdist; an empty list means the project metadata never declared license files, so downstream consumers get no license text.","triggerScenarios":"pyproject.toml lacking `license-files` (or `license = {file = ...}` legacy form that some builders do not translate into License-File entries); an older maturin/setuptools that does not emit PEP 639 License-File metadata.","commonSituations":"Upgrading or downgrading maturin changes whether License-File is emitted; a pyproject cleanup that dropped the license-files key; adding a LICENSE file without wiring it into metadata.","solutions":["Add license declaration to pyproject.toml, e.g. `license = \"MIT\"` plus `license-files = [\"LICENSE\"]` (or the setuptools/maturin equivalent for your builder).","Upgrade maturin to a version that emits PEP 639 License-File metadata.","Rebuild and confirm PKG-INFO now contains `License-File: LICENSE` lines."],"exampleFix":"# pyproject.toml - before\n[project]\nname = \"headroom\"\n# no license declaration\n\n# after\n[project]\nname = \"headroom\"\nlicense = \"MIT\"\nlicense-files = [\"LICENSE\"]","handlingStrategy":"validation","validationCode":"import tarfile\n\ndef declared_license_files(sdist_path: str, root: str) -> list[str]:\n    with tarfile.open(sdist_path, \"r:gz\") as t:\n        text = t.extractfile(f\"{root}/PKG-INFO\").read().decode()\n    return [l.split(\":\", 1)[1].strip() for l in text.splitlines() if l.startswith(\"License-File:\")]\n# assert declared_license_files(...) before promoting an sdist","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Declare license and license-files in pyproject.toml when the project gains a LICENSE file.","Pin a maturin version known to emit PEP 639 metadata, and keep it in CI.","Include the license smoke in every release checklist."],"tags":["python","licensing","sdist","maturin","metadata"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}