Skip to content

Commit 99768b2

Browse files
committed
[clangd] (take 2) Try harder to find a plausible clang as argv0, particularly on Mac.
Summary: This was originally committed in 88bccde, and reverted in 93f7761. This version is now much more testable: the "detect toolchain properties" part is still not tested but also not active in tests. All the command manipulation based on the detected properties is directly tested, and also not active in other tests. Fixes clangd/clangd#211 Fixes clangd/clangd#178 Reviewers: kbobyrev, ilya-biryukov Subscribers: mgorny, ormris, cfe-commits, usaxena95, kadircet, arphaman, jkorous, MaskRay Tags: #clang Differential Revision: https://reviews.llvm.org/D71029
1 parent 4dac97e commit 99768b2

11 files changed

+390
-107
lines changed

clang-tools-extra/clangd/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ add_clang_library(clangDaemon
4141
ClangdServer.cpp
4242
CodeComplete.cpp
4343
CodeCompletionStrings.cpp
44+
CompileCommands.cpp
4445
Compiler.cpp
4546
Context.cpp
4647
Diagnostics.cpp

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,11 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
485485
llvm::makeArrayRef(ClangdServerOpts.QueryDriverGlobs),
486486
std::move(BaseCDB));
487487
}
488+
auto Mangler = CommandMangler::detect();
489+
if (ClangdServerOpts.ResourceDir)
490+
Mangler.ResourceDir = *ClangdServerOpts.ResourceDir;
488491
CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags,
489-
ClangdServerOpts.ResourceDir);
492+
tooling::ArgumentsAdjuster(Mangler));
490493
{
491494
// Switch caller's context with LSPServer's background context. Since we
492495
// rather want to propagate information from LSPServer's context into the
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//===--- CompileCommands.cpp ----------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "CompileCommands.h"
10+
#include "Logger.h"
11+
#include "clang/Frontend/CompilerInvocation.h"
12+
#include "clang/Tooling/ArgumentsAdjusters.h"
13+
#include "llvm/Support/FileSystem.h"
14+
#include "llvm/Support/FileUtilities.h"
15+
#include "llvm/Support/MemoryBuffer.h"
16+
#include "llvm/Support/Path.h"
17+
#include "llvm/Support/Program.h"
18+
19+
namespace clang {
20+
namespace clangd {
21+
namespace {
22+
23+
// Query apple's `xcrun` launcher, which is the source of truth for "how should"
24+
// clang be invoked on this system.
25+
llvm::Optional<std::string> queryXcrun(llvm::ArrayRef<llvm::StringRef> Argv) {
26+
auto Xcrun = llvm::sys::findProgramByName("xcrun");
27+
if (!Xcrun) {
28+
log("Couldn't find xcrun. Hopefully you have a non-apple toolchain...");
29+
return llvm::None;
30+
}
31+
llvm::SmallString<64> OutFile;
32+
llvm::sys::fs::createTemporaryFile("clangd-xcrun", "", OutFile);
33+
llvm::FileRemover OutRemover(OutFile);
34+
llvm::Optional<llvm::StringRef> Redirects[3] = {
35+
/*stdin=*/{""}, /*stdout=*/{OutFile}, /*stderr=*/{""}};
36+
vlog("Invoking {0} to find clang installation", *Xcrun);
37+
int Ret = llvm::sys::ExecuteAndWait(*Xcrun, Argv,
38+
/*Env=*/llvm::None, Redirects,
39+
/*SecondsToWait=*/10);
40+
if (Ret != 0) {
41+
log("xcrun exists but failed with code {0}. "
42+
"If you have a non-apple toolchain, this is OK. "
43+
"Otherwise, try xcode-select --install.",
44+
Ret);
45+
return llvm::None;
46+
}
47+
48+
auto Buf = llvm::MemoryBuffer::getFile(OutFile);
49+
if (!Buf) {
50+
log("Can't read xcrun output: {0}", Buf.getError().message());
51+
return llvm::None;
52+
}
53+
StringRef Path = Buf->get()->getBuffer().trim();
54+
if (Path.empty()) {
55+
log("xcrun produced no output");
56+
return llvm::None;
57+
}
58+
return Path.str();
59+
}
60+
61+
// Resolve symlinks if possible.
62+
std::string resolve(std::string Path) {
63+
llvm::SmallString<128> Resolved;
64+
if (llvm::sys::fs::real_path(Path, Resolved)) {
65+
log("Failed to resolve possible symlink {0}", Path);
66+
return Path;
67+
}
68+
return Resolved.str();
69+
}
70+
71+
// Get a plausible full `clang` path.
72+
// This is used in the fallback compile command, or when the CDB returns a
73+
// generic driver with no path.
74+
std::string detectClangPath() {
75+
// The driver and/or cc1 sometimes depend on the binary name to compute
76+
// useful things like the standard library location.
77+
// We need to emulate what clang on this system is likely to see.
78+
// cc1 in particular looks at the "real path" of the running process, and
79+
// so if /usr/bin/clang is a symlink, it sees the resolved path.
80+
// clangd doesn't have that luxury, so we resolve symlinks ourselves.
81+
82+
// On Mac, `which clang` is /usr/bin/clang. It runs `xcrun clang`, which knows
83+
// where the real clang is kept. We need to do the same thing,
84+
// because cc1 (not the driver!) will find libc++ relative to argv[0].
85+
#ifdef __APPLE__
86+
if (auto MacClang = queryXcrun({"xcrun", "--find", "clang"}))
87+
return resolve(std::move(*MacClang));
88+
#endif
89+
// On other platforms, just look for compilers on the PATH.
90+
for (const char *Name : {"clang", "gcc", "cc"})
91+
if (auto PathCC = llvm::sys::findProgramByName(Name))
92+
return resolve(std::move(*PathCC));
93+
// Fallback: a nonexistent 'clang' binary next to clangd.
94+
static int Dummy;
95+
std::string ClangdExecutable =
96+
llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy);
97+
SmallString<128> ClangPath;
98+
ClangPath = llvm::sys::path::parent_path(ClangdExecutable);
99+
llvm::sys::path::append(ClangPath, "clang");
100+
return ClangPath.str();
101+
}
102+
103+
// On mac, /usr/bin/clang sets SDKROOT and then invokes the real clang.
104+
// The effect of this is to set -isysroot correctly. We do the same.
105+
const llvm::Optional<std::string> detectSysroot() {
106+
#ifndef __APPLE__
107+
return llvm::None;
108+
#endif
109+
110+
// SDKROOT overridden in environment, respect it. Driver will set isysroot.
111+
if (::getenv("SDKROOT"))
112+
return llvm::None;
113+
return queryXcrun({"xcrun", "--show-sdk-path"});
114+
return llvm::None;
115+
}
116+
117+
std::string detectStandardResourceDir() {
118+
static int Dummy; // Just an address in this process.
119+
return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
120+
}
121+
122+
} // namespace
123+
124+
CommandMangler CommandMangler::detect() {
125+
CommandMangler Result;
126+
Result.ClangPath = detectClangPath();
127+
Result.ResourceDir = detectStandardResourceDir();
128+
Result.Sysroot = detectSysroot();
129+
return Result;
130+
}
131+
132+
CommandMangler CommandMangler::forTests() {
133+
return CommandMangler();
134+
}
135+
136+
void CommandMangler::adjust(std::vector<std::string> &Cmd) const {
137+
// Check whether the flag exists, either as -flag or -flag=*
138+
auto Has = [&](llvm::StringRef Flag) {
139+
for (llvm::StringRef Arg : Cmd) {
140+
if (Arg.consume_front(Flag) && (Arg.empty() || Arg[0] == '='))
141+
return true;
142+
}
143+
return false;
144+
};
145+
146+
// clangd should not write files to disk, including dependency files
147+
// requested on the command line.
148+
Cmd = tooling::getClangStripDependencyFileAdjuster()(Cmd, "");
149+
// Strip plugin related command line arguments. Clangd does
150+
// not support plugins currently. Therefore it breaks if
151+
// compiler tries to load plugins.
152+
Cmd = tooling::getStripPluginsAdjuster()(Cmd, "");
153+
Cmd = tooling::getClangSyntaxOnlyAdjuster()(Cmd, "");
154+
155+
if (ResourceDir && !Has("-resource-dir"))
156+
Cmd.push_back(("-resource-dir=" + *ResourceDir));
157+
158+
if (Sysroot && !Has("-isysroot")) {
159+
Cmd.push_back("-isysroot");
160+
Cmd.push_back(*Sysroot);
161+
}
162+
163+
// If the driver is a generic name like "g++" with no path, add a clang path.
164+
// This makes it easier for us to find the standard libraries on mac.
165+
if (ClangPath && llvm::sys::path::is_absolute(*ClangPath) && !Cmd.empty()) {
166+
std::string &Driver = Cmd.front();
167+
if (Driver == "clang" || Driver == "clang++" || Driver == "gcc" ||
168+
Driver == "g++" || Driver == "cc" || Driver == "c++") {
169+
llvm::SmallString<128> QualifiedDriver =
170+
llvm::sys::path::parent_path(*ClangPath);
171+
llvm::sys::path::append(QualifiedDriver, Driver);
172+
Driver = QualifiedDriver.str();
173+
}
174+
}
175+
}
176+
177+
CommandMangler::operator clang::tooling::ArgumentsAdjuster() {
178+
return [Mangler{*this}](const std::vector<std::string> &Args,
179+
llvm::StringRef File) {
180+
auto Result = Args;
181+
Mangler.adjust(Result);
182+
return Result;
183+
};
184+
}
185+
186+
} // namespace clangd
187+
} // namespace clang
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===--- CompileCommands.h - Manipulation of compile flags -------*- C++-*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "clang/Tooling/ArgumentsAdjusters.h"
10+
#include "clang/Tooling/CompilationDatabase.h"
11+
#include <string>
12+
#include <vector>
13+
14+
namespace clang {
15+
namespace clangd {
16+
17+
// CommandMangler transforms compile commands from some external source
18+
// for use in clangd. This means:
19+
// - running the frontend only, stripping args regarding output files etc
20+
// - forcing the use of clangd's builtin headers rather than clang's
21+
// - resolving argv0 as cc1 expects
22+
// - injecting -isysroot flags on mac as the system clang does
23+
struct CommandMangler {
24+
// Absolute path to clang.
25+
llvm::Optional<std::string> ClangPath;
26+
// Directory containing builtin headers.
27+
llvm::Optional<std::string> ResourceDir;
28+
// Root for searching for standard library (passed to -isysroot).
29+
llvm::Optional<std::string> Sysroot;
30+
31+
// A command-mangler that doesn't know anything about the system.
32+
// This is hermetic for unit-tests, but won't work well in production.
33+
static CommandMangler forTests();
34+
// Probe the system and build a command-mangler that knows the toolchain.
35+
// - try to find clang on $PATH, otherwise fake a path near clangd
36+
// - find the resource directory installed near clangd
37+
// - on mac, find clang and isysroot by querying the `xcrun` launcher
38+
static CommandMangler detect();
39+
40+
void adjust(std::vector<std::string> &Cmd) const;
41+
explicit operator clang::tooling::ArgumentsAdjuster();
42+
43+
private:
44+
CommandMangler() = default;
45+
};
46+
47+
} // namespace clangd
48+
} // namespace clang

