blob: c4a80ada2449bd7ae0ae2fe793c12c7be33cb73d [file] [log] [blame]
Basil Eljuse4b14afb2020-09-30 13:07:23 +01001# !/usr/bin/env python
2###############################################################################
3# Copyright (c) 2020, ARM Limited and Contributors. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6###############################################################################
7
8###############################################################################
9# FILE: merge.py
10#
11# DESCRIPTION: Merge two or more .info and json files, sanitizing source file
12# paths.
13# If different .info files contain the same source code duplicated
14# in different directories, we use the absolute paths of the
15# first .info file.
16#
17###############################################################################
18
19
20import os
21import sys
22import argparse
23from argparse import RawTextHelpFormatter
24import subprocess
25import json
26
27
28# Define an argument parser using the argparse library
29parser = argparse.ArgumentParser(epilog="""Example of usage:
30python3 merge.py -a coverage_1.info -a coverage_2.info -o coverage_merge.info \
31-j input_file1.json -j input_file2.json -m merge_file.json
32
Saul Romero884d2142023-01-16 10:31:22 +000033The metadata json file must contain the information for every repo that is
34used to build the binaries that were tested (and where coverage is desired).
35As a minimum this file must look like this:
36{
37 "configuration" : {
38 "sources": [
39 {
40 "COMMIT": "XXXXXXX", # [optional]
41 "LOCATION": "YYY", # Folder where the 'URL' repo is cloned in the
42 test workspace.
43 "LOCAL": "ZZZZ", # Local folder for the repo cloned at
44 the local workspace (optional, if not defined 'LOCATION' is assumed).
45 "REFSPEC": "XXXXXX", # [optional]
46 "URL": "XXXXXXXX",
47 "type": "git"
48 }
49 ]
50 }
51}
Basil Eljuse4b14afb2020-09-30 13:07:23 +010052It is possible to merge any number of files at once.
53If metadata json files are defined then they must pair with their
54corresponding info file, i.e. have the same name.
55If a local workspace is defined then the paths in the info files will
56be translated from the original test workspace to the local workspace
57to enable the usage of LCOV, but the original files will be kept intact.
58By default, the output file must be a new file.
59To overwrite an existing file, use the "--force" option.
60
Saul Romero884d2142023-01-16 10:31:22 +000061Note: the user is expected to merge .info files referring to the same
62project, i.e. same sources. If merging .info files from different projects,
63LCOV can be exploited directly using a command such as "lcov -rc
64lcov_branch_coverage=1 -a coverage_1.info \
Basil Eljuse4b14afb2020-09-30 13:07:23 +010065-a coverage_2.info -o coverage_merge.info."
66""", formatter_class=RawTextHelpFormatter)
67requiredNamed = parser.add_argument_group('required named arguments')
68requiredNamed.add_argument("-a", "--add-file",
69 help="Input info file to be merged.",
70 action='append', required=True)
71requiredNamed.add_argument("-o", "--output",
72 help="Name of the output info (merged) file.",
73 required=False)
74parser.add_argument("-j", "--json-file", action='append',
75 help="Input json file to be merged.")
76parser.add_argument("-m", "--output-json",
77 help="Name of the output json (merged) file.")
78parser.add_argument("--force", dest='force', action='store_true',
79 help="force overwriting of output file.")
80parser.add_argument("--local-workspace", dest='local_workspace',
81 help='Local workspace where source files reside.')
Saul Romero884d2142023-01-16 10:31:22 +000082parser.add_argument("-k", action='store_true', dest='keep_trans',
83 help='Keeps translated info files')
84
Basil Eljuse4b14afb2020-09-30 13:07:23 +010085
86options = parser.parse_args(sys.argv[1:])
87# At least two .info files are expected
88if len(options.add_file) < 2:
89 print('Error: too few input files.\n')
90 sys.exit(1)
91# The same number of info and json files expected
92if options.json_file:
93 if len(options.json_file) != len(options.add_file):
Saul Romero884d2142023-01-16 10:31:22 +000094 print('Unmatched number of info and json files.\n')
Basil Eljuse4b14afb2020-09-30 13:07:23 +010095 sys.exit(1)
96
97file_groups = []
98info_files_to_merge = []
99# Check if files exist
100for file_name in options.add_file:
101 print("Merging '{}'".format(file_name))
102 if not os.path.isfile(file_name):
103 print('Error: file "' + file_name + '" not found.\n')
104 sys.exit(1)
105 if not file_name[-5:] == '.info':
106 print('Error: file "' + file_name +
107 '" has wrong extension. Expected .info file.\n')
108 sys.exit(1)
109 if file_name in info_files_to_merge:
110 print("Error: Duplicated info file '{}'".format(file_name))
111 sys.exit(1)
112 info_files_to_merge.append(file_name)
113 file_group = {"info": file_name, "locations": [], "json": ""}
114 info_name = os.path.basename(file_name).split(".")[0]
115 if options.json_file:
116 json_name = [i for i in options.json_file
117 if os.path.basename(i).split(".")[0] == info_name]
118 if not json_name:
Saul Romero884d2142023-01-16 10:31:22 +0000119 print("Unmatched json file name for '{}'".format(file_name))
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100120 sys.exit(1)
121 json_name = json_name.pop()
122 if not json_name[-5:] == '.json':
123 print('Error: file "' + json_name +
124 '" has wrong extension. Expected .json file.\n')
125 sys.exit(1)
126 if not os.path.isfile(json_name):
127 print('Error: file "' + json_name + '" not found.\n')
128 sys.exit(1)
129 # Now we have to extract the location folders for each info
130 # this is needed if we want translation to local workspace
131 file_group["json"] = json_name
132 with open(json_name) as json_file:
133 json_data = json.load(json_file)
134 locations = []
135 for source in json_data["configuration"]["sources"]:
Saul Romero884d2142023-01-16 10:31:22 +0000136 location = source["LOCATION"]
137 locations.append((location, source.get("LOCAL", location)))
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100138 file_group["locations"] = locations
139 file_groups.append(file_group)
140
141# Check the extension of the output file
142if not options.output[-5:] == '.info':
143 print('Error: file "' + options.output +
144 '" has wrong extension. Expected .info file.\n')
145 sys.exit(1)
146
147if options.local_workspace is not None:
148 # Translation from test to local workspace
149 i = 0
150 while i < len(info_files_to_merge):
151 info_file = open(info_files_to_merge[i], "r")
152 print("Translating workspace for '{}'...".format(
153 info_files_to_merge[i]))
154 info_lines = info_file.readlines()
155 info_file.close()
Saul Romero884d2142023-01-16 10:31:22 +0000156 temp_file = info_file.name.replace('.info', '_local.info')
157 if options.keep_trans:
158 print("Keeping translated info file {}...".format(temp_file))
saul-romero-arm6c40b242021-06-18 10:21:04 +0000159 parts = None
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100160 with open(temp_file, "w+") as f:
161 for line in info_lines:
saul-romero-arm6c40b242021-06-18 10:21:04 +0000162 if "SF" in line:
163 for location in file_groups[i]["locations"]:
Saul Romero884d2142023-01-16 10:31:22 +0000164 if location[0] in line:
165 parts = line[3:].partition(location[0] + "/")
166 local_name = location[1]
167 line = line[:3] + os.path.join(
168 options.local_workspace, location[1], parts[2])
saul-romero-arm6c40b242021-06-18 10:21:04 +0000169 break
170 f.write(line)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100171 info_files_to_merge[i] = temp_file # Replace info file to be merged
172 i += 1
173
174# Merge json files
Saul Romero884d2142023-01-16 10:31:22 +0000175if options.json_file and len(options.json_file):
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100176 json_merged_list = []
177 json_merged = {}
178 j = 0
179 while j < len(options.json_file):
180 json_file = options.json_file[j]
181 with open(json_file) as f:
182 data = json.load(f)
183 for source in data['configuration']['sources']:
184 if source not in json_merged_list:
185 json_merged_list.append(source)
186 j += 1
187 json_merged = {'configuration': {'sources': json_merged_list}}
188 with open(options.output_json, 'w') as f:
Saul Romero884d2142023-01-16 10:31:22 +0000189 json.dump(json_merged, f, indent=4)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100190
191
192# Exploit LCOV merging capabilities
193# Example of LCOV usage: lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
194# -a coverage_2.info -o coverage_merge.info
195command = ['lcov', '-rc', 'lcov_branch_coverage=1']
196
197for file_name in info_files_to_merge:
198 command.append('-a')
199 command.append(file_name)
200command.append('-o')
201command.append(options.output)
202
203subprocess.call(command)
204
205# Delete the temporary files
Saul Romero884d2142023-01-16 10:31:22 +0000206if options.local_workspace is not None and not options.keep_trans:
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100207 for f in info_files_to_merge:
208 os.remove(f)