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