#!/usr/bin/env python3 # stdlib imports import argparse import os import shutil import subprocess import sys import textwrap # stdlib "from" imports from argparse import ArgumentParser, Namespace from pathlib import Path # "local" imports from config import Config from typing import Optional _SCRIPT_NAME = Path(sys.argv[0]).name _SCRIPT_DIR = Path(sys.argv[0]).parent.resolve() _TOP_LEVEL = Path( subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip() ) class ChutneyBinResolver: _DEST = "chutney_bin" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--chutney-bin", dest=self._DEST, help="chutney executable") def resolve(self, args: Namespace) -> Path: res_str = getattr(args, self._DEST) or shutil.which("chutney") if res_str is None: print(textwrap.dedent(""" chutney (https://gitlab.torproject.org/tpo/core/chutney) not found. Try setting --chutney-bin or ensuring it's on your PATH. quick install: python3 -m pip install git+https://gitlab.torproject.org/tpo/core/chutney.git """)) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"chutney resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ArtiBinResolver: _DEST = "arti_bin" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument("--arti-bin", dest=self._DEST, help="arti executable") def resolve(self, args: Namespace) -> Path: res_str = getattr(args, self._DEST) if res_str is None: for s in [ "target/x86_64-unknown-linux-gnu/quicktest/arti", "target/quicktest/arti", "target/x86_64-unknown-linux-gnu/debug/arti", "target/debug/arti", ]: p = _TOP_LEVEL.joinpath(Path(s)) if p.exists(): res_str = str(p) break if res_str is None: print(textwrap.dedent(""" arti client not found. Try setting --arti-bin or building one in this repo: cargo build --locked --profile=quicktest -p arti """)) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"arti resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ArtiExtraBinResolver: _DEST = "arti_extra_bin" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--arti-extra-bin", dest=self._DEST, help="arti executable with extra features", ) def resolve(self, args: Namespace) -> Path: res_str = getattr(args, self._DEST) if res_str is None: for s in [ "target/x86_64-unknown-linux-gnu/quicktest/arti-extra", "target/quicktest/arti-extra", "target/x86_64-unknown-linux-gnu/debug/arti-extra", "target/debug/arti-extra", ]: p = _TOP_LEVEL.joinpath(Path(s)) if p.exists(): res_str = str(p) break if res_str is None: print(textwrap.dedent(""" arti-extra client not found. Try setting --arti-extra-bin or building one in this repo. # See .rust-recent-arti-extra-features-template in .gitlab-ci.yml for current # --features list. $ cargo build --locked --profile=quicktest -p arti --features=full,experimental $ mv target/quicktest/arti target/quicktest/arti-extra """)) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"arti resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ArtiBenchBinResolver: _DEST = "arti_bench_bin" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--arti-bench-bin", dest=self._DEST, help="arti-bench executable" ) def resolve(self, args: Namespace) -> Path: res_str = getattr(args, self._DEST) if res_str is None: for s in [ "target/x86_64-unknown-linux-gnu/release/arti-bench", "target/release/arti-bench", "target/x86_64-unknown-linux-gnu/quicktest/arti-bench", "target/quicktest/arti-bench", "target/x86_64-unknown-linux-gnu/debug/arti-bench", "target/debug/arti-bench", ]: p = _TOP_LEVEL.joinpath(Path(s)) if p.exists(): res_str = str(p) break if res_str is None: print(textwrap.dedent(""" arti-bench not found. Try setting --arti-bench-bin or building one in this repo: cargo build --locked --profile=quicktest -p arti-bench """)) sys.exit(1) res = Path(res_str) if not os.access(res, os.X_OK): print(f"arti-bench resolved to {res}, but it isn't an executable file") sys.exit(1) return res class ChutneyDataDirResolver: _DEST = "chutney_data_dir" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--chutney-data-dir", dest=self._DEST, help="directory for chutney to put its data", ) def resolve(self, args: Namespace) -> Path: return Path( getattr(args, self._DEST) or os.getenv("CHUTNEY_DATA_DIR") or _TOP_LEVEL.joinpath("chutney-net") ) class ChutneyNetworkResolver: _DEST = "network" def __init__(self, parser: ArgumentParser) -> None: parser.add_argument( "--network", "-n", dest=self._DEST, help="flag to 'chutney init' specifying network to use." " (Default: built-in network-builder)", ) def resolve(self, args: Namespace) -> Optional[str]: res: Optional[str] = getattr(args, self._DEST) return res class ConfigResolver: def __init__(self, parser: ArgumentParser) -> None: self._chutney_resolver = ChutneyBinResolver(parser) self._arti_resolver = ArtiBinResolver(parser) self._arti_extra_resolver = ArtiExtraBinResolver(parser) self._arti_bench_resolver = ArtiBenchBinResolver(parser) self._chutney_data_dir_resolver = ChutneyDataDirResolver(parser) self._network_resolver = ChutneyNetworkResolver(parser) def resolve(self, args: Namespace) -> Config: return Config( chutney=str(self._chutney_resolver.resolve(args)), arti=str(self._arti_resolver.resolve(args)), arti_extra=str(self._arti_extra_resolver.resolve(args)), arti_bench=str(self._arti_bench_resolver.resolve(args)), chutney_data_dir=str(self._chutney_data_dir_resolver.resolve(args)), network=self._network_resolver.resolve(args), ) def _main() -> None: # Set up command-line parser, registering config options parser = argparse.ArgumentParser( prog=_SCRIPT_NAME, description="Configure a chutney testbed" ) config_resolver = ConfigResolver(parser) args = parser.parse_args() config = config_resolver.resolve(args) config.dump_json(_SCRIPT_DIR.joinpath("arti.run.json")) if __name__ == "__main__": _main()