1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-23 11:13:28 +01:00
llvm-mirror/tools/split-file/split-file.cpp

174 lines
6.0 KiB
C++
Raw Normal View History

Add test utility 'split-file' See https://lists.llvm.org/pipermail/llvm-dev/2020-July/143373.html "[llvm-dev] Multiple documents in one test file" for some discussions. This patch has explored several alternatives. The current semantics are similar to what @dblaikie proposed. `split-file filename output` splits the input file into multiple parts separated by regex `^(.|//)--- filename` and write each part to the file `output/filename` (`filename` can include path separators). Use case A (organizing input of different formats (e.g. linker script+assembly) in one file). ``` # RUN: split-file %s %t # RUN: llvm-mc %t/asm -o %t.o # RUN: ld.lld -T %t/lds %t.o -o %t This is sometimes better than the %S/Inputs/ approach because the user can see the auxiliary files immediately and don't have to open another file. # asm ... # lds ... ``` Use case B (for utilities which don't have built-in input splitting feature): ``` // RUN: split-file %s %t // RUN: llc < %t/1.ll | FileCheck %s --check-prefix=CASE1 // RUN: llc < %t/2.ll | FileCheck %s --check-prefix=CASE2 Combing tests prudently can improve readability. For example, when testing parsing errors if the recovery mechanism isn't possible, grouping the tests in one file can more readily see test coverage/strategy. //--- 1.ll ... //--- 2.ll ... ``` Since this is a new utility, there is no git history concerns for UpperCase variable names. I use lowerCase variable names like mlir/lld. Reviewed By: jhenderson, lattner Differential Revision: https://reviews.llvm.org/D83834
2020-08-03 19:17:55 +02:00
//===- split-file.cpp - Input splitting utility ---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Split input into multipe parts separated by regex '^(.|//)--- ' and extract
// the specified part.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/WithColor.h"
#include <string>
#include <system_error>
using namespace llvm;
static cl::OptionCategory cat("split-file Options");
static cl::opt<std::string> input(cl::Positional, cl::desc("filename"),
cl::cat(cat));
static cl::opt<std::string> output(cl::Positional, cl::desc("directory"),
cl::value_desc("directory"), cl::cat(cat));
static cl::opt<bool> noLeadingLines("no-leading-lines",
cl::desc("Don't preserve line numbers"),
cl::cat(cat));
static StringRef toolName;
static int errorCount;
LLVM_ATTRIBUTE_NORETURN static void fatal(StringRef filename,
const Twine &message) {
if (filename.empty())
WithColor::error(errs(), toolName) << message << '\n';
else
WithColor::error(errs(), toolName) << filename << ": " << message << '\n';
exit(1);
}
static void error(StringRef filename, int64_t line, const Twine &message) {
++errorCount;
errs() << filename << ':' << line << ": ";
WithColor::error(errs()) << message << '\n';
}
namespace {
struct Part {
const char *begin = nullptr;
const char *end = nullptr;
int64_t leadingLines = 0;
};
} // namespace
static int handle(MemoryBuffer &inputBuf, StringRef input) {
DenseMap<StringRef, Part> partToBegin;
StringRef lastPart, separator;
for (line_iterator i(inputBuf, /*SkipBlanks=*/false, '\0'); !i.is_at_eof();) {
const int64_t lineNo = i.line_number();
const StringRef line = *i++;
const size_t markerLen = line.startswith("//") ? 6 : 5;
if (!(line.size() >= markerLen &&
line.substr(markerLen - 4).startswith("--- ")))
continue;
separator = line.substr(0, markerLen);
const StringRef partName = line.substr(markerLen);
if (partName.empty()) {
error(input, lineNo, "empty part name");
continue;
}
if (isSpace(partName.front()) || isSpace(partName.back())) {
error(input, lineNo, "part name cannot have leading or trailing space");
continue;
}
auto res = partToBegin.try_emplace(partName);
if (!res.second) {
error(input, lineNo,
"'" + separator + partName + "' occurs more than once");
continue;
}
if (!lastPart.empty())
partToBegin[lastPart].end = line.data();
Part &cur = res.first->second;
if (!i.is_at_eof())
cur.begin = i->data();
// If --no-leading-lines is not specified, numEmptyLines is 0. Append
// newlines so that the extracted part preserves line numbers.
cur.leadingLines = noLeadingLines ? 0 : i.line_number() - 1;
lastPart = partName;
}
if (lastPart.empty())
fatal(input, "no part separator was found");
if (errorCount)
return 1;
partToBegin[lastPart].end = inputBuf.getBufferEnd();
std::vector<std::unique_ptr<ToolOutputFile>> outputFiles;
SmallString<256> partPath;
for (auto &keyValue : partToBegin) {
partPath.clear();
sys::path::append(partPath, output, keyValue.first);
std::error_code ec =
sys::fs::create_directories(sys::path::parent_path(partPath));
if (ec)
fatal(input, ec.message());
auto f = std::make_unique<ToolOutputFile>(partPath.str(), ec,
llvm::sys::fs::OF_None);
if (!f)
fatal(input, ec.message());
Part &part = keyValue.second;
for (int64_t i = 0; i != part.leadingLines; ++i)
(*f).os().write('\n');
if (part.begin)
(*f).os().write(part.begin, part.end - part.begin);
outputFiles.push_back(std::move(f));
}
for (std::unique_ptr<ToolOutputFile> &outputFile : outputFiles)
outputFile->keep();
return 0;
}
int main(int argc, const char **argv) {
toolName = sys::path::stem(argv[0]);
cl::HideUnrelatedOptions({&cat});
cl::ParseCommandLineOptions(
argc, argv,
"Split input into multiple parts separated by regex '^(.|//)--- ' and "
"extract the part specified by '^(.|//)--- <part>'\n",
nullptr,
/*EnvVar=*/nullptr,
/*LongOptionsUseDoubleDash=*/true);
if (input.empty())
fatal("", "input filename is not specified");
if (output.empty())
fatal("", "output directory is not specified");
ErrorOr<std::unique_ptr<MemoryBuffer>> bufferOrErr =
MemoryBuffer::getFileOrSTDIN(input);
if (std::error_code ec = bufferOrErr.getError())
fatal(input, ec.message());
// Delete output if it is a file or an empty directory, so that we can create
// a directory.
sys::fs::file_status status;
if (std::error_code ec = sys::fs::status(output, status))
if (ec.value() != static_cast<int>(std::errc::no_such_file_or_directory))
fatal(output, ec.message());
if (status.type() != sys::fs::file_type::file_not_found &&
status.type() != sys::fs::file_type::directory_file &&
status.type() != sys::fs::file_type::regular_file)
fatal(output, "output cannot be a special file");
if (std::error_code ec = sys::fs::remove(output, /*IgnoreNonExisting=*/true))
if (ec.value() != static_cast<int>(std::errc::directory_not_empty) &&
ec.value() != static_cast<int>(std::errc::file_exists))
Add test utility 'split-file' See https://lists.llvm.org/pipermail/llvm-dev/2020-July/143373.html "[llvm-dev] Multiple documents in one test file" for some discussions. This patch has explored several alternatives. The current semantics are similar to what @dblaikie proposed. `split-file filename output` splits the input file into multiple parts separated by regex `^(.|//)--- filename` and write each part to the file `output/filename` (`filename` can include path separators). Use case A (organizing input of different formats (e.g. linker script+assembly) in one file). ``` # RUN: split-file %s %t # RUN: llvm-mc %t/asm -o %t.o # RUN: ld.lld -T %t/lds %t.o -o %t This is sometimes better than the %S/Inputs/ approach because the user can see the auxiliary files immediately and don't have to open another file. # asm ... # lds ... ``` Use case B (for utilities which don't have built-in input splitting feature): ``` // RUN: split-file %s %t // RUN: llc < %t/1.ll | FileCheck %s --check-prefix=CASE1 // RUN: llc < %t/2.ll | FileCheck %s --check-prefix=CASE2 Combing tests prudently can improve readability. For example, when testing parsing errors if the recovery mechanism isn't possible, grouping the tests in one file can more readily see test coverage/strategy. //--- 1.ll ... //--- 2.ll ... ``` Since this is a new utility, there is no git history concerns for UpperCase variable names. I use lowerCase variable names like mlir/lld. Reviewed By: jhenderson, lattner Differential Revision: https://reviews.llvm.org/D83834
2020-08-03 19:17:55 +02:00
fatal(output, ec.message());
return handle(**bufferOrErr, input);
}