1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use anyhow::Context;
use camino::{Utf8Path, Utf8PathBuf};
use serde::{Deserialize, Serialize};

/// A directory that should be added to the target archive
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TargetDirectory(pub Utf8PathBuf);

/// A package that should be added to the target archive
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TargetPackage(pub Utf8PathBuf);

/// A pair of paths, mapping from a file or directory on the host to the target
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct MappedPath {
    /// Source path.
    pub from: Utf8PathBuf,
    /// Destination path.
    pub to: Utf8PathBuf,
}

/// All possible inputs which are used to construct Omicron packages
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum BuildInput {
    /// Adds a single file, which is stored in-memory.
    ///
    /// This is mostly used as a way to cache metadata.
    AddInMemoryFile {
        dst_path: Utf8PathBuf,
        contents: String,
    },

    /// Add a single directory to the target archive.
    ///
    /// This directory doesn't need to exist on the build host.
    AddDirectory(TargetDirectory),

    /// Add a file directly from source to target.
    AddFile {
        /// Describes the files being added.
        mapped_path: MappedPath,

        /// The length of the file.
        ///
        /// Q: Is this necessary? Aren't we already storing the file itself,
        /// making this field redundant?
        ///
        /// A: We use it to help caching, on the "known cache miss" case. In
        /// *most* circumstances where a file has been edited, the length
        /// changes too. Comparing u64s is significantly faster than hashing,
        /// in this situation.
        len: u64,
    },

    /// Add a dowloaded file from source to target.
    ///
    /// This is similar to "AddFile", though it may require downloading an input
    /// first.
    AddBlob {
        path: MappedPath,
        blob: crate::blob::Source,
    },

    /// Add a package from source to target.
    ///
    /// This is similar to "AddFile", though it requires unpacking the package
    /// and re-packaging it into the target.
    AddPackage(TargetPackage),
}

impl BuildInput {
    /// If the input has a path on the host machine, return it.
    pub fn input_path(&self) -> Option<&Utf8Path> {
        match self {
            // This file is stored in-memory, it isn't cached.
            BuildInput::AddInMemoryFile { .. } => None,
            // This path doesn't need to exist on the host, it's just fabricated
            // on the target.
            BuildInput::AddDirectory(_target) => None,
            BuildInput::AddFile { mapped_path, .. } => Some(&mapped_path.from),
            BuildInput::AddBlob { path, .. } => Some(&path.from),
            BuildInput::AddPackage(target_package) => Some(&target_package.0),
        }
    }

    pub fn add_file(mapped_path: MappedPath) -> anyhow::Result<Self> {
        let src = &mapped_path.from;
        let len = src
            .metadata()
            .with_context(|| format!("Failed to get length of {src}"))?
            .len();

        Ok(Self::AddFile { mapped_path, len })
    }
}

/// A ordered collection of build inputs.
pub struct BuildInputs(pub Vec<BuildInput>);

impl BuildInputs {
    pub fn new() -> Self {
        Self(vec![])
    }
}

impl Default for BuildInputs {
    fn default() -> Self {
        Self::new()
    }
}