| | use anyhow::{Result, bail}; |
| | use bincode::{Decode, Encode}; |
| | use next_core::emit_assets; |
| | use rustc_hash::{FxHashMap, FxHashSet}; |
| | use turbo_rcstr::RcStr; |
| | use turbo_tasks::{ |
| | FxIndexSet, NonLocalValue, OperationValue, OperationVc, ResolvedVc, State, TryFlatJoinIterExt, |
| | TryJoinIterExt, ValueDefault, Vc, debug::ValueDebugFormat, trace::TraceRawVcs, |
| | }; |
| | use turbo_tasks_fs::{FileContent, FileSystemPath}; |
| | use turbopack_core::{ |
| | asset::Asset, |
| | output::{ExpandedOutputAssets, OptionOutputAsset, OutputAsset}, |
| | source_map::GenerateSourceMap, |
| | version::OptionVersionedContent, |
| | }; |
| |
|
| | #[derive( |
| | Clone, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Debug, NonLocalValue, Encode, Decode, |
| | )] |
| | struct MapEntry { |
| | assets_operation: OperationVc<ExpandedOutputAssets>, |
| | |
| | path_to_asset: FxHashMap<FileSystemPath, ResolvedVc<Box<dyn OutputAsset>>>, |
| | } |
| |
|
| | |
| | unsafe impl OperationValue for MapEntry {} |
| |
|
| | #[turbo_tasks::value(transparent, operation)] |
| | struct OptionMapEntry(Option<MapEntry>); |
| |
|
| | #[derive( |
| | Clone, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Debug, NonLocalValue, Encode, Decode, |
| | )] |
| | pub struct PathToOutputOperation( |
| | |
| | |
| | |
| | |
| | |
| | |
| | FxHashMap<FileSystemPath, ExpandedOutputAssetsOperationSet>, |
| | ); |
| |
|
| | #[derive( |
| | Clone, |
| | Default, |
| | TraceRawVcs, |
| | PartialEq, |
| | Eq, |
| | ValueDebugFormat, |
| | Debug, |
| | NonLocalValue, |
| | Encode, |
| | Decode, |
| | )] |
| | struct ExpandedOutputAssetsOperationSet( |
| | #[bincode(with = "turbo_bincode::indexset")] FxIndexSet<OperationVc<ExpandedOutputAssets>>, |
| | ); |
| |
|
| | |
| | unsafe impl OperationValue for PathToOutputOperation {} |
| |
|
| | |
| | type OutputOperationToComputeEntry = |
| | FxHashMap<OperationVc<ExpandedOutputAssets>, OperationVc<OptionMapEntry>>; |
| |
|
| | #[turbo_tasks::value] |
| | pub struct VersionedContentMap { |
| | |
| | |
| | map_path_to_op: State<PathToOutputOperation>, |
| | map_op_to_compute_entry: State<OutputOperationToComputeEntry>, |
| | } |
| |
|
| | impl ValueDefault for VersionedContentMap { |
| | fn value_default() -> Vc<Self> { |
| | *VersionedContentMap::new() |
| | } |
| | } |
| |
|
| | impl VersionedContentMap { |
| | |
| | |
| | pub fn new() -> ResolvedVc<Self> { |
| | VersionedContentMap { |
| | map_path_to_op: State::new(PathToOutputOperation(FxHashMap::default())), |
| | map_op_to_compute_entry: State::new(FxHashMap::default()), |
| | } |
| | .resolved_cell() |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl VersionedContentMap { |
| | |
| | |
| | #[turbo_tasks::function] |
| | pub async fn insert_output_assets( |
| | self: ResolvedVc<Self>, |
| | |
| | assets_operation: OperationVc<ExpandedOutputAssets>, |
| | node_root: FileSystemPath, |
| | client_relative_path: FileSystemPath, |
| | client_output_path: FileSystemPath, |
| | ) -> Result<()> { |
| | let this = self.await?; |
| | let compute_entry = compute_entry_operation( |
| | self, |
| | assets_operation, |
| | node_root, |
| | client_relative_path, |
| | client_output_path, |
| | ); |
| | this.map_op_to_compute_entry.update_conditionally(|map| { |
| | map.insert(assets_operation, compute_entry) != Some(compute_entry) |
| | }); |
| | Ok(()) |
| | } |
| |
|
| | |
| | |
| | #[turbo_tasks::function] |
| | async fn compute_entry( |
| | &self, |
| | assets_operation: OperationVc<ExpandedOutputAssets>, |
| | node_root: FileSystemPath, |
| | client_relative_path: FileSystemPath, |
| | client_output_path: FileSystemPath, |
| | ) -> Result<Vc<OptionMapEntry>> { |
| | let entries = get_entries(assets_operation) |
| | .read_strongly_consistent() |
| | .await |
| | |
| | .ok(); |
| |
|
| | self.map_path_to_op.update_conditionally(|map| { |
| | let mut changed = false; |
| |
|
| | |
| | let mut stale_assets = map.0.keys().cloned().collect::<FxHashSet<_>>(); |
| |
|
| | for (k, _) in entries.iter().flatten() { |
| | let res = map |
| | .0 |
| | .entry(k.clone()) |
| | .or_default() |
| | .0 |
| | .insert(assets_operation); |
| | stale_assets.remove(k); |
| | changed = changed || res; |
| | } |
| |
|
| | |
| | for k in &stale_assets { |
| | let res = map |
| | .0 |
| | .get_mut(k) |
| | |
| | .unwrap() |
| | .0 |
| | .swap_remove(&assets_operation); |
| | changed = changed || res |
| | } |
| | changed |
| | }); |
| |
|
| | |
| | emit_assets( |
| | assets_operation.connect(), |
| | node_root, |
| | client_relative_path, |
| | client_output_path, |
| | ) |
| | .as_side_effect() |
| | .await?; |
| | let map_entry = Vc::cell(Some(MapEntry { |
| | assets_operation, |
| | path_to_asset: entries.iter().flatten().cloned().collect(), |
| | })); |
| | Ok(map_entry) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn get(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<OptionVersionedContent>> { |
| | Ok(Vc::cell(match *self.get_asset(path).await? { |
| | Some(asset) => Some(asset.versioned_content().to_resolved().await?), |
| | None => None, |
| | })) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn get_source_map( |
| | self: Vc<Self>, |
| | path: FileSystemPath, |
| | section: Option<RcStr>, |
| | ) -> Result<Vc<FileContent>> { |
| | let Some(asset) = &*self.get_asset(path.clone()).await? else { |
| | return Ok(FileContent::NotFound.cell()); |
| | }; |
| |
|
| | if let Some(generate_source_map) = |
| | ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(*asset) |
| | { |
| | Ok(if let Some(section) = section { |
| | generate_source_map.by_section(section) |
| | } else { |
| | generate_source_map.generate_source_map() |
| | }) |
| | } else { |
| | let path = path.value_to_string().await?; |
| | bail!("no source map for path {}", path); |
| | } |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn get_asset(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<OptionOutputAsset>> { |
| | let result = self.raw_get(path.clone()).await?; |
| | if let Some(MapEntry { |
| | assets_operation: _, |
| | path_to_asset, |
| | }) = &*result |
| | && let Some(&asset) = path_to_asset.get(&path) |
| | { |
| | return Ok(Vc::cell(Some(asset))); |
| | } |
| |
|
| | Ok(Vc::cell(None)) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn keys_in_path(&self, root: FileSystemPath) -> Result<Vc<Vec<RcStr>>> { |
| | let keys = { |
| | let map = &self.map_path_to_op.get().0; |
| | map.keys().cloned().collect::<Vec<_>>() |
| | }; |
| | let keys = keys |
| | .into_iter() |
| | .map(|path| { |
| | let root = root.clone(); |
| | async move { Ok(root.get_path_to(&path).map(RcStr::from)) } |
| | }) |
| | .try_flat_join() |
| | .await?; |
| | Ok(Vc::cell(keys)) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | fn raw_get(&self, path: FileSystemPath) -> Vc<OptionMapEntry> { |
| | let assets = { |
| | let map = &self.map_path_to_op.get().0; |
| | map.get(&path).and_then(|m| m.0.iter().next().copied()) |
| | }; |
| | let Some(assets) = assets else { |
| | return Vc::cell(None); |
| | }; |
| | |
| | let _ = assets.connect(); |
| |
|
| | let compute_entry = { |
| | let map = self.map_op_to_compute_entry.get(); |
| | map.get(&assets).copied() |
| | }; |
| | let Some(compute_entry) = compute_entry else { |
| | return Vc::cell(None); |
| | }; |
| | compute_entry.connect() |
| | } |
| | } |
| |
|
| | type GetEntriesResultT = Vec<(FileSystemPath, ResolvedVc<Box<dyn OutputAsset>>)>; |
| |
|
| | #[turbo_tasks::value(transparent)] |
| | struct GetEntriesResult(GetEntriesResultT); |
| |
|
| | #[turbo_tasks::function(operation)] |
| | async fn get_entries(assets: OperationVc<ExpandedOutputAssets>) -> Result<Vc<GetEntriesResult>> { |
| | let assets_ref = assets.connect().await?; |
| | let entries = assets_ref |
| | .iter() |
| | .map(|&asset| async move { |
| | let path = asset.path().owned().await?; |
| | Ok((path, asset)) |
| | }) |
| | .try_join() |
| | .await?; |
| | Ok(Vc::cell(entries)) |
| | } |
| |
|
| | #[turbo_tasks::function(operation)] |
| | fn compute_entry_operation( |
| | map: ResolvedVc<VersionedContentMap>, |
| | assets_operation: OperationVc<ExpandedOutputAssets>, |
| | node_root: FileSystemPath, |
| | client_relative_path: FileSystemPath, |
| | client_output_path: FileSystemPath, |
| | ) -> Vc<OptionMapEntry> { |
| | map.compute_entry( |
| | assets_operation, |
| | node_root, |
| | client_relative_path, |
| | client_output_path, |
| | ) |
| | } |
| |
|