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, trace::TraceRawVcs,
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, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
195pub struct TotalUpdate {
196 #[turbo_tasks(trace_ignore)]
202 pub to: TraitRef<Box<dyn Version>>,
203}
204
205#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
207pub struct PartialUpdate {
208 #[turbo_tasks(trace_ignore)]
211 pub to: TraitRef<Box<dyn Version>>,
212 pub instruction: crate::update_instruction::UpdateInstruction,
215}
216
217#[turbo_tasks::value(operation)]
220#[derive(Clone)]
221pub struct FileHashVersion {
222 hash: RcStr,
223}
224
225impl FileHashVersion {
226 pub async fn compute(asset_content: &AssetContent) -> Result<Vc<Self>> {
228 match asset_content {
229 AssetContent::File(file_vc) => {
230 let hash = file_vc
231 .content_hash(no_hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
232 .owned()
233 .await?
234 .context("file not found")?;
235 Ok(Self::cell(FileHashVersion { hash }))
236 }
237 AssetContent::Redirect(..) => bail!("not a file"),
238 }
239 }
240}
241
242#[turbo_tasks::value_impl]
243impl Version for FileHashVersion {
244 #[turbo_tasks::function]
245 fn id(&self) -> Vc<RcStr> {
246 Vc::cell(self.hash.clone())
247 }
248}
249
250#[derive(Debug, Eq, PartialEq, TraceRawVcs, NonLocalValue, OperationValue)]
253struct VersionRef(
254 #[turbo_tasks(trace_ignore)] TraitRef<Box<dyn Version>>,
258);
259
260#[turbo_tasks::value(serialization = "skip", evict = "never")]
261pub struct VersionState {
262 version: State<VersionRef>,
263}
264
265#[turbo_tasks::value_impl]
266impl VersionState {
267 #[turbo_tasks::function]
268 pub fn get(&self) -> Vc<Box<dyn Version>> {
269 TraitRef::cell(self.version.get().0.clone())
270 }
271}
272
273impl VersionState {
274 pub async fn new(version: TraitRef<Box<dyn Version>>) -> Result<Vc<Self>> {
275 Ok(Self::cell(VersionState {
276 version: State::new(VersionRef(version)),
277 }))
278 }
279
280 pub async fn set(self: Vc<Self>, new_version: TraitRef<Box<dyn Version>>) -> Result<()> {
281 let this = self.await?;
282 this.version.set(VersionRef(new_version));
283 Ok(())
284 }
285}