mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-16 20:31:20 +00:00
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>
243 lines
6.2 KiB
C++
243 lines
6.2 KiB
C++
// 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
|