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
//! # Buildsrs Builder
//!
//! This crate implements the functionality of building crate artifacts. It receives jobs from the
//! backend, telling it which crates to build and which artifacts. It builds the crates using
//! whichever strategy it is configured to use, by default it will use Docker. It streams the
//! progress while the build is in progress, and finally signs and uploads the artifacts.

use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use cargo_metadata::Metadata;
use futures::stream::{BoxStream, StreamExt};
use std::{path::Path, sync::Arc};
use url::Url;

#[cfg(feature = "docker")]
mod docker;
#[cfg(feature = "docker")]
pub use docker::{DockerBuilder, DockerStrategy};

#[cfg(feature = "options")]
mod options;
#[cfg(feature = "options")]
pub use options::StrategyOptions;

#[cfg(feature = "websocket")]
mod websocket;
#[cfg(feature = "websocket")]
pub use websocket::Connection;

/// Build logs and final file.
#[derive(Clone, Debug)]
pub enum Output<T> {
    /// Logs while fetching
    Fetch(Vec<u8>),
    /// Logs while building
    Build(Vec<u8>),
    /// Final data
    Data(T),
}

/// Stream file contents.
pub type FileStream = BoxStream<'static, Result<Bytes>>;

/// Stream build logs and final data.
pub type OutputStream<T> = BoxStream<'static, Result<Output<T>>>;

/// Trait to get the final data from an [`OutputStream`].
#[async_trait]
pub trait OutputStreamExt {
    /// Type that this stream resolves out.
    type Output;

    /// Get final data, ignoring all log messages.
    async fn resolve(self) -> Result<Self::Output>;
}

#[async_trait]
impl<T> OutputStreamExt for OutputStream<T> {
    type Output = T;

    async fn resolve(mut self) -> Result<Self::Output> {
        while let Some(item) = self.next().await {
            if let Output::Data(data) = item? {
                return Ok(data);
            }
        }

        return Err(anyhow::anyhow!("Did not find data"));
    }
}

/// Represents a strategy for building artifacts.
///
/// A strategy is able to create a builder instance for a given crate. For example, building
/// binaries in Docker might be one strategy, while building binaries in a QEMU VM might be another
/// one.
#[async_trait]
pub trait Strategy: Send + Sync {
    /// Create a builder instance from an extraced crate at the given path.
    async fn builder_from_path(&self, path: &Path) -> Result<DynBuilder>;

    /// Create a builder instance from a `.crate` file.
    async fn builder_from_crate(&self, krate: &Path) -> Result<DynBuilder>;

    /// Create a builder instance from a crate by download URL.
    async fn builder_from_url(&self, url: &Url, checksum: &[u8]) -> Result<DynBuilder>;
}

/// Represents an instance of a builder with a single crate.
///
/// The builder is able to produce artifacts for the crate.
#[async_trait]
pub trait Builder: Send + Sync {
    /// Build crate metadata.
    async fn metadata(&self) -> Result<OutputStream<Metadata>>;
}

/// Dynamic builder.
pub type DynBuilder = Box<dyn Builder>;

/// Dynamic strategy.
pub type DynStrategy = Arc<dyn Strategy>;