clang-tools-extra/clangd/GlobalCompilationDatabase.cpp

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include "llvm/ADT/STLExtras.h"
1919
#include "llvm/ADT/SmallString.h"
2020
#include "llvm/Support/FileSystem.h"
21+
#include "llvm/Support/FileUtilities.h"
2122
#include "llvm/Support/Path.h"
23+
#include "llvm/Support/Program.h"
2224
#include <string>
2325
#include <tuple>
2426
#include <vector>
@@ -27,30 +29,6 @@ namespace clang {
2729
namespace clangd {
2830
namespace {
2931

30-
void adjustArguments(tooling::CompileCommand &Cmd,
31-
llvm::StringRef ResourceDir) {
32-
tooling::ArgumentsAdjuster ArgsAdjuster = tooling::combineAdjusters(
33-
// clangd should not write files to disk, including dependency files
34-
// requested on the command line.
35-
tooling::getClangStripDependencyFileAdjuster(),
36-
// Strip plugin related command line arguments. Clangd does
37-
// not support plugins currently. Therefore it breaks if
38-
// compiler tries to load plugins.
39-
tooling::combineAdjusters(tooling::getStripPluginsAdjuster(),
40-
tooling::getClangSyntaxOnlyAdjuster()));
41-
42-
Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
43-
// Inject the resource dir.
44-
// FIXME: Don't overwrite it if it's already there.
45-
if (!ResourceDir.empty())
46-
Cmd.CommandLine.push_back(("-resource-dir=" + ResourceDir).str());
47-
}
48-
49-
std::string getStandardResourceDir() {
50-
static int Dummy; // Just an address in this process.
51-
return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
52-
}
53-
5432
// Runs the given action on all parent directories of filename, starting from
5533
// deepest directory and going up to root. Stops whenever action succeeds.
5634
void actOnAllParentDirectories(PathRef FileName,
@@ -63,19 +41,9 @@ void actOnAllParentDirectories(PathRef FileName,
6341

6442
} // namespace
6543

66-
static std::string getFallbackClangPath() {
67-
static int Dummy;
68-
std::string ClangdExecutable =
69-
llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy);
70-
SmallString<128> ClangPath;
71-
ClangPath = llvm::sys::path::parent_path(ClangdExecutable);
72-
llvm::sys::path::append(ClangPath, "clang");
73-
return ClangPath.str();
74-
}
75-
7644
tooling::CompileCommand
7745
GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
78-
std::vector<std::string> Argv = {getFallbackClangPath()};
46+
std::vector<std::string> Argv = {"clang"};
7947
// Clang treats .h files as C by default and files without extension as linker
8048
// input, resulting in unhelpful diagnostics.
8149
// Parsing as Objective C++ is friendly to more cases.
@@ -263,9 +231,8 @@ DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
263231

264232
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
265233
std::vector<std::string> FallbackFlags,
266-
llvm::Optional<std::string> ResourceDir)
267-
: Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir)
268-
: getStandardResourceDir()),
234+
tooling::ArgumentsAdjuster Adjuster)
235+
: Base(Base), ArgsAdjuster(std::move(Adjuster)),
269236
FallbackFlags(std::move(FallbackFlags)) {
270237
if (Base)
271238
BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
@@ -286,7 +253,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
286253
Cmd = Base->getCompileCommand(File);
287254
if (!Cmd)
288255
return llvm::None;
289-
adjustArguments(*Cmd, ResourceDir);
256+
if (ArgsAdjuster)
257+
Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename);
290258
return Cmd;
291259
}
292260

