taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

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())