Skip to main content

waterui_cli/toolchain/
dxc.rs

1//! Toolchain support for `dxc`, the DirectX Shader Compiler that compiles
2//! Hydrolysis shaders on Windows builds.
3//!
4//! `shaderloom` invokes it by name from its build script, so it must sit on
5//! the build's `PATH`.
6
7use std::path::PathBuf;
8
9use crate::toolchain::{
10    Host, Installation, Toolchain, ToolchainError,
11    managed_tool::{self, ManagedToolError},
12};
13
14/// `dxc` on the host's `PATH` or under the managed tool directory.
15#[derive(Debug, Clone, Copy, Default)]
16pub struct Dxc;
17
18impl Dxc {
19    /// The `dxc` executable on `host`: `PATH` first, then the managed install
20    /// under `~/.water/tools`.
21    pub async fn path(&self, host: &Host) -> Option<PathBuf> {
22        if let Ok(path) = host.which("dxc").await {
23            return Some(path);
24        }
25        managed_tool::dxc().binary_path(host)
26    }
27}
28
29impl Toolchain for Dxc {
30    type Installation = DxcInstallation;
31
32    async fn check(&self, host: &Host) -> Result<(), ToolchainError<Self::Installation>> {
33        if self.path(host).await.is_some() {
34            Ok(())
35        } else {
36            Err(ToolchainError::fixable(DxcInstallation))
37        }
38    }
39}
40
41/// Install `dxc` from the pinned `microsoft/DirectXShaderCompiler` release
42/// into `~/.water/tools`.
43#[derive(Debug, Clone, Copy)]
44pub struct DxcInstallation;
45
46/// Errors that can occur while installing `dxc`.
47#[derive(Debug, thiserror::Error)]
48#[error(transparent)]
49pub struct FailToInstallDxc(#[from] ManagedToolError);
50
51impl Installation for DxcInstallation {
52    type Error = FailToInstallDxc;
53
54    async fn install(&self, host: &Host) -> Result<(), Self::Error> {
55        managed_tool::dxc().install(host).await?;
56        Ok(())
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::Dxc;
63    use crate::toolchain::testing::TestMachine;
64    use crate::toolchain::{Toolchain, ToolchainError};
65
66    #[test]
67    fn missing_reports_fixable() {
68        let machine = TestMachine::new();
69        let host = machine.host(Vec::<(String, String)>::new());
70        let result = smol::block_on(Dxc.check(&host));
71        assert!(
72            matches!(result, Err(ToolchainError::Fixable(_))),
73            "a host without dxc must report fixable: {result:?}"
74        );
75    }
76
77    #[test]
78    fn ok_when_dxc_on_path() {
79        let machine = TestMachine::new();
80        machine.install("dxc");
81        let host = machine.host(Vec::<(String, String)>::new());
82        smol::block_on(Dxc.check(&host)).expect("dxc on PATH must be ok");
83    }
84
85    #[test]
86    fn ok_when_dxc_is_managed() {
87        let machine = TestMachine::new();
88        let dxc = crate::toolchain::managed_tool::dxc();
89        let install_dir = dxc
90            .install_dir(&machine.host(Vec::<(String, String)>::new()))
91            .unwrap();
92        let binary = install_dir.join(&dxc.binary);
93        std::fs::create_dir_all(binary.parent().unwrap()).unwrap();
94        std::fs::write(&binary, b"").unwrap();
95
96        let host = machine.host(Vec::<(String, String)>::new());
97        smol::block_on(Dxc.check(&host)).expect("a managed dxc must satisfy the check");
98    }
99
100    #[test]
101    fn install_reuses_an_unpacked_copy() {
102        let machine = TestMachine::new();
103        let dxc = crate::toolchain::managed_tool::dxc();
104        let host = machine.host(Vec::<(String, String)>::new());
105        let install_dir = dxc.install_dir(&host).unwrap();
106        let binary = install_dir.join(&dxc.binary);
107        std::fs::create_dir_all(binary.parent().unwrap()).unwrap();
108        std::fs::write(&binary, b"").unwrap();
109
110        // With the binary already unpacked the install returns its directory
111        // without touching the network — the pre-staged file is the proof,
112        // since a download would overwrite it with archive contents.
113        let dir = smol::block_on(dxc.install(&host)).expect("install must succeed");
114        assert_eq!(dir, binary.parent().unwrap());
115        assert_eq!(std::fs::read(&binary).unwrap(), b"");
116    }
117}