Skip to content

Commit 2579b5d

Browse files
committed
Refactor each subcommand to its own file
No other changes, just moving code.
1 parent 4462c42 commit 2579b5d

File tree

6 files changed

+710
-686
lines changed

6 files changed

+710
-686
lines changed

crates/cargo-gpu/src/builder.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use std::io::Write;
2+
3+
use clap::Parser;
4+
use spirv_builder_cli::{Linkage, ShaderModule};
5+
6+
use crate::{install::Install, target_spec_dir};
7+
8+
#[derive(Parser, Debug)]
9+
pub(crate) struct Build {
10+
#[clap(flatten)]
11+
install: Install,
12+
13+
/// Directory containing the shader crate to compile.
14+
#[clap(long, default_value = "./")]
15+
pub shader_crate: std::path::PathBuf,
16+
17+
/// Shader target.
18+
#[clap(long, default_value = "spirv-unknown-vulkan1.2")]
19+
shader_target: String,
20+
21+
/// Set cargo default-features.
22+
#[clap(long)]
23+
no_default_features: bool,
24+
25+
/// Set cargo features.
26+
#[clap(long)]
27+
features: Vec<String>,
28+
29+
/// Path to the output directory for the compiled shaders.
30+
#[clap(long, short, default_value = "./")]
31+
pub output_dir: std::path::PathBuf,
32+
}
33+
34+
impl Build {
35+
pub fn run(&mut self) {
36+
let (dylib_path, spirv_builder_cli_path) = self.install.run();
37+
38+
// Ensure the shader output dir exists
39+
log::debug!("ensuring output-dir '{}' exists", self.output_dir.display());
40+
std::fs::create_dir_all(&self.output_dir).unwrap();
41+
self.output_dir = self.output_dir.canonicalize().unwrap();
42+
43+
// Ensure the shader crate exists
44+
self.shader_crate = self.shader_crate.canonicalize().unwrap();
45+
assert!(
46+
self.shader_crate.exists(),
47+
"shader crate '{}' does not exist. (Current dir is '{}')",
48+
self.shader_crate.display(),
49+
std::env::current_dir().unwrap().display()
50+
);
51+
52+
let spirv_builder_args = spirv_builder_cli::Args {
53+
dylib_path,
54+
shader_crate: self.shader_crate.clone(),
55+
shader_target: self.shader_target.clone(),
56+
path_to_target_spec: target_spec_dir().join(format!("{}.json", self.shader_target)),
57+
no_default_features: self.no_default_features,
58+
features: self.features.clone(),
59+
output_dir: self.output_dir.clone(),
60+
};
61+
62+
// UNWRAP: safe because we know this always serializes
63+
let arg = serde_json::to_string_pretty(&spirv_builder_args).unwrap();
64+
log::info!("using spirv-builder-cli arg: {arg}");
65+
66+
// Call spirv-builder-cli to compile the shaders.
67+
let output = std::process::Command::new(spirv_builder_cli_path)
68+
.arg(arg)
69+
.stdout(std::process::Stdio::inherit())
70+
.stderr(std::process::Stdio::inherit())
71+
.output()
72+
.unwrap();
73+
assert!(output.status.success(), "build failed");
74+
75+
let spirv_manifest = self.output_dir.join("spirv-manifest.json");
76+
if spirv_manifest.is_file() {
77+
log::debug!(
78+
"successfully built shaders, raw manifest is at '{}'",
79+
spirv_manifest.display()
80+
);
81+
} else {
82+
log::error!("missing raw manifest '{}'", spirv_manifest.display());
83+
panic!("missing raw manifest");
84+
}
85+
86+
let shaders: Vec<ShaderModule> =
87+
serde_json::from_reader(std::fs::File::open(&spirv_manifest).unwrap()).unwrap();
88+
89+
let mut linkage: Vec<_> = shaders
90+
.into_iter()
91+
.map(
92+
|ShaderModule {
93+
entry,
94+
path: filepath,
95+
}| {
96+
use relative_path::PathExt;
97+
let path = self.output_dir.join(filepath.file_name().unwrap());
98+
std::fs::copy(&filepath, &path).unwrap();
99+
let path_relative_to_shader_crate =
100+
path.relative_to(&self.shader_crate).unwrap().to_path("");
101+
Linkage::new(entry, path_relative_to_shader_crate)
102+
},
103+
)
104+
.collect();
105+
106+
// Write the shader manifest json file
107+
let manifest_path = self.output_dir.join("manifest.json");
108+
// Sort the contents so the output is deterministic
109+
linkage.sort();
110+
// UNWRAP: safe because we know this always serializes
111+
let json = serde_json::to_string_pretty(&linkage).unwrap();
112+
let mut file = std::fs::File::create(&manifest_path).unwrap_or_else(|e| {
113+
log::error!(
114+
"could not create shader manifest file '{}': {e}",
115+
manifest_path.display(),
116+
);
117+
panic!("{e}")
118+
});
119+
file.write_all(json.as_bytes()).unwrap_or_else(|e| {
120+
log::error!(
121+
"could not write shader manifest file '{}': {e}",
122+
manifest_path.display(),
123+
);
124+
panic!("{e}")
125+
});
126+
127+
log::info!("wrote manifest to '{}'", manifest_path.display());
128+
129+
if spirv_manifest.is_file() {
130+
log::debug!(
131+
"removing spirv-manifest.json file '{}'",
132+
spirv_manifest.display()
133+
);
134+
std::fs::remove_file(spirv_manifest).unwrap();
135+
}
136+
}
137+
}

