1#![allow(clippy::needless_return)]
2
3use log::*;
4use std::fs::{self};
5use std::io::{Result, Write};
6use std::path::{Path, PathBuf};
7
8pub fn build_protos(proto_path: impl AsRef<Path>) -> Result<()> {
9 let path = proto_path.as_ref().to_string_lossy();
10 println!("cargo::rerun-if-changed={path}");
11
12 let prost_config = {
13 let mut config = prost_build::Config::new();
14 config.enum_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
15 config
16 };
17
18 let proto_files = vec![
19 PathBuf::from(format!("{path}/config.proto")),
20 PathBuf::from(format!("{path}/config_api.proto")),
21 PathBuf::from(format!("{path}/vault.proto")),
22 ];
23
24 prost_reflect_build::Builder::new()
25 .descriptor_pool("crate::DESCRIPTOR_POOL")
26 .compile_protos_with_config(prost_config, &proto_files, &[proto_path])?;
27
28 return Ok(());
29}
30
31pub fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
32 fs::create_dir_all(&dst)?;
33 for entry in fs::read_dir(src)? {
34 let entry = entry?;
35 if entry.file_name().to_string_lossy().starts_with(".") {
36 continue;
37 }
38
39 if entry.file_type()?.is_dir() {
40 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
41 } else {
42 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
43 }
44 }
45
46 return Ok(());
47}
48
49fn write_output(mut sink: impl Write, source: &[u8], header: &str) -> Result<()> {
50 sink.write_all(header.as_bytes())?;
51 sink.write_all(b"\n")?;
52 sink.write_all(source)?;
53 sink.write_all(b"\n\n")?;
54 return Ok(());
55}
56
57pub fn pnpm_run(args: &[&str]) -> Result<std::process::Output> {
58 let cmd = "pnpm";
59 let output = std::process::Command::new(cmd)
60 .args(args)
61 .output()
62 .map_err(|err| {
63 eprintln!("Error: Failed to run '{cmd} {}'", args.join(" "));
64 return err;
65 })?;
66
67 let header = format!(
68 "== {cmd} {} (cwd: {:?}) ==",
69 args.join(" "),
70 std::env::current_dir()?
71 );
72 write_output(std::io::stdout(), &output.stdout, &header)?;
73 write_output(std::io::stderr(), &output.stderr, &header)?;
74
75 if !output.status.success() {
76 return Err(std::io::Error::other(format!(
77 "Failed to run '{args:?}'\n\t{}",
78 String::from_utf8_lossy(&output.stderr)
79 )));
80 }
81
82 Ok(output)
83}
84
85pub fn build_js(path: impl AsRef<Path>) -> Result<()> {
86 let path = path.as_ref().to_string_lossy().to_string();
87 let strict_offline: bool = matches!(
88 std::env::var("PNPM_OFFLINE").as_deref(),
89 Ok("TRUE") | Ok("true") | Ok("1")
90 );
91
92 let out_dir = std::env::var("OUT_DIR").unwrap();
95 let build_result = if out_dir.contains("target/package") {
96 pnpm_run(&["--dir", &path, "install", "--ignore-workspace"])
98 } else {
99 let args = if strict_offline {
104 ["--dir", &path, "install", "--frozen-lockfile", "--offline"]
105 } else {
106 [
107 "--dir",
108 &path,
109 "install",
110 "--prefer-frozen-lockfile",
111 "--prefer-offline",
112 ]
113 };
114 let build_result = pnpm_run(&args);
115 if build_result.is_err() {
116 error!(
117 "`pnpm {}` failed. Make sure to install all JS deps first using `pnpm install`",
118 args.join(" ")
119 )
120 }
121 build_result
122 };
123
124 let _ = build_result?;
125
126 let build_output = pnpm_run(&["--dir", &path, "build"]);
127 if build_output.is_err() && cfg!(windows) {
128 error!(
129 "pnpm build failed on Windows. Make sure to enable symlinks: `git config core.symlinks true && git reset --hard`."
130 );
131 }
132
133 let _ = build_output?;
134
135 return Ok(());
136}
137
138pub fn rerun_if_changed(path: impl AsRef<Path>) {
139 let path_str = path.as_ref().to_string_lossy().to_string();
140 if !std::fs::exists(path).unwrap_or(false) {
142 panic!("Path '{path_str}' doesn't exist");
143 }
144 println!("cargo::rerun-if-changed={path_str}");
145}
146
147pub fn init_env_logger() {
148 env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
149}