diff --git a/.gitignore b/.gitignore index a727c0a..4068ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /data +/dist diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index a60c66d..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(wtfnet LANGUAGES NONE) - -set(CARGO_CMD cargo) -set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/cargo-target") -set(BIN_NAME "wtfn${CMAKE_EXECUTABLE_SUFFIX}") -set(BIN_PATH "${CARGO_TARGET_DIR}/release/${BIN_NAME}") - -file(READ "${CMAKE_SOURCE_DIR}/crates/wtfnet-cli/Cargo.toml" CLI_TOML) -string(REGEX MATCH "version = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" CLI_VERSION_MATCH "${CLI_TOML}") -if(CMAKE_MATCH_1) - set(PACKAGE_VERSION "${CMAKE_MATCH_1}") -else() - set(PACKAGE_VERSION "0.1.0") -endif() - -add_custom_command( - OUTPUT "${BIN_PATH}" - COMMAND "${CMAKE_COMMAND}" -E env CARGO_TARGET_DIR="${CARGO_TARGET_DIR}" - "${CARGO_CMD}" build --release --workspace --bin wtfn - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - COMMENT "Building wtfn with cargo" - VERBATIM -) - -add_custom_target(wtfnet_build ALL DEPENDS "${BIN_PATH}") - -install(PROGRAMS "${BIN_PATH}" DESTINATION bin) -install(DIRECTORY "${CMAKE_SOURCE_DIR}/data" DESTINATION share/wtfnet) - -add_dependencies(install wtfnet_build) - -set(CPACK_PACKAGE_NAME "wtfnet") -set(CPACK_PACKAGE_VERSION "${PACKAGE_VERSION}") -set(CPACK_PACKAGE_FILE_NAME "wtfnet-${PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") -if(WIN32) - set(CPACK_GENERATOR "ZIP") -else() - set(CPACK_GENERATOR "TGZ") -endif() -include(CPack) diff --git a/Makefile b/Makefile deleted file mode 100644 index ed27855..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -BUILD_DIR ?= build - -.PHONY: build configure package install clean - -configure: - cmake -S . -B $(BUILD_DIR) - -build: configure - cmake --build $(BUILD_DIR) - -package: build - cmake --build $(BUILD_DIR) --target package - -install: build - cmake --build $(BUILD_DIR) --target install - -clean: - cmake -E rm -rf $(BUILD_DIR) diff --git a/justfile b/justfile new file mode 100644 index 0000000..829afe6 --- /dev/null +++ b/justfile @@ -0,0 +1,13 @@ +# justfile (cross-platform, no bash) +python := env_var_or_default("PYTHON", if os() == "windows" { "python" } else { "python3" }) +dist_dir := "dist" +stage_root := "target/release-package" + +default: + @just --list + +release bin='' target='': + {{python}} scripts/release_meta.py --bin "{{bin}}" --target "{{target}}" --dist-dir "{{dist_dir}}" --stage-root "{{stage_root}}" + +clean-dist: + {{python}} -c "import shutil; shutil.rmtree('dist', ignore_errors=True); shutil.rmtree('target/release-package', ignore_errors=True)" diff --git a/scripts/release_meta.py b/scripts/release_meta.py new file mode 100644 index 0000000..752ad6f --- /dev/null +++ b/scripts/release_meta.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import os +import platform +import shutil +import subprocess +import sys +import tarfile +import zipfile +from pathlib import Path +from typing import Any + + +def run(cmd: list[str], *, capture: bool = False) -> str: + if capture: + return subprocess.check_output(cmd, text=True).strip() + subprocess.check_call(cmd) + return "" + + +def cargo_metadata() -> dict[str, Any]: + out = run(["cargo", "metadata", "--no-deps", "--format-version", "1"], capture=True) + return json.loads(out) + + +def rustc_host_triple() -> str: + v = run(["rustc", "-vV"], capture=True) + for line in v.splitlines(): + if line.startswith("host: "): + return line.split("host: ", 1)[1].strip() + raise RuntimeError("Could not determine host target triple from `rustc -vV`") + + +def is_windows_host() -> bool: + # Works for normal Windows Python and most MSYS/Cygwin Pythons too. + sp = sys.platform.lower() + ps = platform.system().lower() + return ( + os.name == "nt" + or sp.startswith("win") + or sp.startswith("cygwin") + or sp.startswith("msys") + or "windows" in ps + or "cygwin" in ps + or "msys" in ps + ) + + +def exe_suffix_for_target(target_triple: str) -> str: + return ".exe" if "windows" in target_triple else "" + + +def find_bin_targets(meta: dict[str, Any]) -> list[tuple[str, str, str]]: + bins: list[tuple[str, str, str]] = [] + for p in meta.get("packages", []): + for t in p.get("targets", []): + if "bin" in t.get("kind", []): + bins.append((p["name"], p["version"], t["name"])) + bins.sort(key=lambda x: (x[0], x[2], x[1])) # stable deterministic choice + return bins + + +def find_owner_package_for_bin(meta: dict[str, Any], bin_name: str) -> tuple[str, str]: + for p in meta.get("packages", []): + for t in p.get("targets", []): + if t.get("name") == bin_name and "bin" in t.get("kind", []): + return p["name"], p["version"] + raise RuntimeError(f"Could not find a package providing bin '{bin_name}'") + + +def stage_and_archive( + *, + pkg_name: str, + pkg_version: str, + bin_path: Path, + data_dir: Path, + dist_dir: Path, + stage_root: Path, + target_triple_for_name: str, +) -> Path: + pkg_base = f"{pkg_name}-v{pkg_version}-{target_triple_for_name}" + stage_dir = stage_root / pkg_base + stage_data_dir = stage_dir / "data" + + if stage_root.exists(): + shutil.rmtree(stage_root) + stage_data_dir.mkdir(parents=True, exist_ok=True) + dist_dir.mkdir(parents=True, exist_ok=True) + + shutil.copy2(bin_path, stage_dir / bin_path.name) + + mmdbs = sorted(data_dir.glob("*.mmdb")) if data_dir.exists() else [] + if mmdbs: + for f in mmdbs: + shutil.copy2(f, stage_data_dir / f.name) + else: + print("WARN: no ./data/*.mmdb found; packaging binary only.", file=sys.stderr) + + if is_windows_host(): + out = dist_dir / f"{pkg_base}.zip" + with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as z: + for p in stage_dir.rglob("*"): + if p.is_file(): + z.write(p, arcname=str(Path(pkg_base) / p.relative_to(stage_dir))) + return out + else: + out = dist_dir / f"{pkg_base}.tar.gz" + with tarfile.open(out, "w:gz") as tf: + tf.add(stage_dir, arcname=pkg_base) + return out + + +def main() -> int: + ap = argparse.ArgumentParser(description="Build and package Rust binary + data/*.mmdb") + ap.add_argument("--bin", default="", help="Binary target name (optional)") + ap.add_argument("--target", default="", help="Cargo target triple (optional)") + ap.add_argument("--dist-dir", default="dist", help="Output directory for archives") + ap.add_argument("--stage-root", default="target/release-package", help="Staging directory root") + ap.add_argument("--data-dir", default="data", help="Directory containing .mmdb files") + args = ap.parse_args() + + meta = cargo_metadata() + bins = find_bin_targets(meta) + if not bins: + print("ERROR: no binary targets found in workspace.", file=sys.stderr) + return 2 + + bin_name = args.bin.strip() + if not bin_name: + _, _, bin_name = bins[0] + print(f"INFO: --bin not provided; defaulting to '{bin_name}'", file=sys.stderr) + + pkg_name, pkg_version = find_owner_package_for_bin(meta, bin_name) + + host_triple = rustc_host_triple() + target_triple_for_name = args.target.strip() or host_triple + + # Build only the owning package + build_cmd = ["cargo", "build", "-p", pkg_name, "--release"] + if args.target.strip(): + build_cmd += ["--target", args.target.strip()] + run(build_cmd) + + # Locate binary + exe_suffix = exe_suffix_for_target(target_triple_for_name) + bin_dir = Path("target") / (args.target.strip() if args.target.strip() else "release") / "release" \ + if args.target.strip() else Path("target") / "release" + if args.target.strip(): + bin_dir = Path("target") / args.target.strip() / "release" + + bin_path = bin_dir / f"{bin_name}{exe_suffix}" + if not bin_path.exists(): + print(f"ERROR: built binary not found: {bin_path}", file=sys.stderr) + print("Hint: pass the correct bin target name: just release bin=", file=sys.stderr) + return 3 + + out = stage_and_archive( + pkg_name=pkg_name, + pkg_version=pkg_version, + bin_path=bin_path, + data_dir=Path(args.data_dir), + dist_dir=Path(args.dist_dir), + stage_root=Path(args.stage_root), + target_triple_for_name=target_triple_for_name, + ) + + print(f"Created: {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())