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