| #!/usr/bin/env fuchsia-vendored-python |
| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Download some remote artifacts before running a local command. |
| |
| Usage: |
| $0 [options...] -- command... |
| $0 [options...] && ... |
| """ |
| |
| import argparse |
| import multiprocessing |
| import os |
| import subprocess |
| import sys |
| |
| import cl_utils |
| import fuchsia |
| import remote_action |
| import remotetool |
| |
| from pathlib import Path |
| from typing import Dict, Iterable, Sequence, Tuple |
| |
| _SCRIPT_BASENAME = Path(__file__).name |
| |
| _PROJECT_ROOT = fuchsia.project_root_dir() |
| |
| # Needs to be computed with os.path.relpath instead of Path.relative_to |
| # to support testing a fake (test-only) value of PROJECT_ROOT. |
| _PROJECT_ROOT_REL = cl_utils.relpath(_PROJECT_ROOT, start=Path(os.curdir)) |
| |
| |
| def msg(text: str) -> None: |
| print(f"[{_SCRIPT_BASENAME}] {text}") |
| |
| |
| def vmsg(verbose: bool, text: str) -> None: |
| if verbose: |
| msg(text) |
| |
| |
| def _main_arg_parser() -> argparse.ArgumentParser: |
| parser = argparse.ArgumentParser( |
| description="Download from stubs and run a local command.", |
| argument_default=None, |
| ) |
| parser.add_argument( |
| "--verbose", |
| action="store_true", |
| default=False, |
| help="Show what is happening", |
| ) |
| parser.add_argument( |
| "--dry-run", |
| action="store_true", |
| default=False, |
| help="Do not download or run the command.", |
| ) |
| parser.add_argument( |
| "--undownload", |
| action="store_true", |
| default=False, |
| help="Restore download stubs, if they exist, and do not run the command.", |
| ) |
| parser.add_argument( |
| "--download", |
| default=[], |
| type=Path, |
| nargs="*", |
| help="Download these files from their stubs. Arguments are download stub files produced from 'remote_action.py', relative to the working dir.", |
| ) |
| parser.add_argument( |
| "--download_list", |
| default=[], |
| type=Path, |
| nargs="*", |
| help="Download these files named in these list files. Arguments are download stub files produced from 'remote_action.py', relative to the working dir.", |
| ) |
| # Positional args are the command and arguments to run. |
| parser.add_argument( |
| "command", nargs="*", help="The command to run remotely" |
| ) |
| return parser |
| |
| |
| _MAIN_ARG_PARSER = _main_arg_parser() |
| |
| |
| def download_artifacts( |
| stub_paths: Sequence[Path], |
| downloader: remotetool.RemoteTool, |
| working_dir_abs: Path, |
| verbose: bool = False, |
| dry_run: bool = False, |
| ) -> int: |
| """Download remotely stored artifacts. |
| |
| Args: |
| stub_paths: paths that point to either download stubs or real artifacts. |
| Real artifacts are ignored automatically. |
| """ |
| # The download_input_* variant is needed because in this script |
| # we are not guaranteed exclusive access to stubs, so downloads |
| # must be guarded by locking for mutual exclusion. |
| download_statuses = remote_action.download_input_stub_paths_batch( |
| downloader=downloader, |
| stub_paths=stub_paths, |
| working_dir_abs=working_dir_abs, |
| verbose=verbose, |
| ) |
| |
| final_status = 0 |
| for path, status in download_statuses.items(): |
| if status.returncode != 0: |
| final_status = status.returncode |
| msg(f"Error downloading {path}. stderr was:\n{status.stderr_text}") |
| |
| if final_status != 0: |
| msg("At least one download failed.") |
| |
| return final_status |
| |
| |
| def _main( |
| argv: Sequence[str], |
| downloader: remotetool.RemoteTool, |
| working_dir_abs: Path, |
| ) -> int: |
| main_args = _MAIN_ARG_PARSER.parse_args(argv) |
| |
| paths = set(main_args.download) |
| paths.update(cl_utils.expand_paths_from_files(main_args.download_list)) |
| |
| if main_args.undownload: |
| vmsg(main_args.verbose, f"Restoring download stubs.") |
| for p in paths: |
| # Fast, no need to parallelize. |
| remote_action.undownload(p) |
| |
| if main_args.command: |
| msg("Not running command, due to --undownload.") |
| return 0 |
| |
| return 0 |
| |
| # Download artifacts from their stubs. |
| status = download_artifacts( |
| stub_paths=list(paths), |
| downloader=downloader, |
| working_dir_abs=working_dir_abs, |
| verbose=main_args.verbose, |
| dry_run=main_args.dry_run, |
| ) |
| |
| if status != 0: |
| return status |
| |
| if main_args.command: |
| if main_args.dry_run: |
| msg("Stopping, due to --dry-run.") |
| return 0 |
| |
| return subprocess.call(main_args.command) |
| |
| return 0 |
| |
| |
| def main(argv: Sequence[str]) -> int: |
| cfg = _PROJECT_ROOT_REL / remote_action._REPROXY_CFG |
| downloader = remotetool.configure_remotetool(cfg) |
| return _main( |
| argv, |
| downloader=downloader, |
| working_dir_abs=Path(os.curdir).absolute(), |
| ) |
| |
| |
| if __name__ == "__main__": |
| remote_action.init_from_main_once() |
| sys.exit(main(sys.argv[1:])) |