diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c3a391..e8f9861 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,14 +26,21 @@ add_library( ) target_include_directories( clangmetatool - PRIVATE ${CLANG_INCLUDE_DIRS} PUBLIC $ $ ) + +add_library( clangfixup INTERFACE IMPORTED ) +set_property( + TARGET clangfixup + APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CLANG_INCLUDE_DIRS} +) + target_link_libraries( clangmetatool clangTooling + clangfixup ) install( diff --git a/include/clangmetatool/meta_tool.h b/include/clangmetatool/meta_tool.h index 6331ddd..54374f2 100644 --- a/include/clangmetatool/meta_tool.h +++ b/include/clangmetatool/meta_tool.h @@ -15,7 +15,22 @@ #include namespace clangmetatool { +namespace { +// Code to check if there exists a member T::ArgTypes that was created as a +// typedef +template +using void_t = void; + +template +struct has_typedef_ArgTypes : std::false_type { +}; + +template +struct has_typedef_ArgTypes> : std::true_type { +}; + +} /** * MetaTool is a template that reduces the amount of boilerplate * required to write a clang tool. The WrappedTool is a class that @@ -35,11 +50,33 @@ namespace clangmetatool { */ template class MetaTool : public clang::ASTFrontendAction { + private: + static constexpr bool providesArgTypes = has_typedef_ArgTypes::value; + struct NoArgs { typedef void* ArgTypes; }; + public: + typedef typename std::conditional_t::ArgTypes ArgTypes; private: std::map &replacementsMap; clang::ast_matchers::MatchFinder f; WrappedTool *tool; + + ArgTypes& args; + + + template + WrappedTool* create_tool(clang::CompilerInstance &ci, A args) { + return new WrappedTool(&ci, &f, args); + } + WrappedTool* create_tool(clang::CompilerInstance &ci, typename NoArgs::ArgTypes& args) { + return new WrappedTool(&ci, &f); + } + public: + MetaTool + (std::map &replacementsMap, + ArgTypes& args) + :replacementsMap(replacementsMap), tool(NULL), args(args) { } + MetaTool (std::map &replacementsMap) :replacementsMap(replacementsMap), tool(NULL) { } @@ -49,13 +86,14 @@ namespace clangmetatool { delete tool; } + virtual bool BeginSourceFileAction(clang::CompilerInstance &ci) override { // we don't expect to ever have the metatool be invoked more // than once, it would eventually result in us holding // references to unused compiler instance objects, and // eventually segfaulting, so assert here. assert(tool == NULL); - tool = new WrappedTool(&ci, &f); + tool = create_tool(ci, args); return true; } diff --git a/include/clangmetatool/meta_tool_factory.h b/include/clangmetatool/meta_tool_factory.h index ee124bb..60cd1bf 100644 --- a/include/clangmetatool/meta_tool_factory.h +++ b/include/clangmetatool/meta_tool_factory.h @@ -9,7 +9,6 @@ #include namespace clangmetatool { - /** * MetaToolFactory wraps around FrontendAction class that takes a * replacementsMap as argument to the construtor. You can use it in @@ -20,6 +19,8 @@ namespace clangmetatool { class MetaToolFactory : public clang::tooling::FrontendActionFactory { private: + // T *must* provide this + typename T::ArgTypes args; /** * List of replacements to be used in the run. @@ -29,18 +30,24 @@ namespace clangmetatool { /** * Metatool factory takes a reference to the replacements map that - * will be used for this run. + * will be used for this run in, along with any additional arguments that + * need to be passed on to the Tool */ MetaToolFactory + (std::map &replacements, + typename T::ArgTypes& args) + :replacements(replacements), args(args) { } + + MetaToolFactory (std::map &replacements) :replacements(replacements) { } - + /** * This will create the object of your tool giving the * replacemnets map as an argument. */ virtual clang::FrontendAction* create() { - return new T(replacements); + return new T(replacements, args); } }; diff --git a/skeleton/src/main.cpp b/skeleton/src/main.cpp index 4ea843b..123c3b0 100644 --- a/skeleton/src/main.cpp +++ b/skeleton/src/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -14,7 +15,20 @@ class MyTool { public: - MyTool(clang::CompilerInstance* ci, clang::ast_matchers::MatchFinder *f) { + /** + * This typedef is optional, causing two possible constructor signatures: + * For classes the define it: + * MyTool( clang::CompilerInstance*, + * clang::ast_matchers::MatchFinder*, + * ArgTypes ) + * Or, for classes that choose not to define ArgTypes: + * MyTool( clang::CompilerInstance*, + * clang::ast_matchers::MatchFinder* ) + */ + typedef std::tuple<> ArgTypes; + MyTool(clang::CompilerInstance* ci, + clang::ast_matchers::MatchFinder *f, + ArgTypes arguments) { } void postProcessing (std::map &replacementsMap) { @@ -33,8 +47,9 @@ int main(int argc, const char* argv[]) { clang::tooling::RefactoringTool tool(optionsParser.getCompilations(), optionsParser.getSourcePathList()); + MyTool::ArgTypes toolArgs; clangmetatool::MetaToolFactory< clangmetatool::MetaTool > - raf(tool.getReplacements()); + raf(tool.getReplacements(), toolArgs); int r = tool.runAndSave(&raf); diff --git a/t/014-meta-tool-arbitrary-args.t.cpp b/t/014-meta-tool-arbitrary-args.t.cpp new file mode 100644 index 0000000..892f16b --- /dev/null +++ b/t/014-meta-tool-arbitrary-args.t.cpp @@ -0,0 +1,101 @@ +#include "clangmetatool-testconfig.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +bool constructor_called; +bool postprocessing_called; + +class MyTool { +public: + typedef std::tuple ArgTypes; + +private: + clang::CompilerInstance* ci; + clang::ast_matchers::MatchFinder *f; + ArgTypes args; + +public: + MyTool(clang::CompilerInstance* ci, + clang::ast_matchers::MatchFinder *f, + ArgTypes& args) + :ci(ci), f(f), args(args) { + constructor_called = true; + } + void postProcessing + (std::map &replacementsMap) { + ASSERT_NE((void*)NULL, ci); + ASSERT_NE((void*)NULL, f); + + EXPECT_EQ('$', std::get<0>(args)); + EXPECT_EQ(42, std::get<1>(args)); + EXPECT_EQ(std::string("the answer"), std::get<2>(args)); + + postprocessing_called = true; + } +}; + +// A struct to pass as an argument to MyTool +struct AStruct { + int dummyField; + AStruct() : dummyField(0) {}; + bool operator==(const AStruct& other) const { + return dummyField == other.dummyField; + } +}; + +TEST(use_meta_tool, factory) { + llvm::cl::OptionCategory MyToolCategory("my-tool options"); + + int argc = 4; + const char* argv[] = { "foo", "/dev/null", "--", "-xc++" }; + + clang::tooling::CommonOptionsParser + optionsParser + ( argc, argv, + MyToolCategory ); + clang::tooling::RefactoringTool tool + ( optionsParser.getCompilations(), + optionsParser.getSourcePathList()); + + constructor_called = false; + postprocessing_called = false; + + MyTool::ArgTypes toolArgs('$', 42, "the answer"); + clangmetatool::MetaToolFactory< + clangmetatool::MetaTool< MyTool > > raf(tool.getReplacements(), toolArgs); + + int r = tool.runAndSave(&raf); + ASSERT_EQ(0, r); + ASSERT_EQ(true, constructor_called); + ASSERT_EQ(true, postprocessing_called); + +} + + +// ---------------------------------------------------------------------------- +// Copyright 2018 Bloomberg Finance L.P. +// +// 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. +// ----------------------------- END-OF-FILE ---------------------------------- diff --git a/t/CMakeLists.txt b/t/CMakeLists.txt index bd580b5..4a3dc09 100644 --- a/t/CMakeLists.txt +++ b/t/CMakeLists.txt @@ -6,6 +6,10 @@ configure_file( include/clangmetatool-testconfig.h ) +add_library( gtestfixup INTERFACE IMPORTED ) +set_property( TARGET gtestfixup + APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${GTEST_INCLUDE_DIRS} ) + foreach( TEST 001-meta-tool @@ -19,16 +23,15 @@ foreach( 009-definitions-one-func-in-namespace 010-definitions-two-class-defs-with-funcs 011-definitions-templates + 014-meta-tool-arbitrary-args ) add_executable(${TEST}.t ${TEST}.t.cpp) target_include_directories( ${TEST}.t PRIVATE - ${GTEST_INCLUDE_DIRS} ${CMAKE_BINARY_DIR}/t/include ${CMAKE_SOURCE_DIR}/t/include - ${CLANG_INCLUDE_DIRS} $ $ ) @@ -37,6 +40,8 @@ foreach( ${TEST}.t clangmetatool clangTooling + clangfixup + gtestfixup ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} )