crates/cargo-gpu/src/install.rs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
use std::io::Write;
2+
3+
use crate::{cache_dir, spirv::Spirv, target_spec_dir};
4+
5+
const SPIRV_BUILDER_CLI_CARGO_TOML: &str = include_str!("../../spirv-builder-cli/Cargo.toml");
6+
const SPIRV_BUILDER_CLI_MAIN: &str = include_str!("../../spirv-builder-cli/src/main.rs");
7+
const SPIRV_BUILDER_CLI_LIB: &str = include_str!("../../spirv-builder-cli/src/lib.rs");
8+
const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[
9+
("Cargo.toml", SPIRV_BUILDER_CLI_CARGO_TOML),
10+
("src/main.rs", SPIRV_BUILDER_CLI_MAIN),
11+
("src/lib.rs", SPIRV_BUILDER_CLI_LIB),
12+
];
13+
14+
const TARGET_SPECS: &[(&str, &str)] = &[
15+
(
16+
"spirv-unknown-opengl4.0.json",
17+
include_str!("../target-specs/spirv-unknown-opengl4.0.json"),
18+
),
19+
(
20+
"spirv-unknown-opengl4.1.json",
21+
include_str!("../target-specs/spirv-unknown-opengl4.1.json"),
22+
),
23+
(
24+
"spirv-unknown-opengl4.2.json",
25+
include_str!("../target-specs/spirv-unknown-opengl4.2.json"),
26+
),
27+
(
28+
"spirv-unknown-opengl4.3.json",
29+
include_str!("../target-specs/spirv-unknown-opengl4.3.json"),
30+
),
31+
(
32+
"spirv-unknown-opengl4.5.json",
33+
include_str!("../target-specs/spirv-unknown-opengl4.5.json"),
34+
),
35+
(
36+
"spirv-unknown-spv1.0.json",
37+
include_str!("../target-specs/spirv-unknown-spv1.0.json"),
38+
),
39+
(
40+
"spirv-unknown-spv1.1.json",
41+
include_str!("../target-specs/spirv-unknown-spv1.1.json"),
42+
),
43+
(
44+
"spirv-unknown-spv1.2.json",
45+
include_str!("../target-specs/spirv-unknown-spv1.2.json"),
46+
),
47+
(
48+
"spirv-unknown-spv1.3.json",
49+
include_str!("../target-specs/spirv-unknown-spv1.3.json"),
50+
),
51+
(
52+
"spirv-unknown-spv1.4.json",
53+
include_str!("../target-specs/spirv-unknown-spv1.4.json"),
54+
),
55+
(
56+
"spirv-unknown-spv1.5.json",
57+
include_str!("../target-specs/spirv-unknown-spv1.5.json"),
58+
),
59+
(
60+
"spirv-unknown-vulkan1.0.json",
61+
include_str!("../target-specs/spirv-unknown-vulkan1.0.json"),
62+
),
63+
(
64+
"spirv-unknown-vulkan1.1.json",
65+
include_str!("../target-specs/spirv-unknown-vulkan1.1.json"),
66+
),
67+
(
68+
"spirv-unknown-vulkan1.1spv1.4.json",
69+
include_str!("../target-specs/spirv-unknown-vulkan1.1spv1.4.json"),
70+
),
71+
(
72+
"spirv-unknown-vulkan1.2.json",
73+
include_str!("../target-specs/spirv-unknown-vulkan1.2.json"),
74+
),
75+
];
76+
77+
#[derive(clap::Parser, Debug)]
78+
pub(crate) struct Install {
79+
/// spirv-builder dependency, written just like in a Cargo.toml file.
80+
#[clap(long, default_value = Spirv::DEFAULT_DEP)]
81+
spirv_builder: String,
82+
83+
/// Rust toolchain channel to use to build `spirv-builder`.
84+
///
85+
/// This must match the `spirv_builder` argument.
86+
#[clap(long, default_value = Spirv::DEFAULT_CHANNEL)]
87+
rust_toolchain: String,
88+
89+
/// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt.
90+
#[clap(long)]
91+
force_spirv_cli_rebuild: bool,
92+
}
93+
94+
impl Install {
95+
fn spirv_cli(&self) -> Spirv {
96+
Spirv {
97+
dep: self.spirv_builder.clone(),
98+
channel: self.rust_toolchain.clone(),
99+
}
100+
}
101+
102+
fn write_source_files(&self) {
103+
let cli = self.spirv_cli();
104+
let checkout = cli.cached_checkout_path();
105+
std::fs::create_dir_all(checkout.join("src")).unwrap();
106+
for (filename, contents) in SPIRV_BUILDER_FILES.iter() {
107+
log::debug!("writing {filename}");
108+
let path = checkout.join(filename);
109+
let mut file = std::fs::File::create(&path).unwrap();
110+
let replaced_contents = contents
111+
.replace("${SPIRV_BUILDER_SOURCE}", &cli.dep)
112+
.replace("${CHANNEL}", &cli.channel);
113+
file.write_all(replaced_contents.as_bytes()).unwrap();
114+
}
115+
}
116+
117+
fn write_target_spec_files(&self) {
118+
for (filename, contents) in TARGET_SPECS.iter() {
119+
let path = target_spec_dir().join(filename);
120+
if !path.is_file() || self.force_spirv_cli_rebuild {
121+
let mut file = std::fs::File::create(&path).unwrap();
122+
file.write_all(contents.as_bytes()).unwrap();
123+
}
124+
}
125+
}
126+
127+
// Install the binary pair and return the paths, (dylib, cli).
128+
pub fn run(&self) -> (std::path::PathBuf, std::path::PathBuf) {
129+
// Ensure the cache dir exists
130+
let cache_dir = cache_dir();
131+
log::info!("cache directory is '{}'", cache_dir.display());
132+
std::fs::create_dir_all(&cache_dir).unwrap_or_else(|e| {
133+
log::error!(
134+
"could not create cache directory '{}': {e}",
135+
cache_dir.display()
136+
);
137+
panic!("could not create cache dir");
138+
});
139+
140+
let spirv_version = self.spirv_cli();
141+
spirv_version.ensure_version_channel_compatibility();
142+
spirv_version.ensure_toolchain_and_components_exist();
143+
144+
let checkout = spirv_version.cached_checkout_path();
145+
let release = checkout.join("target").join("release");
146+
147+
let dylib_filename = format!(
148+
"{}rustc_codegen_spirv{}",
149+
std::env::consts::DLL_PREFIX,
150+
std::env::consts::DLL_SUFFIX
151+
);
152+
let dylib_path = release.join(&dylib_filename);
153+
let dest_dylib_path = checkout.join(&dylib_filename);
154+
let dest_cli_path = checkout.join("spirv-builder-cli");
155+
if dest_dylib_path.is_file() && dest_cli_path.is_file() {
156+
log::info!(
157+
"cargo-gpu artifacts are already installed in '{}'",
158+
checkout.display()
159+
);
160+
}
161+
162+
if dest_dylib_path.is_file() && dest_cli_path.is_file() && !self.force_spirv_cli_rebuild {
163+
log::info!("...and so we are aborting the install step.");
164+
} else {
165+
log::debug!(
166+
"writing spirv-builder-cli source files into '{}'",
167+
checkout.display()
168+
);
169+
self.write_source_files();
170+
self.write_target_spec_files();
171+
172+
let mut command = std::process::Command::new("cargo");
173+
command
174+
.current_dir(&checkout)
175+
.arg(format!("+{}", spirv_version.channel))
176+
.args(["build", "--release"])
177+
.args(["--no-default-features"]);
178+
179+
command.args([
180+
"--features",
181+
&Self::get_required_spirv_builder_version(spirv_version.channel),
182+
]);
183+
184+
log::debug!("building artifacts with `{:?}`", command);
185+
186+
let output = command
187+
.stdout(std::process::Stdio::inherit())
188+
.stderr(std::process::Stdio::inherit())
189+
.output()
190+
.unwrap();
191+
assert!(output.status.success(), "...build error!");
192+
193+
if dylib_path.is_file() {
194+
log::info!("successfully built {}", dylib_path.display());
195+
std::fs::rename(&dylib_path, &dest_dylib_path).unwrap();
196+
} else {
197+
log::error!("could not find {}", dylib_path.display());
198+
panic!("spirv-builder-cli build failed");
199+
}
200+
201+
let cli_path = if cfg!(target_os = "windows") {
202+
release.join("spirv-builder-cli").with_extension("exe")
203+
} else {
204+
release.join("spirv-builder-cli")
205+
};
206+
if cli_path.is_file() {
207+
log::info!("successfully built {}", cli_path.display());
208+
std::fs::rename(&cli_path, &dest_cli_path).unwrap();
209+
} else {
210+
log::error!("could not find {}", cli_path.display());
211+
log::debug!("contents of '{}':", release.display());
212+
for entry in std::fs::read_dir(&release).unwrap() {
213+
let entry = entry.unwrap();
214+
log::debug!("{}", entry.file_name().to_string_lossy());
215+
}
216+
panic!("spirv-builder-cli build failed");
217+
}
218+
}
219+
(dest_dylib_path, dest_cli_path)
220+
}
221+
222+
/// The `spirv-builder` crate from the main `rust-gpu` repo hasn't always been setup to
223+
/// interact with `cargo-gpu`. Older versions don't have the same `SpirvBuilder` interface. So
224+
/// here we choose the right Cargo feature to enable/disable code in `spirv-builder-cli`.
225+
///
226+
/// TODO:
227+
/// * Download the actual `rust-gpu` repo as pinned in the shader's `Cargo.lock` and get the
228+
/// `spirv-builder` version from there.
229+
/// * Warn the user that certain `cargo-gpu` features aren't available when building with
230+
/// older versions of `spirv-builder`, eg setting the target spec.
231+
fn get_required_spirv_builder_version(toolchain_channel: String) -> String {
232+
let parse_date = chrono::NaiveDate::parse_from_str;
233+
let datetime = parse_date(&toolchain_channel, "nightly-%Y-%m-%d").unwrap();
234+
let pre_cli_date = parse_date("2024-04-24", "%Y-%m-%d").unwrap();
235+
236+
if datetime < pre_cli_date {
237+
"spirv-builder-pre-cli"
238+
} else {
239+
"spirv-builder-0_10"
240+
}
241+
.into()
242+
}
243+
}

0 commit comments

Comments
 (0)