mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-25 16:25:25 +00:00
Rebuild shader crates if changed
This commit is contained in:
parent
8c9583f2c6
commit
80398d8149
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -910,6 +910,12 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-string"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.3.3"
|
||||
@ -1176,6 +1182,8 @@ dependencies = [
|
||||
name = "spirv-builder"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"raw-string",
|
||||
"rustc_codegen_spirv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -7,6 +7,8 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
memchr = "2.3"
|
||||
raw-string = "0.3.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
# See comment in lib.rs invoke_rustc for why this is here
|
||||
|
143
spirv-builder/src/depfile.rs
Normal file
143
spirv-builder/src/depfile.rs
Normal file
@ -0,0 +1,143 @@
|
||||
//! Reading Makefile-style dependency files.
|
||||
//! Taken with permission from https://github.com/m-ou-se/ninj/blob/master/lib/depfile/mod.rs
|
||||
|
||||
use raw_string::{RawStr, RawString};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Error, ErrorKind, Read};
|
||||
use std::mem::replace;
|
||||
use std::path::Path;
|
||||
|
||||
/// Read a Makfile-style dependency file.
|
||||
///
|
||||
/// `f` is called for every target. The first argument is the target, the
|
||||
/// second is the list of dependencies.
|
||||
pub fn read_deps_file(
|
||||
file_name: &Path,
|
||||
f: impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
let file = File::open(file_name)
|
||||
.map_err(|e| Error::new(e.kind(), format!("Unable to read {:?}: {}", file_name, e)))?;
|
||||
read_deps_file_from(file, f)
|
||||
}
|
||||
|
||||
/// Read a Makfile-style dependency file.
|
||||
///
|
||||
/// `f` is called for every target. The first argument is the target, the
|
||||
/// second is the list of dependencies.
|
||||
pub fn read_deps_file_from(
|
||||
file: impl Read,
|
||||
mut f: impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
let mut file = BufReader::new(file);
|
||||
|
||||
let mut state = State::default();
|
||||
|
||||
let mut line = RawString::new();
|
||||
|
||||
loop {
|
||||
line.clear();
|
||||
if file.read_until(b'\n', &mut line.as_mut_bytes())? == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if line.last() == Some(b'\n') {
|
||||
line.pop();
|
||||
}
|
||||
|
||||
if cfg!(windows) && line.last() == Some(b'\r') {
|
||||
line.pop();
|
||||
}
|
||||
|
||||
let mut write_offset = 0;
|
||||
let mut read_offset = 0;
|
||||
|
||||
loop {
|
||||
match memchr::memchr2(b' ', b'\\', line[read_offset..].as_bytes())
|
||||
.map(|i| i + read_offset)
|
||||
{
|
||||
Some(i) if line[i] == b'\\' && i + 1 == line.len() => {
|
||||
// Backslash at the end of the line
|
||||
state.add_part(&line[write_offset..i]);
|
||||
state.finish_path()?;
|
||||
break;
|
||||
}
|
||||
Some(i) if line[i] == b'\\' => {
|
||||
// Backslash before character.
|
||||
let c = line[i + 1];
|
||||
match c {
|
||||
b' ' | b'\\' | b'#' | b'*' | b'[' | b']' | b'|' => {
|
||||
// Escaped character. Drop the '\'.
|
||||
state.add_part(&line[write_offset..i]);
|
||||
write_offset = i + 1;
|
||||
}
|
||||
_ => (), // Keep the '\'.
|
||||
}
|
||||
read_offset = i + 2;
|
||||
}
|
||||
Some(i) => {
|
||||
// A space.
|
||||
debug_assert_eq!(line[i], b' ');
|
||||
state.add_part(&line[write_offset..i]);
|
||||
state.finish_path()?;
|
||||
write_offset = i + 1;
|
||||
read_offset = i + 1;
|
||||
}
|
||||
None => {
|
||||
// End of the line.
|
||||
state.add_part(&line[write_offset..]);
|
||||
state.finish_deps(&mut f)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.target.is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(ErrorKind::InvalidData, "Unexpected end of file"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
/// The (incomplete) path we're currently reading.
|
||||
path: RawString,
|
||||
/// The target, once we've finished reading it.
|
||||
target: Option<RawString>,
|
||||
/// The rest of the paths we've finished reading.
|
||||
deps: Vec<RawString>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn add_part(&mut self, s: &RawStr) {
|
||||
self.path.push_str(s);
|
||||
}
|
||||
fn finish_path(&mut self) -> Result<(), Error> {
|
||||
if !self.path.is_empty() {
|
||||
let mut path = replace(&mut self.path, RawString::new());
|
||||
if self.target.is_none() && path.last() == Some(b':') {
|
||||
path.pop();
|
||||
self.target = Some(path);
|
||||
} else if self.target.is_none() {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"Rule in dependency file has multiple outputs",
|
||||
));
|
||||
} else {
|
||||
self.deps.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn finish_deps(
|
||||
&mut self,
|
||||
f: &mut impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
self.finish_path()?;
|
||||
if let Some(target) = self.target.take() {
|
||||
f(target, replace(&mut self.deps, Vec::new()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,24 +1,22 @@
|
||||
mod depfile;
|
||||
|
||||
use raw_string::{RawStr, RawString};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Output};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SpirvBuilderError {
|
||||
BuildFailed(Output),
|
||||
BuildFailed,
|
||||
}
|
||||
|
||||
impl fmt::Display for SpirvBuilderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SpirvBuilderError::BuildFailed(output) => write!(
|
||||
f,
|
||||
"{}\nstdout:\n{}\nstderr:\n{}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
),
|
||||
SpirvBuilderError::BuildFailed => f.write_str("Build failed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,34 +31,6 @@ pub fn build_spirv(path_to_crate: impl AsRef<Path>) -> Result<(), SpirvBuilderEr
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RustcOutput {
|
||||
reason: String,
|
||||
filenames: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn get_last_artifact(out: &str) -> String {
|
||||
let out = serde_json::Deserializer::from_str(out).into_iter::<RustcOutput>();
|
||||
let last = out
|
||||
.map(|line| line.unwrap())
|
||||
.filter(|line| line.reason == "compiler-artifact")
|
||||
.last()
|
||||
.expect("Did not find output file in rustc output");
|
||||
|
||||
let mut filenames = last.filenames.unwrap();
|
||||
eprintln!("{:?}", filenames);
|
||||
if cfg!(windows) {
|
||||
filenames
|
||||
.iter()
|
||||
.find(|&v| v.ends_with(".dll") || v.ends_with(".spv"))
|
||||
.unwrap()
|
||||
.clone()
|
||||
} else {
|
||||
assert_eq!(filenames.len(), 1);
|
||||
filenames.pop().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke_rustc(path_to_crate: &Path) -> Result<String, SpirvBuilderError> {
|
||||
// Okay, this is a little bonkers: in a normal world, we'd have the user clone
|
||||
// rustc_codegen_spirv and pass in the path to it, and then we'd invoke cargo to build it, grab
|
||||
@ -84,14 +54,63 @@ fn invoke_rustc(path_to_crate: &Path) -> Result<String, SpirvBuilderError> {
|
||||
"spirv-unknown-unknown",
|
||||
"--release",
|
||||
])
|
||||
.stderr(Stdio::inherit())
|
||||
.current_dir(path_to_crate)
|
||||
.env("RUSTFLAGS", rustflags)
|
||||
.env("SPIRV_VAL", "1")
|
||||
.output()
|
||||
.expect("failed to execute cargo build");
|
||||
if build.status.success() {
|
||||
Ok(get_last_artifact(&String::from_utf8(build.stdout).unwrap()))
|
||||
let stdout = String::from_utf8(build.stdout).unwrap();
|
||||
let artifact = get_last_artifact(&stdout);
|
||||
print_deps_of(&artifact);
|
||||
Ok(artifact)
|
||||
} else {
|
||||
Err(SpirvBuilderError::BuildFailed(build))
|
||||
Err(SpirvBuilderError::BuildFailed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RustcOutput {
|
||||
reason: String,
|
||||
filenames: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn get_last_artifact(out: &str) -> String {
|
||||
let out = serde_json::Deserializer::from_str(out).into_iter::<RustcOutput>();
|
||||
let last = out
|
||||
.map(|line| line.unwrap())
|
||||
.filter(|line| line.reason == "compiler-artifact")
|
||||
.last()
|
||||
.expect("Did not find output file in rustc output");
|
||||
|
||||
let mut filenames = last
|
||||
.filenames
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|v| v.ends_with(".spv"));
|
||||
let filename = filenames.next().expect("Crate had no .spv artifacts");
|
||||
assert_eq!(filenames.next(), None, "Crate had multiple .spv artifacts");
|
||||
filename
|
||||
}
|
||||
|
||||
fn print_deps_of(artifact: &str) {
|
||||
let deps_file = Path::new(artifact).with_extension("d");
|
||||
let mut deps_map = HashMap::new();
|
||||
depfile::read_deps_file(&deps_file, |item, deps| {
|
||||
deps_map.insert(item, deps);
|
||||
Ok(())
|
||||
})
|
||||
.expect("Could not read dep file");
|
||||
fn recurse(map: &HashMap<RawString, Vec<RawString>>, artifact: &RawStr) {
|
||||
match map.get(artifact) {
|
||||
Some(entries) => {
|
||||
for entry in entries {
|
||||
recurse(map, entry)
|
||||
}
|
||||
}
|
||||
None => println!("cargo:rerun-if-changed={}", artifact),
|
||||
}
|
||||
}
|
||||
recurse(&deps_map, artifact.into());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user