ephemeris_release/proposal/
assets.rs1use std::path::{Path, PathBuf};
5use std::process::Command;
6use std::{fs, io};
7
8use crate::state::DigestPin;
9
10#[derive(Debug, thiserror::Error)]
12pub(crate) enum AssetError {
13 #[error("cannot run nix to build {package:?}")]
15 Spawn {
16 package: String,
18 #[source]
20 source: io::Error,
21 },
22 #[error("nix could not build {package:?}: {stderr}")]
24 Build {
25 package: String,
27 stderr: String,
29 },
30 #[error("nix built {package:?} and printed no output path")]
32 NoOutputPath {
33 package: String,
35 },
36 #[error("cannot read the built asset {}", path.display())]
38 Output {
39 path: PathBuf,
41 #[source]
43 source: io::Error,
44 },
45}
46
47pub(crate) trait AssetBuilder {
49 fn build(&self, worktree: &Path, package: &str) -> Result<PathBuf, AssetError>;
51}
52
53#[derive(Clone, Copy, Debug)]
55pub(crate) struct NixBuilder;
56
57impl AssetBuilder for NixBuilder {
58 fn build(&self, worktree: &Path, package: &str) -> Result<PathBuf, AssetError> {
59 let reference = format!("git+file://{}#{package}", worktree.display());
60 let output = Command::new("nix")
61 .args(["build", "--no-link", "--print-out-paths", &reference])
62 .output()
63 .map_err(|source| AssetError::Spawn {
64 package: package.to_owned(),
65 source,
66 })?;
67 if !output.status.success() {
68 return Err(AssetError::Build {
69 package: package.to_owned(),
70 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
71 });
72 }
73 String::from_utf8_lossy(&output.stdout)
74 .lines()
75 .next()
76 .map(PathBuf::from)
77 .ok_or_else(|| AssetError::NoOutputPath {
78 package: package.to_owned(),
79 })
80 }
81}
82
83impl DigestPin {
84 pub(crate) fn digest<B: AssetBuilder>(
86 &self,
87 builder: &B,
88 worktree: &Path,
89 ) -> Result<String, AssetError> {
90 let file = builder.build(worktree, &self.package)?.join(&self.output);
91 let bytes = fs::read(&file).map_err(|source| AssetError::Output { path: file, source })?;
92 Ok(self.algorithm.hex(&bytes))
93 }
94}