1#!/usr/bin/env vpython3 2# Copyright 2021 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import argparse 7import os 8import stat 9import sys 10 11sys.path.append( 12 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, 13 'build')) 14import action_helpers 15 16 17def _parse_args(args): 18 description = 'Generator of bash scripts that can invoke the Python ' \ 19 'library for running Rust unit tests with support for ' \ 20 'Chromium test filters, sharding, and test output.' 21 parser = argparse.ArgumentParser(description=description) 22 parser.add_argument('--script-path', 23 dest='script_path', 24 help='Where to write the bash script.', 25 metavar='FILEPATH', 26 required=True) 27 parser.add_argument('--exe-dir', 28 dest='exe_dir', 29 help='Directory where the wrapped executables are', 30 metavar='PATH', 31 required=True) 32 parser.add_argument('--rust-test-executables', 33 dest='rust_test_executables', 34 help='File listing one or more executables to wrap. ' \ 35 '(basenames - no .exe extension or directory)', 36 metavar='FILEPATH', 37 required=True) 38 parser.add_argument('--make-bat', 39 action='store_true', 40 help='Generate a .bat file instead of a bash script') 41 return parser.parse_args(args=args) 42 43 44def _find_test_executables(args): 45 exes = set() 46 input_filepath = args.rust_test_executables 47 with open(input_filepath) as f: 48 for line in f: 49 exe_name = line.strip() 50 # Append ".exe" extension when *targeting* Windows, not when this 51 # script is running on windows. We do that by using `args.make_bat` 52 # as a signal. 53 if exe_name in exes: 54 raise ValueError( 55 f'Duplicate entry "{exe_name}" in {input_filepath}') 56 if args.make_bat: 57 suffix = ".exe" 58 else: 59 suffix = "" 60 exes.add(f'{exe_name}{suffix}') 61 if not exes: 62 raise ValueError(f'Unexpectedly empty file: {input_filepath}') 63 exes = sorted(exes) # For stable results in unit tests. 64 return exes 65 66 67def _validate_if_test_executables_exist(exes): 68 for exe in exes: 69 if not os.path.isfile(exe): 70 raise ValueError(f'File not found: {exe}') 71 72 73def _generate_script(args, should_validate_if_exes_exist=True): 74 THIS_DIR = os.path.abspath(os.path.dirname(__file__)) 75 GEN_SCRIPT_DIR = os.path.dirname(args.script_path) 76 77 # Path from the .bat or bash script to the test exes. 78 exe_dir = os.path.relpath(args.exe_dir, start=GEN_SCRIPT_DIR) 79 exe_dir = os.path.normpath(exe_dir) 80 81 # Path from the .bat or bash script to the python main. 82 main_dir = os.path.relpath(THIS_DIR, start=GEN_SCRIPT_DIR) 83 main_dir = os.path.normpath(main_dir) 84 85 exes = _find_test_executables(args) 86 if should_validate_if_exes_exist: 87 _validate_if_test_executables_exist(exes) 88 89 if args.make_bat: 90 res = '@echo off\n' 91 res += f'vpython3 "%~dp0\\{main_dir}\\rust_main_program.py" ^\n' 92 for exe in exes: 93 res += f' "--rust-test-executable=%~dp0\\{exe_dir}\\{exe}" ^\n' 94 res += ' %*' 95 else: 96 res = '#!/bin/bash\n' 97 res += (f'env vpython3 ' 98 f'"$(dirname $0)/{main_dir}/rust_main_program.py" \\\n') 99 for exe in exes: 100 res += ( 101 f' ' 102 f'"--rust-test-executable=$(dirname $0)/{exe_dir}/{exe}" \\\n') 103 res += ' "$@"' 104 return res 105 106 107def _main(): 108 args = _parse_args(sys.argv[1:]) 109 110 # atomic_output will ensure we only write to the file on disk if what we 111 # give to write() is different than what's currently on disk. 112 with action_helpers.atomic_output(args.script_path) as f: 113 f.write(_generate_script(args).encode()) 114 115 # chmod a+x 116 st = os.stat(args.script_path) 117 if (not st.st_mode & stat.S_IXUSR) or (not st.st_mode & stat.S_IXGRP) or \ 118 (not st.st_mode & stat.S_IXOTH): 119 os.chmod(args.script_path, 120 st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) 121 122 123if __name__ == '__main__': 124 _main() 125