| 21 |
21 |
|
|
| 22 |
22 |
|
use anyhow::{Context, Result};
|
| 23 |
23 |
|
use ops_exec::{Action, Executor, LogSink, ObserveKind, RunOutput, Step};
|
|
24 |
+ |
use std::path::PathBuf;
|
| 24 |
25 |
|
|
| 25 |
26 |
|
/// Everything needed to drive one app's macOS release on a build host.
|
| 26 |
27 |
|
#[derive(Clone, Debug)]
|
| 60 |
61 |
|
Ok(())
|
| 61 |
62 |
|
}
|
| 62 |
63 |
|
|
|
64 |
+ |
/// Expand a leading `~/` in a path that lives on THIS machine.
|
|
65 |
+ |
///
|
|
66 |
+ |
/// The counterpart to [`reject_tilde`], and the reason the two differ: a `~` in
|
|
67 |
+ |
/// a *build-host* path is unexpandable (that host's home is not ours), but the
|
|
68 |
+ |
/// local destination is right here, so our own `HOME` is the correct answer and
|
|
69 |
+ |
/// the tilde can be honored rather than refused.
|
|
70 |
+ |
///
|
|
71 |
+ |
/// `~user` is refused: resolving another user's home means a passwd lookup, and
|
|
72 |
+ |
/// nothing needs it.
|
|
73 |
+ |
pub fn expand_local_tilde(path: &str) -> Result<PathBuf> {
|
|
74 |
+ |
let Some(rest) = path.strip_prefix('~') else {
|
|
75 |
+ |
return Ok(PathBuf::from(path));
|
|
76 |
+ |
};
|
|
77 |
+ |
let home = std::env::var_os("HOME")
|
|
78 |
+ |
.filter(|h| !h.is_empty())
|
|
79 |
+ |
.context("expanding `~`: HOME is not set")?;
|
|
80 |
+ |
match rest {
|
|
81 |
+ |
"" => Ok(PathBuf::from(home)),
|
|
82 |
+ |
_ => match rest.strip_prefix('/') {
|
|
83 |
+ |
Some(tail) => Ok(PathBuf::from(home).join(tail)),
|
|
84 |
+ |
// `~max/x`, `~x` — not a home-relative path we resolve.
|
|
85 |
+ |
None => anyhow::bail!(
|
|
86 |
+ |
"{path:?} uses `~{}`, which is not supported: only `~/` (your own home) is \
|
|
87 |
+ |
expanded. Write it absolute.",
|
|
88 |
+ |
rest.split('/').next().unwrap_or(rest)
|
|
89 |
+ |
),
|
|
90 |
+ |
},
|
|
91 |
+ |
}
|
|
92 |
+ |
}
|
|
93 |
+ |
|
| 63 |
94 |
|
impl ReleasePlan {
|
| 64 |
95 |
|
/// Validate the plan's build-host paths before any step runs.
|
| 65 |
96 |
|
pub fn validate(&self) -> Result<()> {
|
| 276 |
307 |
|
assert!(env_tilde.validate().is_err());
|
| 277 |
308 |
|
}
|
| 278 |
309 |
|
|
|
310 |
+ |
/// The local counterpart to `validate_rejects_tilde_in_build_host_paths`:
|
|
311 |
+ |
/// here the home IS ours, so `~` resolves instead of being refused.
|
|
312 |
+ |
#[test]
|
|
313 |
+ |
fn expand_local_tilde_resolves_against_our_own_home() {
|
|
314 |
+ |
let home = std::env::var("HOME").expect("HOME set in the test env");
|
|
315 |
+ |
assert_eq!(expand_local_tilde("~/Dist/goingson").unwrap(), PathBuf::from(&home).join("Dist/goingson"));
|
|
316 |
+ |
assert_eq!(expand_local_tilde("~").unwrap(), PathBuf::from(&home));
|
|
317 |
+ |
// The bug this closes: the tilde must not survive into the path.
|
|
318 |
+ |
assert!(!expand_local_tilde("~/Dist/goingson").unwrap().starts_with("~"));
|
|
319 |
+ |
}
|
|
320 |
+ |
|
|
321 |
+ |
#[test]
|
|
322 |
+ |
fn expand_local_tilde_leaves_other_paths_alone() {
|
|
323 |
+ |
assert_eq!(expand_local_tilde("/Users/max/Dist").unwrap(), PathBuf::from("/Users/max/Dist"));
|
|
324 |
+ |
assert_eq!(expand_local_tilde("Dist/goingson").unwrap(), PathBuf::from("Dist/goingson"));
|
|
325 |
+ |
// A `~` that isn't leading is just a character.
|
|
326 |
+ |
assert_eq!(expand_local_tilde("/tmp/a~b").unwrap(), PathBuf::from("/tmp/a~b"));
|
|
327 |
+ |
}
|
|
328 |
+ |
|
|
329 |
+ |
#[test]
|
|
330 |
+ |
fn expand_local_tilde_refuses_another_users_home() {
|
|
331 |
+ |
let err = expand_local_tilde("~max/Dist").unwrap_err().to_string();
|
|
332 |
+ |
assert!(err.contains("~max"), "should name what it saw: {err}");
|
|
333 |
+ |
}
|
|
334 |
+ |
|
| 279 |
335 |
|
#[test]
|
| 280 |
336 |
|
fn validate_accepts_absolute_build_host_paths() {
|
| 281 |
337 |
|
let mut plan = go_plan("/Users/max/Code/Apps/goingson", "/Users/max/Dist/goingson/macos");
|