1use std::cell::RefCell;
4use std::collections::HashMap;
5use std::future::Future;
6use std::io::Error;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use bytes::{Bytes, BytesMut};
12use dfir_lang::graph::DfirGraph;
13use futures::{Sink, SinkExt, Stream, StreamExt};
14use hydro_deploy::custom_service::CustomClientPort;
15use hydro_deploy::rust_crate::RustCrateService;
16use hydro_deploy::rust_crate::ports::{DemuxSink, RustCrateSink, RustCrateSource, TaggedSource};
17use hydro_deploy::rust_crate::tracing_options::TracingOptions;
18use hydro_deploy::{CustomService, Deployment, Host, RustCrate};
19use hydro_deploy_integration::{ConnectedSink, ConnectedSource};
20use nameof::name_of;
21use proc_macro2::Span;
22use serde::Serialize;
23use serde::de::DeserializeOwned;
24use slotmap::SparseSecondaryMap;
25use stageleft::{QuotedWithContext, RuntimeData};
26use syn::parse_quote;
27
28use super::deploy_runtime::*;
29use crate::compile::builder::ExternalPortId;
30use crate::compile::deploy_provider::{
31 ClusterSpec, Deploy, ExternalSpec, IntoProcessSpec, Node, ProcessSpec, RegisterPort,
32};
33use crate::compile::trybuild::generate::{
34 HYDRO_RUNTIME_FEATURES, LinkingMode, create_graph_trybuild,
35};
36use crate::location::dynamic::LocationId;
37use crate::location::member_id::TaglessMemberId;
38use crate::location::{LocationKey, MembershipEvent, NetworkHint};
39use crate::staging_util::get_this_crate;
40
41pub enum HydroDeploy {}
46
47impl<'a> Deploy<'a> for HydroDeploy {
48 type Meta = SparseSecondaryMap<LocationKey, Vec<TaglessMemberId>>;
50 type InstantiateEnv = Deployment;
51
52 type Process = DeployNode;
53 type Cluster = DeployCluster;
54 type External = DeployExternal;
55
56 fn o2o_sink_source(
57 _env: &mut Self::InstantiateEnv,
58 _p1: &Self::Process,
59 p1_port: &<Self::Process as Node>::Port,
60 _p2: &Self::Process,
61 p2_port: &<Self::Process as Node>::Port,
62 _name: Option<&str>,
63 networking_info: &crate::networking::NetworkingInfo,
64 _external_types: Option<(&syn::Type, &syn::Type)>,
65 ) -> (syn::Expr, syn::Expr) {
66 match networking_info {
67 crate::networking::NetworkingInfo::Tcp {
68 fault: crate::networking::TcpFault::FailStop,
69 } => {}
70 _ => panic!("Unsupported networking info: {:?}", networking_info),
71 }
72 let p1_port = p1_port.as_str();
73 let p2_port = p2_port.as_str();
74 deploy_o2o(
75 RuntimeData::new("__hydro_lang_trybuild_cli"),
76 p1_port,
77 p2_port,
78 )
79 }
80
81 fn o2o_connect(
82 p1: &Self::Process,
83 p1_port: &<Self::Process as Node>::Port,
84 p2: &Self::Process,
85 p2_port: &<Self::Process as Node>::Port,
86 ) -> Box<dyn FnOnce()> {
87 let p1 = p1.clone();
88 let p1_port = p1_port.clone();
89 let p2 = p2.clone();
90 let p2_port = p2_port.clone();
91
92 Box::new(move || {
93 let self_underlying_borrow = p1.underlying.borrow();
94 let self_underlying = self_underlying_borrow.as_ref().unwrap();
95 let source_port = self_underlying.get_port(p1_port.clone());
96
97 let other_underlying_borrow = p2.underlying.borrow();
98 let other_underlying = other_underlying_borrow.as_ref().unwrap();
99 let recipient_port = other_underlying.get_port(p2_port.clone());
100
101 source_port.send_to(&recipient_port)
102 })
103 }
104
105 fn o2m_sink_source(
106 _env: &mut Self::InstantiateEnv,
107 _p1: &Self::Process,
108 p1_port: &<Self::Process as Node>::Port,
109 _c2: &Self::Cluster,
110 c2_port: &<Self::Cluster as Node>::Port,
111 _name: Option<&str>,
112 networking_info: &crate::networking::NetworkingInfo,
113 _external_types: Option<(&syn::Type, &syn::Type)>,
114 ) -> (syn::Expr, syn::Expr) {
115 match networking_info {
116 crate::networking::NetworkingInfo::Tcp {
117 fault: crate::networking::TcpFault::FailStop,
118 } => {}
119 _ => panic!("Unsupported networking info: {:?}", networking_info),
120 }
121 let p1_port = p1_port.as_str();
122 let c2_port = c2_port.as_str();
123 deploy_o2m(
124 RuntimeData::new("__hydro_lang_trybuild_cli"),
125 p1_port,
126 c2_port,
127 )
128 }
129
130 fn o2m_connect(
131 p1: &Self::Process,
132 p1_port: &<Self::Process as Node>::Port,
133 c2: &Self::Cluster,
134 c2_port: &<Self::Cluster as Node>::Port,
135 ) -> Box<dyn FnOnce()> {
136 let p1 = p1.clone();
137 let p1_port = p1_port.clone();
138 let c2 = c2.clone();
139 let c2_port = c2_port.clone();
140
141 Box::new(move || {
142 let self_underlying_borrow = p1.underlying.borrow();
143 let self_underlying = self_underlying_borrow.as_ref().unwrap();
144 let source_port = self_underlying.get_port(p1_port.clone());
145
146 let recipient_port = DemuxSink {
147 demux: c2
148 .members
149 .borrow()
150 .iter()
151 .enumerate()
152 .map(|(id, c)| {
153 (
154 id as u32,
155 Arc::new(c.underlying.get_port(c2_port.clone()))
156 as Arc<dyn RustCrateSink + 'static>,
157 )
158 })
159 .collect(),
160 };
161
162 source_port.send_to(&recipient_port)
163 })
164 }
165
166 fn m2o_sink_source(
167 _env: &mut Self::InstantiateEnv,
168 _c1: &Self::Cluster,
169 c1_port: &<Self::Cluster as Node>::Port,
170 _p2: &Self::Process,
171 p2_port: &<Self::Process as Node>::Port,
172 _name: Option<&str>,
173 networking_info: &crate::networking::NetworkingInfo,
174 _external_types: Option<(&syn::Type, &syn::Type)>,
175 ) -> (syn::Expr, syn::Expr) {
176 match networking_info {
177 crate::networking::NetworkingInfo::Tcp {
178 fault: crate::networking::TcpFault::FailStop,
179 } => {}
180 _ => panic!("Unsupported networking info: {:?}", networking_info),
181 }
182 let c1_port = c1_port.as_str();
183 let p2_port = p2_port.as_str();
184 deploy_m2o(
185 RuntimeData::new("__hydro_lang_trybuild_cli"),
186 c1_port,
187 p2_port,
188 )
189 }
190
191 fn m2o_connect(
192 c1: &Self::Cluster,
193 c1_port: &<Self::Cluster as Node>::Port,
194 p2: &Self::Process,
195 p2_port: &<Self::Process as Node>::Port,
196 ) -> Box<dyn FnOnce()> {
197 let c1 = c1.clone();
198 let c1_port = c1_port.clone();
199 let p2 = p2.clone();
200 let p2_port = p2_port.clone();
201
202 Box::new(move || {
203 let other_underlying_borrow = p2.underlying.borrow();
204 let other_underlying = other_underlying_borrow.as_ref().unwrap();
205 let recipient_port = other_underlying.get_port(p2_port.clone()).merge();
206
207 for (i, node) in c1.members.borrow().iter().enumerate() {
208 let source_port = node.underlying.get_port(c1_port.clone());
209
210 TaggedSource {
211 source: Arc::new(source_port),
212 tag: i as u32,
213 }
214 .send_to(&recipient_port);
215 }
216 })
217 }
218
219 fn m2m_sink_source(
220 _env: &mut Self::InstantiateEnv,
221 _c1: &Self::Cluster,
222 c1_port: &<Self::Cluster as Node>::Port,
223 _c2: &Self::Cluster,
224 c2_port: &<Self::Cluster as Node>::Port,
225 _name: Option<&str>,
226 networking_info: &crate::networking::NetworkingInfo,
227 _external_types: Option<(&syn::Type, &syn::Type)>,
228 ) -> (syn::Expr, syn::Expr) {
229 match networking_info {
230 crate::networking::NetworkingInfo::Tcp {
231 fault: crate::networking::TcpFault::FailStop,
232 } => {}
233 _ => panic!("Unsupported networking info: {:?}", networking_info),
234 }
235 let c1_port = c1_port.as_str();
236 let c2_port = c2_port.as_str();
237 deploy_m2m(
238 RuntimeData::new("__hydro_lang_trybuild_cli"),
239 c1_port,
240 c2_port,
241 )
242 }
243
244 fn m2m_connect(
245 c1: &Self::Cluster,
246 c1_port: &<Self::Cluster as Node>::Port,
247 c2: &Self::Cluster,
248 c2_port: &<Self::Cluster as Node>::Port,
249 ) -> Box<dyn FnOnce()> {
250 let c1 = c1.clone();
251 let c1_port = c1_port.clone();
252 let c2 = c2.clone();
253 let c2_port = c2_port.clone();
254
255 Box::new(move || {
256 for (i, sender) in c1.members.borrow().iter().enumerate() {
257 let source_port = sender.underlying.get_port(c1_port.clone());
258
259 let recipient_port = DemuxSink {
260 demux: c2
261 .members
262 .borrow()
263 .iter()
264 .enumerate()
265 .map(|(id, c)| {
266 (
267 id as u32,
268 Arc::new(c.underlying.get_port(c2_port.clone()).merge())
269 as Arc<dyn RustCrateSink + 'static>,
270 )
271 })
272 .collect(),
273 };
274
275 TaggedSource {
276 source: Arc::new(source_port),
277 tag: i as u32,
278 }
279 .send_to(&recipient_port);
280 }
281 })
282 }
283
284 fn e2o_many_source(
285 extra_stmts: &mut Vec<syn::Stmt>,
286 _p2: &Self::Process,
287 p2_port: &<Self::Process as Node>::Port,
288 codec_type: &syn::Type,
289 shared_handle: String,
290 ) -> syn::Expr {
291 let connect_ident = syn::Ident::new(
292 &format!("__hydro_deploy_many_{}_connect", &shared_handle),
293 Span::call_site(),
294 );
295 let source_ident = syn::Ident::new(
296 &format!("__hydro_deploy_many_{}_source", &shared_handle),
297 Span::call_site(),
298 );
299 let sink_ident = syn::Ident::new(
300 &format!("__hydro_deploy_many_{}_sink", &shared_handle),
301 Span::call_site(),
302 );
303 let membership_ident = syn::Ident::new(
304 &format!("__hydro_deploy_many_{}_membership", &shared_handle),
305 Span::call_site(),
306 );
307
308 let root = get_this_crate();
309
310 extra_stmts.push(syn::parse_quote! {
311 let #connect_ident = __hydro_lang_trybuild_cli
312 .port(#p2_port)
313 .connect::<#root::runtime_support::hydro_deploy_integration::multi_connection::ConnectedMultiConnection<_, _, #codec_type>>();
314 });
315
316 extra_stmts.push(syn::parse_quote! {
317 let #source_ident = #connect_ident.source;
318 });
319
320 extra_stmts.push(syn::parse_quote! {
321 let #sink_ident = #connect_ident.sink;
322 });
323
324 extra_stmts.push(syn::parse_quote! {
325 let #membership_ident = #connect_ident.membership;
326 });
327
328 parse_quote!(#source_ident)
329 }
330
331 fn e2o_many_sink(shared_handle: String) -> syn::Expr {
332 let sink_ident = syn::Ident::new(
333 &format!("__hydro_deploy_many_{}_sink", &shared_handle),
334 Span::call_site(),
335 );
336 parse_quote!(#sink_ident)
337 }
338
339 fn e2o_source(
340 extra_stmts: &mut Vec<syn::Stmt>,
341 _p1: &Self::External,
342 _p1_port: &<Self::External as Node>::Port,
343 _p2: &Self::Process,
344 p2_port: &<Self::Process as Node>::Port,
345 codec_type: &syn::Type,
346 shared_handle: String,
347 ) -> syn::Expr {
348 let connect_ident = syn::Ident::new(
349 &format!("__hydro_deploy_{}_connect", &shared_handle),
350 Span::call_site(),
351 );
352 let source_ident = syn::Ident::new(
353 &format!("__hydro_deploy_{}_source", &shared_handle),
354 Span::call_site(),
355 );
356 let sink_ident = syn::Ident::new(
357 &format!("__hydro_deploy_{}_sink", &shared_handle),
358 Span::call_site(),
359 );
360
361 let root = get_this_crate();
362
363 extra_stmts.push(syn::parse_quote! {
364 let #connect_ident = __hydro_lang_trybuild_cli
365 .port(#p2_port)
366 .connect::<#root::runtime_support::hydro_deploy_integration::single_connection::ConnectedSingleConnection<_, _, #codec_type>>();
367 });
368
369 extra_stmts.push(syn::parse_quote! {
370 let #source_ident = #connect_ident.source;
371 });
372
373 extra_stmts.push(syn::parse_quote! {
374 let #sink_ident = #connect_ident.sink;
375 });
376
377 parse_quote!(#source_ident)
378 }
379
380 fn e2o_connect(
381 p1: &Self::External,
382 p1_port: &<Self::External as Node>::Port,
383 p2: &Self::Process,
384 p2_port: &<Self::Process as Node>::Port,
385 _many: bool,
386 server_hint: NetworkHint,
387 ) -> Box<dyn FnOnce()> {
388 let p1 = p1.clone();
389 let p1_port = p1_port.clone();
390 let p2 = p2.clone();
391 let p2_port = p2_port.clone();
392
393 Box::new(move || {
394 let self_underlying_borrow = p1.underlying.borrow();
395 let self_underlying = self_underlying_borrow.as_ref().unwrap();
396 let source_port = self_underlying.declare_many_client();
397
398 let other_underlying_borrow = p2.underlying.borrow();
399 let other_underlying = other_underlying_borrow.as_ref().unwrap();
400 let recipient_port = other_underlying.get_port_with_hint(
401 p2_port.clone(),
402 match server_hint {
403 NetworkHint::Auto => hydro_deploy::PortNetworkHint::Auto,
404 NetworkHint::TcpPort(p) => hydro_deploy::PortNetworkHint::TcpPort(p),
405 },
406 );
407
408 source_port.send_to(&recipient_port);
409
410 p1.client_ports
411 .borrow_mut()
412 .insert(p1_port.clone(), source_port);
413 })
414 }
415
416 fn o2e_sink(
417 _p1: &Self::Process,
418 _p1_port: &<Self::Process as Node>::Port,
419 _p2: &Self::External,
420 _p2_port: &<Self::External as Node>::Port,
421 shared_handle: String,
422 ) -> syn::Expr {
423 let sink_ident = syn::Ident::new(
424 &format!("__hydro_deploy_{}_sink", &shared_handle),
425 Span::call_site(),
426 );
427 parse_quote!(#sink_ident)
428 }
429
430 fn cluster_ids(
431 of_cluster: LocationKey,
432 ) -> impl QuotedWithContext<'a, &'a [TaglessMemberId], ()> + Clone + 'a {
433 cluster_members(RuntimeData::new("__hydro_lang_trybuild_cli"), of_cluster)
434 }
435
436 fn cluster_self_id() -> impl QuotedWithContext<'a, TaglessMemberId, ()> + Clone + 'a {
437 cluster_self_id(RuntimeData::new("__hydro_lang_trybuild_cli"))
438 }
439
440 fn cluster_membership_stream(
441 _env: &mut Self::InstantiateEnv,
442 _at_location: &LocationId,
443 location_id: &LocationId,
444 ) -> impl QuotedWithContext<'a, Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>, ()>
445 {
446 cluster_membership_stream(location_id)
447 }
448}
449
450#[expect(missing_docs, reason = "TODO")]
451pub trait DeployCrateWrapper {
452 fn underlying(&self) -> Arc<RustCrateService>;
453
454 fn stdout(&self) -> tokio::sync::mpsc::UnboundedReceiver<String> {
455 self.underlying().stdout()
456 }
457
458 fn stderr(&self) -> tokio::sync::mpsc::UnboundedReceiver<String> {
459 self.underlying().stderr()
460 }
461
462 fn stdout_filter(
463 &self,
464 prefix: impl Into<String>,
465 ) -> tokio::sync::mpsc::UnboundedReceiver<String> {
466 self.underlying().stdout_filter(prefix.into())
467 }
468
469 fn stderr_filter(
470 &self,
471 prefix: impl Into<String>,
472 ) -> tokio::sync::mpsc::UnboundedReceiver<String> {
473 self.underlying().stderr_filter(prefix.into())
474 }
475}
476
477#[expect(missing_docs, reason = "TODO")]
478#[derive(Clone)]
479pub struct TrybuildHost {
480 host: Arc<dyn Host>,
481 display_name: Option<String>,
482 rustflags: Option<String>,
483 profile: Option<String>,
484 additional_hydro_features: Vec<String>,
485 features: Vec<String>,
486 tracing: Option<TracingOptions>,
487 build_envs: Vec<(String, String)>,
488 env: HashMap<String, String>,
489 pin_to_core: Option<usize>,
490 name_hint: Option<String>,
491 cluster_idx: Option<usize>,
492}
493
494impl From<Arc<dyn Host>> for TrybuildHost {
495 fn from(host: Arc<dyn Host>) -> Self {
496 Self {
497 host,
498 display_name: None,
499 rustflags: None,
500 profile: None,
501 additional_hydro_features: vec![],
502 features: vec![],
503 tracing: None,
504 build_envs: vec![],
505 env: HashMap::new(),
506 pin_to_core: None,
507 name_hint: None,
508 cluster_idx: None,
509 }
510 }
511}
512
513impl<H: Host + 'static> From<Arc<H>> for TrybuildHost {
514 fn from(host: Arc<H>) -> Self {
515 Self {
516 host,
517 display_name: None,
518 rustflags: None,
519 profile: None,
520 additional_hydro_features: vec![],
521 features: vec![],
522 tracing: None,
523 build_envs: vec![],
524 env: HashMap::new(),
525 pin_to_core: None,
526 name_hint: None,
527 cluster_idx: None,
528 }
529 }
530}
531
532#[expect(missing_docs, reason = "TODO")]
533impl TrybuildHost {
534 pub fn new(host: Arc<dyn Host>) -> Self {
535 Self {
536 host,
537 display_name: None,
538 rustflags: None,
539 profile: None,
540 additional_hydro_features: vec![],
541 features: vec![],
542 tracing: None,
543 build_envs: vec![],
544 env: HashMap::new(),
545 pin_to_core: None,
546 name_hint: None,
547 cluster_idx: None,
548 }
549 }
550
551 pub fn display_name(self, display_name: impl Into<String>) -> Self {
552 if self.display_name.is_some() {
553 panic!("{} already set", name_of!(display_name in Self));
554 }
555
556 Self {
557 display_name: Some(display_name.into()),
558 ..self
559 }
560 }
561
562 pub fn rustflags(self, rustflags: impl Into<String>) -> Self {
563 if self.rustflags.is_some() {
564 panic!("{} already set", name_of!(rustflags in Self));
565 }
566
567 Self {
568 rustflags: Some(rustflags.into()),
569 ..self
570 }
571 }
572
573 pub fn profile(self, profile: impl Into<String>) -> Self {
574 if self.profile.is_some() {
575 panic!("{} already set", name_of!(profile in Self));
576 }
577
578 Self {
579 profile: Some(profile.into()),
580 ..self
581 }
582 }
583
584 pub fn additional_hydro_features(
585 mut self,
586 additional_hydro_features: impl IntoIterator<Item = impl Into<String>>,
587 ) -> Self {
588 self.additional_hydro_features
589 .extend(additional_hydro_features.into_iter().map(Into::into));
590 self
591 }
592
593 pub fn additional_hydro_feature(mut self, feature: impl Into<String>) -> Self {
594 self.additional_hydro_features.push(feature.into());
595 self
596 }
597
598 pub fn features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
599 self.features.extend(features.into_iter().map(Into::into));
600 self
601 }
602
603 pub fn feature(mut self, feature: impl Into<String>) -> Self {
604 self.features.push(feature.into());
605 self
606 }
607
608 pub fn tracing(self, tracing: TracingOptions) -> Self {
609 if self.tracing.is_some() {
610 panic!("{} already set", name_of!(tracing in Self));
611 }
612
613 Self {
614 tracing: Some(tracing),
615 ..self
616 }
617 }
618
619 pub fn build_env(self, key: impl Into<String>, value: impl Into<String>) -> Self {
620 Self {
621 build_envs: self
622 .build_envs
623 .into_iter()
624 .chain(std::iter::once((key.into(), value.into())))
625 .collect(),
626 ..self
627 }
628 }
629
630 pub fn env(self, key: impl Into<String>, value: impl Into<String>) -> Self {
631 let mut env = self.env;
632 env.insert(key.into(), value.into());
633 Self { env, ..self }
634 }
635
636 pub fn pin_to_core(self, core: usize) -> Self {
637 Self {
638 pin_to_core: Some(core),
639 ..self
640 }
641 }
642}
643
644impl IntoProcessSpec<'_, HydroDeploy> for Arc<dyn Host> {
645 type ProcessSpec = TrybuildHost;
646 fn into_process_spec(self) -> TrybuildHost {
647 TrybuildHost {
648 host: self,
649 display_name: None,
650 rustflags: None,
651 profile: None,
652 additional_hydro_features: vec![],
653 features: vec![],
654 tracing: None,
655 build_envs: vec![],
656 env: HashMap::new(),
657 pin_to_core: None,
658 name_hint: None,
659 cluster_idx: None,
660 }
661 }
662}
663
664impl<H: Host + 'static> IntoProcessSpec<'_, HydroDeploy> for Arc<H> {
665 type ProcessSpec = TrybuildHost;
666 fn into_process_spec(self) -> TrybuildHost {
667 TrybuildHost {
668 host: self,
669 display_name: None,
670 rustflags: None,
671 profile: None,
672 additional_hydro_features: vec![],
673 features: vec![],
674 tracing: None,
675 build_envs: vec![],
676 env: HashMap::new(),
677 pin_to_core: None,
678 name_hint: None,
679 cluster_idx: None,
680 }
681 }
682}
683
684#[expect(missing_docs, reason = "TODO")]
685#[derive(Clone)]
686pub struct DeployExternal {
687 next_port: Rc<RefCell<usize>>,
688 host: Arc<dyn Host>,
689 underlying: Rc<RefCell<Option<Arc<CustomService>>>>,
690 client_ports: Rc<RefCell<HashMap<String, CustomClientPort>>>,
691 allocated_ports: Rc<RefCell<HashMap<ExternalPortId, String>>>,
692}
693
694impl DeployExternal {
695 pub(crate) fn raw_port(&self, external_port_id: ExternalPortId) -> CustomClientPort {
696 self.client_ports
697 .borrow()
698 .get(
699 self.allocated_ports
700 .borrow()
701 .get(&external_port_id)
702 .unwrap(),
703 )
704 .unwrap()
705 .clone()
706 }
707}
708
709impl<'a> RegisterPort<'a, HydroDeploy> for DeployExternal {
710 fn register(&self, external_port_id: ExternalPortId, port: Self::Port) {
711 assert!(
712 self.allocated_ports
713 .borrow_mut()
714 .insert(external_port_id, port.clone())
715 .is_none_or(|old| old == port)
716 );
717 }
718
719 fn as_bytes_bidi(
720 &self,
721 external_port_id: ExternalPortId,
722 ) -> impl Future<
723 Output = (
724 Pin<Box<dyn Stream<Item = Result<BytesMut, Error>>>>,
725 Pin<Box<dyn Sink<Bytes, Error = Error>>>,
726 ),
727 > + 'a {
728 let port = self.raw_port(external_port_id);
729
730 async move {
731 let (source, sink) = port.connect().await.into_source_sink();
732 (
733 Box::pin(source) as Pin<Box<dyn Stream<Item = Result<BytesMut, Error>>>>,
734 Box::pin(sink) as Pin<Box<dyn Sink<Bytes, Error = Error>>>,
735 )
736 }
737 }
738
739 fn as_bincode_bidi<InT, OutT>(
740 &self,
741 external_port_id: ExternalPortId,
742 ) -> impl Future<
743 Output = (
744 Pin<Box<dyn Stream<Item = OutT>>>,
745 Pin<Box<dyn Sink<InT, Error = Error>>>,
746 ),
747 > + 'a
748 where
749 InT: Serialize + 'static,
750 OutT: DeserializeOwned + 'static,
751 {
752 let port = self.raw_port(external_port_id);
753 async move {
754 let (source, sink) = port.connect().await.into_source_sink();
755 (
756 Box::pin(source.map(|item| bincode::deserialize(&item.unwrap()).unwrap()))
757 as Pin<Box<dyn Stream<Item = OutT>>>,
758 Box::pin(
759 sink.with(|item| async move { Ok(bincode::serialize(&item).unwrap().into()) }),
760 ) as Pin<Box<dyn Sink<InT, Error = Error>>>,
761 )
762 }
763 }
764
765 fn as_bincode_sink<T: Serialize + 'static>(
766 &self,
767 external_port_id: ExternalPortId,
768 ) -> impl Future<Output = Pin<Box<dyn Sink<T, Error = Error>>>> + 'a {
769 let port = self.raw_port(external_port_id);
770 async move {
771 let sink = port.connect().await.into_sink();
772 Box::pin(sink.with(|item| async move { Ok(bincode::serialize(&item).unwrap().into()) }))
773 as Pin<Box<dyn Sink<T, Error = Error>>>
774 }
775 }
776
777 fn as_bincode_source<T: DeserializeOwned + 'static>(
778 &self,
779 external_port_id: ExternalPortId,
780 ) -> impl Future<Output = Pin<Box<dyn Stream<Item = T>>>> + 'a {
781 let port = self.raw_port(external_port_id);
782 async move {
783 let source = port.connect().await.into_source();
784 Box::pin(source.map(|item| bincode::deserialize(&item.unwrap()).unwrap()))
785 as Pin<Box<dyn Stream<Item = T>>>
786 }
787 }
788}
789
790impl Node for DeployExternal {
791 type Port = String;
792 type Meta = SparseSecondaryMap<LocationKey, Vec<TaglessMemberId>>;
794 type InstantiateEnv = Deployment;
795
796 fn next_port(&self) -> Self::Port {
797 let next_port = *self.next_port.borrow();
798 *self.next_port.borrow_mut() += 1;
799
800 format!("port_{}", next_port)
801 }
802
803 fn instantiate(
804 &self,
805 env: &mut Self::InstantiateEnv,
806 _meta: &mut Self::Meta,
807 _graph: DfirGraph,
808 extra_stmts: &[syn::Stmt],
809 sidecars: &[syn::Expr],
810 ) {
811 assert!(extra_stmts.is_empty());
812 assert!(sidecars.is_empty());
813 let service = env.CustomService(self.host.clone(), vec![]);
814 *self.underlying.borrow_mut() = Some(service);
815 }
816
817 fn update_meta(&self, _meta: &Self::Meta) {}
818}
819
820impl ExternalSpec<'_, HydroDeploy> for Arc<dyn Host> {
821 fn build(self, _key: LocationKey, _name_hint: &str) -> DeployExternal {
822 DeployExternal {
823 next_port: Rc::new(RefCell::new(0)),
824 host: self,
825 underlying: Rc::new(RefCell::new(None)),
826 allocated_ports: Rc::new(RefCell::new(HashMap::new())),
827 client_ports: Rc::new(RefCell::new(HashMap::new())),
828 }
829 }
830}
831
832impl<H: Host + 'static> ExternalSpec<'_, HydroDeploy> for Arc<H> {
833 fn build(self, _key: LocationKey, _name_hint: &str) -> DeployExternal {
834 DeployExternal {
835 next_port: Rc::new(RefCell::new(0)),
836 host: self,
837 underlying: Rc::new(RefCell::new(None)),
838 allocated_ports: Rc::new(RefCell::new(HashMap::new())),
839 client_ports: Rc::new(RefCell::new(HashMap::new())),
840 }
841 }
842}
843
844pub(crate) enum CrateOrTrybuild {
845 Crate(RustCrate, Arc<dyn Host>),
846 Trybuild(TrybuildHost),
847}
848
849#[expect(missing_docs, reason = "TODO")]
850#[derive(Clone)]
851pub struct DeployNode {
852 next_port: Rc<RefCell<usize>>,
853 service_spec: Rc<RefCell<Option<CrateOrTrybuild>>>,
854 underlying: Rc<RefCell<Option<Arc<RustCrateService>>>>,
855}
856
857impl DeployCrateWrapper for DeployNode {
858 fn underlying(&self) -> Arc<RustCrateService> {
859 Arc::clone(self.underlying.borrow().as_ref().unwrap())
860 }
861}
862
863impl Node for DeployNode {
864 type Port = String;
865 type Meta = SparseSecondaryMap<LocationKey, Vec<TaglessMemberId>>;
867 type InstantiateEnv = Deployment;
868
869 fn next_port(&self) -> String {
870 let next_port = *self.next_port.borrow();
871 *self.next_port.borrow_mut() += 1;
872
873 format!("port_{}", next_port)
874 }
875
876 fn update_meta(&self, meta: &Self::Meta) {
877 let underlying_node = self.underlying.borrow();
878 underlying_node.as_ref().unwrap().update_meta(HydroMeta {
879 clusters: meta.clone(),
880 cluster_id: None,
881 });
882 }
883
884 fn instantiate(
885 &self,
886 env: &mut Self::InstantiateEnv,
887 _meta: &mut Self::Meta,
888 graph: DfirGraph,
889 extra_stmts: &[syn::Stmt],
890 sidecars: &[syn::Expr],
891 ) {
892 let (service, host) = match self.service_spec.borrow_mut().take().unwrap() {
893 CrateOrTrybuild::Crate(c, host) => (c, host),
894 CrateOrTrybuild::Trybuild(trybuild) => {
895 let linking_mode = if !cfg!(target_os = "windows")
897 && trybuild.host.target_type() == hydro_deploy::HostTargetType::Local
898 && trybuild.rustflags.is_none()
899 {
900 LinkingMode::Dynamic
903 } else {
904 LinkingMode::Static
905 };
906 let (bin_name, config) = create_graph_trybuild(
907 graph,
908 extra_stmts,
909 sidecars,
910 trybuild.name_hint.as_deref(),
911 crate::compile::trybuild::generate::DeployMode::HydroDeploy,
912 linking_mode,
913 );
914 let host = trybuild.host.clone();
915 (
916 create_trybuild_service(
917 trybuild,
918 &config.project_dir,
919 &config.target_dir,
920 config.features.as_deref(),
921 &bin_name,
922 &config.linking_mode,
923 ),
924 host,
925 )
926 }
927 };
928
929 *self.underlying.borrow_mut() = Some(env.add_service(service, host));
930 }
931}
932
933#[expect(missing_docs, reason = "TODO")]
934#[derive(Clone)]
935pub struct DeployClusterNode {
936 underlying: Arc<RustCrateService>,
937}
938
939impl DeployCrateWrapper for DeployClusterNode {
940 fn underlying(&self) -> Arc<RustCrateService> {
941 self.underlying.clone()
942 }
943}
944#[expect(missing_docs, reason = "TODO")]
945#[derive(Clone)]
946pub struct DeployCluster {
947 key: LocationKey,
948 next_port: Rc<RefCell<usize>>,
949 cluster_spec: Rc<RefCell<Option<Vec<CrateOrTrybuild>>>>,
950 members: Rc<RefCell<Vec<DeployClusterNode>>>,
951 name_hint: Option<String>,
952}
953
954impl DeployCluster {
955 #[expect(missing_docs, reason = "TODO")]
956 pub fn members(&self) -> Vec<DeployClusterNode> {
957 self.members.borrow().clone()
958 }
959}
960
961impl Node for DeployCluster {
962 type Port = String;
963 type Meta = SparseSecondaryMap<LocationKey, Vec<TaglessMemberId>>;
965 type InstantiateEnv = Deployment;
966
967 fn next_port(&self) -> String {
968 let next_port = *self.next_port.borrow();
969 *self.next_port.borrow_mut() += 1;
970
971 format!("port_{}", next_port)
972 }
973
974 fn instantiate(
975 &self,
976 env: &mut Self::InstantiateEnv,
977 meta: &mut Self::Meta,
978 graph: DfirGraph,
979 extra_stmts: &[syn::Stmt],
980 sidecars: &[syn::Expr],
981 ) {
982 let has_trybuild = self
983 .cluster_spec
984 .borrow()
985 .as_ref()
986 .unwrap()
987 .iter()
988 .any(|spec| matches!(spec, CrateOrTrybuild::Trybuild { .. }));
989
990 let linking_mode = if !cfg!(target_os = "windows")
992 && self
993 .cluster_spec
994 .borrow()
995 .as_ref()
996 .unwrap()
997 .iter()
998 .all(|spec| match spec {
999 CrateOrTrybuild::Crate(_, _) => true, CrateOrTrybuild::Trybuild(t) => {
1001 t.host.target_type() == hydro_deploy::HostTargetType::Local
1002 && t.rustflags.is_none()
1003 }
1004 }) {
1005 LinkingMode::Dynamic
1007 } else {
1008 LinkingMode::Static
1009 };
1010
1011 let maybe_trybuild = if has_trybuild {
1012 Some(create_graph_trybuild(
1013 graph,
1014 extra_stmts,
1015 sidecars,
1016 self.name_hint.as_deref(),
1017 crate::compile::trybuild::generate::DeployMode::HydroDeploy,
1018 linking_mode,
1019 ))
1020 } else {
1021 None
1022 };
1023
1024 let cluster_nodes = self
1025 .cluster_spec
1026 .borrow_mut()
1027 .take()
1028 .unwrap()
1029 .into_iter()
1030 .map(|spec| {
1031 let (service, host) = match spec {
1032 CrateOrTrybuild::Crate(c, host) => (c, host),
1033 CrateOrTrybuild::Trybuild(trybuild) => {
1034 let (bin_name, config) = maybe_trybuild.as_ref().unwrap();
1035 let host = trybuild.host.clone();
1036 (
1037 create_trybuild_service(
1038 trybuild,
1039 &config.project_dir,
1040 &config.target_dir,
1041 config.features.as_deref(),
1042 bin_name,
1043 &config.linking_mode,
1044 ),
1045 host,
1046 )
1047 }
1048 };
1049
1050 env.add_service(service, host)
1051 })
1052 .collect::<Vec<_>>();
1053 meta.insert(
1054 self.key,
1055 (0..(cluster_nodes.len() as u32))
1056 .map(TaglessMemberId::from_raw_id)
1057 .collect(),
1058 );
1059 *self.members.borrow_mut() = cluster_nodes
1060 .into_iter()
1061 .map(|n| DeployClusterNode { underlying: n })
1062 .collect();
1063 }
1064
1065 fn update_meta(&self, meta: &Self::Meta) {
1066 for (cluster_id, node) in self.members.borrow().iter().enumerate() {
1067 node.underlying.update_meta(HydroMeta {
1068 clusters: meta.clone(),
1069 cluster_id: Some(TaglessMemberId::from_raw_id(cluster_id as u32)),
1070 });
1071 }
1072 }
1073}
1074
1075#[expect(missing_docs, reason = "TODO")]
1076#[derive(Clone)]
1077pub struct DeployProcessSpec(RustCrate, Arc<dyn Host>);
1078
1079impl DeployProcessSpec {
1080 #[expect(missing_docs, reason = "TODO")]
1081 pub fn new(t: RustCrate, host: Arc<dyn Host>) -> Self {
1082 Self(t, host)
1083 }
1084}
1085
1086impl ProcessSpec<'_, HydroDeploy> for DeployProcessSpec {
1087 fn build(self, _key: LocationKey, _name_hint: &str) -> DeployNode {
1088 DeployNode {
1089 next_port: Rc::new(RefCell::new(0)),
1090 service_spec: Rc::new(RefCell::new(Some(CrateOrTrybuild::Crate(self.0, self.1)))),
1091 underlying: Rc::new(RefCell::new(None)),
1092 }
1093 }
1094}
1095
1096impl ProcessSpec<'_, HydroDeploy> for TrybuildHost {
1097 fn build(mut self, key: LocationKey, name_hint: &str) -> DeployNode {
1098 self.name_hint = Some(format!("{} (process {})", name_hint, key));
1099 DeployNode {
1100 next_port: Rc::new(RefCell::new(0)),
1101 service_spec: Rc::new(RefCell::new(Some(CrateOrTrybuild::Trybuild(self)))),
1102 underlying: Rc::new(RefCell::new(None)),
1103 }
1104 }
1105}
1106
1107#[expect(missing_docs, reason = "TODO")]
1108#[derive(Clone)]
1109pub struct DeployClusterSpec(Vec<(RustCrate, Arc<dyn Host>)>);
1110
1111impl DeployClusterSpec {
1112 #[expect(missing_docs, reason = "TODO")]
1113 pub fn new(crates: Vec<(RustCrate, Arc<dyn Host>)>) -> Self {
1114 Self(crates)
1115 }
1116}
1117
1118impl ClusterSpec<'_, HydroDeploy> for DeployClusterSpec {
1119 fn build(self, key: LocationKey, _name_hint: &str) -> DeployCluster {
1120 DeployCluster {
1121 key,
1122 next_port: Rc::new(RefCell::new(0)),
1123 cluster_spec: Rc::new(RefCell::new(Some(
1124 self.0
1125 .into_iter()
1126 .map(|(c, h)| CrateOrTrybuild::Crate(c, h))
1127 .collect(),
1128 ))),
1129 members: Rc::new(RefCell::new(vec![])),
1130 name_hint: None,
1131 }
1132 }
1133}
1134
1135impl<T: Into<TrybuildHost>, I: IntoIterator<Item = T>> ClusterSpec<'_, HydroDeploy> for I {
1136 fn build(self, key: LocationKey, name_hint: &str) -> DeployCluster {
1137 let name_hint = format!("{} (cluster {})", name_hint, key);
1138 DeployCluster {
1139 key,
1140 next_port: Rc::new(RefCell::new(0)),
1141 cluster_spec: Rc::new(RefCell::new(Some(
1142 self.into_iter()
1143 .enumerate()
1144 .map(|(idx, b)| {
1145 let mut b = b.into();
1146 b.name_hint = Some(name_hint.clone());
1147 b.cluster_idx = Some(idx);
1148 CrateOrTrybuild::Trybuild(b)
1149 })
1150 .collect(),
1151 ))),
1152 members: Rc::new(RefCell::new(vec![])),
1153 name_hint: Some(name_hint),
1154 }
1155 }
1156}
1157
1158fn create_trybuild_service(
1159 trybuild: TrybuildHost,
1160 dir: &std::path::Path,
1161 target_dir: &std::path::PathBuf,
1162 features: Option<&[String]>,
1163 bin_name: &str,
1164 linking_mode: &LinkingMode,
1165) -> RustCrate {
1166 let crate_dir = match linking_mode {
1168 LinkingMode::Dynamic => dir.join("dylib-examples"),
1169 LinkingMode::Static => dir.to_path_buf(),
1170 };
1171
1172 let mut ret = RustCrate::new(&crate_dir, dir)
1173 .target_dir(target_dir)
1174 .example(bin_name)
1175 .no_default_features();
1176
1177 ret = ret.set_is_dylib(matches!(linking_mode, LinkingMode::Dynamic));
1178
1179 if let Some(display_name) = trybuild.display_name {
1180 ret = ret.display_name(display_name);
1181 } else if let Some(name_hint) = trybuild.name_hint {
1182 if let Some(cluster_idx) = trybuild.cluster_idx {
1183 ret = ret.display_name(format!("{} / {}", name_hint, cluster_idx));
1184 } else {
1185 ret = ret.display_name(name_hint);
1186 }
1187 }
1188
1189 if let Some(rustflags) = trybuild.rustflags {
1190 ret = ret.rustflags(rustflags);
1191 }
1192
1193 if let Some(profile) = trybuild.profile {
1194 ret = ret.profile(profile);
1195 }
1196
1197 if let Some(tracing) = trybuild.tracing {
1198 ret = ret.tracing(tracing);
1199 }
1200
1201 if let Some(core) = trybuild.pin_to_core {
1202 ret = ret.pin_to_core(core);
1203 }
1204
1205 ret = ret.features(
1206 vec!["hydro___feature_deploy_integration".to_owned()]
1207 .into_iter()
1208 .chain(
1209 trybuild
1210 .additional_hydro_features
1211 .into_iter()
1212 .map(|runtime_feature| {
1213 assert!(
1214 HYDRO_RUNTIME_FEATURES.iter().any(|f| f == &runtime_feature),
1215 "{runtime_feature} is not a valid Hydro runtime feature"
1216 );
1217 format!("hydro___feature_{runtime_feature}")
1218 }),
1219 )
1220 .chain(trybuild.features),
1221 );
1222
1223 for (key, value) in trybuild.build_envs {
1224 ret = ret.build_env(key, value);
1225 }
1226
1227 for (key, value) in trybuild.env {
1228 ret = ret.env(key, value);
1229 }
1230
1231 ret = ret.build_env("STAGELEFT_TRYBUILD_BUILD_STAGED", "1");
1232 ret = ret.config("build.incremental = false");
1233
1234 if let Some(features) = features {
1235 ret = ret.features(features);
1236 }
1237
1238 ret
1239}