blob: e3d9d65a63c8ebf9c6ff3948e74dd2a08bcc6000 [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 = []
112 for source in json_data["configuration"]["sources"]:
113 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()
132 common_prefix = os.path.normpath(
133 os.path.commonprefix([line[3:] for line in info_lines
134 if 'SF:' in line]))
135 temp_file = 'temporary_' + str(i) + '.info'
136 with open(temp_file, "w+") as f:
137 for line in info_lines:
138 cf = common_prefix
139 if os.path.basename(common_prefix) in file_groups[i]["locations"]:
140 cf = os.path.dirname(common_prefix)
141 f.write(line.replace(cf, options.local_workspace))
142 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)
154 for source in data['configuration']['sources']:
155 if source not in json_merged_list:
156 json_merged_list.append(source)
157 j += 1
158 json_merged = {'configuration': {'sources': json_merged_list}}
159 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)