ioeric updated this revision to Diff 71845. ioeric marked 4 inline comments as done. ioeric added a comment.
- Added MigrationEnvironment class and a dummy migrate-tool binary. https://reviews.llvm.org/D24380 Files: CMakeLists.txt migrate-tool/AffectedFilesFinder.h migrate-tool/BuildManager.h migrate-tool/CMakeLists.txt migrate-tool/DummyMigrationEnvironment.cpp migrate-tool/DummyMigrationEnvironment.h migrate-tool/HeaderGenerator.cpp migrate-tool/HeaderGenerator.h migrate-tool/MigrateTool.cpp migrate-tool/MigrateTool.h migrate-tool/MigrationEnvironment.h migrate-tool/RefactoringManager.h migrate-tool/tool/CMakeLists.txt migrate-tool/tool/ClangMigrateTool.cpp test/CMakeLists.txt test/migrate-tool/Inputs/old.h test/migrate-tool/old.cpp unittests/CMakeLists.txt unittests/migrate-tool/CMakeLists.txt unittests/migrate-tool/HeaderBuildTest.cpp
Index: unittests/migrate-tool/HeaderBuildTest.cpp =================================================================== --- /dev/null +++ unittests/migrate-tool/HeaderBuildTest.cpp @@ -0,0 +1,100 @@ +//===-- HeaderGeneratorTest.cpp - HeaderGenerator unit tests ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGenerator.h" +#include "gtest/gtest.h" + +namespace clang { +namespace migrate_tool { + +TEST(HeaderGenerator, Empty) { + HeaderGenerator Hdr("a.h"); + std::string Expected = "#ifndef A_H\n" + "#define A_H\n" + "\n" + "#endif // A_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, SingleAlias) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, SingleAliasWithIncludes) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addInclude("x/y/z.h"); + Hdr.addInclude("x/y/zz.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "#include \"x/y/z.h\"\n" + "#include \"x/y/zz.h\"\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, MultipleAliasInOneNamespace) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + Hdr.addAlias("na::nb::D", "x::y::D"); + Hdr.addAlias("na::nb::Q", "x::y::Q"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "using D = ::x::y::D;\n" + "using Q = ::x::y::Q;\n" + "} // namespace nb\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderGenerator, AliasesInMultipleNamespace) { + HeaderGenerator Hdr("a/b/c.h"); + Hdr.addAlias("nb::Q", "x::Q"); + Hdr.addAlias("na::nb::C", "x::y::Z"); + Hdr.addAlias("na::nc::D", "x::y::D"); + Hdr.addAlias("na::nb::Q", "x::y::Q"); + std::string Expected = "#ifndef A_B_C_H\n" + "#define A_B_C_H\n" + "namespace nb {\n" + "using Q = ::x::Q;\n" + "} // namespace nb\n" + "namespace na {\n" + "namespace nb {\n" + "using C = ::x::y::Z;\n" + "using Q = ::x::y::Q;\n" + "} // namespace nb\n" + "namespace nc {\n" + "using D = ::x::y::D;\n" + "} // namespace nc\n" + "} // namespace na\n" + "#endif // A_B_C_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +} // namespace migrate_tool +} // namespace clang Index: unittests/migrate-tool/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/migrate-tool/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +get_filename_component(MIGRATE_TOOL_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../migrate-tool REALPATH) +include_directories( + ${MIGRATE_TOOL_SOURCE_DIR} + ) + +# We'd like to clang/unittests/Tooling/RewriterTestContext.h in the test. +include_directories(${CLANG_SOURCE_DIR}) + +add_extra_unittest(MigrateToolTests + HeaderBuildTest.cpp + ) + +target_link_libraries(MigrateToolTests + migrateTool + clangBasic + clangFormat + clangRewrite + clangTooling + clangToolingCore + ) Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -9,3 +9,4 @@ add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(include-fixer) +add_subdirectory(migrate-tool) Index: test/migrate-tool/old.cpp =================================================================== --- /dev/null +++ test/migrate-tool/old.cpp @@ -0,0 +1,27 @@ +// RUN: migrate-tool -old_header=%S/Inputs/old.h -new_header=%T/new.h -old_name=old::Old -new_name=new::New | FileCheck %s +// RUN: cat %T/new.h | FileCheck %s -check-prefix=CHECK-HEADER +// CHECK-HEADER: #ifndef {{.*}}_NEW_H +// CHECK-HEADER-NEXT: #define {{.*}}_NEW_H +// CHECK-HEADER-NEXT: #include "{{.*}}/old.h" +// CHECK-HEADER-NEXT: namespace new { +// CHECK-HEADER-NEXT: using New = ::old::Old; +// CHECK-HEADER-NEXT: } // namespace new +// CHECK-HEADER-NEXT: #endif // {{.*}}_NEW_H + +// CHECK: Preparation phase... +// CHECK-NEXT: Adding library "{{.*}}/new" with soucee files [{{.*}}/new.h] +// CHECK-NEXT: Rename phase... +// CHECK-NEXT: Getting affected files for symbol "old::Old": [dummy.h,dummy.cpp] +// CHECK-NEXT: Renaming: +// CHECK-NEXT: old::Old to new::New +// CHECK-NEXT: in files [dummy.h,dummy.cpp] +// CHECK-NEXT: Adding includes [{{.*}}/new.h] to files [dummy.h,dummy.cpp] +// CHECK-NEXT: Adding dependency "{{.*}}/new" to "" +// CHECK-NEXT: Adding dependency "{{.*}}/new" to "" +// CHECK-NEXT: Migrate phase... +// CHECK-NEXT: Moving symbols [old::Old] from "{{.*}}/old.h" to "{{.*}}/new.h" +// CHECK-NEXT: Renaming: +// CHECK-NEXT: old::Old to new::New +// CHECK-NEXT: in files [{{.*}}/new.h] +// CHECK-NEXT: Changing namespace from "old" to "new" in filles matching "{{.*}}/new.h" +// CHECK-NEXT: Running change-namespace tool on files [{{.*}}/new.h] Index: test/migrate-tool/Inputs/old.h =================================================================== --- /dev/null +++ test/migrate-tool/Inputs/old.h @@ -0,0 +1,3 @@ +namespace old { +class Old {}; +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -48,6 +48,7 @@ clang-reorder-fields clang-tidy find-all-symbols + migrate-tool modularize pp-trace Index: migrate-tool/tool/ClangMigrateTool.cpp =================================================================== --- /dev/null +++ migrate-tool/tool/ClangMigrateTool.cpp @@ -0,0 +1,86 @@ +//===-- ClangMigrateTool.cpp - Standalone migration tool ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DummyMigrationEnvironment.h" +#include "MigrateTool.h" +#include "clang/Basic/Version.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Signals.h" + +using namespace clang; +using namespace llvm; + +namespace { +cl::OptionCategory MigrateToolCategory("Migrate tool options"); + +enum CodebaseTy { + dummy, ///< Dummy codebase. +}; + +cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); + +cl::opt<CodebaseTy> Codebase("codebase", cl::desc("Name of the codebase"), + cl::values(clEnumVal(dummy, "Dummy codebase."), + clEnumValEnd), + cl::init(dummy), cl::cat(MigrateToolCategory)); + +cl::opt<std::string> OldName("old_name", cl::desc("The old symbol name."), + cl::cat(MigrateToolCategory)); + +cl::opt<std::string> NewName("new_name", cl::desc("The new symbol name."), + cl::cat(MigrateToolCategory)); + +cl::opt<std::string> OldHeader("old_header", cl::desc("The old header name."), + cl::cat(MigrateToolCategory)); + +cl::opt<std::string> NewHeader("new_header", cl::desc("The new header name."), + cl::cat(MigrateToolCategory)); + +static void PrintVersion() { + raw_ostream &OS = outs(); + OS << clang::getClangToolFullVersion("migrate-tool") << '\n'; +} + +int migrateToolMain(int argc, const char **argv) { + sys::PrintStackTraceOnErrorSignal(argv[0]); + cl::HideUnrelatedOptions(MigrateToolCategory); + cl::SetVersionPrinter(PrintVersion); + cl::ParseCommandLineOptions( + argc, argv, + "A tool to migrate a C++ symbol (class, function etc).\n\n" + "This tool automates the entire proces of migrating a C++\n" + "symbol (class, function, enum etc.) including renaming a \n" + "symbol and/or moving a symbol across namespace and/or \n" + "files. Besides moving the symbol definition, it also takes \n" + "care of users of the symbol (i.e. update all references of \n" + "old symbol to new symbol) as well as build rules.\n"); + if (Help) + cl::PrintHelpMessage(); + + migrate_tool::MigrateTool::MigrateSpec Spec(OldHeader, NewHeader, OldName, + NewName); + std::unique_ptr<migrate_tool::MigrationEnvironment> Env; + switch (Codebase) { + case dummy: + Env = migrate_tool::createDummyMigrationEnvironment(); + break; + } + migrate_tool::MigrateTool Tool(Spec, *Env); + if (llvm::Error Err = Tool.run()) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + return 1; + } + return 0; +} + +} // namespace + +int main(int argc, const char **argv) { + return migrateToolMain(argc, argv); +} Index: migrate-tool/tool/CMakeLists.txt =================================================================== --- /dev/null +++ migrate-tool/tool/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(migrate-tool + ClangMigrateTool.cpp + ) + +target_link_libraries(migrate-tool + clangBasic + migrateTool + ) + +install(TARGETS migrate-tool + RUNTIME DESTINATION bin) Index: migrate-tool/RefactoringManager.h =================================================================== --- /dev/null +++ migrate-tool/RefactoringManager.h @@ -0,0 +1,54 @@ +//===-- RefactoringManager.h - Perform refactoring on files. ------*- C++ -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORINGMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORINGMANAGER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <set> + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent `RefactoringManager` that +// performs some refactoring operations. +class RefactoringManager { +public: + // Adds #include directives in \p Includes to each file in \p Files. + virtual llvm::Error + addIncludesToFiles(const std::set<llvm::StringRef> &Includes, + llvm::ArrayRef<std::string> Files) = 0; + + // Applies all rename rules in \p Renames on all \p Files. + // Rename rules are a set of <Old Name, New Name> pair. + virtual llvm::Error renameSymbolsInFiles( + llvm::ArrayRef<std::pair<llvm::StringRef, llvm::StringRef>> Renames, + llvm::ArrayRef<std::string> Files) = 0; + + // Changes namespace \p OldNamespace to \p NewNamespace in \p Files and + // the corresponding files in translation units that match the given \p + // FilePattern. + virtual llvm::Error changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, llvm::ArrayRef<llvm::StringRef> Files) = 0; + + // Moves definitions of \p Symbols from \p OldHeader and the corresponding + // source file to \p NewHeader and the new source file. + virtual llvm::Error + moveSymbolsAcrossFiles(llvm::ArrayRef<llvm::StringRef> Symbols, + llvm::StringRef OldHeader, + llvm::StringRef NewHeader) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORINGMANAGER_H Index: migrate-tool/MigrationEnvironment.h =================================================================== --- /dev/null +++ migrate-tool/MigrationEnvironment.h @@ -0,0 +1,52 @@ +//===-- MigrationEnvironment.h - Codebase-dependent environment. --*- C++ -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATIONENVIRONMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATIONENVIRONMENT_H + +#include "BuildManager.h" +#include "RefactoringManager.h" +#include "AffectedFilesFinder.h" + +namespace clang { +namespace migrate_tool { + +// This contains codebase-dependent components including BuildManager, +// RefactoringManager, and AffectedFilesFinder. +class MigrationEnvironment { +public: + MigrationEnvironment() = delete; + // Creates a migration environement containing codebase-dependent component. + // This also takes ownership of all objects passed in. + + MigrationEnvironment(BuildManager *BuildMgr, RefactoringManager *RefactorMgr, + AffectedFilesFinder *Finder) + : BuildMgr(BuildMgr), RefactorMgr(RefactorMgr), Finder(Finder) {} + + BuildManager &getBuildManager() { return *BuildMgr; } + + RefactoringManager &getRefactoringManager() { return *RefactorMgr; } + + AffectedFilesFinder &getAffectedFilesFinder() { return *Finder; } + +private: + // A codebase-dependent `BuildManager` that takes care of build rules during + // the migration. + std::unique_ptr<BuildManager> BuildMgr; + // A codebase-dependent `RefactoringManager` that performs some refactoring + // operations. + std::unique_ptr<RefactoringManager> RefactorMgr; + // Finds files that are affected by refactoring actions. + std::unique_ptr<AffectedFilesFinder> Finder; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATIONENVIRONMENT_H Index: migrate-tool/MigrateTool.h =================================================================== --- /dev/null +++ migrate-tool/MigrateTool.h @@ -0,0 +1,92 @@ +//===-- MigrateTool.h - Migrate class, function etc. ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATETOOL_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATETOOL_H + +#include "MigrationEnvironment.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace migrate_tool { + +// This tool automates the entire process of migrating a C++ symbol (class, +// function, enum etc.) including renaming a symbol and/or moving a symbol +// across namespace and/or files. Besides moving the symbol definition, it also +// takes care of users of the symbol (i.e. update all references of old symbol +// to new symbol) as well as build rules. +// The migration path is divided into 3 phases: +// 1. Preparation phase. +// Create an alias from the old symbol name to to new symbol name. If the +// symbol is to be moved acrossed files, a new temporary header containing +// type alias is created. +// 2. Renaming phase. +// Update all affected projects (i.e. users of the old symbol) to use the +// new symbol name via the alias above. +// 3. Migration phase. +// Actual migration happens at this phase after all affected projects are +// updated. +// Move the symbol definition to the new file if the symbol is to be moved +// to a different file. +// Rename the symbol (i.e. the definition) if the symbol is to be renamed. +// Change the namespace in which the symbol is defined if the symbol is to +// be moved to a different namespace. +// With this migration path, we can divide the three phases into three separate +// patches. For large scale changes where many projects are affected, the +// renaming phase can be splitted into multiple smaller patches to make the +// both refactoring and review process easier. +class MigrateTool { +public: + class MigrateSpec { + public: + MigrateSpec(llvm::StringRef OldHeader, llvm::StringRef NewHeader, + llvm::StringRef OldName, llvm::StringRef NewName) + : OldHeader(OldHeader), NewHeader(NewHeader), OldName(OldName), + NewName(NewName) {} + + llvm::StringRef getOldHeader() const { return OldHeader; } + llvm::StringRef getNewHeader() const { return NewHeader; } + llvm::StringRef getOldName() const { return OldName; } + llvm::StringRef getNewName() const { return NewName; } + + private: + std::string OldHeader; + std::string NewHeader; + std::string OldName; + std::string NewName; + }; + + // FIXME: support handling multiple `MigrateSpec`s in one run. + MigrateTool(const MigrateSpec &Spec, MigrationEnvironment &Env); + + llvm::Error run(); + +private: + // Preparation phase. See comment for the class above. + llvm::Error prepare(); + + // Renaming phase. See comment for the class above. + llvm::Error rename(); + + // Migration phase. See comment for the class above. + llvm::Error migrate(); + + // Create a new file with the given `Content`. + llvm::Error createFile(llvm::StringRef FilePath, llvm::StringRef Content); + + MigrateSpec Spec; + // The environment contains codebase-dependent components like BuildManager, + // RefactoringManager, AffectedFilesFinder etc. + MigrationEnvironment &Env; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_MIGRATETOOL_H Index: migrate-tool/MigrateTool.cpp =================================================================== --- /dev/null +++ migrate-tool/MigrateTool.cpp @@ -0,0 +1,137 @@ +//===-- MigrateTool.cpp - Migrate class, function etc. ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MigrateTool.h" +#include "HeaderGenerator.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Process.h" +#include <fstream> +#include <set> + +namespace clang { +namespace migrate_tool { + +MigrateTool::MigrateTool(const MigrateSpec &Spec, MigrationEnvironment &Env) + : Spec(Spec), Env(Env) {} + +llvm::Error MigrateTool::createFile(llvm::StringRef FilePath, + llvm::StringRef Content) { + std::error_code EC; + llvm::raw_fd_ostream File(FilePath.str(), EC, + llvm::sys::fs::OpenFlags::F_None); + if (EC) + return llvm::errorCodeToError(EC); + File << Content.str(); + File.close(); + return llvm::Error::success(); +} + +llvm::Error MigrateTool::prepare() { + llvm::outs() << "Preparation phase...\n"; + HeaderGenerator NewHeader(Spec.getNewHeader()); + if (Spec.getNewHeader() == Spec.getOldHeader()) { + // FIXME: rename class within the same file. No need for new header. Just + // insert the alias in the old header. + return llvm::make_error<llvm::StringError>( + "Inplace renaming is not supported yet.", + llvm::inconvertibleErrorCode()); + } + // Construct a new header for the symbol alias. + NewHeader.addInclude(Spec.getOldHeader()); + if (Spec.getNewName() != Spec.getOldName()) + NewHeader.addAlias(Spec.getNewName(), Spec.getOldName()); + + // Create new header and build target. + std::string Code = NewHeader.generateContent(); + if (auto Err = createFile(NewHeader.getName(), Code)) + return Err; + Env.getBuildManager().addLibrary({NewHeader.getName()}); + + return llvm::Error::success(); +} + +llvm::Error MigrateTool::rename() { + llvm::outs() << "Rename phase...\n"; + // Get all files that are affected by the migration, i.e. users of the symbol. + auto Files = Env.getAffectedFilesFinder().getAffectedFiles(Spec.getOldName()); + if (!Files) + return Files.takeError(); + llvm::SmallVector<std::pair<llvm::StringRef, llvm::StringRef>, 4> Renames; + std::set<llvm::StringRef> NewIncludes; + if (Spec.getOldName() != Spec.getNewName()) + Renames.emplace_back(Spec.getOldName(), Spec.getNewName()); + if (Spec.getOldHeader() != Spec.getNewHeader()) + NewIncludes.insert(Spec.getNewHeader()); + if (auto Err = + Env.getRefactoringManager().renameSymbolsInFiles(Renames, *Files)) + return Err; + if (auto Err = + Env.getRefactoringManager().addIncludesToFiles(NewIncludes, *Files)) + return Err; + // Add dependency to affected target. + std::string NewTarget = + Env.getBuildManager().getBuildTargetForFile(Spec.getNewHeader()); + for (llvm::StringRef File : *Files) + Env.getBuildManager().addDependency( + Env.getBuildManager().getBuildTargetForFile(File), NewTarget); + return llvm::Error::success(); +} + +static inline llvm::StringRef +extractNamespaceFromQualifiedName(llvm::StringRef QualifiedName) { + auto splitted = QualifiedName.rsplit(':'); + return splitted.second.empty() ? "" : splitted.first.drop_back(1); +} + +llvm::Error MigrateTool::migrate() { + llvm::outs() << "Migrate phase...\n"; + // Move symbol definitions, rename symbol definitions, change + // surroudning namespaces, and update build rules if necessary. + llvm::SmallVector<std::pair<llvm::StringRef, llvm::StringRef>, 4> Renames; + if (Spec.getOldName() != Spec.getNewName()) + Renames.emplace_back(Spec.getOldName(), Spec.getNewName()); + llvm::SmallVector<llvm::StringRef, 4> MovedSymbols; + MovedSymbols.push_back(Spec.getOldName()); + // FIXME: consider source files. Need to determine whether source files end + // with ".cpp" or ".cc" etc. + // FIXME: copy dependencies from old target to new target. + if (auto Err = Env.getRefactoringManager().moveSymbolsAcrossFiles( + MovedSymbols, Spec.getOldHeader(), Spec.getNewHeader())) + return Err; + if (auto Err = Env.getRefactoringManager().renameSymbolsInFiles( + Renames, {Spec.getNewHeader()})) + return Err; + + llvm::StringRef OldNamespace = + extractNamespaceFromQualifiedName(Spec.getOldName()); + llvm::StringRef NewNamespace = + extractNamespaceFromQualifiedName(Spec.getNewName()); + + llvm::SmallVector<llvm::StringRef, 4> Files; + Files.push_back(Spec.getNewHeader()); + if (auto Err = Env.getRefactoringManager().changeNamespaceInFiles( + OldNamespace, NewNamespace, Spec.getNewHeader(), Files)) + return Err; + + return llvm::Error::success(); +} + +llvm::Error MigrateTool::run() { + if (auto Err = prepare()) + return Err; + if (auto Err = rename()) + return Err; + if (auto Err = migrate()) + return Err; + return llvm::Error::success(); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/HeaderGenerator.h =================================================================== --- /dev/null +++ migrate-tool/HeaderGenerator.h @@ -0,0 +1,58 @@ +//===-- HeaderGenerator.h - Generate new headers. ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERGENERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERGENERATOR_H + +#include "llvm/ADT/StringRef.h" +#include <vector> + +namespace clang { +namespace migrate_tool { + +// This constructs a C++ header containing type aliases. +class HeaderGenerator { +public: + explicit HeaderGenerator(llvm::StringRef HeaderName); + + // Returns the name of the header. + llvm::StringRef getName() const { return HeaderName; } + + /// \brief Adds an `using` shadow declaration from \p TypeName to \p NewName. + /// Both names should be fully-qualified. + /// For example, if \p NewName is `a::b::New` and TypeName is `x::y::Old`. + /// Then, the following code will be added into the header: + /// \code + /// namespace a { + /// namespace b { + /// using New = ::x::y::Old; + /// } // namespace b + /// } // namespace a + /// \endcode + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName); + + // Adds an #include into the header. + void addInclude(llvm::StringRef IncludeHeader); + + // Generates the header content containing given aliases and #include's. + // FIXME: consider code style. + std::string generateContent() const; + +private: + std::string HeaderName; + std::string HeaderGuard; + // Old symbol name, New symbol name pairs. + std::vector<std::pair<std::string, std::string>> Aliases; + std::vector<std::string> Includes; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERGENERATOR_H Index: migrate-tool/HeaderGenerator.cpp =================================================================== --- /dev/null +++ migrate-tool/HeaderGenerator.cpp @@ -0,0 +1,112 @@ +//===-- HeaderGenerator.cpp - Generate new headers. -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGenerator.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <sstream> + +namespace clang { +namespace migrate_tool { + +namespace { + +class Namespace { +public: + Namespace(llvm::StringRef Name, bool IsTopLevel = false) + : Name(Name), IsTopLevel(IsTopLevel) { + assert(!(!Name.empty() && IsTopLevel) && + "A namespace must be either a top-level namespace with no name or " + "not a top-level namespace with name."); + } + + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + std::string OS; + llvm::raw_string_ostream SS(OS); + SS << "using " << NewName.str() << " = ::" << TypeName.str() << ";"; + SS.flush(); + CodeBlocks.push_back(OS); + } + + Namespace *addNestedNamespace(llvm::StringRef Name) { + auto Pair = NestedNamespaces.try_emplace(Name, Name); + return &Pair.first->getValue(); + } + + std::string GenerateNamespaceCodeBlock() const { + std::vector<std::string> Lines; + if (!IsTopLevel) + Lines.push_back("namespace " + Name + " {"); + // Add code blocks. + Lines.insert(Lines.end(), CodeBlocks.begin(), CodeBlocks.end()); + // Add nested namespaces. + // If there are multiple nested namespaces, add newlines around each + // namespace. + for (const auto &Entry : NestedNamespaces) + Lines.push_back(Entry.second.GenerateNamespaceCodeBlock()); + if (!IsTopLevel) + Lines.push_back("} // namespace " + Name); + return llvm::join(Lines.begin(), Lines.end(), "\n"); + } + +private: + std::string Name; + bool IsTopLevel; + std::vector<std::string> CodeBlocks; + llvm::StringMap<Namespace> NestedNamespaces; +}; + +} // namespace + +HeaderGenerator::HeaderGenerator(llvm::StringRef HeaderName) + : HeaderName(HeaderName), HeaderGuard(HeaderName) { + // FIXME: use clang-tidy to generate the header guard. + std::replace(HeaderGuard.begin(), HeaderGuard.end(), '/', '_'); + std::replace(HeaderGuard.begin(), HeaderGuard.end(), '.', '_'); + HeaderGuard = llvm::StringRef(HeaderGuard).upper(); +} + +void HeaderGenerator::addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + Aliases.emplace_back(NewName, TypeName); +} + +void HeaderGenerator::addInclude(llvm::StringRef IncludeHeader) { + Includes.push_back(IncludeHeader); +} + +// FIXME: format generated code. +std::string HeaderGenerator::generateContent() const { + std::vector<std::string> Lines; + Lines.push_back("#ifndef " + HeaderGuard); + Lines.push_back("#define " + HeaderGuard); + for (const auto &Include : Includes) + Lines.push_back("#include \"" + Include + "\""); + + Namespace TopNs("", /*IsTopLevel=*/true); + // Generate namespces containing aliases. + for (const auto &Entry : Aliases) { + llvm::StringRef NewName = Entry.first; + llvm::SmallVector<llvm::StringRef, 4> NewNameSplitted; + NewName.split(NewNameSplitted, "::"); + auto *CurNs = &TopNs; + for (auto I = NewNameSplitted.begin(), E = NewNameSplitted.end() - 1; + I != E; ++I) + CurNs = CurNs->addNestedNamespace(*I); + CurNs->addAlias(NewNameSplitted.back(), Entry.second); + } + + Lines.push_back(TopNs.GenerateNamespaceCodeBlock()); + Lines.push_back("#endif // " + HeaderGuard); + return llvm::join(Lines.begin(), Lines.end(), "\n"); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/DummyMigrationEnvironment.h =================================================================== --- /dev/null +++ migrate-tool/DummyMigrationEnvironment.h @@ -0,0 +1,65 @@ +//===-- DummyMigrationEnvironment.h - Dummy environment. --------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_DUMMYMIGRATIONENVIRONMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_DUMMYMIGRATIONENVIRONMENT_H + +#include "MigrationEnvironment.h" + +namespace clang { +namespace migrate_tool { + +// Creates a dummy migration environment whose components do not perform actual +// work and only print messages. +std::unique_ptr<MigrationEnvironment> createDummyMigrationEnvironment(); + +class DummyBuildManager : public BuildManager { +public: + bool addLibrary(llvm::ArrayRef<std::string> Sources, + llvm::StringRef Name = llvm::StringRef()) override; + + bool addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) override; + + std::string getBuildTargetForFile(llvm::StringRef File) override; + +private: + llvm::StringMap<std::string> FileToBuildTarget; +}; + +class DummyRefactoringManager : public RefactoringManager { +public: + llvm::Error addIncludesToFiles(const std::set<llvm::StringRef> &Includes, + llvm::ArrayRef<std::string> Files) override; + + llvm::Error renameSymbolsInFiles( + llvm::ArrayRef<std::pair<llvm::StringRef, llvm::StringRef>> Renames, + llvm::ArrayRef<std::string> Files) override; + + llvm::Error + changeNamespaceInFiles(llvm::StringRef OldNamespace, + llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, + llvm::ArrayRef<llvm::StringRef> Files) override; + + llvm::Error moveSymbolsAcrossFiles(llvm::ArrayRef<llvm::StringRef> Symbols, + llvm::StringRef OldHeader, + llvm::StringRef NewHeader) override; +}; + +class DummyAffectedFilesFinder : public AffectedFilesFinder { +public: + llvm::Expected<std::vector<std::string>> + getAffectedFiles(llvm::StringRef Symbol); +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_DUMMYMIGRATIONENVIRONMENT_H Index: migrate-tool/DummyMigrationEnvironment.cpp =================================================================== --- /dev/null +++ migrate-tool/DummyMigrationEnvironment.cpp @@ -0,0 +1,101 @@ +//===-- DummyMigrationEnvironment.cpp - Dummy environment. ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DummyMigrationEnvironment.h" + +namespace clang { +namespace migrate_tool { + +bool DummyBuildManager::addLibrary(llvm::ArrayRef<std::string> Sources, + llvm::StringRef Name) { + if (Sources.empty()) + return false; + // If `Name` is not specified, use the name of the first file as the library + // name. + if (Name.empty()) { + Name = Sources[0]; + Name = Name.substr(0, Name.find_last_of('.')); + } + llvm::outs() << "Adding library \"" << Name << "\" with soucee files [" + << llvm::join(Sources.begin(), Sources.end(), ",") << "]\n"; + for (auto Source : Sources) + FileToBuildTarget[Source] = Name; + return true; +} + +bool DummyBuildManager::addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) { + llvm::outs() << "Adding dependency \"" << Dependency << "\" to \"" + << BuildTarget << "\"\n"; + return true; +} + +std::string DummyBuildManager::getBuildTargetForFile(llvm::StringRef File) { + return FileToBuildTarget[File]; +} + +llvm::Error DummyRefactoringManager::addIncludesToFiles( + const std::set<llvm::StringRef> &Includes, + llvm::ArrayRef<std::string> Files) { + llvm::outs() << "Adding includes [" + << llvm::join(Includes.begin(), Includes.end(), ",") + << "] to files [" << llvm::join(Files.begin(), Files.end(), ",") + << "]\n"; + return llvm::Error::success(); +} + +llvm::Error DummyRefactoringManager::renameSymbolsInFiles( + llvm::ArrayRef<std::pair<llvm::StringRef, llvm::StringRef>> Renames, + llvm::ArrayRef<std::string> Files) { + llvm::outs() << "Renaming:\n"; + for (auto Rename : Renames) + llvm::outs() << " " << Rename.first << " to " << Rename.second << "\n"; + llvm::outs() << "in files [" << llvm::join(Files.begin(), Files.end(), ",") + << "]\n"; + return llvm::Error::success(); +} + +llvm::Error DummyRefactoringManager::changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, llvm::ArrayRef<llvm::StringRef> Files) { + llvm::outs() << "Changing namespace from \"" << OldNamespace << "\" to \"" + << NewNamespace << "\" in filles matching \"" << FilePattern + << "\"\n" + << "Running change-namespace tool on files [" + << llvm::join(Files.begin(), Files.end(), ",") << "]\n"; + return llvm::Error::success(); +} + +llvm::Error DummyRefactoringManager::moveSymbolsAcrossFiles( + llvm::ArrayRef<llvm::StringRef> Symbols, llvm::StringRef OldHeader, + llvm::StringRef NewHeader) { + llvm::outs() << "Moving symbols [" + << llvm::join(Symbols.begin(), Symbols.end(), ",") << "] from \"" + << OldHeader << "\" to \"" << NewHeader << "\"\n"; + return llvm::Error::success(); +} + +llvm::Expected<std::vector<std::string>> +DummyAffectedFilesFinder::getAffectedFiles(llvm::StringRef Symbol) { + std::vector<std::string> DummyFiles = {"dummy.h", "dummy.cpp"}; + llvm::outs() << "Getting affected files for symbol \"" << Symbol << "\": [" + << llvm::join(DummyFiles.begin(), DummyFiles.end(), ",") + << "]\n"; + return DummyFiles; +} + +std::unique_ptr<MigrationEnvironment> createDummyMigrationEnvironment() { + return llvm::make_unique<MigrationEnvironment>( + new DummyBuildManager(), new DummyRefactoringManager(), + new DummyAffectedFilesFinder()); +} + + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/CMakeLists.txt =================================================================== --- /dev/null +++ migrate-tool/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(migrateTool + DummyMigrationEnvironment.cpp + HeaderGenerator.cpp + MigrateTool.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) Index: migrate-tool/BuildManager.h =================================================================== --- /dev/null +++ migrate-tool/BuildManager.h @@ -0,0 +1,39 @@ +//===-- BuildManager.h - Manages build targets ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent build manager. +class BuildManager { +public: + // Adds a new library build target with \p Sources as source files. The name + // of the new library will be \p Name if it is provided; otherwise, the name + // will be auto-generated. + virtual bool addLibrary(llvm::ArrayRef<std::string> Sources, + llvm::StringRef Name = llvm::StringRef()) = 0; + + // Adds a new dependency to the build target. + virtual bool addDependency(llvm::StringRef BuildTarget, + llvm::StringRef Dependency) = 0; + + // Returns the name of the build target containing \p File. + virtual std::string getBuildTargetForFile(llvm::StringRef File) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H Index: migrate-tool/AffectedFilesFinder.h =================================================================== --- /dev/null +++ migrate-tool/AffectedFilesFinder.h @@ -0,0 +1,33 @@ +//===-- AffectedFilesFinder.h - Finds files affected by a refactor.-*- C++ -*=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <vector> + +namespace clang { +namespace migrate_tool { + +// This defines interface for finding files that are affected by a refactoring +// action, e.g. renaming a symbol. +class AffectedFilesFinder { +public: + // Get all files that need to be updated when a symbol is renamed and/or + // moved. + virtual llvm::Expected<std::vector<std::string>> + getAffectedFiles(llvm::StringRef Symbol) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_AFFECTEDFILESINGFINDER_H Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-rename) add_subdirectory(clang-reorder-fields) +add_subdirectory(migrate-tool) add_subdirectory(modularize) if(CLANG_ENABLE_STATIC_ANALYZER) add_subdirectory(clang-tidy)
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits