mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-08-15 08:29:24 +00:00
Fixes "dxc failed : unable to parse shader model.". On windows the called program is responsible for splitting arguments from one joined string. Command line arguments on 'nix systems need to be passed as separate strings. This CL makes it so that we pass in the arg separately, and support ignoring empty string args to make writing this code easier. Change-Id: Ia9618c2a743f8fdb49913572e2bbfc4bd1519d3a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/98110 Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Commit-Queue: Antonio Maiorano <amaiorano@google.com>
265 lines
7.3 KiB
C++
265 lines
7.3 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/tint/utils/io/command.h"
|
|
|
|
#include <sys/poll.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
namespace tint::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 collecting 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) {
|
|
if (!arg.empty()) {
|
|
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 tint::utils
|