Index: llvm/CMakeLists.txt =================================================================== --- llvm/CMakeLists.txt +++ llvm/CMakeLists.txt @@ -854,6 +854,7 @@ if( LLVM_INCLUDE_UTILS ) add_subdirectory(utils/FileCheck) + add_subdirectory(utils/FileEdit) add_subdirectory(utils/PerfectShuffle) add_subdirectory(utils/count) add_subdirectory(utils/not) Index: llvm/test/CMakeLists.txt =================================================================== --- llvm/test/CMakeLists.txt +++ llvm/test/CMakeLists.txt @@ -29,6 +29,7 @@ set(LLVM_TEST_DEPENDS BugpointPasses FileCheck + FileEdit LLVMHello UnitTests bugpoint Index: llvm/test/FileEdit/Inputs/edit.in =================================================================== --- /dev/null +++ llvm/test/FileEdit/Inputs/edit.in @@ -0,0 +1,3 @@ +CHECK: Test - foo - Test +CHECK: Test - bar - Test +CHECK: Test - baz - Test Index: llvm/test/FileEdit/multiple-edits-same-line.test =================================================================== --- /dev/null +++ llvm/test/FileEdit/multiple-edits-same-line.test @@ -0,0 +1,16 @@ +; RUN: FileEdit -dest=%t_dir -replace=s/THETYPE/int/ -replace=s/THEFILE/%T/ %s | FileCheck %s + + +{!-- TheFile.txt +#include +THETYPE main(THETYPE argc, char **argv) { + std::cout << "THEFILE" << std::endl; + return 0; +} + +{!-- +CHECK: #include +CHECK-NEXT: int main(int argc, char **argv) { +CHECK-NEXT: std::cout << "{{.*multiple-edits-same-line.*}}" << std::endl; +CHECK-NEXT: return 0; +CHECK-NEXT: } Index: llvm/test/FileEdit/one-file.test =================================================================== --- /dev/null +++ llvm/test/FileEdit/one-file.test @@ -0,0 +1,12 @@ +; RUN: FileEdit -dest=%t_dir %s +; RUN: FileCheck -input-file=%t_dir/TheFile.txt %s + +{!-- TheFile.txt +int main(int argc, char **argv) { + return 0; +} + +{!-- +CHECK: int main(int argc, char **argv) { +CHECK-NEXT: return 0; +CHECK-NEXT: } Index: llvm/test/FileEdit/simple-edits.test =================================================================== --- /dev/null +++ llvm/test/FileEdit/simple-edits.test @@ -0,0 +1,15 @@ +; RUN: FileEdit %s | FileCheck --check-prefix=NONE %s +; RUN: FileEdit -replace=s/foo/bar/ %s | FileCheck --check-prefix=ONE %s +; RUN: FileEdit -replace=s/foo/bar/ -replace=s/bar/baz/ %s | FileCheck --check-prefix=TWO %s + +NONE: CHECK: Test - foo - Test +NONE-NEXT: CHECK: Test - bar - Test +NONE-NEXT: CHECK: Test - baz - Test + +ONE: CHECK: Test - bar - Test +ONE-NEXT: CHECK: Test - bar - Test +ONE-NEXT: CHECK: Test - baz - Test + +TWO: CHECK: Test - bar - Test +TWO-NEXT: CHECK: Test - baz - Test +TWO-NEXT: CHECK: Test - baz - Test Index: llvm/test/FileEdit/two-files.test =================================================================== --- /dev/null +++ llvm/test/FileEdit/two-files.test @@ -0,0 +1,26 @@ +; RUN: FileEdit -dest=%t_dir %s +; RUN: FileCheck -input-file=%t_dir/File1.cpp -check-prefix=FILE1 %s +; RUN: FileCheck -input-file=%t_dir/File2.cpp -check-prefix=FILE2 %s + +{!-- File1.cpp +// File1.cpp +int main(int argc, char **argv) { + return 0; +} + +{!-- File2.cpp +// File2.cpp +int main(int argc, char **argv) { + return 0; +} + +{!-- +FILE1: // File1.cpp +FILE1-NEXT: int main(int argc, char **argv) { +FILE1-NEXT: return 0; +FILE1-NEXT: } + +FILE2: // File2.cpp +FILE2-NEXT: int main(int argc, char **argv) { +FILE2-NEXT: return 0; +FILE2-NEXT: } Index: llvm/utils/FileEdit/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/utils/FileEdit/CMakeLists.txt @@ -0,0 +1,5 @@ +add_llvm_utility(FileEdit + FileEdit.cpp + ) + +target_link_libraries(FileEdit LLVMSupport) Index: llvm/utils/FileEdit/FileEdit.cpp =================================================================== --- /dev/null +++ llvm/utils/FileEdit/FileEdit.cpp @@ -0,0 +1,235 @@ +//===- FileEdit.cpp - Split and transform file into multiple sub-files ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// FileEdit takes an input file and performs various operations on the file, +// such as replacing certain sequences or splitting the file into multiple +// sub-files. This is useful for keeping tests self-contained, as it allows us +// to write a single file containing both test input and test check lines even +// when the test input itself must span multiple files (e.g. a test which tests +// some functionality involving compiling and linking 2 separate compilation +// units). +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +namespace { +enum class EditResult { Discard, UseModified, UseUnmodified }; +struct LineReplacement { + StringRef Pattern; + StringRef Repl; +}; +} // namespace + +static cl::opt + InputFilename(cl::Positional, cl::desc("input file (defaults to stdin)"), + cl::Required); + +static cl::list StripPrefixes( + "strip-prefix", cl::CommaSeparated, + cl::desc("Exclude lines beginning with the given prefix from the output")); + +static cl::list Replacements( + "replace", cl::ZeroOrMore, + cl::desc("Apply the specified replacement regex to each line")); + +static cl::opt + DestFolder("dest", cl::desc("Write split files to the specified folder")); + +static size_t findNextUnescapedSlash(StringRef Str) { + size_t Pos = 0; + while (Pos != StringRef::npos) { + Pos = Str.find_first_of('/'); + if (Pos == StringRef::npos) + return Pos; + if (Pos == 0 || Str[Pos - 1] != '\\') + return Pos; + } + return StringRef::npos; +} + +static bool parseOneReplacementItem(StringRef Str, StringRef &Pattern, + StringRef &Repl) { + if (!Str.consume_front("s/")) + return false; + + for (StringRef *Dest : {&Pattern, &Repl}) { + size_t NextSlash = findNextUnescapedSlash(Str); + if (NextSlash == StringRef::npos) + return false; + *Dest = Str.take_front(NextSlash); + Str = Str.drop_front(NextSlash + 1); + } + + return Str.empty(); +} + +static void buildReplacementVector(std::vector &Repls) { + std::string UniversalRegexStr; + std::vector Patterns; + for (StringRef S : Replacements) { + StringRef Pattern; + StringRef Repl; + if (!parseOneReplacementItem(S, Pattern, Repl)) { + outs() << formatv("FileEdit: Invalid replacement string `{0}`", S); + continue; + } + + Patterns.push_back(Pattern); + Repls.push_back({Pattern, Repl}); + } +} + +static bool handleFileDirective(StringRef Line, StringRef &OutFile) { + if (!Line.consume_front("{!--")) + return false; + OutFile = Line.trim(); + return true; +} + +static std::error_code writeOutput(StringRef FileName, StringRef Text) { + if (FileName.empty()) { + llvm::outs() << Text; + return std::error_code(); + } + + SmallString<128> Path; + if (DestFolder.getNumOccurrences() == 0) + llvm::sys::fs::current_path(Path); + else + Path = DestFolder; + llvm::sys::path::append(Path, FileName); + llvm::sys::path::native(Path); + auto Directory = llvm::sys::path::parent_path(Path); + if (auto EC = llvm::sys::fs::create_directories(Directory, true)) + return EC; + + int FD; + if (auto EC = + llvm::sys::fs::openFileForWrite(Path, FD, llvm::sys::fs::F_Text)) + return EC; + llvm::raw_fd_ostream OS(FD, true); + OS << Text; + OS.flush(); + return std::error_code(); +} + +static EditResult editLine(StringRef Line, + ArrayRef Replacements, + SmallVectorImpl &ModifiedLine) { + if (Line.empty()) + return EditResult::UseUnmodified; + + for (StringRef Prefix : StripPrefixes) { + if (Line.startswith(Prefix)) + return EditResult::Discard; + } + + // Perform all replacements from left to right in the input string. + ModifiedLine.clear(); + bool WasModified = false; + while (!Line.empty()) { + const LineReplacement *UseRepl = nullptr; + size_t FirstPos = StringRef::npos; + + // Find the leftmost replacement in the input string, and map it back to + // the text that should replace it. + for (const LineReplacement &LR : Replacements) { + size_t Pos = Line.find(LR.Pattern); + if (Pos < FirstPos) { + UseRepl = &LR; + FirstPos = Pos; + } + } + if (!UseRepl) { + if (!WasModified) { + // If we didn't find anything to replace on this or any previous + // iteration we can just exit and use the original line. + return EditResult::UseUnmodified; + } + + ModifiedLine.append(Line.begin(), Line.end()); + return EditResult::UseModified; + } + + // The line can be split into 3 groups. Line = Left | Repl | Right. Append + // `Left` as is, use the replacement value for `Repl`, and keep `Right` as + // the remainder of the string for further replacement. + StringRef Left = Line.take_front(FirstPos); + + ModifiedLine.append(Left.begin(), Left.end()); + Line = Line.drop_front(FirstPos); + + ModifiedLine.append(UseRepl->Repl.begin(), UseRepl->Repl.end()); + WasModified = true; + Line = Line.drop_front(UseRepl->Pattern.size()); + } + return EditResult::UseModified; +} + +int main(int argc, char **argv) { + sys::PrintStackTraceOnErrorSignal(argv[0]); + PrettyStackTraceProgram X(argc, argv); + cl::ParseCommandLineOptions(argc, argv); + + // Read the expected strings from the check file. + ErrorOr> InFileOrErr = + MemoryBuffer::getFileOrSTDIN(InputFilename); + if (std::error_code EC = InFileOrErr.getError()) { + errs() << "Could not open input file '" << InputFilename + << "': " << EC.message() << '\n'; + return 1; + } + MemoryBuffer &InFile = *InFileOrErr.get(); + + std::vector Replacements; + + buildReplacementVector(Replacements); + + SmallString<4096> InputFileBuffer; + + line_iterator Iter(InFile, false); + line_iterator End; + StringRef OutFile; + std::string OutputText; + SmallString<80> ModifiedLine; + for (StringRef Line : make_range(Iter, End)) { + StringRef NewOutFile; + if (handleFileDirective(Line, NewOutFile)) { + writeOutput(OutFile, OutputText); + OutFile = NewOutFile; + OutputText.clear(); + continue; + } + + EditResult Result = editLine(Line, Replacements, ModifiedLine); + if (Result == EditResult::Discard) + continue; + if (Result == EditResult::UseModified) + Line = ModifiedLine; + if (!OutputText.empty()) + OutputText += "\n"; + OutputText += Line; + } + + writeOutput(OutFile, OutputText); + return 0; +}