|
| 1 | +//===- llvm-foreach.cpp - Command lines execution ---------------------------===// |
| 2 | +// |
| 3 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | +// See https://llvm.org/LICENSE.txt for license information. |
| 5 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | +// |
| 7 | +//===----------------------------------------------------------------------===// |
| 8 | +// |
| 9 | +// This source is utility to execute command lines. The specified command will |
| 10 | +// be invoked as many times as necessary to use up the list of input items. |
| 11 | +// |
| 12 | +//===----------------------------------------------------------------------===// |
| 13 | + |
| 14 | +#include "llvm/Support/CommandLine.h" |
| 15 | +#include "llvm/Support/FileSystem.h" |
| 16 | +#include "llvm/Support/LineIterator.h" |
| 17 | +#include "llvm/Support/MemoryBuffer.h" |
| 18 | +#include "llvm/Support/Path.h" |
| 19 | +#include "llvm/Support/Program.h" |
| 20 | +#include "llvm/Support/SystemUtils.h" |
| 21 | + |
| 22 | +#include <vector> |
| 23 | + |
| 24 | +using namespace llvm; |
| 25 | + |
| 26 | +static cl::list<std::string> InputFileLists{ |
| 27 | + "in-file-list", cl::OneOrMore, |
| 28 | + cl::desc("Input list of file names, file names must be delimited by a " |
| 29 | + "newline character."), |
| 30 | + cl::value_desc("filename")}; |
| 31 | + |
| 32 | +static cl::list<std::string> InputCommandArgs{ |
| 33 | + cl::Positional, cl::OneOrMore, cl::desc("<command>"), |
| 34 | + cl::value_desc("command")}; |
| 35 | + |
| 36 | +static cl::list<std::string> Replaces{ |
| 37 | + "in-replace", cl::OneOrMore, |
| 38 | + cl::desc("Specify input path in input command, this will be replaced with " |
| 39 | + "names read from corresponding input list of files."), |
| 40 | + cl::value_desc("R")}; |
| 41 | + |
| 42 | +static cl::opt<std::string> OutReplace{ |
| 43 | + "out-replace", |
| 44 | + cl::desc("Specify output path in input command, this will be replaced with " |
| 45 | + "name of temporary file created for writing command's outputs."), |
| 46 | + cl::init(""), cl::value_desc("R")}; |
| 47 | + |
| 48 | +static cl::opt<std::string> OutDirectory{ |
| 49 | + "out-dir", |
| 50 | + cl::desc("Specify directory for output files; If unspecified, assume " |
| 51 | + "system temporary directory."), |
| 52 | + cl::init(""), cl::value_desc("R")}; |
| 53 | + |
| 54 | +static cl::opt<std::string> OutFilesExt{ |
| 55 | + "out-ext", |
| 56 | + cl::desc("Specify extenstion for output files; If unspecified, assume " |
| 57 | + ".out"), |
| 58 | + cl::init("out"), cl::value_desc("R")}; |
| 59 | + |
| 60 | +// Emit list of produced files for better integration with other tools. |
| 61 | +static cl::opt<std::string> OutputFileList{ |
| 62 | + "out-file-list", cl::desc("Specify filename for list of outputs."), |
| 63 | + cl::value_desc("filename"), cl::init("")}; |
| 64 | + |
| 65 | +static void error(const Twine &Msg) { |
| 66 | + errs() << "llvm-foreach: " << Msg << '\n'; |
| 67 | + exit(1); |
| 68 | +} |
| 69 | + |
| 70 | +static void error(std::error_code EC, const Twine &Prefix) { |
| 71 | + if (EC) |
| 72 | + error(Prefix + ": " + EC.message()); |
| 73 | +} |
| 74 | + |
| 75 | +int main(int argc, char **argv) { |
| 76 | + cl::ParseCommandLineOptions( |
| 77 | + argc, argv, |
| 78 | + "llvm-foreach: Execute specified command as many times as\n" |
| 79 | + "necessary to use up the list of input items.\n" |
| 80 | + "Usage:\n" |
| 81 | + "llvm-foreach --in-file-list=a.list --in-replace='{}' -- echo '{}'\n" |
| 82 | + "NOTE: commands containig redirects are not supported by llvm-foreach\n" |
| 83 | + "yet.\n"); |
| 84 | + |
| 85 | + ExitOnError ExitOnErr("llvm-foreach: "); |
| 86 | + |
| 87 | + if (InputFileLists.size() != Replaces.size()) |
| 88 | + error("Number of input file lists and input path replaces don't match."); |
| 89 | + |
| 90 | + std::vector<std::unique_ptr<MemoryBuffer>> MBs; |
| 91 | + std::vector<line_iterator> LineIterators; |
| 92 | + for (auto &InputFileList : InputFileLists) { |
| 93 | + std::unique_ptr<MemoryBuffer> MB = ExitOnErr( |
| 94 | + errorOrToExpected(MemoryBuffer::getFileOrSTDIN(InputFileList))); |
| 95 | + LineIterators.push_back(line_iterator(*MB)); |
| 96 | + MBs.push_back(std::move(MB)); |
| 97 | + } |
| 98 | + |
| 99 | + SmallVector<StringRef, 8> Args(InputCommandArgs.begin(), |
| 100 | + InputCommandArgs.end()); |
| 101 | + |
| 102 | + if (Args.empty()) |
| 103 | + error("No command?"); |
| 104 | + |
| 105 | + struct ArgumentReplace { |
| 106 | + size_t ArgNum = 0; |
| 107 | + // Index in argument string where replace length starts. |
| 108 | + size_t Start = 0; |
| 109 | + size_t ReplaceLen = 0; |
| 110 | + }; |
| 111 | + |
| 112 | + // Find args to replace with filenames from input list. |
| 113 | + std::vector<ArgumentReplace> InReplaceArgs; |
| 114 | + ArgumentReplace OutReplaceArg; |
| 115 | + for (size_t i = 1; i < Args.size(); ++i) { |
| 116 | + for (auto &Replace : Replaces) { |
| 117 | + size_t ReplaceStart = Args[i].find(Replace); |
| 118 | + if (ReplaceStart != StringRef::npos) |
| 119 | + InReplaceArgs.push_back({i, ReplaceStart, Replace.size()}); |
| 120 | + } |
| 121 | + |
| 122 | + if (!OutReplace.empty() && Args[i].contains(OutReplace)) { |
| 123 | + size_t ReplaceStart = Args[i].find(OutReplace); |
| 124 | + if (ReplaceStart != StringRef::npos) |
| 125 | + OutReplaceArg = {i, ReplaceStart, OutReplace.size()}; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + // Emit an error if user requested replace output file in the command but |
| 130 | + // replace string is not found. |
| 131 | + if (!OutReplace.empty() && OutReplaceArg.ArgNum == 0) |
| 132 | + error("Couldn't find replace string for output in the command."); |
| 133 | + |
| 134 | + // Make sure that specified program exists, emit an error if not. |
| 135 | + std::string Prog = |
| 136 | + ExitOnErr(errorOrToExpected(sys::findProgramByName(Args[0]))); |
| 137 | + |
| 138 | + std::vector<std::vector<std::string>> FileLists(LineIterators.size()); |
| 139 | + size_t PrevNumOfLines = 0; |
| 140 | + for (size_t i = 0; i < FileLists.size(); ++i) { |
| 141 | + for (; !LineIterators[i].is_at_eof(); ++LineIterators[i]) { |
| 142 | + FileLists[i].push_back(LineIterators[i]->str()); |
| 143 | + } |
| 144 | + if (i != 0 && FileLists[i].size() != PrevNumOfLines) |
| 145 | + error("All input file lists must have same number of lines!"); |
| 146 | + PrevNumOfLines = FileLists[i].size(); |
| 147 | + } |
| 148 | + |
| 149 | + std::error_code EC; |
| 150 | + raw_fd_ostream OS{OutputFileList, EC, sys::fs::OpenFlags::OF_None}; |
| 151 | + if (!OutputFileList.empty()) |
| 152 | + error(EC, "error opening the file '" + OutputFileList + "'"); |
| 153 | + |
| 154 | + std::string ResOutArg; |
| 155 | + std::vector<std::string> ResInArgs(InReplaceArgs.size()); |
| 156 | + std::string ResFileList = ""; |
| 157 | + for (size_t j = 0; j != FileLists[0].size(); ++j) { |
| 158 | + for (size_t i = 0; i < InReplaceArgs.size(); ++i) { |
| 159 | + ArgumentReplace CurReplace = InReplaceArgs[i]; |
| 160 | + std::string OriginalString = InputCommandArgs[CurReplace.ArgNum]; |
| 161 | + ResInArgs[i] = (Twine(OriginalString.substr(0, CurReplace.Start)) + |
| 162 | + Twine(FileLists[i][j]) + |
| 163 | + Twine(OriginalString.substr(CurReplace.Start + |
| 164 | + CurReplace.ReplaceLen))) |
| 165 | + .str(); |
| 166 | + Args[CurReplace.ArgNum] = ResInArgs[i]; |
| 167 | + } |
| 168 | + |
| 169 | + SmallString<128> Path; |
| 170 | + if (!OutReplace.empty()) { |
| 171 | + // Create a file for command result. Add file name to output |
| 172 | + // file list if needed. |
| 173 | + std::string TempFileNameBase = sys::path::stem(OutReplace); |
| 174 | + if (OutDirectory.empty()) |
| 175 | + EC = sys::fs::createTemporaryFile(TempFileNameBase, OutFilesExt, Path); |
| 176 | + else { |
| 177 | + SmallString<128> PathPrefix(OutDirectory); |
| 178 | + // "CreateUniqueFile" functions accepts "Model" - special string with |
| 179 | + // substring containing sequence of "%" symbols. In the resulting |
| 180 | + // filename "%" symbols sequence from "Model" string will be replaced |
| 181 | + // with random chars to make it unique. |
| 182 | + llvm::sys::path::append(PathPrefix, |
| 183 | + TempFileNameBase + "-%%%%%%." + OutFilesExt); |
| 184 | + EC = sys::fs::createUniqueFile(PathPrefix, Path); |
| 185 | + } |
| 186 | + error(EC, "Could not create a file for command output."); |
| 187 | + |
| 188 | + std::string OriginalString = InputCommandArgs[OutReplaceArg.ArgNum]; |
| 189 | + ResOutArg = |
| 190 | + (Twine(OriginalString.substr(0, OutReplaceArg.Start)) + Twine(Path) + |
| 191 | + Twine(OriginalString.substr(OutReplaceArg.Start + |
| 192 | + OutReplaceArg.ReplaceLen))) |
| 193 | + .str(); |
| 194 | + Args[OutReplaceArg.ArgNum] = ResOutArg; |
| 195 | + |
| 196 | + if (!OutputFileList.empty()) |
| 197 | + OS << Path << "\n"; |
| 198 | + } |
| 199 | + |
| 200 | + std::string ErrMsg; |
| 201 | + // TODO: Add possibility to execute commands in parallel. |
| 202 | + int Result = |
| 203 | + sys::ExecuteAndWait(Prog, Args, /*Env=*/None, /*Redirects=*/None, |
| 204 | + /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg); |
| 205 | + if (Result != 0) |
| 206 | + error(ErrMsg); |
| 207 | + } |
| 208 | + |
| 209 | + if (!OutputFileList.empty()) { |
| 210 | + OS.close(); |
| 211 | + } |
| 212 | + |
| 213 | + return 0; |
| 214 | +} |
0 commit comments