Hi klimek, djasper, silvas, tareqsiraj, arielbernal, Sarcasm,
Introducing new tool 'clang-replace' that finds files containing
serialized Replacements and applies those changes after deduplication
and detecting conflicts.
Currently the tool does not apply changes. It stops just after the
deduplication and conflict report phase. Forthcoming patches will
complete functionality.
Both build systems updated for new tool.
Includes a conflict test case.
Depends on D1422.
http://llvm-reviews.chandlerc.com/D1424
Files:
CMakeLists.txt
Makefile
clang-replace/ApplyReplacements.cpp
clang-replace/ApplyReplacements.h
clang-replace/CMakeLists.txt
clang-replace/Makefile
clang-replace/tool/CMakeLists.txt
clang-replace/tool/ClangReplaceMain.cpp
clang-replace/tool/Makefile
test/CMakeLists.txt
test/clang-replace/conflict.cpp
test/clang-replace/conflict/common.h
test/clang-replace/conflict/expected.txt
test/clang-replace/conflict/file1.yaml
test/clang-replace/conflict/file2.yaml
test/clang-replace/conflict/file3.yaml
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -1,6 +1,7 @@
add_subdirectory(remove-cstr-calls)
add_subdirectory(tool-template)
add_subdirectory(cpp11-migrate)
+add_subdirectory(clang-replace)
add_subdirectory(modularize)
add_subdirectory(clang-tidy)
Index: Makefile
===================================================================
--- Makefile
+++ Makefile
@@ -12,7 +12,7 @@
include $(CLANG_LEVEL)/../../Makefile.config
PARALLEL_DIRS := remove-cstr-calls tool-template modularize
-DIRS := cpp11-migrate clang-tidy unittests
+DIRS := cpp11-migrate clang-tidy clang-replace unittests
include $(CLANG_LEVEL)/Makefile
Index: clang-replace/ApplyReplacements.cpp
===================================================================
--- /dev/null
+++ clang-replace/ApplyReplacements.cpp
@@ -0,0 +1,217 @@
+//===-- Core/ApplyChangeDescriptions.cpp ----------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file provides the implementation for finding and applying change
+/// description files.
+///
+//===----------------------------------------------------------------------===//
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Tooling/ReplacementsYaml.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace clang;
+
+typedef std::vector<tooling::ReplacementsDocument> ReplacementsDocs;
+typedef std::set<StringRef> UniqueFiles;
+typedef StringMap<std::vector<tooling::Replacement> > FileToReplacementsMap;
+
+static void eatDiagnostics(const SMDiagnostic &, void *) {}
+
+/// \brief Recursively descends through a directory structure rooted at \p
+/// Directory and attempts to parse all *.yaml files as ReplacementsDocs. All
+/// docs that successfully parse are added to \p ChangeDocs.
+///
+/// \param[in] Directory Directory to begin search for ReplacementsDocs.
+/// \param[out] ChangeDocs Collection of all found and parsed ReplacementsDocs.
+/// \param[in] Diagnostics DiagnosticsEngine used for error output.
+///
+/// \returns An error_code indicating success or a failure in navigating the
+/// directory structure.
+static error_code collectReplacementsDocs(const StringRef Directory,
+ ReplacementsDocs &ChangeDocs,
+ DiagnosticsEngine &Diagnostics) {
+ using namespace llvm::sys::fs;
+ using namespace llvm::sys::path;
+
+ error_code ErrorCode;
+
+ for (recursive_directory_iterator I(Directory, ErrorCode), E;
+ I != E && !ErrorCode; I.increment(ErrorCode)) {
+ if (filename(I->path())[0] == '.') {
+ // Indicate not to descend into directories beginning with '.'
+ I.no_push();
+ continue;
+ }
+
+ if (extension(I->path()) != ".yaml")
+ continue;
+
+ OwningPtr<MemoryBuffer> Out;
+ error_code BufferError = MemoryBuffer::getFile(I->path(), Out);
+ if (BufferError) {
+ errs() << "Error reading " << I->path() << ": " << BufferError.message()
+ << "\n";
+ continue;
+ }
+
+ yaml::Input YIn(Out->getBuffer());
+ YIn.setDiagHandler(&eatDiagnostics);
+ tooling::ReplacementsDocument Doc;
+ YIn >> Doc;
+ if (YIn.error()) {
+ // File doesn't appear to be a header change description. Ignore it.
+ continue;
+ }
+
+ // Only keep files that properly parse.
+ ChangeDocs.push_back(Doc);
+ }
+
+ return ErrorCode;
+}
+
+/// \brief Dumps information for a sequence of conflicting Replacements.
+///
+/// \param[in] File FileEntry for the file the conflicting Replacements are
+/// for.
+/// \param[in] Replacements Full list of Replacements.
+/// \param[in] firstConflictIdx Offset into \p Replacements of the first
+/// conflicting Replacement.
+/// \param[in] numConflicting Number of consecutive Replacements in
+/// \p Replacements that conflict with one another.
+/// \param[in] SM SourceManager used for reporting.
+/// \param[in] Diagnostics DiagnosticsEngine used for reporting.
+static void
+reportConflict(const FileEntry *File,
+ const FileToReplacementsMap::mapped_type &Replacements,
+ unsigned firstConflictIdx, unsigned numConflicting,
+ SourceManager &SM, DiagnosticsEngine &Diagnostics) {
+ FileID FID = SM.translateFile(File);
+ if (FID.isInvalid())
+ FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
+
+ // FIXME: Output something a little more user-friendly (e.g. unified diff?)
+ errs() << "The following changes conflict:\n";
+ for (unsigned i = firstConflictIdx, e = firstConflictIdx + numConflicting;
+ i < e; ++i) {
+ const tooling::Replacement &R = Replacements[i];
+ if (R.getLength() == 0) {
+ errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
+ << SM.getColumnNumber(FID, R.getOffset()) << " "
+ << R.getReplacementText() << "\n";
+ } else {
+ if (R.getReplacementText().empty())
+ errs() << " Remove ";
+ else
+ errs() << " Replace ";
+
+ errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
+ << SM.getColumnNumber(FID, R.getOffset()) << "-"
+ << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
+ << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
+
+ if (R.getReplacementText().empty())
+ errs() << "\n";
+ else
+ errs() << " with \"" << R.getReplacementText() << "\"\n";
+ }
+ }
+}
+
+/// \brief Deduplicates and tests for conflicts among the replacements for each
+/// file in \c Replacements. Any conflicts found are reported.
+///
+/// \param[in,out] Replacements Container of all replacements grouped by file
+/// to be deduplicated and checked for conflicts.
+/// \param[in] SM SourceManager required for conflict reporting
+/// \param[in] Diagnostics DiagnosticsEngine used for output.
+///
+/// \returns \li true if conflicts were detected
+/// \li false if no conflicts were detected
+static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
+ SourceManager &SM,
+ DiagnosticsEngine &Diagnostics) {
+ bool conflictsFound = false;
+
+ for (FileToReplacementsMap::iterator I = Replacements.begin(),
+ E = Replacements.end();
+ I != E; ++I) {
+
+ const FileEntry *Entry = SM.getFileManager().getFile(I->getKey());
+ if (!Entry) {
+ errs() << "Described file '" << I->getKey()
+ << "' doesn't exist. Ignoring...\n";
+ continue;
+ }
+
+ std::vector<tooling::Range> Conflicts;
+ tooling::deduplicate(I->getValue(), Conflicts);
+
+ if (Conflicts.empty())
+ continue;
+
+ conflictsFound = true;
+
+ errs() << "There are conflicting changes to " << I->getKey() << ":\n";
+
+ for (std::vector<tooling::Range>::const_iterator
+ ConflictI = Conflicts.begin(),
+ ConflictE = Conflicts.end();
+ ConflictI != ConflictE; ++ConflictI)
+ reportConflict(Entry, I->getValue(), ConflictI->getOffset(),
+ ConflictI->getLength(), SM, Diagnostics);
+ }
+
+ return conflictsFound;
+}
+
+bool applyChangeDescriptions(const StringRef Directory,
+ DiagnosticsEngine &Diagnostics) {
+
+ // FIXME: Use Diagnostics for output
+
+ ReplacementsDocs Docs;
+
+ error_code ErrorCode =
+ collectReplacementsDocs(Directory, Docs, Diagnostics);
+
+ if (ErrorCode) {
+ errs() << "Trouble iterating over directory '" << Directory
+ << "': " << ErrorCode.message() << "\n";
+ return false;
+ }
+
+ FileToReplacementsMap GroupedReplacements;
+
+ // Group all replacements by target file.
+ for (ReplacementsDocs::const_iterator DocI = Docs.begin(), DocE = Docs.end();
+ DocI != DocE; ++DocI)
+ for (std::vector<tooling::Replacement>::const_iterator
+ RI = DocI->Replacements.begin(),
+ RE = DocI->Replacements.end();
+ RI != RE; ++RI)
+ GroupedReplacements[RI->getFilePath()].push_back(*RI);
+
+ FileManager Files((FileSystemOptions()));
+ SourceManager SM(Diagnostics, Files);
+
+ // Ask clang to deduplicate and report conflicts.
+ if (deduplicateAndDetectConflicts(GroupedReplacements, SM, Diagnostics))
+ return false;
+
+ // FIXME: Time to apply
+
+ return true;
+}
Index: clang-replace/ApplyReplacements.h
===================================================================
--- /dev/null
+++ clang-replace/ApplyReplacements.h
@@ -0,0 +1,38 @@
+//===-- Core/ApplyChangeDescriptions.h --------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file provides the interface for finding and applying change
+/// description files.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef CPP11_MIGRATE_APPLYCHANGEDESCRIPTIONS_H
+#define CPP11_MIGRATE_APPLYCHANGEDESCRIPTIONS_H
+
+#include "llvm/ADT/StringRef.h"
+
+// Forward declarations
+
+namespace clang {
+class DiagnosticsEngine;
+} // namespace clang
+
+/// \brief Recursively descends through \p Directory looking for change description
+/// files and applies the changes therein.
+///
+/// \param Directory Root directory to begin search.
+/// \param Diagnostics DiagnosticsEngine used for error/warning output.
+///
+/// \returns \li true If all changes were applied successfully.
+/// \li false If there were conflicts or some other error occurred.
+bool applyChangeDescriptions(const llvm::StringRef Directory,
+ clang::DiagnosticsEngine &Diagnostics);
+
+#endif // CPP11_MIGRATE_APPLYCHANGEDESCRIPTIONS_H
Index: clang-replace/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang-replace/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(LLVM_LINK_COMPONENTS
+ ${LLVM_TARGETS_TO_BUILD}
+ asmparser
+ bitreader
+ support
+ mc
+ )
+
+add_clang_library(clangReplace
+ ApplyReplacements.cpp
+ )
+target_link_libraries(clangReplace
+ clangTooling
+ clangBasic
+ clangRewriteFrontend
+ )
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+add_subdirectory(tool)
Index: clang-replace/Makefile
===================================================================
--- /dev/null
+++ clang-replace/Makefile
@@ -0,0 +1,16 @@
+##===- clang-replace/Makefile ------------------------------*- Makefile -*-===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL := ../../..
+LIBRARYNAME := clangReplace
+include $(CLANG_LEVEL)/../../Makefile.config
+
+DIRS = tool
+
+include $(CLANG_LEVEL)/Makefile
Index: clang-replace/tool/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang-replace/tool/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(LLVM_LINK_COMPONENTS
+ ${LLVM_TARGETS_TO_BUILD}
+ asmparser
+ bitreader
+ support
+ mc
+ )
+
+add_clang_executable(clang-replace
+ ClangReplaceMain.cpp
+ )
+target_link_libraries(clang-replace
+ clangReplace
+ )
+
+install(TARGETS clang-replace
+ RUNTIME DESTINATION bin)
Index: clang-replace/tool/ClangReplaceMain.cpp
===================================================================
--- /dev/null
+++ clang-replace/tool/ClangReplaceMain.cpp
@@ -0,0 +1,38 @@
+//===-- ClangReplaceMain.cpp - Main file for clang-replace tool -----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file provides the main function for the clang-replace tool.
+///
+//===----------------------------------------------------------------------===//
+
+#include "ApplyReplacements.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "llvm/Support/CommandLine.h"
+
+using namespace llvm;
+using namespace clang;
+
+static cl::opt<std::string>
+Directory(cl::Positional, cl::Required,
+ cl::desc("<Search Root Directory>"));
+
+int main(int argc, char **argv) {
+ cl::ParseCommandLineOptions(argc, argv);
+
+ IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
+ DiagnosticsEngine Diagnostics(
+ IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
+ DiagOpts.getPtr());
+
+ if (applyChangeDescriptions(Directory, Diagnostics))
+ return 0;
+ return 1;
+}
Index: clang-replace/tool/Makefile
===================================================================
--- /dev/null
+++ clang-replace/tool/Makefile
@@ -0,0 +1,28 @@
+##===- clang-replace/tool/Makefile -------------------------*- Makefile -*-===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL := ../../../..
+include $(CLANG_LEVEL)/../../Makefile.config
+
+TOOLNAME = clang-replace
+
+# No plugins, optimize startup time.
+TOOL_NO_EXPORTS = 1
+
+SOURCES = ClangReplaceMain.cpp
+
+LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc mcparser option
+USEDLIBS = clangReplace.a clangFormat.a clangTooling.a clangFrontend.a \
+ clangSerialization.a clangDriver.a clangRewriteFrontend.a \
+ clangRewriteCore.a clangParse.a clangSema.a clangAnalysis.a \
+ clangAST.a clangASTMatchers.a clangEdit.a clangLex.a clangBasic.a
+
+include $(CLANG_LEVEL)/Makefile
+
+CPP.Flags += -I$(PROJ_SRC_DIR)/..
Index: test/CMakeLists.txt
===================================================================
--- test/CMakeLists.txt
+++ test/CMakeLists.txt
@@ -27,7 +27,7 @@
clang clang-headers FileCheck count not
# Individual tools we test.
- remove-cstr-calls cpp11-migrate modularize clang-tidy
+ remove-cstr-calls clang-replace cpp11-migrate modularize clang-tidy
# Unit tests
ExtraToolsUnitTests
Index: test/clang-replace/conflict.cpp
===================================================================
--- /dev/null
+++ test/clang-replace/conflict.cpp
@@ -0,0 +1,5 @@
+// RUN: mkdir -p %T/conflict
+// RUN: for f in %S/conflict/*.yaml; do sed "s#\$(path)#%S/conflict#" $f > %T/conflict/`basename $f`; done
+// RUN: sed "s#\$(path)#%S/conflict#" %S/conflict/expected.txt > %T/conflict/expected.txt
+// RUN: not clang-replace %T/conflict > %T/conflict/output.txt 2>&1
+// RUN: cmp %T/conflict/output.txt %T/conflict/expected.txt
Index: test/clang-replace/conflict/common.h
===================================================================
--- /dev/null
+++ test/clang-replace/conflict/common.h
@@ -0,0 +1,17 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+extern void ext(int (&)[5]);
+
+void func(int t) {
+ int ints[5];
+ for (unsigned i = 0; i < 5; ++i) {
+ ints[i] = t;
+ }
+
+ int *i = 0;
+
+ ext(ints);
+}
+
+#endif // COMMON_H
Index: test/clang-replace/conflict/expected.txt
===================================================================
--- /dev/null
+++ test/clang-replace/conflict/expected.txt
@@ -0,0 +1,11 @@
+There are conflicting changes to $(path)/common.h:
+The following changes conflict:
+ Replace 8:8-8:33 with "auto & i : ints"
+ Replace 8:8-8:33 with "int & elem : ints"
+The following changes conflict:
+ Replace 9:5-9:11 with "elem"
+ Replace 9:5-9:11 with "i"
+The following changes conflict:
+ Remove 12:3-12:14
+ Insert at 12:12 (int*)
+ Replace 12:12-12:12 with "nullptr"
Index: test/clang-replace/conflict/file1.yaml
===================================================================
--- /dev/null
+++ test/clang-replace/conflict/file1.yaml
@@ -0,0 +1,16 @@
+---
+Context: "Main Source File: source1.cpp"
+Replacements:
+ - FilePath: "$(path)/common.h"
+ Offset: 106
+ Length: 26
+ ReplacementText: "auto & i : ints"
+ - FilePath: "$(path)/common.h"
+ Offset: 140
+ Length: 7
+ ReplacementText: "i"
+ - FilePath: "$(path)/common.h"
+ Offset: 160
+ Length: 12
+ ReplacementText: ""
+...
Index: test/clang-replace/conflict/file2.yaml
===================================================================
--- /dev/null
+++ test/clang-replace/conflict/file2.yaml
@@ -0,0 +1,16 @@
+---
+Context: "Main Source File: source2.cpp"
+Replacements:
+ - FilePath: "$(path)/common.h"
+ Offset: 106
+ Length: 26
+ ReplacementText: "int & elem : ints"
+ - FilePath: "$(path)/common.h"
+ Offset: 140
+ Length: 7
+ ReplacementText: "elem"
+ - FilePath: "$(path)/common.h"
+ Offset: 169
+ Length: 1
+ ReplacementText: "nullptr"
+...
Index: test/clang-replace/conflict/file3.yaml
===================================================================
--- /dev/null
+++ test/clang-replace/conflict/file3.yaml
@@ -0,0 +1,8 @@
+---
+Context: "Main Source File: source1.cpp"
+Replacements:
+ - FilePath: "$(path)/common.h"
+ Offset: 169
+ Length: 0
+ ReplacementText: "(int*)"
+...
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits