Add tint::Command
Command is a helper used by tests for executing a process with a number of arguments and an optional stdin string, and then collecting and returning the process's stdout and stderr output as strings. Will be used to invoke HLSL and MSL shader compilers to verify our test generated code actually compiles. Change-Id: I5cd4ca63af9aaa29be7448bb4fa8422e6d42a8ce Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41942 Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
parent
a87fda9225
commit
ce7e18e87c
5
BUILD.gn
5
BUILD.gn
|
@ -872,6 +872,8 @@ source_set("tint_unittests_core_src") {
|
|||
"src/type/u32_type_test.cc",
|
||||
"src/type/vector_type_test.cc",
|
||||
"src/type_determiner_test.cc",
|
||||
"src/utils/command_test.cc",
|
||||
"src/utils/command.h",
|
||||
"src/utils/tmpfile_test.cc",
|
||||
"src/utils/tmpfile.h",
|
||||
"src/utils/unique_vector_test.cc",
|
||||
|
@ -885,10 +887,13 @@ source_set("tint_unittests_core_src") {
|
|||
]
|
||||
|
||||
if (is_linux || is_mac) {
|
||||
sources += [ "src/utils/command_posix.cc" ]
|
||||
sources += [ "src/utils/tmpfile_posix.cc" ]
|
||||
} else if (is_win) {
|
||||
sources += [ "src/utils/command_windows.cc" ]
|
||||
sources += [ "src/utils/tmpfile_windows.cc" ]
|
||||
} else {
|
||||
sources += [ "src/utils/command_other.cc" ]
|
||||
sources += [ "src/utils/tmpfile_other.cc" ]
|
||||
}
|
||||
|
||||
|
|
|
@ -497,6 +497,8 @@ if(${TINT_BUILD_TESTS})
|
|||
type/type_manager_test.cc
|
||||
type/u32_type_test.cc
|
||||
type/vector_type_test.cc
|
||||
utils/command_test.cc
|
||||
utils/command.h
|
||||
utils/tmpfile_test.cc
|
||||
utils/tmpfile.h
|
||||
utils/unique_vector_test.cc
|
||||
|
@ -512,9 +514,12 @@ if(${TINT_BUILD_TESTS})
|
|||
|
||||
if(UNIX OR APPLE)
|
||||
list(APPEND TINT_TEST_SRCS utils/tmpfile_posix.cc)
|
||||
list(APPEND TINT_TEST_SRCS utils/command_posix.cc)
|
||||
elseif(WIN32)
|
||||
list(APPEND TINT_TEST_SRCS utils/command_windows.cc)
|
||||
list(APPEND TINT_TEST_SRCS utils/tmpfile_windows.cc)
|
||||
else()
|
||||
list(APPEND TINT_TEST_SRCS utils/command_other.cc)
|
||||
list(APPEND TINT_TEST_SRCS utils/tmpfile_other.cc)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_UTILS_COMMAND_H_
|
||||
#define SRC_UTILS_COMMAND_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace tint {
|
||||
namespace utils {
|
||||
|
||||
/// Command is a helper used by tests for executing a process with a number of
|
||||
/// arguments and an optional stdin string, and then collecting and returning
|
||||
/// the process's stdout and stderr output as strings.
|
||||
class Command {
|
||||
public:
|
||||
/// Output holds the output of the process
|
||||
struct Output {
|
||||
/// stdout from the process
|
||||
std::string out;
|
||||
/// stderr from the process
|
||||
std::string err;
|
||||
/// process error code
|
||||
int error_code = 0;
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
/// @param path path to the executable
|
||||
explicit Command(const std::string& path);
|
||||
|
||||
/// Looks for an executable with the given name in the current working
|
||||
/// directory, and if not found there, in each of the directories in the
|
||||
/// `PATH` environment variable.
|
||||
/// @param executable the executable name
|
||||
/// @returns a Command which will return true for Found() if the executable
|
||||
/// was found.
|
||||
static Command LookPath(const std::string& executable);
|
||||
|
||||
/// @return true if the executable exists at the path provided to the
|
||||
/// constructor
|
||||
bool Found() const;
|
||||
|
||||
/// Invokes the command with the given argument strings, blocking until the
|
||||
/// process has returned.
|
||||
/// @param args the string arguments to pass to the process
|
||||
/// @returns the process output
|
||||
template <typename... ARGS>
|
||||
Output operator()(ARGS... args) const {
|
||||
return Exec({std::forward<ARGS>(args)...});
|
||||
}
|
||||
|
||||
/// Exec invokes the command with the given argument strings, blocking until
|
||||
/// the process has returned.
|
||||
/// @param args the string arguments to pass to the process
|
||||
/// @returns the process output
|
||||
Output Exec(std::initializer_list<std::string> args) const;
|
||||
|
||||
/// @param input the input data to pipe to the process's stdin
|
||||
void SetInput(const std::string& input) { input_ = input; }
|
||||
|
||||
private:
|
||||
std::string const path_;
|
||||
std::string input_;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_UTILS_COMMAND_H_
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/utils/command.h"
|
||||
|
||||
namespace tint {
|
||||
namespace utils {
|
||||
|
||||
Command::Command(const std::string&) {}
|
||||
|
||||
Command Command::LookPath(const std::string& executable) {
|
||||
return Command("");
|
||||
}
|
||||
|
||||
bool Command::Found() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
Command::Output Command::Exec(
|
||||
std::initializer_list<std::string> arguments) const {
|
||||
Output out;
|
||||
out.err = "Command not supported by this target";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace tint
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/utils/command.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace tint {
|
||||
namespace utils {
|
||||
|
||||
namespace {
|
||||
|
||||
/// File is a simple wrapper around a POSIX file descriptor
|
||||
class File {
|
||||
constexpr static const int kClosed = -1;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
File() : handle_(kClosed) {}
|
||||
|
||||
/// Constructor
|
||||
explicit File(int handle) : handle_(handle) {}
|
||||
|
||||
/// Destructor
|
||||
~File() { Close(); }
|
||||
|
||||
/// Move assignment operator
|
||||
File& operator=(File&& rhs) {
|
||||
Close();
|
||||
handle_ = rhs.handle_;
|
||||
rhs.handle_ = kClosed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Closes the file (if it wasn't already closed)
|
||||
void Close() {
|
||||
if (handle_ != kClosed) {
|
||||
close(handle_);
|
||||
}
|
||||
handle_ = kClosed;
|
||||
}
|
||||
|
||||
/// @returns the file handle
|
||||
operator int() { return handle_; }
|
||||
|
||||
/// @returns true if the file is not closed
|
||||
operator bool() { return handle_ != kClosed; }
|
||||
|
||||
private:
|
||||
File(const File&) = delete;
|
||||
File& operator=(const File&) = delete;
|
||||
|
||||
int handle_ = kClosed;
|
||||
};
|
||||
|
||||
/// Pipe is a simple wrapper around a POSIX pipe() function
|
||||
class Pipe {
|
||||
public:
|
||||
/// Constructs the pipe
|
||||
Pipe() {
|
||||
int pipes[2] = {};
|
||||
if (pipe(pipes) == 0) {
|
||||
read = File(pipes[0]);
|
||||
write = File(pipes[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes both the read and write files (if they're not already closed)
|
||||
void Close() {
|
||||
read.Close();
|
||||
write.Close();
|
||||
}
|
||||
|
||||
/// @returns true if the pipe has an open read or write file
|
||||
operator bool() { return read || write; }
|
||||
|
||||
/// The reader end of the pipe
|
||||
File read;
|
||||
|
||||
/// The writer end of the pipe
|
||||
File write;
|
||||
};
|
||||
|
||||
bool ExecutableExists(const std::string& path) {
|
||||
struct stat s {};
|
||||
if (stat(path.c_str(), &s) != 0) {
|
||||
return false;
|
||||
}
|
||||
return s.st_mode & S_IXUSR;
|
||||
}
|
||||
|
||||
std::string FindExecutable(const std::string& name) {
|
||||
if (ExecutableExists(name)) {
|
||||
return name;
|
||||
}
|
||||
if (name.find("/") == std::string::npos) {
|
||||
auto* path_env = getenv("PATH");
|
||||
if (!path_env) {
|
||||
return "";
|
||||
}
|
||||
std::istringstream path{path_env};
|
||||
std::string dir;
|
||||
while (getline(path, dir, ':')) {
|
||||
auto test = dir + "/" + name;
|
||||
if (ExecutableExists(test)) {
|
||||
return test;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Command::Command(const std::string& path) : path_(path) {}
|
||||
|
||||
Command Command::LookPath(const std::string& executable) {
|
||||
return Command(FindExecutable(executable));
|
||||
}
|
||||
|
||||
bool Command::Found() const {
|
||||
return ExecutableExists(path_);
|
||||
}
|
||||
|
||||
Command::Output Command::Exec(
|
||||
std::initializer_list<std::string> arguments) const {
|
||||
if (!Found()) {
|
||||
Output out;
|
||||
out.err = "Executable not found";
|
||||
return out;
|
||||
}
|
||||
|
||||
// Pipes used for piping std[in,out,err] to / from the target process.
|
||||
Pipe stdin_pipe;
|
||||
Pipe stdout_pipe;
|
||||
Pipe stderr_pipe;
|
||||
|
||||
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
|
||||
Output output;
|
||||
output.err = "Command::Exec(): Failed to create pipes";
|
||||
return output;
|
||||
}
|
||||
|
||||
// execv() and friends replace the current process image with the target
|
||||
// process image. To keep process that called this function going, we need to
|
||||
// fork() this process into a child and parent process.
|
||||
//
|
||||
// The child process is responsible for hooking up the pipes to
|
||||
// std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
|
||||
// run the target command.
|
||||
//
|
||||
// The parent process is responsible for feeding any input to the stdin_pipe
|
||||
// and collectting output from the std[out,err]_pipes.
|
||||
|
||||
int child_id = fork();
|
||||
if (child_id < 0) {
|
||||
Output output;
|
||||
output.err = "Command::Exec(): fork() failed";
|
||||
return output;
|
||||
}
|
||||
|
||||
if (child_id > 0) {
|
||||
// fork() - parent
|
||||
|
||||
// Close the stdout and stderr writer pipes.
|
||||
// This is required for getting poll() POLLHUP events.
|
||||
stdout_pipe.write.Close();
|
||||
stderr_pipe.write.Close();
|
||||
|
||||
// Write the input to the child process
|
||||
if (!input_.empty()) {
|
||||
ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
|
||||
if (n != static_cast<ssize_t>(input_.size())) {
|
||||
Output output;
|
||||
output.err = "Command::Exec(): write() for stdin failed";
|
||||
return output;
|
||||
}
|
||||
}
|
||||
stdin_pipe.write.Close();
|
||||
|
||||
// Accumulate the stdout and stderr output from the child process
|
||||
pollfd poll_fds[2];
|
||||
poll_fds[0].fd = stdout_pipe.read;
|
||||
poll_fds[0].events = POLLIN;
|
||||
poll_fds[1].fd = stderr_pipe.read;
|
||||
poll_fds[1].events = POLLIN;
|
||||
|
||||
Output output;
|
||||
bool stdout_open = true;
|
||||
bool stderr_open = true;
|
||||
while (stdout_open || stderr_open) {
|
||||
if (poll(poll_fds, 2, -1) < 0) {
|
||||
break;
|
||||
}
|
||||
char buf[256];
|
||||
if (poll_fds[0].revents & POLLIN) {
|
||||
auto n = read(stdout_pipe.read, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
output.out += std::string(buf, buf + n);
|
||||
}
|
||||
}
|
||||
if (poll_fds[0].revents & POLLHUP) {
|
||||
stdout_open = false;
|
||||
}
|
||||
if (poll_fds[1].revents & POLLIN) {
|
||||
auto n = read(stderr_pipe.read, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
output.err += std::string(buf, buf + n);
|
||||
}
|
||||
}
|
||||
if (poll_fds[1].revents & POLLHUP) {
|
||||
stderr_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the resulting error code
|
||||
waitpid(child_id, &output.error_code, 0);
|
||||
|
||||
return output;
|
||||
} else {
|
||||
// fork() - child
|
||||
|
||||
// Redirect the stdin, stdout, stderr pipes for the execv process
|
||||
if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
|
||||
(dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
|
||||
(dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
|
||||
fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
|
||||
exit(errno);
|
||||
}
|
||||
|
||||
// Close the pipes, once redirected above, we're now done with them.
|
||||
stdin_pipe.Close();
|
||||
stdout_pipe.Close();
|
||||
stderr_pipe.Close();
|
||||
|
||||
// Run target executable
|
||||
std::vector<const char*> args;
|
||||
args.emplace_back(path_.c_str());
|
||||
for (auto& arg : arguments) {
|
||||
args.emplace_back(arg.c_str());
|
||||
}
|
||||
args.emplace_back(nullptr);
|
||||
auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
|
||||
exit(res);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace tint
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/utils/command.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace tint {
|
||||
namespace utils {
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
TEST(CommandTest, Echo) {
|
||||
auto cmd = Command::LookPath("cmd");
|
||||
if (!cmd.Found()) {
|
||||
GTEST_SKIP() << "cmd not found on PATH";
|
||||
}
|
||||
|
||||
auto res = cmd("/C", "echo", "hello world");
|
||||
EXPECT_EQ(res.error_code, 0);
|
||||
EXPECT_EQ(res.out, "hello world\r\n");
|
||||
EXPECT_EQ(res.err, "");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
TEST(CommandTest, Echo) {
|
||||
auto cmd = Command::LookPath("echo");
|
||||
if (!cmd.Found()) {
|
||||
GTEST_SKIP() << "echo not found on PATH";
|
||||
}
|
||||
|
||||
auto res = cmd("hello world");
|
||||
EXPECT_EQ(res.error_code, 0);
|
||||
EXPECT_EQ(res.out, "hello world\n");
|
||||
EXPECT_EQ(res.err, "");
|
||||
}
|
||||
|
||||
TEST(CommandTest, Cat) {
|
||||
auto cmd = Command::LookPath("cat");
|
||||
if (!cmd.Found()) {
|
||||
GTEST_SKIP() << "cat not found on PATH";
|
||||
}
|
||||
|
||||
cmd.SetInput("hello world");
|
||||
auto res = cmd();
|
||||
EXPECT_EQ(res.error_code, 0);
|
||||
EXPECT_EQ(res.out, "hello world");
|
||||
EXPECT_EQ(res.err, "");
|
||||
}
|
||||
|
||||
TEST(CommandTest, True) {
|
||||
auto cmd = Command::LookPath("true");
|
||||
if (!cmd.Found()) {
|
||||
GTEST_SKIP() << "true not found on PATH";
|
||||
}
|
||||
|
||||
auto res = cmd();
|
||||
EXPECT_EQ(res.error_code, 0);
|
||||
EXPECT_EQ(res.out, "");
|
||||
EXPECT_EQ(res.err, "");
|
||||
}
|
||||
|
||||
TEST(CommandTest, False) {
|
||||
auto cmd = Command::LookPath("false");
|
||||
if (!cmd.Found()) {
|
||||
GTEST_SKIP() << "false not found on PATH";
|
||||
}
|
||||
|
||||
auto res = cmd();
|
||||
EXPECT_NE(res.error_code, 0);
|
||||
EXPECT_EQ(res.out, "");
|
||||
EXPECT_EQ(res.err, "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
} // namespace utils
|
||||
} // namespace tint
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/utils/command.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <Windows.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace tint {
|
||||
namespace utils {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Handle is a simple wrapper around the Win32 HANDLE
|
||||
class Handle {
|
||||
public:
|
||||
/// Constructor
|
||||
Handle() : handle_(nullptr) {}
|
||||
|
||||
/// Constructor
|
||||
explicit Handle(HANDLE handle) : handle_(handle) {}
|
||||
|
||||
/// Destructor
|
||||
~Handle() { Close(); }
|
||||
|
||||
/// Move assignment operator
|
||||
Handle& operator=(Handle&& rhs) {
|
||||
Close();
|
||||
handle_ = rhs.handle_;
|
||||
rhs.handle_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Closes the handle (if it wasn't already closed)
|
||||
void Close() {
|
||||
if (handle_) {
|
||||
CloseHandle(handle_);
|
||||
}
|
||||
handle_ = nullptr;
|
||||
}
|
||||
|
||||
/// @returns the handle
|
||||
operator HANDLE() { return handle_; }
|
||||
|
||||
/// @returns true if the handle is not invalid
|
||||
operator bool() { return handle_ != nullptr; }
|
||||
|
||||
private:
|
||||
Handle(const Handle&) = delete;
|
||||
Handle& operator=(const Handle&) = delete;
|
||||
|
||||
HANDLE handle_ = nullptr;
|
||||
};
|
||||
|
||||
/// Pipe is a simple wrapper around a Win32 CreatePipe() function
|
||||
class Pipe {
|
||||
public:
|
||||
/// Constructs the pipe
|
||||
Pipe() {
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
HANDLE hread;
|
||||
HANDLE hwrite;
|
||||
if (CreatePipe(&hread, &hwrite, &sa, 0)) {
|
||||
read = Handle(hread);
|
||||
write = Handle(hwrite);
|
||||
// Ensure the read handle to the pipe is not inherited
|
||||
if (!SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) {
|
||||
read.Close();
|
||||
write.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns true if the pipe has an open read or write file
|
||||
operator bool() { return read || write; }
|
||||
|
||||
/// The reader end of the pipe
|
||||
Handle read;
|
||||
|
||||
/// The writer end of the pipe
|
||||
Handle write;
|
||||
};
|
||||
|
||||
bool ExecutableExists(const std::string& path) {
|
||||
DWORD type = 0;
|
||||
return GetBinaryTypeA(path.c_str(), &type);
|
||||
}
|
||||
|
||||
std::string FindExecutable(const std::string& name) {
|
||||
if (ExecutableExists(name)) {
|
||||
return name;
|
||||
}
|
||||
if (ExecutableExists(name + ".exe")) {
|
||||
return name + ".exe";
|
||||
}
|
||||
if (name.find("/") == std::string::npos &&
|
||||
name.find("\\") == std::string::npos) {
|
||||
char* path_env = nullptr;
|
||||
size_t path_env_len = 0;
|
||||
if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
|
||||
return "";
|
||||
}
|
||||
std::istringstream path{path_env};
|
||||
free(path_env);
|
||||
std::string dir;
|
||||
while (getline(path, dir, ';')) {
|
||||
auto test = dir + "\\" + name;
|
||||
if (ExecutableExists(test)) {
|
||||
return test;
|
||||
}
|
||||
if (ExecutableExists(test + ".exe")) {
|
||||
return test + ".exe";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Command::Command(const std::string& path) : path_(path) {}
|
||||
|
||||
Command Command::LookPath(const std::string& executable) {
|
||||
return Command(FindExecutable(executable));
|
||||
}
|
||||
|
||||
bool Command::Found() const {
|
||||
return ExecutableExists(path_);
|
||||
}
|
||||
|
||||
Command::Output Command::Exec(
|
||||
std::initializer_list<std::string> arguments) const {
|
||||
Pipe stdout_pipe;
|
||||
Pipe stderr_pipe;
|
||||
Pipe stdin_pipe;
|
||||
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
|
||||
Output output;
|
||||
output.err = "Command::Exec(): Failed to create pipes";
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!input_.empty()) {
|
||||
if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr,
|
||||
nullptr)) {
|
||||
Output output;
|
||||
output.err = "Command::Exec() Failed to write stdin";
|
||||
return output;
|
||||
}
|
||||
}
|
||||
stdin_pipe.write.Close();
|
||||
|
||||
STARTUPINFOA si{};
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = stdout_pipe.write;
|
||||
si.hStdError = stderr_pipe.write;
|
||||
si.hStdInput = stdin_pipe.read;
|
||||
|
||||
std::stringstream args;
|
||||
args << path_;
|
||||
for (auto& arg : arguments) {
|
||||
args << " " << arg;
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi{};
|
||||
if (!CreateProcessA(nullptr, // No module name (use command line)
|
||||
const_cast<LPSTR>(args.str().c_str()), // Command line
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
TRUE, // Handles are inherited
|
||||
0, // No creation flags
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi)) { // Pointer to PROCESS_INFORMATION structure
|
||||
Output out;
|
||||
out.err = "Command::Exec() CreateProcess() failed";
|
||||
return out;
|
||||
}
|
||||
|
||||
stdout_pipe.write.Close();
|
||||
stderr_pipe.write.Close();
|
||||
|
||||
Output output;
|
||||
|
||||
char buf[256];
|
||||
HANDLE handles[] = {stdout_pipe.read, stderr_pipe.read};
|
||||
|
||||
bool stdout_open = true;
|
||||
bool stderr_open = true;
|
||||
while (stdout_open || stderr_open) {
|
||||
auto res = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||||
switch (res) {
|
||||
case WAIT_FAILED:
|
||||
output.err = "Command::Exec() WaitForMultipleObjects() returned " +
|
||||
std::to_string(res);
|
||||
return output;
|
||||
case WAIT_OBJECT_0: { // stdout
|
||||
DWORD n = 0;
|
||||
if (ReadFile(stdout_pipe.read, buf, sizeof(buf), &n, NULL)) {
|
||||
output.out += std::string(buf, buf + n);
|
||||
} else {
|
||||
stdout_open = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 1: { // stderr
|
||||
DWORD n = 0;
|
||||
if (ReadFile(stderr_pipe.read, buf, sizeof(buf), &n, NULL)) {
|
||||
output.err += std::string(buf, buf + n);
|
||||
} else {
|
||||
stderr_open = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace tint
|
Loading…
Reference in New Issue