configure.py (3978B)
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 # Oldest Rust release that can build this tree. Keep the reason for the number 17 # next to it -- the point of the check is to report an unusable toolchain here, 18 # once, instead of as a wall of parse errors in the middle of the build. 19 # 20 # 1.85 edition 2024 / resolver "3" (Cargo.toml) 21 # 1.86 Vec::pop_if (common/failure-injection) 22 # 1.88 let chains (used throughout) 23 # 1.93 std::fmt::from_fn (taler-common, taler-api, taler-wise) 24 # 25 # Debian trixie ships 1.85, which is too old; trixie-backports and testing both 26 # ship a new enough rustc. Keep this in step with `rust-version` in Cargo.toml. 27 RUST_MIN_VERSION = "1.93" 28 BUILD_VARIABLE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*=.*", re.DOTALL) 29 30 31 def version_tuple(version: str) -> tuple[int, int, int]: 32 parts = [int(part) for part in version.split(".")[:3]] 33 return tuple(parts + [0] * (3 - len(parts))) 34 35 36 def tool_version(path: str) -> str | None: 37 try: 38 output = subprocess.run( 39 [path, "--version"], 40 check=True, 41 stdout=subprocess.PIPE, 42 stderr=subprocess.PIPE, 43 text=True, 44 ).stdout 45 except (OSError, subprocess.SubprocessError): 46 return None 47 found = re.search(r"\d+(?:\.\d+)+", output) 48 return found.group(0) if found else None 49 50 51 def find_tool(name: str) -> tuple[str, str | None]: 52 requested = os.environ.get(name.upper()) or name 53 path = shutil.which(requested) 54 if path is None: 55 print(f"configure: error: tool '{name}' not available", file=sys.stderr) 56 print( 57 f"configure: hint: install '{name}' or point {name.upper()}= at it", 58 file=sys.stderr, 59 ) 60 raise SystemExit(1) 61 return path, tool_version(path) 62 63 64 def parse_args() -> argparse.Namespace: 65 parser = argparse.ArgumentParser( 66 usage="./configure [--prefix=DIR] [VARIABLE=VALUE]...", 67 description="Configure the installation prefix and Rust toolchain.", 68 ) 69 parser.add_argument("--prefix", default="/usr/local", help="installation prefix") 70 parser.add_argument("build_variables", nargs="*", metavar="VARIABLE=VALUE") 71 args = parser.parse_args() 72 if not args.prefix: 73 parser.error("installation prefix must not be empty") 74 for argument in args.build_variables: 75 if not BUILD_VARIABLE.fullmatch(argument): 76 parser.error(f"unrecognized argument: {argument}") 77 variable = argument.partition("=")[0] 78 print( 79 f"configure: WARNING: unsupported variable '{variable}' is ignored", 80 file=sys.stderr, 81 ) 82 return args 83 84 85 def main() -> int: 86 args = parse_args() 87 cargo_path, cargo_version = find_tool("cargo") 88 rustc_path, rustc_version = find_tool("rustc") 89 90 for name, path, version in ( 91 ("cargo", cargo_path, cargo_version), 92 ("rustc", rustc_path, rustc_version), 93 ): 94 suffix = f" (version {version})" if version else "" 95 print(f"found {name} as {path}{suffix}") 96 97 if rustc_version and version_tuple(rustc_version) < version_tuple(RUST_MIN_VERSION): 98 print( 99 f"configure: WARNING: rustc {rustc_version} is older than" 100 f" {RUST_MIN_VERSION}; this tree will not compile with it." 101 " On Debian stable: apt install -t trixie-backports rustc cargo", 102 file=sys.stderr, 103 ) 104 105 config = ( 106 "# This makefile fragment is generated by configure.\n" 107 f"prefix = {args.prefix}\n" 108 f"cargo = {cargo_path}\n" 109 f"rustc = {rustc_path}\n" 110 ) 111 Path(".config.mk").write_text(config, encoding="utf-8") 112 print("writing .config.mk") 113 return 0 114 115 116 if __name__ == "__main__": 117 sys.exit(main())