Skip to main content

chaud_hot/workspace/graph/
env.rs

1use super::{KrateIdx, KrateIndex};
2use crate::cargo::metadata::{Metadata, TargetKind, TargetName};
3use crate::cargo::{Cargo, StdioMode};
4use crate::util::assert::err_unreachable;
5use crate::util::etx;
6use anyhow::{Context as _, Result, bail, ensure};
7use camino::{Utf8Path, Utf8PathBuf};
8use std::env::VarError;
9use std::process::Command;
10use std::{env, fs};
11
12#[derive(Debug)]
13pub struct BuildEnv {
14    root: KrateIdx,
15    bin: TargetName,
16    chaud_dir: Utf8PathBuf,
17    cargo: Cargo,
18    flags: Box<[String]>,
19    /// Feature flags from `chaud-rustc` that must be passed to cargo.
20    rustc_features_env: Option<String>,
21}
22
23impl BuildEnv {
24    pub(super) fn new(
25        cargo: Cargo,
26        feature_flags: Option<&'static str>,
27        meta: &Metadata,
28        index: &KrateIndex,
29    ) -> Result<Self> {
30        new_inner(cargo, feature_flags, meta, index).context("Failed to load build env")
31    }
32
33    pub fn root(&self) -> KrateIdx {
34        self.root
35    }
36
37    pub fn bin(&self) -> &TargetName {
38        &self.bin
39    }
40
41    pub fn chaud_dir(&self) -> &Utf8Path {
42        &self.chaud_dir
43    }
44
45    pub fn flags(&self) -> &[String] {
46        &self.flags
47    }
48
49    pub fn cargo_rustc(&self, mode: StdioMode) -> Command {
50        let mut cmd = self.cargo.cmd("rustc", mode);
51        cmd.args(&self.flags);
52        if let Some(f) = &self.rustc_features_env {
53            cmd.env("__CHAUD_RUSTC_FEATURE_FLAGS", f);
54        }
55        cmd
56    }
57}
58
59fn new_inner(
60    cargo: Cargo,
61    ct_feature_flags: Option<&'static str>,
62    meta: &Metadata,
63    index: &KrateIndex,
64) -> Result<BuildEnv> {
65    let exe_file = Utf8PathBuf::try_from(std::env::current_exe()?)?;
66    let exe_dir = exe_file.parent().context("exe has no parent")?;
67
68    let bin = exe_file.file_stem().context("exe has no stem")?;
69
70    let mut root = None;
71    for pkg in meta.packages() {
72        if pkg.manifest_path() != cargo.mani() {
73            continue;
74        }
75
76        ensure!(root.is_none(), "Multiple packages for {:?}", cargo.mani());
77
78        for t in pkg.targets() {
79            if !t.kind().contains(&TargetKind::Bin) {
80                continue;
81            }
82
83            if t.name() != bin {
84                continue;
85            }
86
87            ensure!(
88                root.is_none(),
89                "Multiple `bin` candidates for {bin:?} in {:?}",
90                cargo.mani().path()
91            );
92
93            let Some(krate) = index.get_pkg(pkg.name()) else {
94                err_unreachable!();
95            };
96
97            root = Some((krate, t.name().clone()));
98        }
99
100        ensure!(
101            root.is_some(),
102            "No `bin` target for {bin:?} in {:?}",
103            cargo.mani().path()
104        );
105    }
106    let (root, bin) = root.with_context(etx!("No package for {:?}", cargo.mani().path()))?;
107
108    let mut profile = exe_dir
109        .components()
110        .next_back()
111        .context("missing profile component")?
112        .as_str();
113    if profile == "debug" {
114        profile = "dev";
115    }
116
117    let chaud_dir = exe_dir.join("chaud");
118    fs::create_dir_all(&chaud_dir)?;
119
120    let flags = [
121        "--bin",
122        bin.as_str(),
123        "--profile",
124        profile,
125        "-Fchaud/unsafe-hot-reload",
126    ];
127
128    let rt_feature_flags = match env::var("CHAUD_FEATURE_FLAGS") {
129        Ok(f) => Some(f),
130        Err(VarError::NotPresent) => None,
131        Err(VarError::NotUnicode(_)) => bail!("Invalid UTF-8 in CHAUD_FEATURE_FLAGS"),
132    };
133    let rt_feature_flags = rt_feature_flags.as_deref();
134
135    if let (Some(ct), Some(rt)) = (ct_feature_flags, rt_feature_flags) {
136        ensure!(
137            ct == rt,
138            "Compile-time and run-time CHAUD_FEATURE_FLAGS divereged. ct: {:?}, rt: {:?}",
139            ct_feature_flags,
140            rt_feature_flags
141        );
142    }
143
144    let mut features_env = None;
145    if let (Some(ct), None) = (ct_feature_flags, rt_feature_flags) {
146        features_env = Some(ct.to_owned());
147    }
148
149    let feature_flags = ct_feature_flags.or(rt_feature_flags).unwrap_or("");
150
151    let feature_flags =
152        shlex::split(feature_flags).context("shlex of CHAUD_FEATURE_FLAGS failed")?;
153
154    let flags = flags
155        .into_iter()
156        .map(|s| s.to_owned())
157        .chain(feature_flags)
158        .collect();
159
160    let this = BuildEnv {
161        root,
162        bin,
163        chaud_dir,
164        cargo,
165        flags,
166        rustc_features_env: features_env,
167    };
168
169    log::trace!("{this:?}");
170
171    Ok(this)
172}