ic_build/artifacts/candid/
mod.rs1use ic_host_artifacts::artifact::{ArtifactError, ArtifactIdentity};
5use ic_host_fs::read::hash_file;
6use ic_host_process::tool::{
7 AdmittedTool, CompletedExecution, ExecutionContext, OutputLimits, ToolError,
8};
9use std::{fmt, path::Path};
10
11#[cfg(test)]
12mod test_support;
13#[cfg(test)]
14mod tests;
15
16#[derive(Debug)]
18pub enum ExtractionError {
19 SourcePath,
21 Input(ArtifactError),
23 Tool(ToolError),
25 SourceInspection {
27 source: ArtifactError,
29 evidence: Box<CompletedExecution>,
31 },
32 SourceChanged {
34 before: ArtifactIdentity,
36 after: ArtifactIdentity,
38 evidence: Box<CompletedExecution>,
40 },
41}
42
43impl fmt::Display for ExtractionError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::SourcePath => f.write_str("Candid source path must be absolute"),
47 Self::Input(source) => write!(f, "Candid source inspection failed: {source}"),
48 Self::Tool(source) => source.fmt(f),
49 Self::SourceInspection { .. } => f.write_str("Candid source re-inspection failed"),
50 Self::SourceChanged { .. } => f.write_str("Candid source changed during extraction"),
51 }
52 }
53}
54impl std::error::Error for ExtractionError {
55 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
56 match self {
57 Self::Input(source) | Self::SourceInspection { source, .. } => Some(source),
58 Self::Tool(source) => Some(source),
59 _ => None,
60 }
61 }
62}
63
64pub struct ExtractedCandid {
66 pub source_identity: ArtifactIdentity,
68 pub tool_identity: ArtifactIdentity,
70 pub evidence: CompletedExecution,
72}
73
74impl fmt::Debug for ExtractedCandid {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 f.debug_struct("ExtractedCandid")
77 .field("source_identity", &self.source_identity)
78 .field("tool_identity", &self.tool_identity)
79 .field("evidence", &self.evidence)
80 .finish()
81 }
82}
83
84pub fn extract(
98 tool: &AdmittedTool,
99 source: &Path,
100 context: &ExecutionContext<'_>,
101 source_bytes: u64,
102 output: OutputLimits,
103) -> Result<ExtractedCandid, ExtractionError> {
104 if !source.is_absolute() {
105 return Err(ExtractionError::SourcePath);
106 }
107 let before = hash_file(source, source_bytes).map_err(ExtractionError::Input)?;
108 let evidence = tool
109 .run(&[source.as_os_str().to_owned()], context, output)
110 .and_then(CompletedExecution::require_success)
111 .and_then(CompletedExecution::require_complete)
112 .map_err(ExtractionError::Tool)?;
113 let after = match hash_file(source, source_bytes) {
114 Ok(identity) => identity,
115 Err(source) => {
116 return Err(ExtractionError::SourceInspection {
117 source,
118 evidence: Box::new(evidence),
119 });
120 }
121 };
122 if before != after {
123 return Err(ExtractionError::SourceChanged {
124 before,
125 after,
126 evidence: Box::new(evidence),
127 });
128 }
129 Ok(ExtractedCandid {
130 source_identity: before,
131 tool_identity: tool.identity(),
132 evidence,
133 })
134}