hydro_deploy/rust_crate/
mod.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3
4use nameof::name_of;
5use tracing_options::TracingOptions;
6
7use super::Host;
8use crate::rust_crate::build::BuildParams;
9use crate::{HostTargetType, ServiceBuilder};
10
11pub mod build;
12pub mod ports;
13
14pub mod service;
15pub use service::*;
16
17pub(crate) mod flamegraph;
18pub mod tracing_options;
19
20#[derive(PartialEq, Clone)]
21pub enum CrateTarget {
22    Default,
23    Bin(String),
24    Example(String),
25}
26
27/// Specifies a crate that uses `hydro_deploy_integration` to be
28/// deployed as a service.
29#[derive(Clone)]
30pub struct RustCrate {
31    src: PathBuf,
32    target: CrateTarget,
33    profile: Option<String>,
34    rustflags: Option<String>,
35    target_dir: Option<PathBuf>,
36    build_env: Vec<(String, String)>,
37    no_default_features: bool,
38    features: Option<Vec<String>>,
39    config: Vec<String>,
40    tracing: Option<TracingOptions>,
41    args: Vec<String>,
42    display_name: Option<String>,
43}
44
45impl RustCrate {
46    /// Creates a new `RustCrate` that will be deployed on the given host.
47    /// The `src` argument is the path to the crate's directory, and the `on`
48    /// argument is the host that the crate will be deployed on.
49    pub fn new(src: impl Into<PathBuf>) -> Self {
50        Self {
51            src: src.into(),
52            target: CrateTarget::Default,
53            profile: None,
54            rustflags: None,
55            target_dir: None,
56            build_env: vec![],
57            no_default_features: false,
58            features: None,
59            config: vec![],
60            tracing: None,
61            args: vec![],
62            display_name: None,
63        }
64    }
65
66    /// Sets the target to be a binary with the given name,
67    /// equivalent to `cargo run --bin <name>`.
68    pub fn bin(mut self, bin: impl Into<String>) -> Self {
69        if self.target != CrateTarget::Default {
70            panic!("{} already set", name_of!(target in Self));
71        }
72
73        self.target = CrateTarget::Bin(bin.into());
74        self
75    }
76
77    /// Sets the target to be an example with the given name,
78    /// equivalent to `cargo run --example <name>`.
79    pub fn example(mut self, example: impl Into<String>) -> Self {
80        if self.target != CrateTarget::Default {
81            panic!("{} already set", name_of!(target in Self));
82        }
83
84        self.target = CrateTarget::Example(example.into());
85        self
86    }
87
88    /// Sets the profile to be used when building the crate.
89    /// Equivalent to `cargo run --profile <profile>`.
90    pub fn profile(mut self, profile: impl Into<String>) -> Self {
91        if self.profile.is_some() {
92            panic!("{} already set", name_of!(profile in Self));
93        }
94
95        self.profile = Some(profile.into());
96        self
97    }
98
99    pub fn rustflags(mut self, rustflags: impl Into<String>) -> Self {
100        if self.rustflags.is_some() {
101            panic!("{} already set", name_of!(rustflags in Self));
102        }
103
104        self.rustflags = Some(rustflags.into());
105        self
106    }
107
108    pub fn target_dir(mut self, target_dir: impl Into<PathBuf>) -> Self {
109        if self.target_dir.is_some() {
110            panic!("{} already set", name_of!(target_dir in Self));
111        }
112
113        self.target_dir = Some(target_dir.into());
114        self
115    }
116
117    pub fn build_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
118        self.build_env.push((key.into(), value.into()));
119        self
120    }
121
122    pub fn no_default_features(mut self) -> Self {
123        self.no_default_features = true;
124        self
125    }
126
127    pub fn features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
128        if self.features.is_none() {
129            self.features = Some(vec![]);
130        }
131
132        self.features
133            .as_mut()
134            .unwrap()
135            .extend(features.into_iter().map(|s| s.into()));
136
137        self
138    }
139
140    pub fn config(mut self, config: impl Into<String>) -> Self {
141        self.config.push(config.into());
142        self
143    }
144
145    pub fn tracing(mut self, perf: impl Into<TracingOptions>) -> Self {
146        if self.tracing.is_some() {
147            panic!("{} already set", name_of!(tracing in Self));
148        }
149
150        self.tracing = Some(perf.into());
151        self
152    }
153
154    /// Sets the arguments to be passed to the binary when it is launched.
155    pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
156        self.args.extend(args.into_iter().map(|s| s.into()));
157        self
158    }
159
160    /// Sets the display name for this service, which will be used in logging.
161    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
162        if self.display_name.is_some() {
163            panic!("{} already set", name_of!(display_name in Self));
164        }
165
166        self.display_name = Some(display_name.into());
167        self
168    }
169
170    pub fn get_build_params(&self, target: HostTargetType) -> BuildParams {
171        let (bin, example) = match &self.target {
172            CrateTarget::Default => (None, None),
173            CrateTarget::Bin(bin) => (Some(bin.clone()), None),
174            CrateTarget::Example(example) => (None, Some(example.clone())),
175        };
176
177        BuildParams::new(
178            self.src.clone(),
179            bin,
180            example,
181            self.profile.clone(),
182            self.rustflags.clone(),
183            self.target_dir.clone(),
184            self.build_env.clone(),
185            self.no_default_features,
186            target,
187            self.features.clone(),
188            self.config.clone(),
189        )
190    }
191}
192
193impl ServiceBuilder for RustCrate {
194    type Service = RustCrateService;
195    fn build(self, id: usize, on: Arc<dyn Host>) -> Self::Service {
196        let build_params = self.get_build_params(on.target_type());
197
198        RustCrateService::new(
199            id,
200            on,
201            build_params,
202            self.tracing,
203            Some(self.args),
204            self.display_name,
205            vec![],
206        )
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::deployment;
214
215    #[tokio::test]
216    async fn test_crate_panic() {
217        let mut deployment = deployment::Deployment::new();
218
219        let service = deployment.add_service(
220            RustCrate::new("../hydro_deploy_examples")
221                .example("panic_program")
222                .profile("dev"),
223            deployment.Localhost(),
224        );
225
226        deployment.deploy().await.unwrap();
227
228        let mut stdout = service.try_read().unwrap().stdout();
229
230        deployment.start().await.unwrap();
231
232        assert_eq!(stdout.recv().await.unwrap(), "hello!");
233
234        assert!(stdout.recv().await.is_none());
235    }
236}