1 /**
2 This tool converts D code coverage files from absolute to relative paths.
3 
4 D code coverage files are generated based on absolute path names if absolute paths are
5 used in the build command. This is reflected in the file's actual name, which reflects all
6 the path components. The absolute path is also listed at the end of the code coverage
7 report.
8 
9 This tool checks a coverage file to see if absolute names where used. If so, it renames
10 the file and updates the report to use a relative path.
11 
12 Copyright (c) 2017-2020, eBay Inc.
13 Initially written by Jon Degenhardt
14 
15 License: Boost Licence 1.0 (http://boost.org/LICENSE_1_0.txt)
16 
17 **/
18 module buildtools.codecov_to_relative_paths;
19 
20 import std.algorithm : findSplit;
21 import std.array : appender;
22 import std.conv : to;
23 import std.file : exists, isDir, isFile, remove, rename;
24 import std.path : absolutePath, baseName, buildPath, buildNormalizedPath, dirName, extension,
25     isAbsolute, stripExtension;
26 import std.range : empty;
27 import std.stdio;
28 import std.string : tr;
29 
30 /** Convert a D code coverage file to use relative paths.
31  *
32  * Files provides on the command line are checked to see if the name represents an
33  * absolute path. If so, the file is renamed to reflect the relative name and the
34  * last line of the coverage report is changed to reflect this as well.
35  */
36 int main(string[] cmdArgs)
37 {
38     auto programName = (cmdArgs.length > 0) ? cmdArgs[0].stripExtension.baseName : "Unknown_program_name";
39 
40     if (cmdArgs.length < 2)
41     {
42         writefln("Synopsis: %s coverage-file [coverage-file...]", programName);
43         return 1;
44     }
45 
46     auto coverageFiles = cmdArgs[1..$];
47 
48     foreach (cf; coverageFiles)
49     {
50         if (!cf.exists || !cf.isFile)
51         {
52             writefln("%s is not a file", cf);
53             return 1;
54         }
55     }
56 
57     foreach (cf; coverageFiles)
58     {
59         auto rootDir = cf.absolutePath.buildNormalizedPath.dirName;
60         auto fileName = cf.baseName;
61         auto fileNameNoExt = fileName.stripExtension;
62         auto lines = appender!(string[])();
63         foreach (l; cf.File.byLine) lines ~= l.to!string;
64         if (lines.data.length > 0)
65         {
66             /* Check that the last line matches our file name. */
67             auto lastLine = lines.data[$ - 1];
68             auto lastLineSplit = lastLine.findSplit(" ");
69             auto lastLinePath = lastLineSplit[1].empty ? "" : lastLineSplit[0];
70             auto lastLinePathNoExt = lastLinePath.stripExtension;
71             if (lastLinePath.isAbsolute &&
72                 lastLinePathNoExt.tr("\\/", "--") == fileNameNoExt &&
73                 rootDir.length + 1 <= lastLine.length &&
74                 rootDir.length + 1 <= fileName.length)
75             {
76                 auto updatedLastLine = lastLine[rootDir.length + 1 .. $];
77                 auto newFileName = fileName[rootDir.length + 1 .. $];
78                 if (newFileName != fileName)
79                 {
80                     auto ofile = newFileName.File("w");
81                     foreach (l; lines.data[0 .. $ - 1]) ofile.writeln(l);
82                     ofile.writeln(updatedLastLine);
83                     fileName.remove;
84                 }
85             }
86         }
87     }
88 
89     return 0;
90 }