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) Index: migrate-tool/BuildManager.h =================================================================== --- /dev/null +++ migrate-tool/BuildManager.h @@ -0,0 +1,33 @@ +//===-- 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/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent build manager. +class BuildManager { +public: + virtual bool addHeaderOnlyLibrary(llvm::StringRef HeaderPath) = 0; + + virtual bool addDependency(llvm::StringRef Target, + llvm::StringRef Dependency) = 0; + + virtual std::string getTargetForFile(llvm::StringRef File) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_BUILDMANAGER_H Index: migrate-tool/CMakeLists.txt =================================================================== --- /dev/null +++ migrate-tool/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(migrateTool + HeaderBuild.cpp + MigrateTool.cpp + + LINK_LIBS + clangAST + clangBasic + clangFormat + clangTooling + clangToolingCore + ) Index: migrate-tool/HeaderBuild.h =================================================================== --- /dev/null +++ migrate-tool/HeaderBuild.h @@ -0,0 +1,42 @@ +//===-- HeaderBuild.h - Builds 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_HEADERBUILD_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERBUILD_H + +#include "llvm/ADT/StringMap.h" + +namespace clang { +namespace migrate_tool { + +// This constructs a C++ header containing type aliases. +class Header { +public: + Header(llvm::StringRef HeaderName); + + llvm::StringRef getName() const { return HeaderName; } + + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName); + + void addInclude(llvm::StringRef IncludeHeader); + + std::string generateContent() const; + +private: + std::string HeaderName; + std::string HeaderGuard; + // Old symbol name, New symbol name pairs. + std::vector> Aliases; + std::vector Includes; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_HEADERBUILD_H Index: migrate-tool/HeaderBuild.cpp =================================================================== --- /dev/null +++ migrate-tool/HeaderBuild.cpp @@ -0,0 +1,112 @@ +//===-- HeaderBuild.cpp - Build 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 "HeaderBuild.h" +#include +#include + +namespace clang { +namespace migrate_tool { + +namespace { + +std::string joinStrings(const std::vector &Strings) { + std::ostringstream SS; + for (const auto &Str : Strings) + SS << Str << "\n"; + std::string Result = SS.str(); + if (!Result.empty()) + Result.pop_back(); + return Result; +} +class Namespace { +public: + Namespace(llvm::StringRef Name, bool IsTopLevel = false) + : Name(Name), IsTopLevel(IsTopLevel) {} + + void addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + std::ostringstream SS; + SS << "using " << NewName.str() << " = ::" << TypeName.str() << ";"; + CodeBlocks.push_back(SS.str()); + } + + Namespace *addNestedNamespace(llvm::StringRef Name) { + auto Pair = NestedNamespaces.try_emplace(Name, Name); + return &Pair.first->getValue(); + } + + std::string GenerateNamespaceCodeBlock() const { + std::vector 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 joinStrings(Lines); + } + +private: + std::string Name; + bool IsTopLevel; + std::vector CodeBlocks; + llvm::StringMap NestedNamespaces; +}; + +} // namespace + +Header::Header(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 Header::addAlias(llvm::StringRef NewName, llvm::StringRef TypeName) { + Aliases.emplace_back(NewName, TypeName); +} + +void Header::addInclude(llvm::StringRef IncludeHeader) { + Includes.push_back(IncludeHeader); +} + +// FIXME: format generated code. +std::string Header::generateContent() const { + std::vector 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 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.rbegin(), Entry.second); + } + + Lines.push_back(TopNs.GenerateNamespaceCodeBlock()); + Lines.push_back("#endif // " + HeaderGuard); + return joinStrings(Lines); +} + +} // namespace migrate_tool +} // namespace clang Index: migrate-tool/MigrateTool.h =================================================================== --- /dev/null +++ migrate-tool/MigrateTool.h @@ -0,0 +1,99 @@ +//===-- 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 "BuildManager.h" +#include "RefactorManager.h" +#include "clang/Basic/FileManager.h" +#include "llvm/ADT/SmallVector.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: + enum class MigrateType { + Class, + Unknown, + }; + + 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, + BuildManager *BuildMgr, RefactorManager *Refactor); + + 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`. + void addFile(llvm::StringRef FilePath, llvm::StringRef Content); + + MigrateSpec Spec; + // A codebase-dependent `BuildManager` that takes care of build rules during + // the migration. + BuildManager *BuildMgr; + // A codebase-dependent `RefactorManager` that performs some refactoring + // operations. + RefactorManager *RefactorMgr; +}; + +} // 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,128 @@ +//===-- 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 "HeaderBuild.h" +#include "clang/Basic/FileSystemOptions.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Process.h" +#include +#include + +namespace clang { +namespace migrate_tool { + +MigrateTool::MigrateTool(const MigrateSpec &Spec, BuildManager *BuildMgr, + RefactorManager *Refactor) + : Spec(Spec), BuildMgr(BuildMgr), RefactorMgr(Refactor) {} + +void MigrateTool::addFile(llvm::StringRef FilePath, llvm::StringRef Content) { + std::ofstream File(FilePath.str(), std::ofstream::out); + File << Content.str(); + File.close(); +} + +llvm::Error MigrateTool::prepare() { + llvm::outs() << "Preparation phase.\n"; + Header 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( + "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(); + addFile(NewHeader.getName(), Code); + BuildMgr->addHeaderOnlyLibrary(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 = RefactorMgr->getAffectedFiles(Spec.getOldName()); + if (!Files) + return Files.takeError(); + llvm::SmallVector, 4> Renames; + std::set 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 = RefactorMgr->renameSymbolsInFiles(Renames, *Files)) + return Err; + if (auto Err = RefactorMgr->addIncludesToFiles(NewIncludes, *Files)) + return Err; + // Add dependency to affected target. + std::string NewTarget = BuildMgr->getTargetForFile(Spec.getNewHeader()); + for (llvm::StringRef File : *Files) + BuildMgr->addDependency(BuildMgr->getTargetForFile(File), NewTarget); + return llvm::Error::success(); +} + +static llvm::StringRef +extractNamespaceFromQualifiedName(llvm::StringRef QualifiedName) { + auto Pos = QualifiedName.rfind(':'); + return (Pos == llvm::StringRef::npos) ? llvm::StringRef() + : QualifiedName.substr(Pos + 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, 4> Renames; + if (Spec.getOldName() != Spec.getNewName()) + Renames.emplace_back(Spec.getOldName(), Spec.getNewName()); + llvm::SmallVector MovedSymbols; + MovedSymbols.push_back(Spec.getOldName()); + // FIXME: consider source files. Need to determine whether source files send + // with ".cpp" or ".cc" etc. + // FIXME: copy dependencies from old target to new target. + if (auto Err = RefactorMgr->moveSymbolsAcrossFiles( + MovedSymbols, Spec.getOldHeader(), Spec.getNewHeader())) + return Err; + if (auto Err = + RefactorMgr->renameSymbolsInFiles(Renames, {Spec.getNewHeader()})) + return Err; + + llvm::StringRef OldNamespace = + extractNamespaceFromQualifiedName(Spec.getOldName()); + llvm::StringRef NewNamespace = + extractNamespaceFromQualifiedName(Spec.getNewName()); + + llvm::SmallVector Files; + Files.push_back(Spec.getNewHeader()); + if (auto Err = RefactorMgr->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/RefactorManager.h =================================================================== --- /dev/null +++ migrate-tool/RefactorManager.h @@ -0,0 +1,52 @@ +//===-- RefactorManager.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_REFACTORMANAGER_H +#define LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORMANAGER_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace migrate_tool { + +// This defines interfaces for a codebase-dependent `RefactorManager` that +// performs some refactoring operations. +class RefactorManager { +public: + virtual llvm::Expected> + getAffectedFiles(llvm::StringRef Symbol) = 0; + + virtual llvm::Error + addIncludesToFiles(const std::set &Includes, + const std::vector &Files) = 0; + + virtual llvm::Error renameSymbolsInFiles( + const llvm::SmallVectorImpl> + &Renames, + const std::vector &Files) = 0; + + virtual llvm::Error changeNamespaceInFiles( + llvm::StringRef OldNamespace, llvm::StringRef NewNamespace, + llvm::StringRef FilePattern, + const llvm::SmallVectorImpl &Files) = 0; + + virtual llvm::Error + moveSymbolsAcrossFiles(const llvm::SmallVectorImpl &Symbols, + llvm::StringRef OldHeader, + llvm::StringRef NewHeader) = 0; +}; + +} // namespace migrate_tool +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_MIGRATE_TOOL_REFACTORMANAGER_H Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(clang-query) add_subdirectory(clang-tidy) add_subdirectory(include-fixer) +add_subdirectory(migrate-tool) 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/migrate-tool/HeaderBuildTest.cpp =================================================================== --- /dev/null +++ unittests/migrate-tool/HeaderBuildTest.cpp @@ -0,0 +1,100 @@ +//===-- HeaderBuildTest.cpp - HeaderBuild unit tests ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderBuild.h" +#include "gtest/gtest.h" + +namespace clang { +namespace migrate_tool { + +TEST(HeaderBuild, Empty) { + Header Hdr("a.h"); + std::string Expected = "#ifndef A_H\n" + "#define A_H\n" + "\n" + "#endif // A_H"; + EXPECT_EQ(Expected, Hdr.generateContent()); +} + +TEST(HeaderBuild, SingleAlias) { + Header 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(HeaderBuild, SingleAliasWithIncludes) { + Header 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(HeaderBuild, MultipleAliasInOneNamespace) { + Header 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(HeaderBuild, AliasesInMultipleNamespace) { + Header 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