Skip to main content

stout_install/
receipt.rs

1//! INSTALL_RECEIPT.json handling
2
3use crate::error::Result;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7/// INSTALL_RECEIPT.json structure (Homebrew compatible)
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct InstallReceipt {
10    pub homebrew_version: String,
11    pub installed_as_dependency: bool,
12    pub installed_on_request: bool,
13    pub install_time: u64,
14    pub source: ReceiptSource,
15    #[serde(default)]
16    pub runtime_dependencies: Vec<RuntimeDependency>,
17    #[serde(default)]
18    pub poured_from_bottle: bool,
19    #[serde(default)]
20    pub built_as_bottle: bool,
21    #[serde(default)]
22    pub changed_files: Vec<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ReceiptSource {
27    pub tap: String,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub path: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RuntimeDependency {
34    pub full_name: String,
35    pub version: String,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub revision: Option<u32>,
38}
39
40impl InstallReceipt {
41    /// Create a new receipt for a bottle installation
42    pub fn new_bottle(tap: &str, on_request: bool, dependencies: Vec<RuntimeDependency>) -> Self {
43        let now = std::time::SystemTime::now()
44            .duration_since(std::time::UNIX_EPOCH)
45            .unwrap()
46            .as_secs();
47
48        Self {
49            homebrew_version: "4.0.0".to_string(), // Compatible version
50            installed_as_dependency: !on_request,
51            installed_on_request: on_request,
52            install_time: now,
53            source: ReceiptSource {
54                tap: tap.to_string(),
55                path: None,
56            },
57            runtime_dependencies: dependencies,
58            poured_from_bottle: true,
59            built_as_bottle: true,
60            changed_files: Vec::new(),
61        }
62    }
63
64    /// Create a new receipt for a source-built installation
65    pub fn new_source(tap: &str, on_request: bool, dependencies: Vec<RuntimeDependency>) -> Self {
66        let now = std::time::SystemTime::now()
67            .duration_since(std::time::UNIX_EPOCH)
68            .unwrap()
69            .as_secs();
70
71        Self {
72            homebrew_version: "4.0.0".to_string(),
73            installed_as_dependency: !on_request,
74            installed_on_request: on_request,
75            install_time: now,
76            source: ReceiptSource {
77                tap: tap.to_string(),
78                path: None,
79            },
80            runtime_dependencies: dependencies,
81            poured_from_bottle: false, // Built from source
82            built_as_bottle: false,
83            changed_files: Vec::new(),
84        }
85    }
86}
87
88/// Write an INSTALL_RECEIPT.json file
89pub fn write_receipt(install_path: impl AsRef<Path>, receipt: &InstallReceipt) -> Result<()> {
90    let path = install_path.as_ref().join("INSTALL_RECEIPT.json");
91    let json = serde_json::to_string_pretty(receipt)?;
92    std::fs::write(&path, json)?;
93    Ok(())
94}
95
96/// Read an existing INSTALL_RECEIPT.json
97#[allow(dead_code)]
98pub fn read_receipt(install_path: impl AsRef<Path>) -> Result<InstallReceipt> {
99    let path = install_path.as_ref().join("INSTALL_RECEIPT.json");
100    let json = std::fs::read_to_string(&path)?;
101    let receipt: InstallReceipt = serde_json::from_str(&json)?;
102    Ok(receipt)
103}