3 # mcov: script to convert gcov data to lcov format on Mac.
5 # Based on lcov (http://ltp.sourceforge.net/coverage/lcov.php)
6 # Written by ajeya at google dot com.
9 # mcov --directory <base directory> --output <output file> --verbose <level>
17 use File
::Spec
::Functions
;
21 sub process_dafile
(@
);
22 sub canonical_path
(@
);
23 sub split_filename
(@
);
24 sub read_gcov_header
(@
);
25 sub read_gcov_file
(@
);
27 # scalars with default values
28 my $directory = Cwd
::abs_path
(Cwd
::getcwd
);
29 my $data_file_extension = ".gcda";
30 my $output_filename = "output.lcov";
31 my $gcov_tool = "/usr/bin/gcov";
34 # TODO(ajeya): GetOptions doesn't handle case where the script is called with
35 # no arguments. This needs to be fixed.
36 my $result = GetOptions
("directory|d=s" => \
$directory,
37 "output|o=s" => \
$output_filename,
38 "verbose" => \
$verbosity);
40 print "Usage: $0 --directory <base directory> --output <output file>";
41 print " [--verbose <level>]\n";
45 # convert the directory path to absolute path.
46 $directory = Cwd
::abs_path
($directory);
48 # convert the output file path to absolute path.
49 $output_filename = Cwd
::abs_path
($output_filename);
51 # Output expected args for buildbot debugging assistance.
53 print "mcov: after abs_pathing\n";
54 print "mcov: getcwd() = $cwd\n";
55 print "mcov: directory for data files is $directory\n";
56 print "mcov: output filename is $output_filename\n";
58 # Sanity check; die if path is wrong.
59 # We don't check for output_filename because... we create it.
60 if (! -d
$directory) {
61 print "mcov: Bad args passed; exiting with error.\n";
65 # open file for output
66 open(INFO_HANDLE
, ">$output_filename");
68 my @file_list; # scalar to hold the list of all gcda files.
70 printf("Scanning $directory for $data_file_extension files ...\n");
73 if ($file =~ m/\Q$data_file_extension\E$/i) {
74 push(@file_list, Cwd
::abs_path
($file));
77 printf("Found %d data files in %s\n", $#file_list + 1, $directory);
80 # Process all files in list
81 foreach my $file (@file_list) {
82 process_dafile
($file);
86 # Remove the misc gcov files that are created.
87 my @gcov_list = glob("*.gcov");
88 foreach my $gcov_file (@gcov_list) {
97 # argument(s): a file path with gcda extension
99 # This method calls gcov to generate the coverage data and write the output in
100 # lcov format to the output file.
101 sub process_dafile
(@
) {
103 print("Processing $filename ...\n");
105 my $da_filename; # Name of data file to process
106 my $base_name; # data filename without ".da/.gcda" extension
107 my $gcov_error; # Error code of gcov tool
108 my $object_dir; # Directory containing all object files
109 my $gcov_file; # Name of a .gcov file
110 my @gcov_data; # Contents of a .gcov file
111 my @gcov_list; # List of generated .gcov files
112 my $base_dir; # Base directory for current da file
113 local *OLD_STDOUT
; # Handle to store STDOUT temporarily
115 # Get directory and basename of data file
116 ($base_dir, $base_name) = split_filename
(canonical_path
($filename));
118 # Check for writable $base_dir (gcov will try to write files there)
120 print("ERROR: cannot write to directory $base_dir\n");
124 # Construct name of graph file
125 $da_filename = File
::Spec
::Functions
::catfile
($base_dir,
126 join(".", $base_name, "gcno"));
128 # Ignore empty graph file (e.g. source file with no statement)
129 if (-z
$da_filename) {
130 warn("WARNING: empty $da_filename (skipped)\n");
134 # Set $object_dir to real location of object files. This may differ
135 # from $base_dir if the graph file is just a link to the "real" object
137 $object_dir = dirname
($da_filename);
139 # Save the current STDOUT to OLD_STDOUT and set STDOUT to /dev/null to mute
142 open(OLD_STDOUT
, ">>&STDOUT");
143 open(STDOUT
, ">/dev/null");
146 # run gcov utility with the supplied gcno file and object directory.
147 $gcov_error = system($gcov_tool, $da_filename, "-o", $object_dir);
149 # Restore STDOUT if we changed it before.
151 open(STDOUT
, ">>&OLD_STDOUT");
155 warn("WARNING: GCOV failed for $da_filename!\n");
159 # Collect data from resulting .gcov files and create .info file
160 @gcov_list = glob("*.gcov");
162 if (!scalar(@gcov_list)) {
163 warn("WARNING: gcov did not create any files for $da_filename!\n");
166 foreach $gcov_file (@gcov_list) {
167 my $source_filename = read_gcov_header
($gcov_file);
169 if (!defined($source_filename)) {
173 $source_filename = canonical_path
($source_filename);
175 # Read in contents of gcov file
176 @gcov_data = read_gcov_file
($gcov_file);
179 if (!scalar(@gcov_data)) {
180 warn("WARNING: skipping empty file $gcov_file\n");
185 print(INFO_HANDLE
"SF:", Cwd
::abs_path
($source_filename), "\n");
187 # Write coverage information for each instrumented line
188 # Note: @gcov_content contains a list of (flag, count, source)
189 # tuple for each source code line
191 # Check for instrumented line
193 print(INFO_HANDLE
"DA:", $gcov_data[3], ",", $gcov_data[1], "\n");
195 # Remove already processed data from array
196 splice(@gcov_data,0,4);
198 print(INFO_HANDLE
"end_of_record\n");
200 # Remove .gcov file after processing
206 # argument(s): any file path
207 # returns: the file path as a string
209 # clean up the file path being passed.
210 sub canonical_path
(@
) {
212 return (File
::Spec
::Functions
::canonpath
($filename));
216 # argument(s): any file path
217 # returns: an array with the path components
219 # splits the file path into path and filename (with no extension).
220 sub split_filename
(@
){
222 my ($base, $path, $ext) = File
::Basename
::fileparse
($filename, '\.[^\.]*');
223 return ($path, $base);
227 # argument(s): path to gcov file
228 # returns: an array the contens of the gcov header.
230 # reads the gcov file and returns the parsed contents of a gcov header as an
232 sub read_gcov_header
(@
) {
237 if (!open(INPUT
, $filename)) {
238 warn("WARNING: cannot read $filename!\n");
239 return (undef,undef);
243 foreach my $line (@lines) {
245 # check for lines with source string.
246 if ($line =~ /^\s+-:\s+0:Source:(.*)$/) {
247 # Source: header entry
258 # argument(s): path to gcov file
259 # returns: an array with the contents of the gcov file.
261 # reads the gcov file and returns the parsed contents of a gcov file
263 sub read_gcov_file
(@
) {
269 if (!open(INPUT
, $filename)) {
270 warn("WARNING: cannot read $filename!\n");
274 # Parse gcov output and populate the array
276 foreach my $line (@lines) {
278 if ($line =~ /^\s*([^:]+):\s*(\d+?):(.*)$/) {
279 # <exec count>:<line number>:<source code>
282 # Uninstrumented line
287 } elsif ($2 eq "0") {
288 #ignore comments and other header info
290 # Source code execution data
292 # Check for zero count
293 if ($number eq "#####") {
297 push(@result, $number);