#!/usr/bin/env python3 ''' Author: blob42 Email: contact@blob42.xyz Github: https://github.com/blob42 Description: run cargo commands on a remote workstation/server ''' import argparse from invoke.exceptions import UnexpectedExit from fabric import Connection import sys from pathlib import Path import shutil import sys import os from rich import print import logging from rich.logging import RichHandler LOG_FORMAT = '%(message)s' logging.basicConfig( level='WARN', format=LOG_FORMAT, datefmt='[%X]', handlers=[RichHandler(markup=True)] ) log = logging.getLogger('rcargo') CARGO_BIN_PATH='~/.cargo/bin/' LOCAL_BIN_PATH='' DEPS = [ 'rsync' ] ERR_CMD = 1 ERR_DEPENDENCY = 2 #TODO: binary and package names might differ: use `cargo install --list to extract bin name` def inst_get_binary(conn: Connection, package: str, debug: bool = False) -> None: find_bin = f'cargo install --list | grep -A1 {package} | sed -nE "s/ *//; 2 p"' bin_name = '' try: res = conn.run(find_bin, echo=False, hide='out') if len(res.stdout) == 0: raise ValueError bin_name = res.stdout.strip() log.info(f'fetching binary [bold blue]{bin_name}[/bold blue] from package [bold green]{package}[/bold green]') except Exception as e: raise e bin_path = Path(LOCAL_BIN_PATH, bin_name).expanduser() local_path = Path(LOCAL_BIN_PATH).expanduser().__str__()+'/' try: if os.path.exists(bin_path): os.remove(bin_path) conn.get(bin_path, local=local_path) except FileNotFoundError as e: print('could not copy installed package {} from remote'.format(package)) sys.exit(ERR_CMD) print(f'installed [bold blue]{bin_name}[/bold blue] at {LOCAL_BIN_PATH}') def list(args): """List installed rust binaries on remote host""" conn = Connection(args.host) cargo_list = conn.run("cargo install --list | grep 'v.*:$'", echo=True) # print(f"{cargo_list.stdout.strip()}") if cargo_list.failed: print(cargo_list.stderr) sys.exit(ERR_CMD) def install(args): """Install rust packages from crates.io on remote host and copy binary to local machine.""" args.debug and print(f'{args}') print(f'{LOCAL_BIN_PATH}') # args could be a package name or a list of parameters for cargo install package = '' params = [] if len(args.package) == 1 and not args.package[0].startswith('-'): package = args.package[0] else: for arg in args.package: if arg.startswith('--') or arg.startswith('-'): params.append(arg) else: package = arg # if len(package) == 0: # print('no package specified') # sys.exit(ERR_CMD) args.debug and print(f"package {package}, params: {params}") conn = Connection(args.host) param_str = ' '.join(params) cargo_install = conn.run(f'cargo install {param_str} {package}', warn=True, echo=True) # print('{}'.format(cargo_install.stdout.strip())) if cargo_install.failed: print(cargo_install.stderr) sys.exit(ERR_CMD) if len(package) > 0: inst_get_binary(conn, package, args.debug) def ensure_deps(): for dep in DEPS: installed = shutil.which(dep) if installed is None: print('{} dependency missing'.format(dep), file=sys.stderr) sys.exit(ERR_DEPENDENCY) # def any_cmd(args): # conn = Connection(args.host) # run = conn.run(f'cargo {args.command}', echo=True) if __name__ == '__main__': ensure_deps() default_host = os.environ.get('RCARGO_HOST', 'localhost') parser = argparse.ArgumentParser(description='remote rust cargo commands') parser.add_argument('-H', '--host', default=default_host, help="remote cargo host") parser.add_argument('-d', '--debug', action='store_true') parser.add_argument('-b', '--bin-path', default=CARGO_BIN_PATH, help='local path for installed binary', metavar='') subparsers = parser.add_subparsers(dest='command') list_parser = subparsers.add_parser('list', help='list installed packages on remote host') list_parser.set_defaults(func=list) install_parser = subparsers.add_parser('install', prefix_chars="+", help='install packages from crates.io with remote build') install_parser.set_defaults(func=install) install_parser.add_argument('package', nargs="*", help='package name', metavar='PACKAGE_NAME') build = subparsers.add_parser('build', help='remote build local cargo package') args = parser.parse_args() LOCAL_BIN_PATH = args.bin_path if args.debug: log.setLevel('INFO') if args.host == 'localhost': print('you need to define a host with -H option or RCARGO_HOST env variable') sys.exit(1) if args.command is not None: args.func(args) else: parser.print_help()