1# Copyright 2023 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Merge multiple profraws together using llvm-profdata.""" 15 16import argparse 17import enum 18import json 19import logging 20import subprocess 21import sys 22from pathlib import Path 23from typing import Any 24 25_LOG = logging.getLogger(__name__) 26 27 28class FailureMode(enum.Enum): 29 ANY = 'any' 30 ALL = 'all' 31 32 def __str__(self): 33 return self.value 34 35 36def _parse_args() -> dict[str, Any]: 37 parser = argparse.ArgumentParser(description=__doc__) 38 parser.add_argument( 39 '--llvm-profdata-path', 40 type=Path, 41 required=True, 42 help='Path to the llvm-profdata binary to use for merging.', 43 ) 44 parser.add_argument( 45 '--test-metadata-path', 46 type=Path, 47 required=True, 48 help='Path to the *.test_metadata.json file that describes all of the ' 49 'tests being used to generate a coverage report.', 50 ) 51 parser.add_argument( 52 '--profdata-path', 53 type=Path, 54 required=True, 55 help='Path for the output merged profdata file to use with generating a' 56 ' coverage report for the tests described in --test-metadata.', 57 ) 58 parser.add_argument( 59 '--depfile-path', 60 type=Path, 61 required=True, 62 help='Path for the output depfile to convey the extra input ' 63 'requirements from parsing --test-metadata.', 64 ) 65 parser.add_argument( 66 '--failure-mode', 67 type=FailureMode, 68 choices=list(FailureMode), 69 required=False, 70 default=FailureMode.ANY, 71 help='Sets the llvm-profdata --failure-mode option.', 72 ) 73 return vars(parser.parse_args()) 74 75 76def merge_profraws( 77 llvm_profdata_path: Path, 78 test_metadata_path: Path, 79 profdata_path: Path, 80 depfile_path: Path, 81 failure_mode: FailureMode, 82) -> int: 83 """Merge multiple profraws together using llvm-profdata.""" 84 85 # Open the test_metadata_path, parse it to JSON, and extract out the 86 # profraws. 87 test_metadata = json.loads(test_metadata_path.read_text()) 88 profraw_paths = [ 89 str(obj['path']) 90 for obj in test_metadata 91 if 'type' in obj and obj['type'] == 'profraw' 92 ] 93 94 # Generate merged profdata. 95 command = [ 96 str(llvm_profdata_path), 97 'merge', 98 '--sparse', 99 '--failure-mode', 100 str(failure_mode), 101 '-o', 102 str(profdata_path), 103 ] + profraw_paths 104 105 _LOG.info('') 106 _LOG.info(' '.join(command)) 107 _LOG.info('') 108 109 output = subprocess.run(command) 110 if output.returncode != 0: 111 return output.returncode 112 113 # Generate the depfile that describes the dependency on the profraws used to 114 # create profdata_path. 115 depfile_path.write_text( 116 ''.join( 117 [ 118 str(profdata_path), 119 ': \\\n', 120 *[str(path) + ' \\\n' for path in profraw_paths], 121 ] 122 ) 123 ) 124 125 return 0 126 127 128def main() -> int: 129 return merge_profraws(**_parse_args()) 130 131 132if __name__ == "__main__": 133 sys.exit(main()) 134