blob: a29b5f367bcbe8be820a99b6173f30369f709db7 [file] [log] [blame]
Fathi Boudra422bf772019-12-02 11:10:16 +02001#!/usr/bin/env python3
2#
3# Copyright (c) 2019, Arm Limited. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8#
9# Run the Coverity tool on the Trusted Firmware and produce a tarball ready to
10# be submitted to Coverity Scan Online.
11#
12
13import sys
14import argparse
15import urllib.request
16import tarfile
17import os
18import subprocess
19import re
20import utils
21import coverity_tf_conf
22
23
24def tarball_name(filename):
25 "Isolate the tarball name without the filename's extension."
26 # Handle a selection of "composite" extensions
27 for ext in [".tar.gz", ".tar.bz2"]:
28 if filename.endswith(ext):
29 return filename[:-len(ext)]
30 # For all other extensions, let the vanilla splitext() function handle it
31 return os.path.splitext(filename)[0]
32
33assert tarball_name("foo.gz") == "foo"
34assert tarball_name("bar.tar.gz") == "bar"
35assert tarball_name("baz.tar.bz2") == "baz"
36
37
38def get_coverity_tool():
39 coverity_tarball = "cov-analysis-linux64-2017.07.tar.gz"
40 url = "http://files.oss.arm.com/downloads/tf-a/" + coverity_tarball
41 print("Downloading Coverity Build tool from %s..." % url)
42 file_handle = urllib.request.urlopen(url)
43 output = open(coverity_tarball, "wb")
44 output.write(file_handle.read())
45 output.close()
46 print("Download complete.")
47
48 print("\nUnpacking tarball %s..." % coverity_tarball)
49 tarfile.open(coverity_tarball).extractall()
50 print("Tarball unpacked.")
51
52 print("\nNow please load the Coverity tool in your PATH...")
53 print("E.g.:")
54 cov_dir_name = tarball_name(coverity_tarball)
55 cov_dir_path = os.path.abspath(os.path.join(cov_dir_name, "bin"))
56 print(" export PATH=%s$PATH" % (cov_dir_path + os.pathsep))
57
58
59def print_coverage(coverity_dir, tf_dir, exclude_paths=[], log_filename=None):
60 analyzed = []
61 not_analyzed = []
62 excluded = []
63
64 # Print the coverage report to a file (or stdout if no file is specified)
65 if log_filename is not None:
66 log_file = open(log_filename, "w")
67 else:
68 log_file = sys.stdout
69
70 # Get the list of files analyzed by Coverity.
71 #
72 # To do that, we examine the build log file Coverity generated and look for
73 # compilation lines. These are the lines starting with "COMPILING:". We consider
74 # only those lines that actually compile C files, i.e. lines of the form:
75 # gcc -c file.c -o file.o
76 # This filters out other compilation lines like generation of dependency files
77 # (*.d) and such.
78 # We then extract the C filename.
79 coverity_build_log = os.path.join(coverity_dir, "build-log.txt")
80 with open(coverity_build_log, encoding="utf-8") as build_log:
81 for line in build_log:
82 results = re.search("COMPILING:.*-c *(.*\.c).*-o.*\.o", line)
83 if results is not None:
84 filename = results.group(1)
85 if filename not in analyzed:
86 analyzed.append(filename)
87
88 # Now get the list of C files in the Trusted Firmware source tree.
89 # Header files and assembly files are ignored, as well as anything that
90 # matches the patterns list in the exclude_paths[] list.
91 # Build a list of files that are in this source tree but were not analyzed
92 # by comparing the 2 sets of files.
93 all_files_count = 0
94 old_cwd = os.path.abspath(os.curdir)
95 os.chdir(tf_dir)
96 git_process = utils.exec_prog("git", ["ls-files", "*.c"],
97 out=subprocess.PIPE, out_text_mode=True)
98 for filename in git_process.stdout:
99 # Remove final \n in filename
100 filename = filename.strip()
101
102 def is_excluded(filename, excludes):
103 for pattern in excludes:
104 if re.match(pattern[0], filename):
105 excluded.append((filename, pattern[1]))
106 return True
107 return False
108
109 if is_excluded(filename, exclude_paths):
110 continue
111
112 # Keep track of the number of C files in the source tree. Used to
113 # compute the coverage percentage at the end.
114 all_files_count += 1
115 if filename not in analyzed:
116 not_analyzed.append(filename)
117 os.chdir(old_cwd)
118
119 # Compute the coverage percentage
120 # Note: The 1.0 factor here is used to make a float division instead of an
121 # integer one.
122 percentage = (1 - ((1.0 * len(not_analyzed) ) / all_files_count)) * 100
123
124 #
125 # Print a report
126 #
127 log_file.write("Files coverage: %d%%\n\n" % percentage)
128 log_file.write("Analyzed %d files\n" % len(analyzed))
129
130 if len(excluded) > 0:
131 log_file.write("\n%d files were ignored on purpose:\n" % len(excluded))
132 for exc in excluded:
133 log_file.write(" - {0:50} (Reason: {1})\n".format(exc[0], exc[1]))
134
135 if len(not_analyzed) > 0:
136 log_file.write("\n%d files were not analyzed:\n" % len(not_analyzed))
137 for f in not_analyzed:
138 log_file.write(" - %s\n" % f)
139 log_file.write("""
140===============================================================================
141Please investigate why the above files are not run through Coverity.
142
143There are 2 possible reasons:
144
1451) The build coverage is insufficient. Please review the tf-cov-make script to
146 add the missing build config(s) that will involve the file in the build.
147
1482) The file is expected to be ignored, for example because it is deprecated
149 code. Please update the TF Coverity configuration to list the file and
150 indicate the reason why it is safe to ignore it.
151===============================================================================
152""")
153 log_file.close()
154
155
156def parse_cmd_line(argv, prog_name):
157 parser = argparse.ArgumentParser(
158 prog=prog_name,
159 description="Run Coverity on Trusted Firmware",
160 epilog="""
161 Please ensure the AArch64 & AArch32 cross-toolchains are loaded in your
162 PATH. Ditto for the Coverity tools. If you don't have the latter then
163 you can use the --get-coverity-tool to download them for you.
164 """)
165 parser.add_argument("--tf", default=None,
166 metavar="<Trusted Firmware source dir>",
167 help="Specify the location of ARM Trusted Firmware sources to analyze")
168 parser.add_argument("--get-coverity-tool", default=False,
169 help="Download the Coverity build tool and exit",
170 action="store_true")
171 parser.add_argument("--mode", choices=["offline", "online"], default="online",
172 help="Choose between online or offline mode for the analysis")
173 parser.add_argument("--output", "-o",
174 help="Name of the output file containing the results of the analysis")
175 parser.add_argument("--build-cmd", "-b",
176 help="Command used to build TF through Coverity")
177 parser.add_argument("--analysis-profile", "-p",
178 action="append", nargs=1,
179 help="Analysis profile for a local analysis")
180 args = parser.parse_args(argv)
181
182 # Set a default name for the output file if none is provided.
183 # If running in offline mode, this will be a text file;
184 # If running in online mode, this will be a tarball name.
185 if not args.output:
186 if args.mode == "offline":
187 args.output = "arm-tf-coverity-report.txt"
188 else:
189 args.output = "arm-tf-coverity-results.tgz"
190
191 return args
192
193
194if __name__ == "__main__":
195 prog_name = sys.argv[0]
196 args = parse_cmd_line(sys.argv[1:], prog_name)
197
198 # If the user asked to download the Coverity build tool then just do that
199 # and exit.
200 if args.get_coverity_tool:
201 # If running locally, use the commercial version of Coverity from the
202 # EUHPC cluster.
203 if args.mode == "offline":
204 print("To load the Coverity tools, use the following command:")
205 print("export PATH=/arm/tools/coverity/static-analysis/8.7.1/bin/:$PATH")
206 else:
207 get_coverity_tool()
208 sys.exit(0)
209
210 if args.tf is None:
211 print("ERROR: Please specify the Trusted Firmware sources using the --tf option.",
212 file=sys.stderr)
213 sys.exit(1)
214
215 # Get some important paths in the platform-ci scripts
216 tf_scripts_dir = os.path.abspath(os.path.dirname(prog_name))
217 tf_coverity_dir = os.path.join(os.path.normpath(
218 os.path.join(tf_scripts_dir, os.pardir, os.pardir)),"coverity")
219
220 if not args.build_cmd:
221 tf_build_script = os.path.join(tf_scripts_dir, "tf-cov-make")
222 args.build_cmd = tf_build_script + " " + args.tf
223
224 run_coverity_script = os.path.join(tf_coverity_dir, "run_coverity.sh")
225
226 ret = subprocess.call([run_coverity_script, "check_tools", args.mode])
227 if ret != 0:
228 sys.exit(1)
229
230 ret = subprocess.call([run_coverity_script, "configure"])
231 if ret != 0:
232 sys.exit(1)
233
234 ret = subprocess.call([run_coverity_script, "build", args.build_cmd])
235 if ret != 0:
236 sys.exit(1)
237
238 if args.mode == "online":
239 ret = subprocess.call([run_coverity_script, "package", args.output])
240 else:
241 for profile in args.analysis_profile:
242 ret = subprocess.call([run_coverity_script, "analyze",
243 args.output,
244 args.tf,
245 profile[0]])
246 if ret != 0:
247 break
248 if ret != 0:
249 print("An error occured (%d)." % ret, file=sys.stderr)
250 sys.exit(ret)
251
252 print("-----------------------------------------------------------------")
253 print("Results can be found in file '%s'" % args.output)
254 if args.mode == "online":
255 print("This tarball can be uploaded at Coverity Scan Online:" )
256 print("https://scan.coverity.com/projects/arm-software-arm-trusted-firmware/builds/new?tab=upload")
257 print("-----------------------------------------------------------------")
258
259 print_coverage("cov-int", args.tf, coverity_tf_conf.exclude_paths, "tf_coverage.log")
260 with open("tf_coverage.log") as log_file:
261 for line in log_file:
262 print(line, end="")