Skip to content

Commit 0543397

Browse files
Fznamznonromanovvlad
authored andcommitted
Implement llvm-foreach tool (#793)
[SYCL] Implement llvm-foreach tool The tool has simple functionality: execute specified command for each file in input file list. The tool will be used in the clang driver for cases when number of action's outputs is unknown and used tools don't support file lists. Signed-off-by: Mariya Podchishchaeva <[email protected]>
1 parent 3c9006e commit 0543397

File tree

7 files changed

+297
-0
lines changed

7 files changed

+297
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
; UNSUPPORTED: system-windows
2+
;
3+
; RUN: echo 'Content of first file' > %t1.tgt
4+
; RUN: echo 'Content of second file' > %t2.tgt
5+
; RUN: echo "%t1.tgt" > %t.list
6+
; RUN: echo "%t2.tgt" >> %t.list
7+
; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res
8+
; RUN: FileCheck < %t.res %s
9+
; CHECK: [[FIRST:.+1.tgt]]
10+
; CHECK: [[SECOND:.+2.tgt]]
11+
;
12+
; RUN: llvm-foreach --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- cp "{}" %t
13+
; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST
14+
; CHECK-LIST: [[FIRST:.+\.out]]
15+
; CHECK-LIST: [[SECOND:.+\.out]]
16+
; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- FileCheck --input-file="{}" %s --check-prefix=CHECK-CONTENT
17+
; CHECK-CONTENT: Content of
18+
19+
; RUN: echo 'something' > %t3.tgt
20+
; RUN: echo 'something again' > %t4.tgt
21+
; RUN: echo "%t3.tgt" > %t1.list
22+
; RUN: echo "%t4.tgt" >> %t1.list
23+
; RUN: llvm-foreach --in-replace="{}" --in-replace="in" --in-file-list=%t.list --in-file-list=%t1.list -- echo -first-part-of-arg={}.out -first-part-of-arg=in.out > %t1.res
24+
; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS
25+
; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]]
26+
; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
; REQUIRES: system-windows
2+
;
3+
; RUN: echo 'Content of first file' > %t1.tgt
4+
; RUN: echo 'Content of second file' > %t2.tgt
5+
; RUN: echo "%t1.tgt" > %t.list
6+
; RUN: echo "%t2.tgt" >> %t.list
7+
; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res
8+
; RUN: FileCheck < %t.res %s
9+
; CHECK: [[FIRST:.+1.tgt]]
10+
; CHECK: [[SECOND:.+2.tgt]]
11+
;
12+
; RUN: llvm-foreach --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- copy "{}" %t
13+
; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST
14+
; CHECK-LIST: [[FIRST:.+\.out]]
15+
; CHECK-LIST: [[SECOND:.+\.out]]
16+
; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- FileCheck --input-file="{}" %s --check-prefix=CHECK-CONTENT
17+
; CHECK-CONTENT: Content of
18+
19+
; RUN: echo 'something' > %t3.tgt
20+
; RUN: echo 'something again' > %t4.tgt
21+
; RUN: echo "%t3.tgt" > %t1.list
22+
; RUN: echo "%t4.tgt" >> %t1.list
23+
; RUN: llvm-foreach --in-replace="{}" --in-replace="in" --in-file-list=%t.list --in-file-list=%t1.list -- echo -first-part-of-arg={}.out -first-part-of-arg=in.out > %t1.res
24+
; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS
25+
; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]]
26+
; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]]

llvm/tools/LLVMBuild.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ subdirectories =
3535
llvm-ifs
3636
llvm-exegesis
3737
llvm-extract
38+
llvm-foreach
3839
llvm-jitlistener
3940
llvm-jitlink
4041
llvm-link
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set(LLVM_LINK_COMPONENTS
2+
Support
3+
)
4+
5+
add_llvm_tool(llvm-foreach
6+
llvm-foreach.cpp
7+
)

llvm/tools/llvm-foreach/LLVMBuild.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
;===- ./tools/llvm-foreach/LLVMBuild.txt ---------------------*- Conf -*--===;
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 is an LLVMBuild description file for the components in this subdirectory.
10+
;
11+
; For more information on the LLVMBuild system, please see:
12+
;
13+
; http://llvm.org/docs/LLVMBuild.html
14+
;
15+
;===------------------------------------------------------------------------===;
16+
17+
[component_0]
18+
type = Tool
19+
name = llvm-foreach
20+
parent = Tools
21+
required_libraries = Support
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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+
}

sycl/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ add_custom_target( sycl-toolchain
186186
llvm-as
187187
llvm-ar
188188
llvm-dis
189+
llvm-foreach
189190
llvm-no-spir-kernel
190191
llvm-spirv
191192
llvm-link
@@ -227,6 +228,7 @@ set( SYCL_TOOLCHAIN_DEPLOY_COMPONENTS
227228
llvm-as
228229
llvm-ar
229230
llvm-dis
231+
llvm-foreach
230232
llvm-no-spir-kernel
231233
llvm-spirv
232234
llvm-link

0 commit comments

Comments
 (0)