import shutil
from pathlib import Path
from typing import Annotated
import typer
import zerv
from bake import command, console
from bakelib import GitHubActionsTools, PythonSpace, RustSpace, params
from bakelib.publisher.crates import CratesPublisher
from bakelib.publisher.pypi import PyPIPublisher as _PyPIPublisher
from bakelib.space.lib import BaseLibSpace
from tests.python.utils import symlink_zerv_to_venv_bin
class PyPIPublisher(_PyPIPublisher):
_target: str | None = None
def _build_for_publish(self) -> None:
cmd = "maturin build --release --strip --out dist/"
if self._target:
cmd += f" --target {self._target}"
self.ctx.run(cmd)
class MyBakebook(RustSpace, PythonSpace, GitHubActionsTools, BaseLibSpace):
zerv_test_native_git: bool = False
zerv_test_docker: bool = True
zerv_force_rust_log_off: bool = False
_target: str | None = None
def get_publish_registries(self) -> set[str]:
return set(PyPIPublisher.valid_registries) | set(CratesPublisher.valid_registries)
def get_publisher(self, registry: str) -> PyPIPublisher | CratesPublisher:
if registry in PyPIPublisher.valid_registries:
publisher = PyPIPublisher(self.ctx, registry)
publisher._target = self._target
return publisher
if registry in CratesPublisher.valid_registries:
return CratesPublisher(self.ctx, registry)
valid = (*PyPIPublisher.valid_registries, *CratesPublisher.valid_registries)
console.error(f"Invalid registry: {registry!r}. Expected one of {valid}.")
raise typer.Exit(1)
def _update_config(self, **kwargs: bool | None) -> None:
for key, value in kwargs.items():
if value is not None:
setattr(self, key, value)
@command()
def test_rust(
self,
*,
zerv_test_native_git: bool | None = None,
zerv_test_docker: bool | None = None,
zerv_force_rust_log_off: bool | None = None,
):
self._update_config(
zerv_test_native_git=zerv_test_native_git,
zerv_test_docker=zerv_test_docker,
zerv_force_rust_log_off=zerv_force_rust_log_off,
)
env: dict[str, str] = {}
env["ZERV_TEST_NATIVE_GIT"] = str(self.zerv_test_native_git).lower()
env["ZERV_TEST_DOCKER"] = str(self.zerv_test_docker).lower()
env["ZERV_FORCE_RUST_LOG_OFF"] = str(self.zerv_force_rust_log_off).lower()
env["RUST_BACKTRACE"] = "1"
env["RUST_LOG"] = "cargo_tarpaulin=off"
self.ctx.run(
"cargo tarpaulin "
"--features test-utils "
"--out Xml --out Html --out Lcov "
"--output-dir coverage "
"--include-tests "
"--exclude-files 'src/main.rs' "
"--exclude-files '**/tests/**' "
"--exclude-files 'src/test_utils/git/native.rs' "
"-- --quiet",
env=env,
shell=True,
)
@command()
def test_python(
self,
build: Annotated[
bool, typer.Option("--build", "-b", help="Build before running tests")
] = False,
):
if build:
self.ctx.run("maturin develop")
if not self.ctx.dry_run:
symlink_zerv_to_venv_bin()
tests_path = "tests/python"
coverage_path = "python/zerv"
self._test(tests_paths=tests_path, coverage_path=coverage_path)
def test(
self,
*,
zerv_test_native_git: bool | None = None,
zerv_test_docker: bool | None = None,
zerv_force_rust_log_off: bool | None = None,
) -> None:
self._update_config(
zerv_test_native_git=zerv_test_native_git,
zerv_test_docker=zerv_test_docker,
zerv_force_rust_log_off=zerv_force_rust_log_off,
)
self.test_rust()
self.test_python(build=True)
@command()
def gen_docs(self):
self.ctx.run("cargo xtask generate-docs")
@command()
def open_coverage(self):
self.ctx.run("open coverage/tarpaulin-report.html")
@command()
def extract_mermaid_svgs(self):
self.ctx.run("./scripts/extract_mermaid_from_markers.sh")
@command()
def publish(
self,
*,
registry: Annotated[
str,
typer.Option(help="Publish registry (test-pypi, pypi, or crates)"),
] = "test-pypi",
token: params.PublishTokenOption = None,
version: params.PublishVersionOption = None,
target: Annotated[
str | None,
typer.Option(
help="Rust target triple (e.g., aarch64-apple-darwin, x86_64-pc-windows-msvc)"
),
] = None,
):
self._target = target
return super().publish(registry=registry, token=token, version=version)
@property
def _version(self) -> str:
cargo_raw = RustSpace._version.fget(self)
pyproject_raw = PythonSpace._version.fget(self)
pyproject_semver = zerv.render(version=pyproject_raw, output_format="semver")
cargo_semver = zerv.render(version=cargo_raw, output_format="semver")
if pyproject_semver != cargo_semver:
raise ValueError(
f"Version mismatch: pyproject.toml={pyproject_raw} ({pyproject_semver}), "
f"Cargo.toml={cargo_raw} ({cargo_semver})"
)
return cargo_raw
@_version.setter
def _version(self, value: str) -> None:
self._version_setter(value)
def _pre_publish_setup(self) -> None:
self.ctx.run("maturin develop")
if not self.ctx.dry_run:
symlink_zerv_to_venv_bin()
CratesPublisher._pre_publish_setup(self.ctx) PyPIPublisher._pre_publish_setup(self.ctx)
for p in Path("python").glob("*.data"):
if p.is_dir():
shutil.rmtree(p)
bakebook = MyBakebook()
@bakebook.command()
def uvx_install_zerv_test():
bakebook.ctx.run(
"uv tool install zerv-version "
"--index-url https://test.pypi.org/simple/ "
"--extra-index-url https://pypi.org/simple "
"--prerelease allow "
"--reinstall "
"--index-strategy unsafe-best-match"
)