turbopack_core/
version.rs1use anyhow::{Context, Result, bail};
2use turbo_rcstr::RcStr;
3use turbo_tasks::{
4 NonLocalValue, OperationValue, ReadRef, ResolvedVc, State, TraitRef, Vc,
5 debug::ValueDebugFormat,
6};
7use turbo_tasks_hash::HashAlgorithm;
8
9use crate::asset::{AssetContent, no_hash_salt};
10
11#[turbo_tasks::value(transparent)]
12pub struct OptionVersionedContent(Option<ResolvedVc<Box<dyn VersionedContent>>>);
13
14#[turbo_tasks::value_trait]
19pub trait VersionedContent {
20 #[turbo_tasks::function]
24 fn content(self: Vc<Self>) -> Vc<AssetContent>;
25
26 #[turbo_tasks::function]
29 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>>;
30
31 #[turbo_tasks::function]
34 async fn update(self: Vc<Self>, from: Vc<Box<dyn Version>>) -> Result<Vc<Update>> {
35 let to = self.version();
39 let from_ref = from.into_trait_ref().await?;
40 let to_ref = to.into_trait_ref().await?;
41
42 if TraitRef::ptr_eq(&from_ref, &to_ref) {
44 return Ok(Update::None.cell());
45 }
46
47 let from_id = from.id();
52 let to_id = to.id();
53 let from_id = from_id.await?;
54 let to_id = to_id.await?;
55 Ok(if *from_id == *to_id {
56 Update::None.cell()
57 } else {
58 Update::Total(TotalUpdate { to: to_ref }).cell()
59 })
60 }
61}
62
63#[turbo_tasks::value]
65pub struct VersionedAssetContent {
66 asset_content: ReadRef<AssetContent>,
71}
72
73#[turbo_tasks::value_impl]
74impl VersionedContent for VersionedAssetContent {
75 #[turbo_tasks::function]
76 fn content(&self) -> Vc<AssetContent> {
77 (*self.asset_content).clone().cell()
78 }
79
80 #[turbo_tasks::function]
81 async fn version(&self) -> Result<Vc<Box<dyn Version>>> {
82 Ok(Vc::upcast(
83 FileHashVersion::compute(&self.asset_content).await?,
84 ))
85 }
86}
87
88#[turbo_tasks::value_impl]
89impl VersionedAssetContent {
90 #[turbo_tasks::function]
91 pub async fn new(asset_content: Vc<AssetContent>) -> Result<Vc<Self>> {
93 let asset_content = asset_content.await?;
94 Ok(Self::cell(VersionedAssetContent { asset_content }))
95 }
96}
97
98impl From<AssetContent> for Vc<VersionedAssetContent> {
99 fn from(asset_content: AssetContent) -> Self {
100 VersionedAssetContent::new(asset_content.cell())
101 }
102}
103
104impl From<AssetContent> for Vc<Box<dyn VersionedContent>> {
105 fn from(asset_content: AssetContent) -> Self {
106 Vc::upcast(VersionedAssetContent::new(asset_content.cell()))
107 }
108}
109
110pub trait VersionedContentExt: Send {
111 fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>>;
112}
113
114impl VersionedContentExt for AssetContent {
115 fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>> {
116 Vc::upcast(VersionedAssetContent::new(self))
117 }
118}
119
120#[turbo_tasks::value_trait]
125pub trait Version {
126 #[turbo_tasks::function]
130 fn id(self: Vc<Self>) -> Vc<RcStr>;
131}
132
133#[turbo_tasks::value_trait]
139pub trait MergeableVersionedContent: VersionedContent {
140 #[turbo_tasks::function]
141 fn get_merger(self: Vc<Self>) -> Vc<Box<dyn VersionedContentMerger>>;
142}
143
144#[turbo_tasks::value_trait]
147pub trait VersionedContentMerger {
148 #[turbo_tasks::function]
149 fn merge(self: Vc<Self>, contents: Vc<VersionedContents>) -> Vc<Box<dyn VersionedContent>>;
150}
151
152#[turbo_tasks::value(transparent)]
153pub struct VersionedContents(Vec<ResolvedVc<Box<dyn VersionedContent>>>);
154
155#[turbo_tasks::value(operation)]
156pub struct NotFoundVersion;
157
158#[turbo_tasks::value_impl]
159impl NotFoundVersion {
160 #[turbo_tasks::function]
161 pub fn new() -> Vc<Self> {
162 NotFoundVersion.cell()
163 }
164}
165
166#[turbo_tasks::value_impl]
167impl Version for NotFoundVersion {
168 #[turbo_tasks::function]
169 fn id(&self) -> Vc<RcStr> {
170 Vc::cell(Default::default())
171 }
172}
173
174#[turbo_tasks::value(serialization = "skip", shared)]
176#[derive(Debug)]
177pub enum Update {
178 Total(TotalUpdate),
181
182 Partial(PartialUpdate),
185
186 Missing,
188
189 None,
191}
192
193#[derive(PartialEq, Eq, Debug, Clone, ValueDebugFormat, NonLocalValue)]
195pub struct TotalUpdate {
196 pub to: TraitRef<Box<dyn Version>>,
201}
202
203#[derive(PartialEq, Eq, Debug, Clone, ValueDebugFormat, NonLocalValue)]
205pub struct PartialUpdate {
206 pub to: TraitRef<Box<dyn Version>>,
209 pub instruction: crate::update_instruction::UpdateInstruction,
212}
213
214#[turbo_tasks::value(operation)]
217#[derive(Clone)]
218pub struct FileHashVersion {
219 hash: RcStr,
220}
221
222impl FileHashVersion {
223 pub async fn compute(asset_content: &AssetContent) -> Result<Vc<Self>> {
225 match asset_content {
226 AssetContent::File(file_vc) => {
227 let hash = file_vc
228 .content_hash(no_hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
229 .owned()
230 .await?
231 .context("file not found")?;
232 Ok(Self::cell(FileHashVersion { hash }))
233 }
234 AssetContent::Redirect(..) => bail!("not a file"),
235 }
236 }
237}
238
239#[turbo_tasks::value_impl]
240impl Version for FileHashVersion {
241 #[turbo_tasks::function]
242 fn id(&self) -> Vc<RcStr> {
243 Vc::cell(self.hash.clone())
244 }
245}
246
247#[derive(Debug, Eq, PartialEq, NonLocalValue, OperationValue)]
250struct VersionRef(
251 #[turbo_tasks(unsafe_ignore)] TraitRef<Box<dyn Version>>,
255);
256
257#[turbo_tasks::value(serialization = "skip", evict = "never")]
258pub struct VersionState {
259 version: State<VersionRef>,
260}
261
262#[turbo_tasks::value_impl]
263impl VersionState {
264 #[turbo_tasks::function]
265 pub fn get(&self) -> Vc<Box<dyn Version>> {
266 TraitRef::cell(self.version.get().0.clone())
267 }
268}
269
270impl VersionState {
271 pub async fn new(version: TraitRef<Box<dyn Version>>) -> Result<Vc<Self>> {
272 Ok(Self::cell(VersionState {
273 version: State::new(VersionRef(version)),
274 }))
275 }
276
277 pub async fn set(self: Vc<Self>, new_version: TraitRef<Box<dyn Version>>) -> Result<()> {
278 let this = self.await?;
279 this.version.set(VersionRef(new_version));
280 Ok(())
281 }
282}