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 
28 import dextool.compilation_db : CompileCommand;
29 
30 version (unittest) {
31     import unit_threaded : shouldBeIn, shouldEqual;
32 }
33 
34 @safe:
35 
36 struct Compiler {
37     string value;
38     alias value this;
39 }
40 
41 struct SystemIncludePath {
42     string value;
43     alias value this;
44 }
45 
46 /** Execute and inspect the compiler for the system includes.
47  *
48  * Note that how the compilers are inspected is hard coded.
49  */
50 SystemIncludePath[] deduceSystemIncludes(ref CompileCommand cmd, const Compiler compiler) {
51     import std.process : execute;
52 
53     if (cmd.command.length == 0 || compiler.length == 0)
54         return null;
55 
56     if (auto v = compiler in cacheSysIncludes) {
57         return *v;
58     }
59 
60     auto args = systemCompilerArg(cmd, compiler);
61 
62     auto res = execute(args);
63     if (res.status != 0) {
64         logger.tracef("Failed to inspect the compiler for system includes: %-(%s %)", args);
65         logger.trace(res.output);
66         return null;
67     }
68 
69     auto incls = parseCompilerOutput(res.output);
70     cacheSysIncludes[compiler] = incls;
71 
72     return incls;
73 }
74 
75 private:
76 
77 string[] systemCompilerArg(ref CompileCommand cmd, const Compiler compiler) {
78     string[] args = ["-v", "/dev/null", "-fsyntax-only"];
79     if (auto v = language(compiler, cmd.command)) {
80         args = [v] ~ args;
81     }
82     if (auto v = sysroot(cmd.command)) {
83         args ~= v;
84     }
85     return [compiler.value] ~ args;
86 }
87 
88 SystemIncludePath[] parseCompilerOutput(T)(T output) {
89     import std.algorithm : countUntil, map;
90     import std.array : array;
91     import std.string : stripLeft, splitLines;
92 
93     auto lines = output.splitLines;
94     const start = lines.countUntil("#include <...> search starts here:") + 1;
95     const end = lines.countUntil("End of search list.");
96     if (start == 0 || end == 0)
97         return null;
98 
99     auto incls = lines[start .. end].map!(a => SystemIncludePath(a.stripLeft)).array;
100 
101     return incls;
102 }
103 
104 SystemIncludePath[][Compiler] cacheSysIncludes;
105 
106 // assumes that compilers adher to the gcc and llvm commands use of --sysroot / -isysroot.
107 // depends on the fact that CompileCommand.Command always splits e.g. a --isysroot=foo to ["--sysroot", "foo"].
108 string[] sysroot(ref CompileCommand.Command cmd) {
109     import std.algorithm : countUntil;
110     import std.string : startsWith;
111 
112     auto index = cmd.countUntil!(a => a.startsWith("--sysroot")) + 1;
113     if (index > 0 && (index + 1) < cmd.length)
114         return cmd[index .. index + 1];
115 
116     index = cmd.countUntil!(a => a.startsWith("-isysroot")) + 1;
117     if (index > 0 && (index + 1) < cmd.length)
118         return cmd[index .. index + 1];
119 
120     return null;
121 }
122 
123 // assumes that compilers adher to the gcc and llvm commands of using -xLANG
124 string language(Compiler compiler, ref CompileCommand.Command cmd) {
125     import std.algorithm : countUntil;
126     import std.path : baseName;
127     import std.string : startsWith;
128     import std.typecons : No;
129 
130     auto index = cmd.countUntil!(a => a.startsWith("-x")) + 1;
131     if (index > 0)
132         return cmd[index];
133 
134     switch (compiler.baseName) {
135     case "cc":
136     case "clang":
137     case "gcc":
138         return "-xc";
139     case "c++":
140     case "clang++":
141     case "g++":
142         return "-xc++";
143     default:
144     }
145 
146     return null;
147 }
148 
149 @("shall parse the system flags")
150 unittest {
151     import std.typecons : Tuple;
152 
153     // arrange
154     immutable compiler_output = `Using built-in specs.
155 COLLECT_GCC=gcc
156 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
157 OFFLOAD_TARGET_NAMES=nvptx-none
158 OFFLOAD_TARGET_DEFAULT=1
159 Target: x86_64-linux-gnu
160 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
161 -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
162 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
163 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
164  --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
165 Thread model: posix
166 gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)
167 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'
168  /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
169  -Wformat-security
170 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
171         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
172 
173 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
174 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
175 ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
176 #include "..." search starts here:
177 #include <...> search starts here:
178  /usr/lib/gcc/x86_64-linux-gnu/7/include/foo
179  /usr/local/include
180  /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
181  /usr/include/x86_64-linux-gnu
182  /usr/include
183 End of search list.
184 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
185         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
186 
187 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
188 Compiler executable checksum: c8081a99abb72bbfd9129549110a350c
189 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/
190 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
191 r/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/
192 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'`;
193 
194     // act
195     auto sysflags = parseCompilerOutput(compiler_output);
196 
197     // assert
198     "/usr/lib/gcc/x86_64-linux-gnu/7/include/foo".shouldBeIn(sysflags);
199     "/usr/local/include".shouldBeIn(sysflags);
200     "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed".shouldBeIn(sysflags);
201     "/usr/include/x86_64-linux-gnu".shouldBeIn(sysflags);
202     "/usr/include".shouldBeIn(sysflags);
203 }