mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-22 23:05:32 +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",
|
"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]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -1176,6 +1182,8 @@ dependencies = [
|
|||||||
name = "spirv-builder"
|
name = "spirv-builder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"raw-string",
|
||||||
"rustc_codegen_spirv",
|
"rustc_codegen_spirv",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -7,6 +7,8 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
memchr = "2.3"
|
||||||
|
raw-string = "0.3.5"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
# See comment in lib.rs invoke_rustc for why this is here
|
# 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 serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SpirvBuilderError {
|
pub enum SpirvBuilderError {
|
||||||
BuildFailed(Output),
|
BuildFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SpirvBuilderError {
|
impl fmt::Display for SpirvBuilderError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
SpirvBuilderError::BuildFailed(output) => write!(
|
SpirvBuilderError::BuildFailed => f.write_str("Build failed"),
|
||||||
f,
|
|
||||||
"{}\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
output.status,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,34 +31,6 @@ pub fn build_spirv(path_to_crate: impl AsRef<Path>) -> Result<(), SpirvBuilderEr
|
|||||||
Ok(())
|
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> {
|
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
|
// 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
|
// 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",
|
"spirv-unknown-unknown",
|
||||||
"--release",
|
"--release",
|
||||||
])
|
])
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
.current_dir(path_to_crate)
|
.current_dir(path_to_crate)
|
||||||
.env("RUSTFLAGS", rustflags)
|
.env("RUSTFLAGS", rustflags)
|
||||||
.env("SPIRV_VAL", "1")
|
.env("SPIRV_VAL", "1")
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute cargo build");
|
.expect("failed to execute cargo build");
|
||||||
if build.status.success() {
|
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 {
|
} 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