Relax some preconditions for using FixedCompilationDatabase.
FixedCompilationDatabase (FCD) requires that the arguments it consumes after '--' must not include positional parameters or the argv[0] of the tool. This patch relaxes those restrictions. llvm-svn: 194968
This commit is contained in:
parent
ce0697f475
commit
eb56f4fe2f
|
@ -20,6 +20,16 @@
|
|||
#include "llvm/Support/system_error.h"
|
||||
#include <sstream>
|
||||
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Driver/Action.h"
|
||||
#include "clang/Driver/Driver.h"
|
||||
#include "clang/Driver/DriverDiagnostic.h"
|
||||
#include "clang/Driver/Job.h"
|
||||
#include "clang/Driver/Compilation.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "llvm/Support/Host.h"
|
||||
#include "llvm/Option/Arg.h"
|
||||
|
||||
namespace clang {
|
||||
namespace tooling {
|
||||
|
||||
|
@ -100,6 +110,175 @@ CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir,
|
|||
|
||||
CompilationDatabasePlugin::~CompilationDatabasePlugin() {}
|
||||
|
||||
// Helper for recursively searching through a chain of actions and collecting
|
||||
// all inputs, direct and indirect, of compile jobs.
|
||||
struct CompileJobAnalyzer {
|
||||
void run(const driver::Action *A) {
|
||||
runImpl(A, false);
|
||||
}
|
||||
|
||||
SmallVector<std::string, 2> Inputs;
|
||||
|
||||
private:
|
||||
|
||||
void runImpl(const driver::Action *A, bool Collect) {
|
||||
bool CollectChildren = Collect;
|
||||
switch (A->getKind()) {
|
||||
case driver::Action::CompileJobClass:
|
||||
CollectChildren = true;
|
||||
break;
|
||||
|
||||
case driver::Action::InputClass: {
|
||||
if (Collect) {
|
||||
const driver::InputAction *IA = cast<driver::InputAction>(A);
|
||||
Inputs.push_back(IA->getInputArg().getSpelling());
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
// Don't care about others
|
||||
;
|
||||
}
|
||||
|
||||
for (driver::ActionList::const_iterator I = A->begin(), E = A->end();
|
||||
I != E; ++I)
|
||||
runImpl(*I, CollectChildren);
|
||||
}
|
||||
};
|
||||
|
||||
// Special DiagnosticConsumer that looks for warn_drv_input_file_unused
|
||||
// diagnostics from the driver and collects the option strings for those unused
|
||||
// options.
|
||||
class UnusedInputDiagConsumer : public DiagnosticConsumer {
|
||||
public:
|
||||
UnusedInputDiagConsumer() : Other(0) {}
|
||||
|
||||
// Useful for debugging, chain diagnostics to another consumer after
|
||||
// recording for our own purposes.
|
||||
UnusedInputDiagConsumer(DiagnosticConsumer *Other) : Other(Other) {}
|
||||
|
||||
virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
|
||||
const Diagnostic &Info) LLVM_OVERRIDE {
|
||||
if (Info.getID() == clang::diag::warn_drv_input_file_unused) {
|
||||
// Arg 1 for this diagnostic is the option that didn't get used.
|
||||
UnusedInputs.push_back(Info.getArgStdStr(0));
|
||||
}
|
||||
if (Other)
|
||||
Other->HandleDiagnostic(DiagLevel, Info);
|
||||
}
|
||||
|
||||
DiagnosticConsumer *Other;
|
||||
SmallVector<std::string, 2> UnusedInputs;
|
||||
};
|
||||
|
||||
// Unary functor for asking "Given a StringRef S1, does there exist a string
|
||||
// S2 in Arr where S1 == S2?"
|
||||
struct MatchesAny {
|
||||
MatchesAny(ArrayRef<std::string> Arr) : Arr(Arr) {}
|
||||
bool operator() (StringRef S) {
|
||||
for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I)
|
||||
if (*I == S)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
ArrayRef<std::string> Arr;
|
||||
};
|
||||
|
||||
/// \brief Strips any positional args and possible argv[0] from a command-line
|
||||
/// provided by the user to construct a FixedCompilationDatabase.
|
||||
///
|
||||
/// FixedCompilationDatabase requires a command line to be in this format as it
|
||||
/// constructs the command line for each file by appending the name of the file
|
||||
/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the
|
||||
/// start of the command line although its value is not important as it's just
|
||||
/// ignored by the Driver invoked by the ClangTool using the
|
||||
/// FixedCompilationDatabase.
|
||||
///
|
||||
/// FIXME: This functionality should probably be made available by
|
||||
/// clang::driver::Driver although what the interface should look like is not
|
||||
/// clear.
|
||||
///
|
||||
/// \param[in] Args Args as provided by the user.
|
||||
/// \param[out] Resulting stripped command line.
|
||||
///
|
||||
/// \returns \li true if successful.
|
||||
/// \li false if \c Args cannot be used for compilation jobs (e.g.
|
||||
/// contains an option like -E or -version).
|
||||
bool stripPositionalArgs(std::vector<const char *> Args,
|
||||
std::vector<std::string> &Result) {
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
UnusedInputDiagConsumer DiagClient;
|
||||
DiagnosticsEngine Diagnostics(
|
||||
IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()),
|
||||
&*DiagOpts, &DiagClient, false);
|
||||
|
||||
// Neither clang executable nor default image name are required since the
|
||||
// jobs the driver builds will not be executed.
|
||||
OwningPtr<driver::Driver> NewDriver(new driver::Driver(
|
||||
/* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),
|
||||
/* DefaultImageName= */ "", Diagnostics));
|
||||
NewDriver->setCheckInputsExist(false);
|
||||
|
||||
// This becomes the new argv[0]. The value is actually not important as it
|
||||
// isn't used for invoking Tools.
|
||||
Args.insert(Args.begin(), "clang-tool");
|
||||
|
||||
// By adding -c, we force the driver to treat compilation as the last phase.
|
||||
// It will then issue warnings via Diagnostics about un-used options that
|
||||
// would have been used for linking. If the user provided a compiler name as
|
||||
// the original argv[0], this will be treated as a linker input thanks to
|
||||
// insertng a new argv[0] above. All un-used options get collected by
|
||||
// UnusedInputdiagConsumer and get stripped out later.
|
||||
Args.push_back("-c");
|
||||
|
||||
// Put a dummy C++ file on to ensure there's at least one compile job for the
|
||||
// driver to construct. If the user specified some other argument that
|
||||
// prevents compilation, e.g. -E or something like -version, we may still end
|
||||
// up with no jobs but then this is the user's fault.
|
||||
Args.push_back("placeholder.cpp");
|
||||
|
||||
const OwningPtr<driver::Compilation> Compilation(
|
||||
NewDriver->BuildCompilation(Args));
|
||||
|
||||
const driver::JobList &Jobs = Compilation->getJobs();
|
||||
|
||||
CompileJobAnalyzer CompileAnalyzer;
|
||||
|
||||
for (driver::JobList::const_iterator I = Jobs.begin(), E = Jobs.end(); I != E;
|
||||
++I) {
|
||||
if ((*I)->getKind() == driver::Job::CommandClass) {
|
||||
const driver::Command *Cmd = cast<driver::Command>(*I);
|
||||
// Collect only for Assemble jobs. If we do all jobs we get duplicates
|
||||
// since Link jobs point to Assemble jobs as inputs.
|
||||
if (Cmd->getSource().getKind() == driver::Action::AssembleJobClass)
|
||||
CompileAnalyzer.run(&Cmd->getSource());
|
||||
}
|
||||
}
|
||||
|
||||
if (CompileAnalyzer.Inputs.empty()) {
|
||||
// No compile jobs found.
|
||||
// FIXME: Emit a warning of some kind?
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove all compilation input files from the command line. This is
|
||||
// necessary so that getCompileCommands() can construct a command line for
|
||||
// each file.
|
||||
std::vector<const char *>::iterator End = std::remove_if(
|
||||
Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs));
|
||||
|
||||
// Remove all inputs deemed unused for compilation.
|
||||
End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs));
|
||||
|
||||
// Remove the -c add above as well. It will be at the end right now.
|
||||
assert(*(End - 1) == "-c");
|
||||
--End;
|
||||
|
||||
Result = std::vector<std::string>(Args.begin() + 1, End);
|
||||
return true;
|
||||
}
|
||||
|
||||
FixedCompilationDatabase *
|
||||
FixedCompilationDatabase::loadFromCommandLine(int &Argc,
|
||||
const char **Argv,
|
||||
|
@ -107,9 +286,13 @@ FixedCompilationDatabase::loadFromCommandLine(int &Argc,
|
|||
const char **DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));
|
||||
if (DoubleDash == Argv + Argc)
|
||||
return NULL;
|
||||
std::vector<std::string> CommandLine(DoubleDash + 1, Argv + Argc);
|
||||
std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc);
|
||||
Argc = DoubleDash - Argv;
|
||||
return new FixedCompilationDatabase(Directory, CommandLine);
|
||||
|
||||
std::vector<std::string> StrippedArgs;
|
||||
if (!stripPositionalArgs(CommandLine, StrippedArgs))
|
||||
return 0;
|
||||
return new FixedCompilationDatabase(Directory, StrippedArgs);
|
||||
}
|
||||
|
||||
FixedCompilationDatabase::
|
||||
|
|
|
@ -450,7 +450,9 @@ TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {
|
|||
|
||||
TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
|
||||
int Argc = 5;
|
||||
const char *Argv[] = { "1", "2", "--\0no-constant-folding", "3", "4" };
|
||||
const char *Argv[] = {
|
||||
"1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4"
|
||||
};
|
||||
OwningPtr<FixedCompilationDatabase> Database(
|
||||
FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
|
||||
ASSERT_TRUE(Database.isValid());
|
||||
|
@ -460,8 +462,8 @@ TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
|
|||
ASSERT_EQ(".", Result[0].Directory);
|
||||
std::vector<std::string> CommandLine;
|
||||
CommandLine.push_back("clang-tool");
|
||||
CommandLine.push_back("3");
|
||||
CommandLine.push_back("4");
|
||||
CommandLine.push_back("-DDEF3");
|
||||
CommandLine.push_back("-DDEF4");
|
||||
CommandLine.push_back("source");
|
||||
ASSERT_EQ(CommandLine, Result[0].CommandLine);
|
||||
EXPECT_EQ(2, Argc);
|
||||
|
@ -484,5 +486,41 @@ TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {
|
|||
EXPECT_EQ(2, Argc);
|
||||
}
|
||||
|
||||
TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) {
|
||||
const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"};
|
||||
int Argc = sizeof(Argv) / sizeof(char*);
|
||||
OwningPtr<FixedCompilationDatabase> Database(
|
||||
FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
|
||||
ASSERT_TRUE(Database.isValid());
|
||||
std::vector<CompileCommand> Result =
|
||||
Database->getCompileCommands("source");
|
||||
ASSERT_EQ(1ul, Result.size());
|
||||
ASSERT_EQ(".", Result[0].Directory);
|
||||
std::vector<std::string> Expected;
|
||||
Expected.push_back("clang-tool");
|
||||
Expected.push_back("-c");
|
||||
Expected.push_back("-DDEF3");
|
||||
Expected.push_back("source");
|
||||
ASSERT_EQ(Expected, Result[0].CommandLine);
|
||||
EXPECT_EQ(2, Argc);
|
||||
}
|
||||
|
||||
TEST(ParseFixedCompilationDatabase, HandlesArgv0) {
|
||||
const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"};
|
||||
int Argc = sizeof(Argv) / sizeof(char*);
|
||||
OwningPtr<FixedCompilationDatabase> Database(
|
||||
FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
|
||||
ASSERT_TRUE(Database.isValid());
|
||||
std::vector<CompileCommand> Result =
|
||||
Database->getCompileCommands("source");
|
||||
ASSERT_EQ(1ul, Result.size());
|
||||
ASSERT_EQ(".", Result[0].Directory);
|
||||
std::vector<std::string> Expected;
|
||||
Expected.push_back("clang-tool");
|
||||
Expected.push_back("source");
|
||||
ASSERT_EQ(Expected, Result[0].CommandLine);
|
||||
EXPECT_EQ(2, Argc);
|
||||
}
|
||||
|
||||
} // end namespace tooling
|
||||
} // end namespace clang
|
||||
|
|
Loading…
Reference in New Issue