diff --git a/.github/workflows/marin-libs-wheels.yaml b/.github/workflows/marin-libs-wheels.yaml
new file mode 100644
index 0000000000..25cc61fb8c
--- /dev/null
+++ b/.github/workflows/marin-libs-wheels.yaml
@@ -0,0 +1,100 @@
+name: marin-libs - Build Wheels
+
+on:
+ workflow_dispatch:
+ inputs:
+ mode:
+ description: "Build mode"
+ type: choice
+ options: [nightly, manual]
+ default: manual
+ schedule:
+ - cron: "0 6 * * *" # 06:00 UTC daily
+ push:
+ tags:
+ - "marin-libs-v*"
+ pull_request:
+ paths:
+ - "lib/**"
+ - "scripts/python_libs_package.py"
+ - ".github/workflows/marin-libs-wheels.yaml"
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: false # don't kill an in-flight nightly mid-publish
+
+permissions:
+ contents: write # creating GH releases
+ pull-requests: read
+
+jobs:
+ resolve:
+ runs-on: ubuntu-latest
+ outputs:
+ mode: ${{ steps.pick.outputs.mode }}
+ version: ${{ steps.pick.outputs.version }}
+ steps:
+ - id: pick
+ run: |
+ set -euo pipefail
+ if [[ "${GITHUB_EVENT_NAME}" == "push" && "${GITHUB_REF}" == refs/tags/marin-libs-v* ]]; then
+ echo "mode=stable" >> "$GITHUB_OUTPUT"
+ echo "version=${GITHUB_REF_NAME#marin-libs-v}" >> "$GITHUB_OUTPUT"
+ elif [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then
+ echo "mode=nightly" >> "$GITHUB_OUTPUT"
+ echo "version=" >> "$GITHUB_OUTPUT"
+ elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
+ echo "mode=${{ github.event.inputs.mode }}" >> "$GITHUB_OUTPUT"
+ echo "version=" >> "$GITHUB_OUTPUT"
+ else
+ # pull_request: build-only smoke test
+ echo "mode=manual" >> "$GITHUB_OUTPUT"
+ echo "version=" >> "$GITHUB_OUTPUT"
+ fi
+
+ build:
+ needs: resolve
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # for git rev-parse in manual mode
+ - uses: astral-sh/setup-uv@v7
+
+ - name: Build wheels
+ run: |
+ uv run python scripts/python_libs_package.py \
+ --mode "${{ needs.resolve.outputs.mode }}" \
+ ${{ needs.resolve.outputs.version && format('--version {0}', needs.resolve.outputs.version) || '' }} \
+ --skip-publish
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: marin-libs-wheels
+ # BUILD_INFO.json travels with the wheels so the publish job uses
+ # the same resolved version the build job stamped in, instead of
+ # re-computing it (which would drift across midnight UTC).
+ path: |
+ dist/*.whl
+ dist/BUILD_INFO.json
+ retention-days: 14
+
+ publish:
+ needs: [resolve, build]
+ if: github.event_name != 'pull_request'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: astral-sh/setup-uv@v7
+ - uses: actions/download-artifact@v4
+ with:
+ name: marin-libs-wheels
+ path: dist
+ - name: Publish releases and prune nightlies
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ uv run python scripts/python_libs_package.py \
+ --mode "${{ needs.resolve.outputs.mode }}" \
+ ${{ needs.resolve.outputs.version && format('--version {0}', needs.resolve.outputs.version) || '' }} \
+ --publish-only
diff --git a/lib/fray/pyproject.toml b/lib/fray/pyproject.toml
index 339ee1b459..f73c606168 100644
--- a/lib/fray/pyproject.toml
+++ b/lib/fray/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "marin-fray"
-version = "0.1.0"
+version = "0.99"
requires-python = ">=3.11,<3.13"
dependencies = [
"click>=8.0",
diff --git a/lib/haliax/src/haliax/__about__.py b/lib/haliax/src/haliax/__about__.py
index f1b75e00ec..7d4731fee1 100644
--- a/lib/haliax/src/haliax/__about__.py
+++ b/lib/haliax/src/haliax/__about__.py
@@ -2,4 +2,4 @@
#
# SPDX-License-Identifier: Apache-2.0
-__version__ = "1.4"
+__version__ = "0.99"
diff --git a/lib/iris/pyproject.toml b/lib/iris/pyproject.toml
index fdd5383cf4..cbe17fee04 100644
--- a/lib/iris/pyproject.toml
+++ b/lib/iris/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "marin-iris"
-version = "0.1.0"
+version = "0.99"
requires-python = ">=3.11,<3.13"
dependencies = [
"marin-rigging",
diff --git a/lib/levanter/pyproject.toml b/lib/levanter/pyproject.toml
index cc6b8877df..88d1ab23a5 100644
--- a/lib/levanter/pyproject.toml
+++ b/lib/levanter/pyproject.toml
@@ -10,7 +10,7 @@ module-name = "levanter"
[project]
name = "marin-levanter"
-version = "1.2"
+version = "0.99"
description = "Scalable Training for Foundation Models with Named Tensors and JAX"
readme = "README.md"
requires-python = ">=3.11"
@@ -34,7 +34,7 @@ classifiers = [
]
dependencies = [
- "marin-haliax>=1.4.dev450",
+ "marin-haliax",
"equinox>=0.11.7,!=0.12.0",
"jax>=0.8.0",
"marin-fray",
diff --git a/lib/marin/pyproject.toml b/lib/marin/pyproject.toml
index be799b444e..df48799239 100644
--- a/lib/marin/pyproject.toml
+++ b/lib/marin/pyproject.toml
@@ -5,7 +5,7 @@ requires-python = ">=3.11,<3.13"
[project]
name = "marin"
-version = "0.1.0"
+version = "0.99"
requires-python = ">=3.11"
dependencies = [
diff --git a/lib/rigging/pyproject.toml b/lib/rigging/pyproject.toml
index 9cc66eb52b..a33152caa1 100644
--- a/lib/rigging/pyproject.toml
+++ b/lib/rigging/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "marin-rigging"
-version = "0.1.0"
+version = "0.99"
requires-python = ">=3.11,<3.13"
dependencies = [
"fsspec>=2024.0.0",
diff --git a/lib/zephyr/pyproject.toml b/lib/zephyr/pyproject.toml
index 88c45d9cf3..5e1deb29f8 100644
--- a/lib/zephyr/pyproject.toml
+++ b/lib/zephyr/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "marin-zephyr"
-version = "0.1.0"
+version = "0.99"
description = "Lightweight dataset library for distributed data processing"
readme = "README.md"
requires-python = ">=3.11,<3.13"
diff --git a/scripts/python_libs_package.py b/scripts/python_libs_package.py
new file mode 100644
index 0000000000..f1f1e7926e
--- /dev/null
+++ b/scripts/python_libs_package.py
@@ -0,0 +1,447 @@
+#!/usr/bin/env python3
+# Copyright The Marin Authors
+# SPDX-License-Identifier: Apache-2.0
+
+"""Build and publish marin-* lib wheels.
+
+Builds the seven pure-Python marin-* lib packages (marin, marin-iris,
+marin-fray, marin-haliax, marin-levanter, marin-rigging, marin-zephyr) into
+dist/, then optionally publishes them as GitHub Releases.
+
+Four modes:
+ nightly -- version becomes .dev; overwrites the rolling
+ marin--latest tag in place. No dated history is kept;
+ reproducibility comes from stable tags, not historical nightlies.
+ stable -- version is taken from --version; creates marin--v
+ and overwrites marin--stable.
+ manual -- version becomes +manual.; build only (no publish).
+ Useful for inspecting wheels from a workflow_dispatch run.
+ vendor -- version becomes .dev; copy wheels to a
+ local directory (no GH publish). For local-iteration loops
+ where a marin worktree feeds wheels into an experiment repo's
+ find-links. The timestamp guarantees rebuilt wheels beat any
+ nightly already published earlier the same day.
+
+Usage:
+ python scripts/python_libs_package.py --mode nightly
+ python scripts/python_libs_package.py --mode stable --version 1.0.0
+ python scripts/python_libs_package.py --mode nightly --skip-publish
+ python scripts/python_libs_package.py --skip-build --publish-only
+ python scripts/python_libs_package.py --mode vendor --vendor ../tiny-tpu/vendor
+
+The build is done from a temporary in-place patch of each package's version
+file plus a cross-pin rewrite of every sibling dependency. Mutations are
+reverted on exit (success OR failure) so the working tree stays clean.
+After building, dist/BUILD_INFO.json records the resolved version so that a
+subsequent --publish-only call (typically the publish job in CI) uses the
+exact version the build job produced, even if the run straddles midnight UTC.
+"""
+
+import argparse
+import json
+import re
+import shutil
+import subprocess
+import sys
+from contextlib import contextmanager
+from datetime import datetime, timezone
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+DIST_DIR = REPO_ROOT / "dist"
+REPO = "marin-community/marin"
+
+
+# Each entry: (dist name, lib subdir, version-file path relative to lib subdir, version-file kind)
+# kind = "pyproject" -> patch version = "..." in pyproject.toml
+# kind = "about_py" -> patch __version__ = "..." in src//__about__.py
+PACKAGES: dict[str, dict[str, str]] = {
+ "marin": {"path": "lib/marin", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-iris": {"path": "lib/iris", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-fray": {"path": "lib/fray", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-rigging": {"path": "lib/rigging", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-zephyr": {"path": "lib/zephyr", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-levanter": {"path": "lib/levanter", "version_file": "pyproject.toml", "kind": "pyproject"},
+ "marin-haliax": {"path": "lib/haliax", "version_file": "src/haliax/__about__.py", "kind": "about_py"},
+}
+
+SIBLING_NAMES = sorted(PACKAGES.keys(), key=len, reverse=True)
+
+
+# ---------- helpers ----------------------------------------------------------
+
+
+def _check_tool(name: str, install_hint: str) -> None:
+ if shutil.which(name) is None:
+ print(f"ERROR: '{name}' not found. Install with: {install_hint}", file=sys.stderr)
+ sys.exit(1)
+
+
+def _git_short_sha() -> str:
+ return subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], text=True, cwd=REPO_ROOT).strip()
+
+
+def _read_base_version(pkg: str) -> str:
+ info = PACKAGES[pkg]
+ path = REPO_ROOT / info["path"] / info["version_file"]
+ text = path.read_text()
+ if info["kind"] == "pyproject":
+ m = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
+ else:
+ m = re.search(r'^__version__\s*=\s*"([^"]+)"', text, re.MULTILINE)
+ if not m:
+ raise RuntimeError(f"Could not read version from {path}")
+ return m.group(1)
+
+
+def _set_version(text: str, kind: str, new_version: str) -> str:
+ if kind == "pyproject":
+ new_text, count = re.subn(
+ r'^version\s*=\s*"[^"]+"',
+ f'version = "{new_version}"',
+ text,
+ count=1,
+ flags=re.MULTILINE,
+ )
+ else:
+ new_text, count = re.subn(
+ r'^__version__\s*=\s*"[^"]+"',
+ f'__version__ = "{new_version}"',
+ text,
+ count=1,
+ flags=re.MULTILINE,
+ )
+ if count != 1:
+ raise RuntimeError(f"Failed to patch version (kind={kind})")
+ return new_text
+
+
+# Match dependency list items: lines that are indented and start with a quoted
+# sibling name. Anchored on `^\s+"` so we never touch metadata lines like
+# `name = "marin"` (no leading whitespace) or single-line `gpu = ["..."]`
+# entries (no marin siblings appear in those today; verified by grep).
+_SIBLING_ALT = "|".join(re.escape(s) for s in sorted(PACKAGES, key=len, reverse=True))
+_SIBLING_ITEM_RE = re.compile(
+ rf'^(?P\s+)"(?P{_SIBLING_ALT})(?![-\w])(?P\[[^\]]*\])?[^"]*"(?P.*)$',
+ re.MULTILINE,
+)
+
+
+def _rewrite_sibling_pins(text: str, version: str) -> str:
+ """Pin every sibling marin-* package in dependency list items to ==."""
+ return _SIBLING_ITEM_RE.sub(
+ lambda m: (f'{m.group("indent")}"{m.group("name")}{m.group("extras") or ""}=={version}"{m.group("tail")}'),
+ text,
+ )
+
+
+@contextmanager
+def patched_tree(version: str):
+ """Patch every package's version file and sibling pins; revert on exit.
+
+ Captures the original text of each path exactly once, before any mutation,
+ so the finally block restores the truly-original content even if multiple
+ patches touched the same file.
+ """
+ originals: dict[Path, str] = {}
+ try:
+ for info in PACKAGES.values():
+ pyproject_path = REPO_ROOT / info["path"] / "pyproject.toml"
+ version_path = REPO_ROOT / info["path"] / info["version_file"]
+
+ if pyproject_path not in originals:
+ originals[pyproject_path] = pyproject_path.read_text()
+ if version_path not in originals:
+ originals[version_path] = version_path.read_text()
+
+ # Apply version patch first; for haliax this writes __about__.py
+ # (separate file from pyproject), for the rest it overwrites the
+ # pyproject we just snapshotted above.
+ patched_version = _set_version(originals[version_path], info["kind"], version)
+ version_path.write_text(patched_version)
+
+ # Then sibling-pin rewrite always targets pyproject.toml. Re-read
+ # in case version patch already wrote pyproject.
+ current_pyproject = pyproject_path.read_text()
+ new_pyproject = _rewrite_sibling_pins(current_pyproject, version)
+ if new_pyproject != current_pyproject:
+ pyproject_path.write_text(new_pyproject)
+ yield
+ finally:
+ for path, text in originals.items():
+ path.write_text(text)
+
+
+# ---------- build ------------------------------------------------------------
+
+
+def _highest_base_version() -> str:
+ """Return the highest version currently declared across the seven libs.
+
+ All seven packages share one synthetic version per build so cross-pins
+ resolve cleanly. Picking the max keeps the synthetic version above the
+ most recent stable so uv prefers it.
+ """
+ bases = [_read_base_version(p) for p in PACKAGES]
+ return max(bases, key=lambda v: tuple(int(p) if p.isdigit() else 0 for p in re.split(r"[.\-+]", v)))
+
+
+def resolve_version(mode: str, explicit: str | None) -> str:
+ """Return the build version for the requested mode.
+
+ nightly -> .dev
+ stable ->
+ manual -> +manual.
+ vendor -> .dev
+ """
+ if mode == "stable":
+ if not explicit:
+ raise SystemExit("--version is required for --mode stable")
+ return explicit
+ if mode == "nightly":
+ date = datetime.now(timezone.utc).strftime("%Y%m%d")
+ return f"{_highest_base_version()}.dev{date}"
+ if mode == "manual":
+ sha = _git_short_sha()
+ return f"{_highest_base_version()}+manual.{sha}"
+ if mode == "vendor":
+ # Second-precision timestamp guarantees the freshly-built wheel beats
+ # any nightly built earlier today, so `uv sync` in the consumer always
+ # picks up the local copy without cache games.
+ ts = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
+ return f"{_highest_base_version()}.dev{ts}"
+ raise SystemExit(f"Unknown mode: {mode}")
+
+
+# Path inside dist/ where build_wheels persists the resolved version + mode.
+# Publish reads this instead of re-resolving so a workflow that straddles
+# midnight UTC can't compute a different date in build vs publish.
+BUILD_INFO_PATH = DIST_DIR / "BUILD_INFO.json"
+
+
+def write_build_info(version: str, mode: str) -> None:
+ BUILD_INFO_PATH.write_text(json.dumps({"version": version, "mode": mode}, indent=2))
+
+
+def read_build_info() -> dict[str, str] | None:
+ if not BUILD_INFO_PATH.is_file():
+ return None
+ return json.loads(BUILD_INFO_PATH.read_text())
+
+
+def build_wheels(version: str, mode: str) -> None:
+ """Build all seven marin-* wheels into DIST_DIR with version patched in.
+
+ Persists BUILD_INFO.json next to the wheels so the publish step (which
+ runs in a separate job and downloads dist/ as an artifact) can read the
+ exact version this build used instead of re-resolving it. That guarantees
+ a workflow run straddling midnight UTC produces a consistent version
+ across build and publish.
+ """
+ _check_tool("uv", "https://docs.astral.sh/uv/")
+
+ if DIST_DIR.exists():
+ shutil.rmtree(DIST_DIR)
+ DIST_DIR.mkdir()
+
+ with patched_tree(version):
+ for name, info in PACKAGES.items():
+ pkg_dir = REPO_ROOT / info["path"]
+ print(f"\n--- Building {name} ({version}) ---")
+ subprocess.run(
+ ["uv", "build", "--wheel", "--out-dir", str(DIST_DIR), str(pkg_dir)],
+ check=True,
+ cwd=REPO_ROOT,
+ )
+
+ wheels = sorted(DIST_DIR.glob("*.whl"))
+ print(f"\nBuilt {len(wheels)} wheel(s):")
+ for w in wheels:
+ print(f" {w.name}")
+ if len(wheels) != len(PACKAGES):
+ raise RuntimeError(f"Expected {len(PACKAGES)} wheels, got {len(wheels)}")
+
+ write_build_info(version, mode)
+
+
+# ---------- vendor -----------------------------------------------------------
+
+
+def vendor_copy(target: Path) -> None:
+ """Drop freshly-built wheels into target/, replacing any prior marin-* wheels.
+
+ Cleans only files matching marin*-*.whl so unrelated files in the target
+ directory (e.g. .gitkeep, README) are left alone. Used by --mode vendor
+ to feed local wheels into a downstream experiment's find-links.
+ """
+ target.mkdir(parents=True, exist_ok=True)
+ stale = sorted(target.glob("marin*-*.whl"))
+ for s in stale:
+ s.unlink()
+ if stale:
+ print(f"\nRemoved {len(stale)} stale marin-* wheel(s) from {target}")
+ print(f"\nCopying wheels to {target}:")
+ for wheel in sorted(DIST_DIR.glob("*.whl")):
+ dest = target / wheel.name
+ shutil.copy2(wheel, dest)
+ print(f" -> {dest.name}")
+
+
+def lock_consumer(project_dir: Path) -> None:
+ """Re-lock the consumer project so it picks up the freshly-vendored wheels.
+
+ uv lock preserves existing resolutions when constraints are already
+ satisfied, so a plain `uv lock` after vendoring keeps the old version.
+ --upgrade-package for each marin-* package forces re-resolution against
+ the new wheels in the vendor find-links directory.
+ """
+ upgrade_flags: list[str] = []
+ for pkg in PACKAGES:
+ upgrade_flags += ["--upgrade-package", pkg]
+ print(f"\nRe-locking {project_dir} ...")
+ subprocess.run(["uv", "lock", *upgrade_flags], check=True, cwd=project_dir)
+
+
+# ---------- publish ----------------------------------------------------------
+
+
+def _wheel_for(pkg: str) -> Path:
+ """Return the dist/ wheel matching pkg (uv normalises hyphens to underscores)."""
+ stem = pkg.replace("-", "_")
+ candidates = list(DIST_DIR.glob(f"{stem}-*.whl"))
+ if not candidates:
+ raise FileNotFoundError(f"No wheel for {pkg} in {DIST_DIR}")
+ if len(candidates) > 1:
+ raise RuntimeError(f"Multiple wheels for {pkg}: {[c.name for c in candidates]}")
+ return candidates[0]
+
+
+def _gh_release_replace(tag: str, files: list[Path], title: str, notes: str, prerelease: bool) -> None:
+ """Idempotently (re)create a GitHub release with the given assets."""
+ subprocess.run(
+ ["gh", "release", "delete", tag, "--yes", "--cleanup-tag", "--repo", REPO],
+ check=False,
+ capture_output=True,
+ )
+ cmd = [
+ "gh",
+ "release",
+ "create",
+ tag,
+ *[str(f) for f in files],
+ "--repo",
+ REPO,
+ "--title",
+ title,
+ "--notes",
+ notes,
+ ]
+ if prerelease:
+ cmd.append("--prerelease")
+ subprocess.run(cmd, check=True)
+
+
+def publish_releases(version: str, mode: str) -> None:
+ """Per-package GH release.
+
+ nightly -> overwrite the rolling marin--latest tag in place. No dated
+ tags are kept; consumers point find-links at the rolling URL
+ and always get the most recent build. Reproducibility comes
+ from stable tags, not from historical nightlies.
+ stable -> create marin--v and overwrite marin--stable.
+ """
+ _check_tool("gh", "https://cli.github.com/")
+
+ if mode == "nightly":
+ for pkg in PACKAGES:
+ wheel = _wheel_for(pkg)
+ tag = f"{pkg}-latest"
+ print(f"\n--- Publishing {tag} ({version}) ---")
+ _gh_release_replace(
+ tag=tag,
+ files=[wheel],
+ title=f"{pkg} (latest)",
+ notes=f"Rolling nightly. Currently pointing at {version}.",
+ prerelease=True,
+ )
+ return
+
+ if mode == "stable":
+ for pkg in PACKAGES:
+ wheel = _wheel_for(pkg)
+ for tag, label in ((f"{pkg}-v{version}", "stable"), (f"{pkg}-stable", "rolling stable")):
+ print(f"\n--- Publishing {tag} ---")
+ _gh_release_replace(
+ tag=tag,
+ files=[wheel],
+ title=f"{pkg} {version}",
+ notes=f"{pkg} {version} ({label})",
+ prerelease=False,
+ )
+ return
+
+ raise SystemExit(f"publish_releases called with unsupported mode: {mode}")
+
+
+# ---------- main -------------------------------------------------------------
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument("--mode", choices=["nightly", "stable", "manual", "vendor"], default="nightly")
+ parser.add_argument("--version", default=None, help="Required for --mode stable")
+ parser.add_argument(
+ "--vendor",
+ type=Path,
+ default=None,
+ help="Target directory to drop wheels into (required for --mode vendor)",
+ )
+ parser.add_argument("--skip-build", action="store_true", help="Reuse existing dist/")
+ parser.add_argument("--skip-publish", action="store_true", help="Build only")
+ parser.add_argument("--publish-only", action="store_true", help="Same as --skip-build")
+ args = parser.parse_args()
+
+ if args.publish_only:
+ args.skip_build = True
+
+ if args.mode == "vendor":
+ if args.vendor is None:
+ raise SystemExit("--vendor PATH is required for --mode vendor")
+ version = resolve_version(args.mode, args.version)
+ print(f"Mode: {args.mode}\nVersion: {version}")
+ build_wheels(version, args.mode)
+ vendor_target = args.vendor.expanduser().resolve()
+ vendor_copy(vendor_target)
+ lock_consumer(vendor_target.parent)
+ print("\nDone.")
+ return
+
+ if not args.skip_build:
+ version = resolve_version(args.mode, args.version)
+ print(f"Mode: {args.mode}\nVersion: {version}")
+ build_wheels(version, args.mode)
+ else:
+ if not DIST_DIR.exists() or not list(DIST_DIR.glob("*.whl")):
+ raise SystemExit(f"No wheels in {DIST_DIR}; remove --skip-build/--publish-only or build first.")
+ info = read_build_info()
+ if info is not None:
+ version = info["version"]
+ print(f"Mode: {args.mode}\nVersion: {version} (from BUILD_INFO.json)")
+ else:
+ # Legacy path: dist/ has wheels but no BUILD_INFO.json. Fall back
+ # to re-resolving; this is the only branch where the midnight-drift
+ # bug could resurface.
+ version = resolve_version(args.mode, args.version)
+ print(f"Mode: {args.mode}\nVersion: {version} (re-resolved; no BUILD_INFO.json)")
+
+ if args.skip_publish or args.mode == "manual":
+ print(f"\nBuild complete. Wheels in {DIST_DIR}/")
+ return
+
+ publish_releases(version, args.mode)
+
+ print("\nDone.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/uv.lock b/uv.lock
index 5c07f42571..c46033215c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -4638,7 +4638,7 @@ wheels = [
[[package]]
name = "marin"
-version = "0.1.0"
+version = "0.99"
source = { editable = "lib/marin" }
dependencies = [
{ name = "braceexpand" },
@@ -4959,7 +4959,7 @@ test = [
[[package]]
name = "marin-fray"
-version = "0.1.0"
+version = "0.99"
source = { editable = "lib/fray" }
dependencies = [
{ name = "click" },
@@ -5113,7 +5113,7 @@ dev = [
[[package]]
name = "marin-iris"
-version = "0.1.0"
+version = "0.99"
source = { editable = "lib/iris" }
dependencies = [
{ name = "click" },
@@ -5225,11 +5225,12 @@ examples = [
[[package]]
name = "marin-levanter"
-version = "1.2"
+version = "0.99"
source = { editable = "lib/levanter" }
dependencies = [
{ name = "async-lru" },
{ name = "braceexpand" },
+ { name = "chex" },
{ name = "dataclasses-json" },
{ name = "datasets" },
{ name = "deepdiff" },
@@ -5346,6 +5347,7 @@ test = [
requires-dist = [
{ name = "async-lru", specifier = "~=2.0" },
{ name = "braceexpand", specifier = ">=0.1.7" },
+ { name = "chex", specifier = ">=0.1.86" },
{ name = "dataclasses-json", specifier = "~=0.6.4" },
{ name = "datasets", specifier = ">=3.1.0,<5.0" },
{ name = "deepdiff" },
@@ -5436,7 +5438,7 @@ test = [
[[package]]
name = "marin-rigging"
-version = "0.1.0"
+version = "0.99"
source = { editable = "lib/rigging" }
dependencies = [
{ name = "fsspec" },
@@ -5494,7 +5496,7 @@ requires-dist = [
[[package]]
name = "marin-zephyr"
-version = "0.1.0"
+version = "0.99"
source = { editable = "lib/zephyr" }
dependencies = [
{ name = "braceexpand" },
@@ -6053,13 +6055,13 @@ name = "mlx-lm"
version = "0.29.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "jinja2", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
+ { name = "jinja2", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
{ name = "mlx", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
- { name = "numpy", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
- { name = "protobuf", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
- { name = "pyyaml", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
- { name = "sentencepiece", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
- { name = "transformers", marker = "sys_platform == 'darwin' or (sys_platform != 'linux' and extra == 'extra-5-marin-gpu') or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu') or (extra != 'extra-5-marin-gpu' and extra != 'extra-5-marin-tpu')" },
+ { name = "numpy", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
+ { name = "protobuf", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
+ { name = "pyyaml", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
+ { name = "sentencepiece", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
+ { name = "transformers", marker = "sys_platform == 'darwin' or (extra == 'extra-14-marin-levanter-gpu' and extra == 'extra-14-marin-levanter-tpu') or (extra == 'extra-5-marin-gpu' and extra == 'extra-5-marin-tpu')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e3/62/f46e1355256a114808517947f8e83ad6be310c7288c551db0fa678f47923/mlx_lm-0.29.1.tar.gz", hash = "sha256:b99180d8f33d33a077b814e550bfb2d8a59ae003d668fd1f4b3fff62a381d34b", size = 232302, upload-time = "2025-12-16T16:58:27.959Z" }
wheels = [