chaud_hot/workspace/graph/
env.rs1use 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 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}