1"""This script helps to generate source based code coverage report.
2
3Usage:
4    python genReport.py --objects [OBJECTS] --format [FORMAT]
5
6"""
7# Copyright (C) 2023 The Android Open Source Project
8#
9# Licensed under the Apache License, Version 2.0 (the "License");
10# you may not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13#      http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS,
17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20
21import argparse
22import os
23import subprocess
24import sys
25
26def genProfdata(files, llvmDir):
27    llvmProfdataBin = os.path.join(llvmDir, 'llvm-profdata')
28
29    # To prevent args characters from exceeding the limitation,
30    # we split the files into several batches and merge them one by one.
31    batch_size = 10
32    profraws_length = len(files)
33    profdata_list = []
34    for offset in range(0, profraws_length, batch_size):
35        profdata_filename = f'{offset}.profdata'
36        profdata_list.append(profdata_filename)
37
38        subprocess_cmd = [llvmProfdataBin, 'merge']
39        subprocess_cmd.extend(files[offset:min(offset+batch_size, profraws_length)])
40        subprocess_cmd.extend([f'-o={profdata_filename}'])
41
42        subprocess.call(subprocess_cmd)
43
44    subprocess_cmd = [llvmProfdataBin, 'merge']
45    subprocess_cmd.extend(profdata_list)
46    subprocess_cmd.extend(['-o=out.profdata'])
47    subprocess.call(subprocess_cmd)
48
49    for profdata in profdata_list:
50        os.remove(profdata)
51
52def genHtml(llvmDir, objects, out):
53    llvmCovBin = os.path.join(llvmDir, 'llvm-cov')
54    subprocess_cmd = [
55        llvmCovBin,
56        'show',
57        '-instr-profile=out.profdata',
58    ]
59    subprocess_cmd.extend(objects)
60    subprocess_cmd.extend(['-use-color', '--format=html'])
61    with(open(out+'.html', 'w', encoding='utf-8')) as f:
62        subprocess.call(subprocess_cmd, stdout=f)
63
64def genJson(llvmDir, objects, out, summary_only=True):
65    llvmCovBin = os.path.join(llvmDir, 'llvm-cov')
66    subprocess_cmd = [
67        llvmCovBin,
68        'export',
69        '-instr-profile=out.profdata',
70    ]
71    subprocess_cmd.extend(objects)
72    if summary_only:
73        subprocess_cmd.extend(['-summary-only'])
74
75    with(open(out+'.json', 'w', encoding='utf-8')) as f:
76        subprocess.call(subprocess_cmd, stdout=f)
77
78def genLcov(llvmDir, objects, out, summary_only=True):
79    llvmCovBin = os.path.join(llvmDir, 'llvm-cov')
80    subprocess_cmd = [
81        llvmCovBin,
82        'export',
83        '-format=lcov',
84        '-instr-profile=out.profdata',
85    ]
86    subprocess_cmd.extend(objects)
87    if summary_only:
88        subprocess_cmd.extend(['-summary-only'])
89
90    with(open(out+'.lcov', 'w', encoding='utf-8')) as f:
91        subprocess.call(subprocess_cmd, stdout=f)
92
93
94def main():
95    arg_parser = argparse.ArgumentParser()
96
97    arg_parser.add_argument(
98        '--objects',
99        type=str,
100        required=True,
101        nargs='+',
102        help='List the elf files which are included in the test')
103
104    arg_parser.add_argument(
105        '--format',
106        type=str,
107        default='html',
108        help='Output format of the "llvm-cov show/export" command. The '
109        'supported formats are "text", "html" and "lcov".')
110
111    arg_parser.add_argument(
112        '--llvm-dir',
113        type=str,
114        default='prebuilts/clang/host/linux-x86/llvm-binutils-stable/',
115        help='Provide path to LLVM binary directory to override the default '
116             'one')
117
118    arg_parser.add_argument(
119        '--profraw-dir',
120        type=str,
121        default='tmp/',
122        help='Provide path to directory containing .profraw files')
123
124    arg_parser.add_argument(
125        '--output',
126        type=str,
127        default='out',
128        help='Provide output filename(without extension)')
129
130    arg_parser.add_argument(
131        '--summary-only',
132        default=True,
133        action=argparse.BooleanOptionalAction,
134        help='Flag of whether to enable summary only')
135
136    args = arg_parser.parse_args()
137
138    if not os.path.isdir(args.llvm_dir):
139        print('Provide path to LLVM binary directory')
140        return
141
142    if not os.path.isdir(args.profraw_dir):
143        print('Provide path to directory containing .profraw files')
144        return
145
146    profrawFiles = [
147        os.path.join(args.profraw_dir, f)
148        for f in os.listdir(args.profraw_dir) if f.endswith('.profraw')]
149    if len(profrawFiles) == 0:
150        print('No profraw files found in directory ' + args.profraw_dir)
151
152    genProfdata(profrawFiles, args.llvm_dir)
153    objects = []
154    for obj in args.objects:
155        objects.extend(['-object', obj])
156
157    if args.format == 'html':
158        genHtml(args.llvm_dir, objects, args.output)
159    elif args.format == 'json':
160        genJson(args.llvm_dir, objects, args.output, args.summary_only)
161    elif args.format == 'lcov':
162        genLcov(args.llvm_dir, objects, args.output, args.summary_only)
163    else:
164        print('Only json / html / lcov supported')
165        return
166
167if __name__ == '__main__':
168    sys.exit(main())
169