1use anyhow::Result;
2use tracing::{Instrument, Level, Span};
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5 FxIndexSet, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
6 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
7};
8use turbo_tasks_fs::{FileSystemPath, rebase};
9use turbopack_core::{
10 asset::Asset,
11 output::{OutputAsset, OutputAssets},
12};
13
14#[turbo_tasks::function]
20pub async fn emit_all_assets(
21 assets: Vc<OutputAssets>,
22 node_root: FileSystemPath,
23 client_relative_path: FileSystemPath,
24 client_output_path: FileSystemPath,
25) -> Result<()> {
26 emit_assets(
27 all_assets_from_entries(assets),
28 node_root,
29 client_relative_path,
30 client_output_path,
31 )
32 .as_side_effect()
33 .await?;
34 Ok(())
35}
36
37#[turbo_tasks::function]
43pub async fn emit_assets(
44 assets: Vc<OutputAssets>,
45 node_root: FileSystemPath,
46 client_relative_path: FileSystemPath,
47 client_output_path: FileSystemPath,
48) -> Result<()> {
49 let _: Vec<()> = assets
50 .await?
51 .iter()
52 .copied()
53 .map(|asset| {
54 let node_root = node_root.clone();
55 let client_relative_path = client_relative_path.clone();
56 let client_output_path = client_output_path.clone();
57
58 async move {
59 let path = asset.path().owned().await?;
60 let span = tracing::info_span!("emit asset", name = %path.value_to_string().await?);
61 async move {
62 Ok(if path.is_inside_ref(&node_root) {
63 Some(emit(*asset).as_side_effect().await?)
64 } else if path.is_inside_ref(&client_relative_path) {
65 Some(
69 emit_rebase(*asset, client_relative_path, client_output_path)
70 .as_side_effect()
71 .await?,
72 )
73 } else {
74 None
75 })
76 }
77 .instrument(span)
78 .await
79 }
80 })
81 .try_flat_join()
82 .await?;
83 Ok(())
84}
85
86#[turbo_tasks::function]
87async fn emit(asset: Vc<Box<dyn OutputAsset>>) -> Result<()> {
88 asset
89 .content()
90 .resolve()
91 .await?
92 .write(asset.path().owned().await?)
93 .as_side_effect()
94 .await?;
95 Ok(())
96}
97
98#[turbo_tasks::function]
99async fn emit_rebase(
100 asset: Vc<Box<dyn OutputAsset>>,
101 from: FileSystemPath,
102 to: FileSystemPath,
103) -> Result<()> {
104 let path = rebase(asset.path().owned().await?, from, to)
105 .owned()
106 .await?;
107 let content = asset.content();
108 content
109 .resolve()
110 .await?
111 .write(path)
112 .as_side_effect()
113 .await?;
114 Ok(())
115}
116
117struct OutputAssetVisit {
118 emit_spans: bool,
119}
120impl Visit<(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>)> for OutputAssetVisit {
121 type Edge = (ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>);
122 type EdgesIntoIter = Vec<Self::Edge>;
123 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
124
125 fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<Self::Edge> {
126 VisitControlFlow::Continue(edge)
127 }
128
129 fn edges(
130 &mut self,
131 node: &(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>),
132 ) -> Self::EdgesFuture {
133 get_referenced_assets(self.emit_spans, node.0)
134 }
135
136 fn span(
137 &mut self,
138 node: &(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>),
139 ) -> tracing::Span {
140 if let Some(ident) = &node.1 {
141 tracing::info_span!("asset", name = display(ident))
142 } else {
143 Span::current()
144 }
145 }
146}
147
148#[turbo_tasks::function]
151pub async fn all_assets_from_entries(entries: Vc<OutputAssets>) -> Result<Vc<OutputAssets>> {
152 let emit_spans = tracing::enabled!(Level::INFO);
153 Ok(Vc::cell(
154 AdjacencyMap::new()
155 .skip_duplicates()
156 .visit(
157 entries
158 .await?
159 .iter()
160 .map(async |asset| {
161 Ok((
162 ResolvedVc::upcast(*asset),
163 if emit_spans {
164 Some(asset.path().to_string().await?)
165 } else {
166 None
167 },
168 ))
169 })
170 .try_join()
171 .await?,
172 OutputAssetVisit { emit_spans },
173 )
174 .await
175 .completed()?
176 .into_inner()
177 .into_postorder_topological()
178 .map(|(asset, _)| asset)
179 .collect::<FxIndexSet<_>>()
180 .into_iter()
181 .collect(),
182 ))
183}
184
185async fn get_referenced_assets(
187 emit_spans: bool,
188 asset: ResolvedVc<Box<dyn OutputAsset>>,
189) -> Result<Vec<(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>)>> {
190 asset
191 .references()
192 .await?
193 .iter()
194 .map(async |asset| {
195 Ok((
196 *asset,
197 if emit_spans {
198 Some(asset.path().to_string().await?)
199 } else {
200 None
201 },
202 ))
203 })
204 .try_join()
205 .await
206}