/// 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. /// \return Resulting stripped command line. /// \li true if successful. /// \li false if \c Args cannot be used for compilation jobs (e.g. /// contains an option like -E or -version). static bool stripPositionalArgs(std::vector<const char *> Args, std::vector<std::string> &Result, std::string &ErrorMsg) { IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); llvm::raw_string_ostream Output(ErrorMsg); TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts); UnusedInputDiagConsumer DiagClient(DiagnosticPrinter); DiagnosticsEngine Diagnostics( IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, &DiagClient, false); // The clang executable path isn't required since the jobs the driver builds // will not be executed. std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), Diagnostics)); NewDriver->setCheckInputsExist(false); // This becomes the new argv[0]. The value is used to detect libc++ include // dirs on Mac, it isn't used for other platforms. std::string Argv0 = GetClangToolCommand(); Args.insert(Args.begin(), Argv0.c_str()); // 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"); Args.erase(std::remove_if(Args.begin(), Args.end(), FilterUnusedFlags()), Args.end()); const std::unique_ptr<driver::Compilation> Compilation( NewDriver->BuildCompilation(Args)); if (!Compilation) return false; const driver::JobList &Jobs = Compilation->getJobs(); CompileJobAnalyzer CompileAnalyzer; for (const auto &Cmd : Jobs) { // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs // we get duplicates since Link jobs point to Assemble jobs as inputs. // -flto* flags make the BackendJobClass, which still needs analyzer. if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || Cmd.getSource().getKind() == driver::Action::BackendJobClass || Cmd.getSource().getKind() == driver::Action::CompileJobClass) { CompileAnalyzer.run(&Cmd.getSource()); } } if (CompileAnalyzer.Inputs.empty()) { ErrorMsg = "warning: no compile jobs found\n"; 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(strcmp(*(End - 1), "-c") == 0); --End; Result = std::vector<std::string>(Args.begin() + 1, End); return true; }
/// \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. /// \return Resulting stripped command line. /// \li true if successful. /// \li false if \c Args cannot be used for compilation jobs (e.g. /// contains an option like -E or -version). static 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); // The clang executable path isn't required since the jobs the driver builds // will not be executed. std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), 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"); // Remove -no-integrated-as; it's not used for syntax checking, // and it confuses targets which don't support this option. Args.erase(std::remove_if(Args.begin(), Args.end(), MatchesAny(std::string("-no-integrated-as"))), Args.end()); const std::unique_ptr<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(strcmp(*(End - 1), "-c") == 0); --End; Result = std::vector<std::string>(Args.begin() + 1, End); return true; }