1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 This module can deduce the system compiler flags, if possible, from the
11 compiler specified in a CompileCommand.
12 
13 The module assumes that during an execution the system flags for a compiler do
14 not change thus they can be cached. This avoids having to invoke the compiler
15 more than necessary.
16 
17 This module exists for those times that:
18  * a cross-compiler which uses other system headers than the hosts system
19    compiler. E.g. clang-tidy do not know *what* these are thus this module
20    discoveres them and provide them.
21  * multiple compiler versions are used in a build and each have different
22    headers.
23 */
24 module dextool.compilation_db.system_compiler;
25 
26 import logger = std.experimental.logger;
27 import std.algorithm : countUntil, map;
28 import std.array : empty, array;
29 import std.string : startsWith, stripLeft, splitLines;
30 
31 import dextool.compilation_db : CompileCommand;
32 
33 version (unittest) {
34     import unit_threaded : shouldBeIn, shouldEqual;
35 }
36 
37 @safe:
38 
39 struct Compiler {
40     string value;
41     alias value this;
42 }
43 
44 struct SystemIncludePath {
45     string value;
46     alias value this;
47 }
48 
49 /** Execute and inspect the compiler for the system includes.
50  *
51  * Note that how the compilers are inspected is hard coded.
52  */
53 SystemIncludePath[] deduceSystemIncludes(CompileCommand cmd, const Compiler compiler) {
54     return deduceSystemIncludes(cmd.command, compiler);
55 }
56 
57 /// ditto
58 SystemIncludePath[] deduceSystemIncludes(const string[] cmd, const Compiler compiler) {
59     import std.process : execute;
60 
61     if (cmd.empty || compiler.empty)
62         return null;
63 
64     if (auto v = compiler in cacheSysIncludes) {
65         return *v;
66     }
67 
68     auto args = systemCompilerArg(cmd, compiler);
69 
70     auto res = execute(args);
71     if (res.status != 0) {
72         logger.tracef("Failed to inspect the compiler for system includes: %-(%s %)", args);
73         logger.trace(res.output);
74         return null;
75     }
76 
77     auto incls = parseCompilerOutput(res.output);
78     cacheSysIncludes[compiler] = incls;
79 
80     return incls;
81 }
82 
83 private:
84 
85 string[] systemCompilerArg(const string[] cmd, const Compiler compiler) {
86     string[] args = ["-v", "/dev/null", "-fsyntax-only"];
87     if (auto v = language(compiler, cmd)) {
88         args = [v] ~ args;
89     }
90     if (auto v = sysroot(cmd)) {
91         args ~= v;
92     }
93     return [compiler.value] ~ args;
94 }
95 
96 SystemIncludePath[] parseCompilerOutput(const string output) {
97     auto lines = output.splitLines;
98     const start = lines.countUntil("#include <...> search starts here:") + 1;
99     const end = lines.countUntil("End of search list.");
100     if (start == 0 || end == 0 || start > end)
101         return null;
102 
103     return lines[start .. end].map!(a => SystemIncludePath(a.stripLeft)).array;
104 }
105 
106 SystemIncludePath[][Compiler] cacheSysIncludes;
107 
108 // assumes that compilers adher to the gcc and llvm commands use of --sysroot / -isysroot.
109 // depends on the fact that CompileCommand.Command always splits e.g. a --isysroot=foo to ["--sysroot", "foo"].
110 const(string[]) sysroot(const string[] cmd) {
111     foreach (flag; ["--sysroot", "-isysroot"]) {
112         auto index = cmd.countUntil!(a => a.startsWith(flag)) + 1;
113         if (index > 0 && (index + 1) < cmd.length)
114             return cmd[index .. index + 1];
115     }
116 
117     return null;
118 }
119 
120 @("shall extract --sysroot and its argument")
121 unittest {
122     ["foo", "--sysroot", "bar"].sysroot.shouldEqual(["--sysroot", "bar"]);
123     ["foo", "-isysroot", "bar"].sysroot.shouldEqual(["-isysroot", "bar"]);
124 }
125 
126 // assumes that compilers adher to the gcc and llvm commands of using -xLANG
127 string language(Compiler compiler, const string[] cmd) {
128     import std.path : baseName;
129     import std.typecons : No;
130 
131     auto index = cmd.countUntil!(a => a.startsWith("-x")) + 1;
132     if (index > 0 && index < cmd.length)
133         return cmd[index];
134 
135     switch (compiler.baseName) {
136     case "cc":
137     case "clang":
138     case "gcc":
139         return "-xc";
140     case "c++":
141     case "clang++":
142     case "g++":
143         return "-xc++";
144     default:
145     }
146 
147     return null;
148 }
149 
150 @("shall parse the system flags")
151 unittest {
152     import std.typecons : Tuple;
153 
154     // arrange
155     immutable compiler_output = `Using built-in specs.
156 COLLECT_GCC=gcc
157 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
158 OFFLOAD_TARGET_NAMES=nvptx-none
159 OFFLOAD_TARGET_DEFAULT=1
160 Target: x86_64-linux-gnu
161 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with
162 -gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-n
163 ls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-d
164 efault-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic
165  --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
166 Thread model: posix
167 gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)
168 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'
169  /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu /dev/null -quiet -dumpbase null -mtune=generic -march=x86-64 -auxbase null -version -fsyntax-only -o /dev/null -fstack-protector-strong -Wformat
170  -Wformat-security
171 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
172         compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP
173 
174 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
175 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
176 ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
177 #include "..." search starts here:
178 #include <...> search starts here:
179  /usr/lib/gcc/x86_64-linux-gnu/7/include/foo
180  /usr/local/include
181  /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
182  /usr/include/x86_64-linux-gnu
183  /usr/include
184 End of search list.
185 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
186         compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP
187 
188 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
189 Compiler executable checksum: c8081a99abb72bbfd9129549110a350c
190 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/
191 LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/us
192 r/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/
193 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'`;
194 
195     // act
196     auto sysflags = parseCompilerOutput(compiler_output);
197 
198     // assert
199     "/usr/lib/gcc/x86_64-linux-gnu/7/include/foo".shouldBeIn(sysflags);
200     "/usr/local/include".shouldBeIn(sysflags);
201     "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed".shouldBeIn(sysflags);
202     "/usr/include/x86_64-linux-gnu".shouldBeIn(sysflags);
203     "/usr/include".shouldBeIn(sysflags);
204 }