@@ -296,7 +264,8 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
296264
std::lock_guard<std::mutex> Lock(Mutex);
297265
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
298266
FallbackFlags.end());
299-
adjustArguments(Cmd, ResourceDir);
267+
if (ArgsAdjuster)
268+
Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
300269
return Cmd;
301270
}
302271

clang-tools-extra/clangd/GlobalCompilationDatabase.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1111

12+
#include "CompileCommands.h"
1213
#include "Function.h"
1314
#include "Path.h"
15+
#include "clang/Tooling/ArgumentsAdjusters.h"
1416
#include "clang/Tooling/CompilationDatabase.h"
1517
#include "llvm/ADT/Optional.h"
1618
#include "llvm/ADT/StringMap.h"
@@ -19,7 +21,6 @@
1921
#include <vector>
2022

2123
namespace clang {
22-
2324
namespace clangd {
2425

2526
class Logger;
@@ -118,15 +119,17 @@ std::unique_ptr<GlobalCompilationDatabase>
118119
getQueryDriverDatabase(llvm::ArrayRef<std::string> QueryDriverGlobs,
119120
std::unique_ptr<GlobalCompilationDatabase> Base);
120121

122+
121123
/// Wraps another compilation database, and supports overriding the commands
122124
/// using an in-memory mapping.
123125
class OverlayCDB : public GlobalCompilationDatabase {
124126
public:
125127
// Base may be null, in which case no entries are inherited.
126128
// FallbackFlags are added to the fallback compile command.
129+
// Adjuster is applied to all commands, fallback or not.
127130
OverlayCDB(const GlobalCompilationDatabase *Base,
128131
std::vector<std::string> FallbackFlags = {},
129-
llvm::Optional<std::string> ResourceDir = llvm::None);
132+
tooling::ArgumentsAdjuster Adjuster = nullptr);
130133

131134
llvm::Optional<tooling::CompileCommand>
132135
getCompileCommand(PathRef File) const override;
@@ -142,7 +145,7 @@ class OverlayCDB : public GlobalCompilationDatabase {
142145
mutable std::mutex Mutex;
143146
llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
144147
const GlobalCompilationDatabase *Base;
145-
std::string ResourceDir;
148+
tooling::ArgumentsAdjuster ArgsAdjuster;
146149
std::vector<std::string> FallbackFlags;
147150
CommandChanged::Subscription BaseChanged;
148151
};

0 commit comments

Comments
 (0)