omena_transform_passes/runtime/
incremental.rs1use omena_incremental::{
8 IncrementalGraphInputV0, IncrementalNodeInputV0, IncrementalRevisionV0,
9 OmenaIncrementalDatabaseV0,
10};
11use omena_parser::StyleDialect;
12use omena_transform_cst::TransformPassKind;
13
14use crate::{
15 TransformExecutionContextV0, TransformExecutionSummaryV0,
16 TransformIncrementalExecutionSummaryV0,
17 execute_transform_passes_on_source_with_dialect_and_context, plan_transform_passes,
18};
19
20pub fn execute_transform_passes_incremental_with_database(
21 source: &str,
22 dialect: StyleDialect,
23 requested: &[TransformPassKind],
24 context: &TransformExecutionContextV0,
25 incremental_database: &mut OmenaIncrementalDatabaseV0,
26 previous_execution: Option<&TransformExecutionSummaryV0>,
27 revision: IncrementalRevisionV0,
28) -> TransformIncrementalExecutionSummaryV0 {
29 let incremental_input =
30 transform_pass_incremental_graph_input(source, dialect, requested, context, revision);
31 let update = incremental_database.plan_and_upsert_graph_input(&incremental_input);
32 let reused_previous_execution =
33 update.incremental_plan.dirty_node_count == 0 && previous_execution.is_some();
34 let execution = match (reused_previous_execution, previous_execution) {
35 (true, Some(previous_execution)) => previous_execution.clone(),
36 _ => execute_transform_passes_on_source_with_dialect_and_context(
37 source, dialect, requested, context,
38 ),
39 };
40
41 TransformIncrementalExecutionSummaryV0 {
42 schema_version: "0",
43 product: "omena-transform-passes.incremental-execution",
44 incremental_engine: "omena-incremental",
45 query_model: "persistentSalsaDatabase+transformPassDependencyGraph",
46 reuse_policy: "reuse previous transform execution when the omena-incremental plan is clean",
47 reused_previous_execution,
48 incremental_plan: update.incremental_plan,
49 next_snapshot: update.next_snapshot,
50 execution,
51 ready_surfaces: vec![
52 "transformSalsaQueries",
53 "transformPassIncrementalGraph",
54 "cleanTransformExecutionReuse",
55 ],
56 }
57}
58
59pub fn transform_pass_incremental_graph_input(
60 source: &str,
61 dialect: StyleDialect,
62 requested: &[TransformPassKind],
63 context: &TransformExecutionContextV0,
64 revision: IncrementalRevisionV0,
65) -> IncrementalGraphInputV0 {
66 let pass_plan = plan_transform_passes(requested);
67 let dialect_label = transform_style_dialect_label(dialect);
68 let context_digest = transform_execution_context_digest(context);
69 let ordered_pass_ids = pass_plan.ordered_pass_ids.join("|");
70 let mut nodes = vec![
71 IncrementalNodeInputV0 {
72 id: "transform:source".to_string(),
73 digest: stable_transform_digest(&["source", dialect_label, source]),
74 dependency_ids: Vec::new(),
75 },
76 IncrementalNodeInputV0 {
77 id: "transform:context".to_string(),
78 digest: stable_transform_digest(&["context", context_digest.as_str()]),
79 dependency_ids: Vec::new(),
80 },
81 IncrementalNodeInputV0 {
82 id: "transform:plan".to_string(),
83 digest: stable_transform_digest(&["plan", ordered_pass_ids.as_str()]),
84 dependency_ids: Vec::new(),
85 },
86 ];
87
88 let mut previous_pass_node_id = None;
89 for pass_id in pass_plan.ordered_pass_ids {
90 let node_id = format!("transform:pass:{pass_id}");
91 let mut dependency_ids = vec![
92 "transform:source".to_string(),
93 "transform:context".to_string(),
94 "transform:plan".to_string(),
95 ];
96 if let Some(previous_pass_node_id) = previous_pass_node_id {
97 dependency_ids.push(previous_pass_node_id);
98 }
99
100 nodes.push(IncrementalNodeInputV0 {
101 id: node_id.clone(),
102 digest: stable_transform_digest(&["pass", pass_id]),
103 dependency_ids,
104 });
105 previous_pass_node_id = Some(node_id);
106 }
107
108 let mut execution_dependency_ids = vec![
109 "transform:source".to_string(),
110 "transform:context".to_string(),
111 "transform:plan".to_string(),
112 ];
113 if let Some(previous_pass_node_id) = previous_pass_node_id {
114 execution_dependency_ids.push(previous_pass_node_id);
115 }
116 nodes.push(IncrementalNodeInputV0 {
117 id: "transform:execution".to_string(),
118 digest: stable_transform_digest(&["execution", ordered_pass_ids.as_str()]),
119 dependency_ids: execution_dependency_ids,
120 });
121
122 IncrementalGraphInputV0 { revision, nodes }
123}
124
125fn transform_style_dialect_label(dialect: StyleDialect) -> &'static str {
126 match dialect {
127 StyleDialect::Css => "css",
128 StyleDialect::Scss => "scss",
129 StyleDialect::Sass => "sass",
130 StyleDialect::Less => "less",
131 }
132}
133
134fn transform_execution_context_digest(context: &TransformExecutionContextV0) -> String {
135 let serialized = match serde_json::to_string(context) {
136 Ok(serialized) => serialized,
137 Err(error) => format!("serialization-error:{error}"),
138 };
139 stable_transform_digest(&["transform-context", serialized.as_str()])
140}
141
142fn stable_transform_digest(parts: &[&str]) -> String {
143 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
144 for part in parts {
145 for byte in part.as_bytes() {
146 hash ^= u64::from(*byte);
147 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
148 }
149 hash ^= 0xff;
150 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
151 }
152 format!("fnv1a64:{hash:016x}")
153}