uv tool install git+https://gist.github.com/ochafik/d4d49db7c9844eb85e9d161fdfafaf44
crun https://github.com/ggml-org/llama.cpp -- llama-server --fim-qwen-14b-spec
crun https://github.com/ggml-org/llama.cpp -- llama-tts --tts-oute-default -p 'Hello olivier how are we doing today?' && open output.wav
-
-
Save ochafik/d4d49db7c9844eb85e9d161fdfafaf44 to your computer and use it in GitHub Desktop.
crun: to make, what npx is to npm and uvx to uv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| crun - cmake runner (npx for cmake projects) | |
| Usage: | |
| crun <repo-url>[@branch] [cmake-args] [-- bin [args]] | |
| Examples: | |
| crun https://github.com/ggml-org/llama.cpp -- llama-server --fim-qwen-14b-spec | |
| crun https://github.com/ggml-org/llama.cpp -- llama-tts --tts-oute-default -p 'Hello olivier how are we doing today?' && open output.wav | |
| """ | |
| import sys, subprocess, tempfile, hashlib | |
| from pathlib import Path | |
| from urllib.parse import urlparse | |
| from pydantic import BaseModel, Field, field_validator | |
| class Config(BaseModel): | |
| """Build configuration""" | |
| url: str | |
| branch: str | None = None | |
| cmake_args: list[str] = Field(default_factory=list) | |
| build_type: str = "Release" | |
| bin_name: str | None = None | |
| bin_args: list[str] = Field(default_factory=list) | |
| @field_validator("url") | |
| @classmethod | |
| def normalize_url(cls, v: str) -> str: | |
| return v.removesuffix(".git") | |
| @property | |
| def repo_id(self) -> str: | |
| """Unique repo identifier for caching""" | |
| p = urlparse(self.url) | |
| return f"{p.netloc or 'local'}__{p.path.lstrip('/').replace('/', '__').replace('..', '_')}" | |
| @property | |
| def ref(self) -> str: | |
| """Git ref to checkout""" | |
| return self.branch or "HEAD" | |
| def build_cache_key(self, ref: str) -> str: | |
| """Unique build cache key: repo + ref + cmake args""" | |
| args_str = " ".join(sorted(self.cmake_args)) | |
| combined = f"{self.repo_id}::{ref}::{args_str}" | |
| hash_hex = hashlib.sha256(combined.encode()).hexdigest()[:16] | |
| return hash_hex | |
| def parse_args() -> Config: | |
| """Parse command-line arguments into Config""" | |
| if len(sys.argv) < 2: | |
| sys.exit("Usage: crun <repo-url>[@branch] [cmake-args] [-- bin [args]]") | |
| url, *args = sys.argv[1:] | |
| branch = url.split("@", 1)[1] if "@" in url else None | |
| url = url.split("@")[0] | |
| sep = args.index("--") if "--" in args else len(args) | |
| cmake_args, bin_args = args[:sep], args[sep+1:] | |
| # Extract CMAKE_BUILD_TYPE | |
| build_type = "Release" | |
| filtered_args = [] | |
| for arg in cmake_args: | |
| if "CMAKE_BUILD_TYPE=" in arg: | |
| build_type = arg.split("=")[1] | |
| elif "CMAKE_BUILD_TYPE" not in arg: | |
| filtered_args.append(arg) | |
| return Config( | |
| url=url, | |
| branch=branch, | |
| cmake_args=[*filtered_args, f"-DCMAKE_BUILD_TYPE={build_type}"], | |
| build_type=build_type, | |
| bin_name=bin_args[0] if bin_args else None, | |
| bin_args=bin_args[1:] if len(bin_args) > 1 else [] | |
| ) | |
| def run(cmd: list[str], **kw) -> None: | |
| """Execute command, raising on failure""" | |
| subprocess.run(cmd, check=True, **kw) | |
| def setup_repo(cfg: Config, cache: Path) -> Path: | |
| """Clone or update repository, return repo directory""" | |
| repo_dir = cache / "repos" / cfg.repo_id | |
| repo_dir.parent.mkdir(parents=True, exist_ok=True) | |
| if repo_dir.exists(): | |
| print(f"Fetching updates for {cfg.repo_id}") | |
| run(["git", "-C", str(repo_dir), "fetch", "--depth", "1", "origin", cfg.ref]) | |
| else: | |
| clone_args = ["git", "clone", "--depth", "1"] | |
| if cfg.branch: | |
| clone_args.extend(["--branch", cfg.branch, "--single-branch"]) | |
| clone_args.extend([cfg.url, str(repo_dir)]) | |
| print(f"Cloning {cfg.url}" + (f"@{cfg.branch}" if cfg.branch else "")) | |
| run(clone_args) | |
| return repo_dir | |
| def create_worktree(cfg: Config, repo_dir: Path, cache: Path) -> tuple[Path, Path]: | |
| """Create git worktree and persistent build directory""" | |
| # Reuse worktree if it exists for this ref | |
| work_dir = cache / "worktrees" / f"{cfg.repo_id}.{cfg.ref.replace('/', '_')}" | |
| if work_dir.exists(): | |
| print(f"Reusing worktree for {cfg.ref}") | |
| else: | |
| work_dir.parent.mkdir(parents=True, exist_ok=True) | |
| print(f"Checking out {cfg.ref}") | |
| try: | |
| run(["git", "-C", str(repo_dir), "worktree", "add", str(work_dir), cfg.ref]) | |
| except subprocess.CalledProcessError: | |
| print(f"Fetching missing ref: {cfg.ref}") | |
| run(["git", "-C", str(repo_dir), "fetch", "--depth", "1", "origin", cfg.ref]) | |
| run(["git", "-C", str(repo_dir), "worktree", "add", str(work_dir), "FETCH_HEAD"]) | |
| # Persistent build directory based on repo + ref + cmake args | |
| build_key = cfg.build_cache_key(cfg.ref) | |
| build_dir = cache / "builds" / build_key | |
| build_dir.mkdir(parents=True, exist_ok=True) | |
| return work_dir, build_dir | |
| def build_project(cfg: Config, work_dir: Path, build_dir: Path) -> Path: | |
| """Build with CMake, return build directory""" | |
| print(f"Building ({cfg.build_type}) in {build_dir.name}") | |
| run(["cmake", "-S", str(work_dir), "-B", str(build_dir), *cfg.cmake_args]) | |
| run(["cmake", "--build", str(build_dir), "--parallel", "--config", cfg.build_type, "-t", cfg.bin_name or "all"]) | |
| return build_dir | |
| def run_binary(cfg: Config, build_dir: Path) -> None: | |
| """Execute binary if specified""" | |
| if not cfg.bin_name: | |
| return | |
| exe = build_dir / cfg.bin_name | |
| if not exe.exists(): | |
| exe = build_dir / cfg.build_type / cfg.bin_name # Try config subdir | |
| if not exe.exists(): | |
| exe = build_dir / "bin" / cfg.bin_name # Try bin subdir | |
| if not exe.exists(): | |
| sys.exit(f"Error: Binary '{cfg.bin_name}' not found in build directory") | |
| print(f"Running {exe.name}") | |
| subprocess.run([str(exe), *cfg.bin_args]) # Let exit code through | |
| def main() -> None: | |
| cfg = parse_args() | |
| cache = Path.home() / ".cache" / "crun" | |
| repo_dir = setup_repo(cfg, cache) | |
| work_dir, build_dir = create_worktree(cfg, repo_dir, cache) | |
| build_project(cfg, work_dir, build_dir) | |
| run_binary(cfg, build_dir) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [build-system] | |
| requires = ["hatchling"] | |
| build-backend = "hatchling.build" | |
| [project] | |
| name = "crun" | |
| version = "0.1.0" | |
| description = "CMake runner - npx for cmake projects" | |
| readme = "README.md" | |
| requires-python = ">=3.11" | |
| dependencies = ["pydantic>=2.0"] | |
| [project.scripts] | |
| crun = "crun:main" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment