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