cargo_zng/res/built_in/
sh.rs1use std::process::Command;
2
3use super::*;
4
5const SH_HELP: &str = r#"
6Run a bash script
7
8Script is configured using environment variables (like other tools):
9
10ZR_SOURCE_DIR — Resources directory that is being build.
11ZR_TARGET_DIR — Target directory where resources are being built to.
12ZR_CACHE_DIR — Dir to use for intermediary data for the specific request.
13ZR_WORKSPACE_DIR — Cargo workspace that contains source dir. Also the working dir.
14ZR_REQUEST — Request file that called the tool (.zr-sh).
15ZR_REQUEST_DD — Parent dir of the request file.
16ZR_TARGET — Target file implied by the request file name.
17ZR_TARGET_DD — Parent dir of the target file.
18
19ZR_FINAL — Set if the script previously printed `zng-res::on-final={args}`.
20
21In a Cargo workspace the `zng::env::about` metadata is also set:
22
23ZR_APP_ID — package.metadata.zng.about.app_id or "qualifier.org.app" in snake_case
24ZR_APP — package.metadata.zng.about.app or package.name
25ZR_ORG — package.metadata.zng.about.org or the first package.authors
26ZR_VERSION — package.version
27ZR_DESCRIPTION — package.description
28ZR_HOMEPAGE — package.homepage
29ZR_LICENSE — package.license
30ZR_PKG_NAME — package.name
31ZR_PKG_AUTHORS — package.authors
32ZR_CRATE_NAME — package.name in snake_case
33ZR_QUALIFIER — package.metadata.zng.about.qualifier or the first components `ZR_APP_ID` except the last two
34ZR_META_* — any other custom string value in package.metadata.zng.about.*
35
36Script can make requests to the resource builder by printing to stdout.
37Current supported requests:
38
39zng-res::warning={msg} — Prints the `{msg}` as a warning after the script exits.
40zng-res::on-final={args} — Schedule second run with `ZR_FINAL={args}`, on final pass.
41
42If the script fails the entire stderr is printed and the resource build fails. Scripts run with
43`set -e` by default.
44
45Tries to run on $ZR_SH, $PROGRAMFILES/Git/bin/bash.exe, bash, sh.
46"#;
47pub(super) fn sh() {
48 help(SH_HELP);
49 let script = fs::read_to_string(path(ZR_REQUEST)).unwrap_or_else(|e| fatal!("{e}"));
50 sh_run(script, false, None).unwrap_or_else(|e| fatal!("{e}"));
51}
52
53fn sh_options() -> Vec<std::ffi::OsString> {
54 let mut r = vec![];
55 if let Ok(sh) = env::var("ZR_SH")
56 && !sh.is_empty()
57 {
58 let sh = PathBuf::from(sh);
59 if sh.exists() {
60 r.push(sh.into_os_string());
61 }
62 }
63
64 #[cfg(windows)]
65 if let Ok(pf) = env::var("PROGRAMFILES") {
66 let sh = PathBuf::from(pf).join("Git/bin/bash.exe");
67 if sh.exists() {
68 r.push(sh.into_os_string());
69 }
70 }
71 #[cfg(windows)]
72 if let Ok(c) = env::var("SYSTEMDRIVE") {
73 let sh = PathBuf::from(c).join("Program Files (x86)/Git/bin/bash.exe");
74 if sh.exists() {
75 r.push(sh.into_os_string());
76 }
77 }
78
79 r.push("bash".into());
80 r.push("sh".into());
81
82 r
83}
84pub(crate) fn sh_run(mut script: String, capture: bool, current_dir: Option<&Path>) -> io::Result<String> {
85 script.insert_str(0, "set -e\n");
86
87 for opt in sh_options() {
88 let r = sh_run_try(&opt, &script, capture, current_dir)?;
89 if let Some(r) = r {
90 return Ok(r);
91 }
92 }
93 Err(io::Error::new(
94 io::ErrorKind::NotFound,
95 "cannot find bash, tried $ZR_SH, $PROGRAMFILES/Git/bin/bash.exe, bash, sh",
96 ))
97}
98fn sh_run_try(sh: &std::ffi::OsStr, script: &str, capture: bool, current_dir: Option<&Path>) -> io::Result<Option<String>> {
99 let mut sh = Command::new(sh);
100 if let Some(d) = current_dir {
101 sh.current_dir(d);
102 }
103 sh.arg("-c").arg(script);
104 sh.stdin(std::process::Stdio::null());
105 sh.stderr(std::process::Stdio::inherit());
106 let r = if capture {
107 sh.output().map(|o| (o.status, String::from_utf8_lossy(&o.stdout).into_owned()))
108 } else {
109 sh.stdout(std::process::Stdio::inherit());
110 sh.status().map(|s| (s, String::new()))
111 };
112 match r {
113 Ok((s, o)) => {
114 if !s.success() {
115 return Err(match s.code() {
116 Some(c) => io::Error::other(format!("script failed, exit code {c}")),
117 None => io::Error::other("script failed"),
118 });
119 }
120 Ok(Some(o))
121 }
122 Err(e) => {
123 if e.kind() == io::ErrorKind::NotFound {
124 Ok(None)
125 } else {
126 Err(e)
127 }
128 }
129 }
130}