mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-20 18:52:28 +00:00
Closes #4246. lancedb publishes no source distribution, so a platform without a prebuilt wheel (#4104, #4239) has nothing to install. This adds an sdist to the Python release. ## The sdist job The release workflow builds an sdist with the maturin version of the wheel jobs. On a tag, the publish job uploads it with the wheels: to PyPI, and to Fury for beta tags. The job also runs on the pull request dry run, which now starts on a change to `python/pyproject.toml`. ## Checks - The job builds a wheel from the sdist with `--locked` and smoke-tests it in a fresh venv. - `ci/check_sdist.py` fails if a tracked file of the Python package is missing from the tarball. - `twine check --strict` checks that the metadata renders. `pydistcheck` enforces PyPI's 100 MiB size limit. - The unit tests of both check scripts run in the CI scripts workflow. ## Cargo.lock maturin ships the repository lockfile, which pins crates that only the dropped `nodejs` member uses, so `cargo --locked` fails (PyO3/maturin#2609). The job first runs `cargo metadata --locked` on the repository workspace, so a lockfile that has drifted from `Cargo.toml` fails there. It then removes the nodejs-only pins and repacks the sdist. `ci/check_cargo_lock.py` fails if the prune changes any package it keeps, or any dependency of one. The published file is the repacked one. ## Behavior change After the first release with an sdist, pip on a platform with no current wheel builds the newest version from source, which needs a Rust toolchain and `protoc`. Before, pip installed the last version with a wheel for that platform.
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
SCRIPT = REPO_ROOT / "ci" / "check_cargo_lock.py"
|
|
CRATES_IO = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
|
|
|
def package(name, version, deps=(), source=CRATES_IO, checksum="aa"):
|
|
text = f'[[package]]\nname = "{name}"\nversion = "{version}"\n'
|
|
if source:
|
|
text += f'source = "{source}"\nchecksum = "{checksum}"\n'
|
|
if deps:
|
|
text += f"dependencies = {list(deps)!r}\n".replace("'", '"')
|
|
return text
|
|
|
|
|
|
LIBC = package("libc", "0.2.1")
|
|
UNTRUSTED = package("untrusted", "0.9.0")
|
|
# The prune drops napi and untrusted 0.7.1, the edge from app to it, and the
|
|
# version qualifier on app's remaining untrusted edge.
|
|
BEFORE = "version = 4\n\n" + "".join(
|
|
[
|
|
package(
|
|
"app", "1.0.0", ["libc", "untrusted 0.7.1", "untrusted 0.9.0"], source=None
|
|
),
|
|
package("napi", "3.0.0", ["untrusted 0.7.1"]),
|
|
LIBC,
|
|
package("untrusted", "0.7.1"),
|
|
UNTRUSTED,
|
|
]
|
|
)
|
|
APP = package("app", "1.0.0", ["libc", "untrusted"], source=None)
|
|
|
|
|
|
class CheckCargoLockTest(unittest.TestCase):
|
|
def run_check(self, after, header="version = 4\n\n"):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
before_path = Path(tmp, "before.lock")
|
|
after_path = Path(tmp, "after.lock")
|
|
before_path.write_text(BEFORE)
|
|
after_path.write_text(header + after)
|
|
return subprocess.run(
|
|
[sys.executable, SCRIPT, before_path, after_path],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
def test_prune_passes(self):
|
|
result = self.run_check(APP + LIBC + UNTRUSTED)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_added_entry_fails(self):
|
|
result = self.run_check(APP + LIBC + UNTRUSTED + package("ring", "0.17.0"))
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("ring", result.stderr)
|
|
|
|
def test_changed_version_fails(self):
|
|
result = self.run_check(APP + package("libc", "0.2.2") + UNTRUSTED)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("0.2.2", result.stderr)
|
|
|
|
def test_changed_source_fails(self):
|
|
libc = package("libc", "0.2.1", source="git+https://example.com/libc")
|
|
result = self.run_check(APP + libc + UNTRUSTED)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("example.com", result.stderr)
|
|
|
|
def test_changed_checksum_fails(self):
|
|
libc = package("libc", "0.2.1", checksum="bb")
|
|
result = self.run_check(APP + libc + UNTRUSTED)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("'bb'", result.stderr)
|
|
|
|
def test_added_dependency_fails(self):
|
|
libc = package("libc", "0.2.1", ["untrusted"])
|
|
result = self.run_check(APP + libc + UNTRUSTED)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("dependencies of ('libc'", result.stderr)
|
|
|
|
def test_removed_dependency_fails(self):
|
|
app = package("app", "1.0.0", ["untrusted"], source=None)
|
|
result = self.run_check(app + LIBC + UNTRUSTED)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("dependencies of ('app'", result.stderr)
|
|
|
|
def test_changed_lock_version_fails(self):
|
|
result = self.run_check(APP + LIBC + UNTRUSTED, header="version = 3\n\n")
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("top-level 'version'", result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|