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 }