diff options
author | Brad King <brad.king@kitware.com> | 2014-01-10 18:24:56 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2014-02-26 16:54:30 (GMT) |
commit | f2efefcde133208c355f235baf5dee4d4287e466 (patch) | |
tree | f75b6cb2a49fe587cdfc4c0871a23c2ceebb5c52 /src | |
parent | 0c62ed33c937ccb101fcb6f30306626da1c312e5 (diff) | |
download | CastXML-f2efefcde133208c355f235baf5dee4d4287e466.zip CastXML-f2efefcde133208c355f235baf5dee4d4287e466.tar.gz CastXML-f2efefcde133208c355f235baf5dee4d4287e466.tar.bz2 |
Add API to run Clang internally
Use the Clang Driver API to construct an invocation of Clang like the
main "clang" compiler tool would for syntax-only or preprocess-only
actions. Add two front-end actions, one for the '-E' preprocess-only
option and one for our syntax-only AST traversal. Implement a
syntax-only action that falls back to the Clang syntax-only action
unless xml output is requested. Leave xml output unimplemented for now.
Capture the '-o <output-file>' option ourselves since the Clang Driver
API will ignore it when using a syntax-only action. Forward it to the
compiler invocation frontend options just before constructing the
frontend action implementation.
When '--castxml-cc-<id>' is given tell the driver to suppress its own
standard include path detection. Give the driver options to configure
it to match the detected target triple and header search path. Tell
it to suppress its builtin definitions. Hook in to the front-end
action for each input source file to put the detected predefines into
the preprocessor configuration.
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 15 | ||||
-rw-r--r-- | src/Options.h | 4 | ||||
-rw-r--r-- | src/RunClang.cxx | 285 | ||||
-rw-r--r-- | src/RunClang.h | 26 | ||||
-rw-r--r-- | src/castxml.cxx | 32 |
5 files changed, 356 insertions, 6 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0320e1..d4be67b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,19 @@ configure_file( "${CastXML_BINARY_DIR}/CMakeFiles/castxmlSourceDir.txt" @ONLY ) +set(clang_libs + clangFrontend + clangDriver + clangSerialization + clangParse + clangSema + clangAnalysis + clangEdit + clangAST + clangLex + clangBasic + ) + llvm_map_components_to_libraries(llvm_libs native option bitreader) add_executable(castxml @@ -27,10 +40,12 @@ add_executable(castxml Detect.cxx Detect.h Options.h + RunClang.cxx RunClang.h Utils.cxx Utils.h ) target_link_libraries(castxml cxsys + ${clang_libs} ${llvm_libs} ) set_property(SOURCE Utils.cxx APPEND PROPERTY COMPILE_DEFINITIONS diff --git a/src/Options.h b/src/Options.h index 84c1fa6..58ba3b5 100644 --- a/src/Options.h +++ b/src/Options.h @@ -22,9 +22,11 @@ struct Options { - Options(): GccXml(false), HaveCC(false) {} + Options(): PPOnly(false), GccXml(false), HaveCC(false) {} + bool PPOnly; bool GccXml; bool HaveCC; + std::string OutputFile; std::vector<std::string> Includes; std::string Predefines; std::string Triple; diff --git a/src/RunClang.cxx b/src/RunClang.cxx new file mode 100644 index 0000000..6db3f95 --- /dev/null +++ b/src/RunClang.cxx @@ -0,0 +1,285 @@ +/* + Copyright Kitware, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "RunClang.h" +#include "Options.h" + +#include "clang/AST/ASTConsumer.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "clang/Driver/Tool.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/raw_ostream.h" + +#include <iostream> + +//---------------------------------------------------------------------------- +class ASTConsumer: public clang::ASTConsumer +{ + clang::CompilerInstance& CI; + llvm::raw_ostream& OS; +public: + ASTConsumer(clang::CompilerInstance& ci, llvm::raw_ostream& os): + CI(ci), OS(os) {} + + void HandleTranslationUnit(clang::ASTContext& ctx) { + // TODO: Run visitor on this translation unit. + } +}; + +//---------------------------------------------------------------------------- +template <class T> +class CastXMLPredefines: public T +{ +protected: + Options const& Opts; + + CastXMLPredefines(Options const& opts): Opts(opts) {} + std::string UpdatePredefines(std::string const& predefines) { + // Clang's InitializeStandardPredefinedMacros forces some + // predefines even when -undef is given. Filter them out. + // Also substitute our chosen predefines prior to those that came + // from the command line. + char const predef_start[] = "# 1 \"<built-in>\" 3\n"; + char const predef_end[] = "# 1 \"<command line>\" 1\n"; + std::string::size_type start = predefines.find(predef_start); + std::string::size_type end = predefines.find(predef_end); + if(start != std::string::npos && end != std::string::npos) { + return (predefines.substr(0, start+sizeof(predef_start)-1) + + this->Opts.Predefines + + predefines.substr(end)); + } else { + return predefines + this->Opts.Predefines; + } + } + + bool BeginSourceFileAction(clang::CompilerInstance& CI, + llvm::StringRef /*Filename*/) { + if(this->Opts.HaveCC) { + CI.getPreprocessor().setPredefines( + this->UpdatePredefines(CI.getPreprocessor().getPredefines())); + } + return true; + } +}; + +//---------------------------------------------------------------------------- +class CastXMLPrintPreprocessedAction: + public CastXMLPredefines<clang::PrintPreprocessedAction> +{ +public: + CastXMLPrintPreprocessedAction(Options const& opts): + CastXMLPredefines(opts) {} +}; + +//---------------------------------------------------------------------------- +class CastXMLSyntaxOnlyAction: + public CastXMLPredefines<clang::SyntaxOnlyAction> +{ + clang::ASTConsumer* CreateASTConsumer(clang::CompilerInstance &CI, + llvm::StringRef InFile) { + using llvm::sys::path::filename; + if(!this->Opts.GccXml) { + return clang::SyntaxOnlyAction::CreateASTConsumer(CI, InFile); + } else if(llvm::raw_ostream* OS = + CI.createDefaultOutputFile(false, filename(InFile), "xml")) { + return new ASTConsumer(CI, *OS); + } else { + return 0; + } + } +public: + CastXMLSyntaxOnlyAction(Options const& opts): + CastXMLPredefines(opts) {} +}; + +//---------------------------------------------------------------------------- +static clang::FrontendAction* +CreateFrontendAction(clang::CompilerInstance* CI, Options const& opts) +{ + clang::frontend::ActionKind action = + CI->getInvocation().getFrontendOpts().ProgramAction; + switch(action) { + case clang::frontend::PrintPreprocessedInput: + return new CastXMLPrintPreprocessedAction(opts); + case clang::frontend::ParseSyntaxOnly: + return new CastXMLSyntaxOnlyAction(opts); + default: + std::cerr << "error: unsupported action: " << int(action) << "\n"; + return 0; + } +} + +//---------------------------------------------------------------------------- +static bool runClangCI(clang::CompilerInstance* CI, Options const& opts) +{ + // Create a diagnostics engine for this compiler instance. + CI->createDiagnostics(); + if(!CI->hasDiagnostics()) { + return false; + } + + // We do not need function bodies. + CI->getFrontendOpts().SkipFunctionBodies = true; + + // Set frontend options we captured directly. + CI->getFrontendOpts().OutputFile = opts.OutputFile; + + // Construct our Clang front-end action. This dispatches + // handling of each input file with an action based on the + // flags provided (e.g. -E to preprocess-only). + llvm::OwningPtr<clang::FrontendAction> + action(CreateFrontendAction(CI, opts)); + if(action) { + return CI->ExecuteAction(*action); + } else { + return false; + } +} + +//---------------------------------------------------------------------------- +static llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> +runClangCreateDiagnostics(const char* const* argBeg, const char* const* argEnd) +{ + llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> + diagOpts(new clang::DiagnosticOptions); + llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> + diagID(new clang::DiagnosticIDs()); + llvm::OwningPtr<llvm::opt::OptTable> + opts(clang::driver::createDriverOptTable()); + unsigned missingArgIndex, missingArgCount; + llvm::OwningPtr<llvm::opt::InputArgList> + args(opts->ParseArgs(argBeg, argEnd, missingArgIndex, missingArgCount)); + clang::ParseDiagnosticArgs(*diagOpts, *args); + clang::TextDiagnosticPrinter* diagClient = + new clang::TextDiagnosticPrinter(llvm::errs(), &*diagOpts); + llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> + diags(new clang::DiagnosticsEngine(diagID, &*diagOpts, diagClient)); + clang::ProcessWarningOptions(*diags, *diagOpts, /*ReportDiags=*/false); + return diags; +} + +//---------------------------------------------------------------------------- +static int runClangImpl(const char* const* argBeg, + const char* const* argEnd, + Options const& opts) +{ + // Construct a diagnostics engine for use while processing driver options. + llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags = + runClangCreateDiagnostics(argBeg, argEnd); + + // Use the approach in clang::createInvocationFromCommandLine to + // get system compiler setting arguments from the Driver. + clang::driver::Driver d("clang", llvm::sys::getDefaultTargetTriple(), + "dummy.out", *diags); + llvm::SmallVector<const char *, 16> cArgs; + cArgs.push_back("<clang>"); + cArgs.insert(cArgs.end(), argBeg, argEnd); + + // Tell the driver not to generate any commands past syntax parsing. + if(opts.PPOnly) { + cArgs.push_back("-E"); + } else { + cArgs.push_back("-fsyntax-only"); + } + + // Ask the driver to build the compiler commands for us. + llvm::OwningPtr<clang::driver::Compilation> c(d.BuildCompilation(cArgs)); + + // For '-###' just print the jobs and exit early. + if(c->getArgs().hasArg(clang::driver::options::OPT__HASH_HASH_HASH)) { + c->getJobs().Print(llvm::errs(), "\n", true); + return 0; + } + + // Reject '-o' with multiple inputs. + if(!opts.OutputFile.empty() && c->getJobs().size() > 1) { + diags->Report(clang::diag::err_drv_output_argument_with_multiple_files); + return 1; + } + + // Run Clang for each compilation computed by the driver. + // This should be once per input source file. + bool result = true; + for(clang::driver::JobList::const_iterator i = c->getJobs().begin(), + e = c->getJobs().end(); i != e; ++i) { + clang::driver::Command* cmd = llvm::dyn_cast<clang::driver::Command>(*i); + if(cmd && strcmp(cmd->getCreator().getName(), "clang") == 0) { + // Invoke Clang with this set of arguments. + llvm::OwningPtr<clang::CompilerInstance> + CI(new clang::CompilerInstance()); + const char* const* cmdArgBeg = cmd->getArguments().data(); + const char* const* cmdArgEnd = cmdArgBeg + cmd->getArguments().size(); + if (clang::CompilerInvocation::CreateFromArgs + (CI->getInvocation(), cmdArgBeg, cmdArgEnd, *diags)) { + result = runClangCI(CI.get(), opts) && result; + } else { + result = false; + } + } else { + // Skip this unexpected job. + llvm::SmallString<128> buf; + llvm::raw_svector_ostream msg(buf); + (*i)->Print(msg, "\n", true); + diags->Report(clang::diag::err_fe_expected_clang_command); + diags->Report(clang::diag::err_fe_expected_compiler_job) + << msg.str(); + result = false; + } + } + return result? 0:1; +} + +//---------------------------------------------------------------------------- +int runClang(const char* const* argBeg, + const char* const* argEnd, + Options const& opts) +{ + llvm::SmallVector<const char*, 32> args(argBeg, argEnd); + + if(opts.HaveCC) { + // Configure target to match that of given compiler. + if(!opts.Triple.empty()) { + args.push_back("-target"); + args.push_back(opts.Triple.c_str()); + } + + // Tell Clang driver not to add its header search paths. + args.push_back("-nostdinc"); + + // Add header search paths detected from given compiler. + for(std::vector<std::string>::const_iterator i = opts.Includes.begin(), + e = opts.Includes.end(); i != e; ++i) { + args.push_back("-isystem"); + args.push_back(i->c_str()); + } + + // Tell Clang not to add its predefines. + args.push_back("-undef"); + } + + return runClangImpl(args.data(), args.data() + args.size(), opts); +} diff --git a/src/RunClang.h b/src/RunClang.h new file mode 100644 index 0000000..4918137 --- /dev/null +++ b/src/RunClang.h @@ -0,0 +1,26 @@ +/* + Copyright Kitware, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifndef CASTXML_RUNCLANG_H +#define CASTXML_RUNCLANG_H + +struct Options; + +/// runClang - Run Clang with given user arguments and detected options. +int runClang(const char* const* argBeg, + const char* const* argEnd, + Options const& opts); + +#endif // CASTXML_RUNCLANG_H diff --git a/src/castxml.cxx b/src/castxml.cxx index 983a508..3766e5d 100644 --- a/src/castxml.cxx +++ b/src/castxml.cxx @@ -16,6 +16,7 @@ #include "Detect.h" #include "Options.h" +#include "RunClang.h" #include "Utils.h" #include "llvm/ADT/SmallVector.h" @@ -74,11 +75,33 @@ int main(int argc, const char* const * argv) ; return 1; } + } else if(strcmp(argv[i], "-E") == 0) { + opts.PPOnly = true; + } else if(strcmp(argv[i], "-o") == 0) { + if((i+1) < argc) { + opts.OutputFile = argv[++i]; + } else { + std::cerr << + "error: argument to '-o' is missing (expected 1 value)\n" + "\n" << + usage + ; + return 1; + } } else { clang_args.push_back(argv[i]); } } + if(opts.PPOnly && opts.GccXml) { + std::cerr << + "error: '--castxml-gccxml' and '-E' may not both be given\n" + "\n" << + usage + ; + return 1; + } + if(cc_id) { opts.HaveCC = true; if(cc_args.empty()) { @@ -96,11 +119,10 @@ int main(int argc, const char* const * argv) } } - std::cerr << opts.Predefines; - for(std::vector<std::string>::iterator i = opts.Includes.begin(), - e = opts.Includes.end(); i != e; ++i) { - std::cerr << *i << "\n"; + if(clang_args.empty()) { + return 0; } - return 0; + return runClang(clang_args.data(), clang_args.data() + clang_args.size(), + opts); } |