blob: 24e82e2ce7623763a2ff0e000244b19bbde073b2 [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
33It is possible to merge any number of files at once.
34If metadata json files are defined then they must pair with their
35corresponding info file, i.e. have the same name.
36If a local workspace is defined then the paths in the info files will
37be translated from the original test workspace to the local workspace
38to enable the usage of LCOV, but the original files will be kept intact.
39By default, the output file must be a new file.
40To overwrite an existing file, use the "--force" option.
41
42Note: the user is expected to merge .info files referring to the same project.
43If merging .info files from different projects, LCOV can be exploited directly
44using a command such as "lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
45-a coverage_2.info -o coverage_merge.info."
46""", formatter_class=RawTextHelpFormatter)
47requiredNamed = parser.add_argument_group('required named arguments')
48requiredNamed.add_argument("-a", "--add-file",
49 help="Input info file to be merged.",
50 action='append', required=True)
51requiredNamed.add_argument("-o", "--output",
52 help="Name of the output info (merged) file.",
53 required=False)
54parser.add_argument("-j", "--json-file", action='append',
55 help="Input json file to be merged.")
56parser.add_argument("-m", "--output-json",
57 help="Name of the output json (merged) file.")
58parser.add_argument("--force", dest='force', action='store_true',
59 help="force overwriting of output file.")
60parser.add_argument("--local-workspace", dest='local_workspace',
61 help='Local workspace where source files reside.')
62
63options = parser.parse_args(sys.argv[1:])
64# At least two .info files are expected
65if len(options.add_file) < 2:
66 print('Error: too few input files.\n')
67 sys.exit(1)
68# The same number of info and json files expected
69if options.json_file:
70 if len(options.json_file) != len(options.add_file):
71 print('Umatched number of info and json files.\n')
72 sys.exit(1)
73
74file_groups = []
75info_files_to_merge = []
76# Check if files exist
77for file_name in options.add_file:
78 print("Merging '{}'".format(file_name))
79 if not os.path.isfile(file_name):
80 print('Error: file "' + file_name + '" not found.\n')
81 sys.exit(1)
82 if not file_name[-5:] == '.info':
83 print('Error: file "' + file_name +
84 '" has wrong extension. Expected .info file.\n')
85 sys.exit(1)
86 if file_name in info_files_to_merge:
87 print("Error: Duplicated info file '{}'".format(file_name))
88 sys.exit(1)
89 info_files_to_merge.append(file_name)
90 file_group = {"info": file_name, "locations": [], "json": ""}
91 info_name = os.path.basename(file_name).split(".")[0]
92 if options.json_file:
93 json_name = [i for i in options.json_file
94 if os.path.basename(i).split(".")[0] == info_name]
95 if not json_name:
96 print("Umatched json file name for '{}'".format(file_name))
97 sys.exit(1)
98 json_name = json_name.pop()
99 if not json_name[-5:] == '.json':
100 print('Error: file "' + json_name +
101 '" has wrong extension. Expected .json file.\n')
102 sys.exit(1)
103 if not os.path.isfile(json_name):
104 print('Error: file "' + json_name + '" not found.\n')
105 sys.exit(1)
106 # Now we have to extract the location folders for each info
107 # this is needed if we want translation to local workspace
108 file_group["json"] = json_name
109 with open(json_name) as json_file:
110 json_data = json.load(json_file)
111 locations = []
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300112 for source in json_data["parameters"]["sources"]:
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100113 locations.append(source["LOCATION"])
114 file_group["locations"] = locations
115 file_groups.append(file_group)
116
117# Check the extension of the output file
118if not options.output[-5:] == '.info':
119 print('Error: file "' + options.output +
120 '" has wrong extension. Expected .info file.\n')
121 sys.exit(1)
122
123if options.local_workspace is not None:
124 # Translation from test to local workspace
125 i = 0
126 while i < len(info_files_to_merge):
127 info_file = open(info_files_to_merge[i], "r")
128 print("Translating workspace for '{}'...".format(
129 info_files_to_merge[i]))
130 info_lines = info_file.readlines()
131 info_file.close()
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100132 temp_file = 'temporary_' + str(i) + '.info'
saul-romero-arm6c40b242021-06-18 10:21:04 +0000133 parts = None
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100134 with open(temp_file, "w+") as f:
135 for line in info_lines:
saul-romero-arm6c40b242021-06-18 10:21:04 +0000136 if "SF" in line:
137 for location in file_groups[i]["locations"]:
138 if location in line:
139 parts = line[3:].partition(location)
140 line = line.replace(parts[0], options.local_workspace + "/")
141 break
142 f.write(line)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100143 info_files_to_merge[i] = temp_file # Replace info file to be merged
144 i += 1
145
146# Merge json files
147if len(options.json_file):
148 json_merged_list = []
149 json_merged = {}
150 j = 0
151 while j < len(options.json_file):
152 json_file = options.json_file[j]
153 with open(json_file) as f:
154 data = json.load(f)
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300155 for source in data['parameters']['sources']:
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100156 if source not in json_merged_list:
157 json_merged_list.append(source)
158 j += 1
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300159 json_merged = {'parameters': {'sources': json_merged_list}}
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100160 with open(options.output_json, 'w') as f:
161 json.dump(json_merged, f)
162
163
164# Exploit LCOV merging capabilities
165# Example of LCOV usage: lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
166# -a coverage_2.info -o coverage_merge.info
167command = ['lcov', '-rc', 'lcov_branch_coverage=1']
168
169for file_name in info_files_to_merge:
170 command.append('-a')
171 command.append(file_name)
172command.append('-o')
173command.append(options.output)
174
175subprocess.call(command)
176
177# Delete the temporary files
178if options.local_workspace is not None:
179 for f in info_files_to_merge:
180 os.remove(f)