blob: 827d314aefab3b676c7c24885615763ba1f9c41f [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)
Paul Sokolovskyecc118a2022-04-14 23:10:40 +030054parser.add_argument("-j", "--json-file", action='append', default=[],
Basil Eljuse4b14afb2020-09-30 13:07:23 +010055 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:
Paul Sokolovskyda1a4412022-04-28 19:29:44 +030066 print('Warning: too few input files.\n')
Basil Eljuse4b14afb2020-09-30 13:07:23 +010067# The same number of info and json files expected
68if options.json_file:
69 if len(options.json_file) != len(options.add_file):
70 print('Umatched number of info and json files.\n')
71 sys.exit(1)
72
73file_groups = []
74info_files_to_merge = []
75# Check if files exist
76for file_name in options.add_file:
77 print("Merging '{}'".format(file_name))
78 if not os.path.isfile(file_name):
79 print('Error: file "' + file_name + '" not found.\n')
80 sys.exit(1)
81 if not file_name[-5:] == '.info':
82 print('Error: file "' + file_name +
83 '" has wrong extension. Expected .info file.\n')
84 sys.exit(1)
85 if file_name in info_files_to_merge:
86 print("Error: Duplicated info file '{}'".format(file_name))
87 sys.exit(1)
88 info_files_to_merge.append(file_name)
89 file_group = {"info": file_name, "locations": [], "json": ""}
90 info_name = os.path.basename(file_name).split(".")[0]
91 if options.json_file:
92 json_name = [i for i in options.json_file
93 if os.path.basename(i).split(".")[0] == info_name]
94 if not json_name:
95 print("Umatched json file name for '{}'".format(file_name))
96 sys.exit(1)
97 json_name = json_name.pop()
98 if not json_name[-5:] == '.json':
99 print('Error: file "' + json_name +
100 '" has wrong extension. Expected .json file.\n')
101 sys.exit(1)
102 if not os.path.isfile(json_name):
103 print('Error: file "' + json_name + '" not found.\n')
104 sys.exit(1)
105 # Now we have to extract the location folders for each info
106 # this is needed if we want translation to local workspace
107 file_group["json"] = json_name
108 with open(json_name) as json_file:
109 json_data = json.load(json_file)
110 locations = []
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300111 for source in json_data["parameters"]["sources"]:
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100112 locations.append(source["LOCATION"])
113 file_group["locations"] = locations
114 file_groups.append(file_group)
115
116# Check the extension of the output file
117if not options.output[-5:] == '.info':
118 print('Error: file "' + options.output +
119 '" has wrong extension. Expected .info file.\n')
120 sys.exit(1)
121
122if options.local_workspace is not None:
123 # Translation from test to local workspace
124 i = 0
125 while i < len(info_files_to_merge):
126 info_file = open(info_files_to_merge[i], "r")
127 print("Translating workspace for '{}'...".format(
128 info_files_to_merge[i]))
129 info_lines = info_file.readlines()
130 info_file.close()
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100131 temp_file = 'temporary_' + str(i) + '.info'
saul-romero-arm6c40b242021-06-18 10:21:04 +0000132 parts = None
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100133 with open(temp_file, "w+") as f:
134 for line in info_lines:
saul-romero-arm6c40b242021-06-18 10:21:04 +0000135 if "SF" in line:
136 for location in file_groups[i]["locations"]:
137 if location in line:
138 parts = line[3:].partition(location)
139 line = line.replace(parts[0], options.local_workspace + "/")
140 break
141 f.write(line)
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100142 info_files_to_merge[i] = temp_file # Replace info file to be merged
143 i += 1
144
145# Merge json files
146if len(options.json_file):
147 json_merged_list = []
148 json_merged = {}
149 j = 0
150 while j < len(options.json_file):
151 json_file = options.json_file[j]
152 with open(json_file) as f:
153 data = json.load(f)
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300154 for source in data['parameters']['sources']:
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100155 if source not in json_merged_list:
156 json_merged_list.append(source)
157 j += 1
Paul Sokolovsky5c2332a2021-12-25 18:22:16 +0300158 json_merged = {'parameters': {'sources': json_merged_list}}
Basil Eljuse4b14afb2020-09-30 13:07:23 +0100159 with open(options.output_json, 'w') as f:
160 json.dump(json_merged, f)
161
162
163# Exploit LCOV merging capabilities
164# Example of LCOV usage: lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
165# -a coverage_2.info -o coverage_merge.info
166command = ['lcov', '-rc', 'lcov_branch_coverage=1']
167
168for file_name in info_files_to_merge:
169 command.append('-a')
170 command.append(file_name)
171command.append('-o')
172command.append(options.output)
173
174subprocess.call(command)
175
176# Delete the temporary files
177if options.local_workspace is not None:
178 for f in info_files_to_merge:
179 os.remove(f)