configure.py (3403B)
1 #!/usr/bin/env python3 2 3 """Configure the taler-rust installation prefix and Rust toolchain.""" 4 5 from __future__ import annotations 6 7 import argparse 8 import os 9 from pathlib import Path 10 import re 11 import shutil 12 import subprocess 13 import sys 14 15 16 # Supported Rust baseline from trixie-backports; keep in sync with Cargo.toml. 17 RUST_MIN_VERSION = "1.95" 18 BUILD_VARIABLE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*=.*", re.DOTALL) 19 20 21 def version_tuple(version: str) -> tuple[int, int, int]: 22 parts = [int(part) for part in version.split(".")[:3]] 23 return tuple(parts + [0] * (3 - len(parts))) 24 25 26 def tool_version(path: str) -> str | None: 27 try: 28 output = subprocess.run( 29 [path, "--version"], 30 check=True, 31 stdout=subprocess.PIPE, 32 stderr=subprocess.PIPE, 33 text=True, 34 ).stdout 35 except (OSError, subprocess.SubprocessError): 36 return None 37 found = re.search(r"\d+(?:\.\d+)+", output) 38 return found.group(0) if found else None 39 40 41 def find_tool(name: str) -> tuple[str, str | None]: 42 requested = os.environ.get(name.upper()) or name 43 path = shutil.which(requested) 44 if path is None: 45 print(f"configure: error: tool '{name}' not available", file=sys.stderr) 46 print( 47 f"configure: hint: install '{name}' or point {name.upper()}= at it", 48 file=sys.stderr, 49 ) 50 raise SystemExit(1) 51 return path, tool_version(path) 52 53 54 def parse_args() -> argparse.Namespace: 55 parser = argparse.ArgumentParser( 56 usage="./configure [--prefix=DIR] [VARIABLE=VALUE]...", 57 description="Configure the installation prefix and Rust toolchain.", 58 ) 59 parser.add_argument("--prefix", default="/usr/local", help="installation prefix") 60 parser.add_argument("build_variables", nargs="*", metavar="VARIABLE=VALUE") 61 args = parser.parse_args() 62 if not args.prefix: 63 parser.error("installation prefix must not be empty") 64 for argument in args.build_variables: 65 if not BUILD_VARIABLE.fullmatch(argument): 66 parser.error(f"unrecognized argument: {argument}") 67 variable = argument.partition("=")[0] 68 print( 69 f"configure: WARNING: unsupported variable '{variable}' is ignored", 70 file=sys.stderr, 71 ) 72 return args 73 74 75 def main() -> int: 76 args = parse_args() 77 cargo_path, cargo_version = find_tool("cargo") 78 rustc_path, rustc_version = find_tool("rustc") 79 80 for name, path, version in ( 81 ("cargo", cargo_path, cargo_version), 82 ("rustc", rustc_path, rustc_version), 83 ): 84 suffix = f" (version {version})" if version else "" 85 print(f"found {name} as {path}{suffix}") 86 87 if rustc_version and version_tuple(rustc_version) < version_tuple(RUST_MIN_VERSION): 88 print( 89 f"configure: WARNING: rustc {rustc_version} is older than" 90 f" {RUST_MIN_VERSION}; this tree will not compile with it." 91 " On Debian stable: apt install -t trixie-backports rustc cargo", 92 file=sys.stderr, 93 ) 94 95 config = ( 96 "# This makefile fragment is generated by configure.\n" 97 f"prefix = {args.prefix}\n" 98 f"cargo = {cargo_path}\n" 99 f"rustc = {rustc_path}\n" 100 ) 101 Path(".config.mk").write_text(config, encoding="utf-8") 102 print("writing .config.mk") 103 return 0 104 105 106 if __name__ == "__main__": 107 sys.exit(main())