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