@@ -10,103 +10,126 @@ use github_actions_models::workflow::Trigger;
10
10
use std:: ops:: Deref ;
11
11
use std:: sync:: LazyLock ;
12
12
13
+ /// The value type that controls the activation/deactivation of caching
13
14
#[ derive( PartialEq ) ]
14
- enum ControlValue {
15
+ enum CacheControlValue {
15
16
Boolean ,
16
17
String ,
17
18
}
18
19
19
- enum CacheControl {
20
+ /// The input that controls the behaviour of a configurable caching Action
21
+ enum CacheControlInput {
22
+ /// Opt-in means that cache becomes **enabled** when the control value matches.
20
23
OptIn ( & ' static str ) ,
24
+ /// Opt-out means that cache becomes **disabled** when the control value matches.
21
25
OptOut ( & ' static str ) ,
22
26
}
23
27
24
28
/// The general schema for a cache-aware actions
25
- struct CacheAwareAction < ' w > {
29
+ struct ControllableCacheAction < ' w > {
26
30
/// The owner/repo part within the Action full coordinate
27
31
uses : Uses < ' w > ,
28
32
/// The input that controls caching behavior
29
- cache_control : CacheControl ,
33
+ control_input : CacheControlInput ,
30
34
/// The type of value used to opt-in/opt-out (Boolean, String)
31
- control_value : ControlValue ,
35
+ control_value : CacheControlValue ,
32
36
/// Whether this Action adopts caching as the default behavior
33
37
caching_by_default : bool ,
34
38
}
35
39
40
+ enum CacheAwareAction < ' w > {
41
+ Configurable ( ControllableCacheAction < ' w > ) ,
42
+ NotConfigurable ( Uses < ' w > ) ,
43
+ }
44
+
45
+ impl CacheAwareAction < ' _ > {
46
+ fn uses ( & self ) -> Uses {
47
+ match self {
48
+ CacheAwareAction :: Configurable ( inner) => inner. uses ,
49
+ CacheAwareAction :: NotConfigurable ( inner) => * inner,
50
+ }
51
+ }
52
+ }
53
+
36
54
/// The list of know cache-aware actions
37
55
/// In the future we can easily retrieve this list from the static API,
38
56
/// since it should be easily serializable
39
57
static KNOWN_CACHE_AWARE_ACTIONS : LazyLock < Vec < CacheAwareAction > > = LazyLock :: new ( || {
40
58
vec ! [
41
59
// https://github.com/actions/cache/blob/main/action.yml
42
- CacheAwareAction {
60
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
43
61
uses: Uses :: from_step( "actions/cache" ) . unwrap( ) ,
44
- cache_control : CacheControl :: OptOut ( "lookup-only" ) ,
45
- control_value: ControlValue :: Boolean ,
62
+ control_input : CacheControlInput :: OptOut ( "lookup-only" ) ,
63
+ control_value: CacheControlValue :: Boolean ,
46
64
caching_by_default: true ,
47
- } ,
48
- CacheAwareAction {
65
+ } ) ,
66
+ // https://github.com/actions/setup-java/blob/main/action.yml
67
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
49
68
uses: Uses :: from_step( "actions/setup-java" ) . unwrap( ) ,
50
- cache_control : CacheControl :: OptIn ( "cache" ) ,
51
- control_value: ControlValue :: String ,
69
+ control_input : CacheControlInput :: OptIn ( "cache" ) ,
70
+ control_value: CacheControlValue :: String ,
52
71
caching_by_default: false ,
53
- } ,
72
+ } ) ,
54
73
// https://github.com/actions/setup-go/blob/main/action.yml
55
- CacheAwareAction {
74
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
56
75
uses: Uses :: from_step( "actions/setup-go" ) . unwrap( ) ,
57
- cache_control : CacheControl :: OptIn ( "cache" ) ,
58
- control_value: ControlValue :: Boolean ,
76
+ control_input : CacheControlInput :: OptIn ( "cache" ) ,
77
+ control_value: CacheControlValue :: Boolean ,
59
78
caching_by_default: true ,
60
- } ,
79
+ } ) ,
61
80
// https://github.com/actions/setup-node/blob/main/action.yml
62
- CacheAwareAction {
81
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
63
82
uses: Uses :: from_step( "actions/setup-node" ) . unwrap( ) ,
64
- cache_control : CacheControl :: OptIn ( "cache" ) ,
65
- control_value: ControlValue :: String ,
83
+ control_input : CacheControlInput :: OptIn ( "cache" ) ,
84
+ control_value: CacheControlValue :: String ,
66
85
caching_by_default: false ,
67
- } ,
86
+ } ) ,
68
87
// https://github.com/actions/setup-python/blob/main/action.yml
69
- CacheAwareAction {
88
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
70
89
uses: Uses :: from_step( "actions/setup-python" ) . unwrap( ) ,
71
- cache_control : CacheControl :: OptIn ( "cache" ) ,
72
- control_value: ControlValue :: String ,
90
+ control_input : CacheControlInput :: OptIn ( "cache" ) ,
91
+ control_value: CacheControlValue :: String ,
73
92
caching_by_default: false ,
74
- } ,
93
+ } ) ,
75
94
// https://github.com/actions/setup-dotnet/blob/main/action.yml
76
- CacheAwareAction {
95
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
77
96
uses: Uses :: from_step( "actions/setup-dotnet" ) . unwrap( ) ,
78
- cache_control : CacheControl :: OptIn ( "cache" ) ,
79
- control_value: ControlValue :: Boolean ,
97
+ control_input : CacheControlInput :: OptIn ( "cache" ) ,
98
+ control_value: CacheControlValue :: Boolean ,
80
99
caching_by_default: false ,
81
- } ,
100
+ } ) ,
82
101
// https://github.com/astral-sh/setup-uv/blob/main/action.yml
83
- CacheAwareAction {
102
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
84
103
uses: Uses :: from_step( "astral-sh/setup-uv" ) . unwrap( ) ,
85
- cache_control : CacheControl :: OptOut ( "enable-cache" ) ,
86
- control_value: ControlValue :: String ,
104
+ control_input : CacheControlInput :: OptOut ( "enable-cache" ) ,
105
+ control_value: CacheControlValue :: String ,
87
106
caching_by_default: true ,
88
- } ,
107
+ } ) ,
89
108
// https://github.com/Swatinem/rust-cache/blob/master/action.yml
90
- CacheAwareAction {
109
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
91
110
uses: Uses :: from_step( "Swatinem/rust-cache" ) . unwrap( ) ,
92
- cache_control : CacheControl :: OptOut ( "lookup-only" ) ,
93
- control_value: ControlValue :: Boolean ,
111
+ control_input : CacheControlInput :: OptOut ( "lookup-only" ) ,
112
+ control_value: CacheControlValue :: Boolean ,
94
113
caching_by_default: true ,
95
- } ,
114
+ } ) ,
96
115
// https://github.com/ruby/setup-ruby/blob/master/action.yml
97
- CacheAwareAction {
116
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
98
117
uses: Uses :: from_step( "ruby/setup-ruby" ) . unwrap( ) ,
99
- cache_control : CacheControl :: OptIn ( "bundler-cache" ) ,
100
- control_value: ControlValue :: Boolean ,
118
+ control_input : CacheControlInput :: OptIn ( "bundler-cache" ) ,
119
+ control_value: CacheControlValue :: Boolean ,
101
120
caching_by_default: false ,
102
- } ,
121
+ } ) ,
103
122
// https://github.com/PyO3/maturin-action/blob/main/action.yml
104
- CacheAwareAction {
123
+ CacheAwareAction :: Configurable ( ControllableCacheAction {
105
124
uses: Uses :: from_step( "PyO3/maturin-action" ) . unwrap( ) ,
106
- cache_control : CacheControl :: OptIn ( "sccache" ) ,
107
- control_value: ControlValue :: Boolean ,
125
+ control_input : CacheControlInput :: OptIn ( "sccache" ) ,
126
+ control_value: CacheControlValue :: Boolean ,
108
127
caching_by_default: false ,
109
- } ,
128
+ } ) ,
129
+ // https://github.com/Mozilla-Actions/sccache-action/blob/main/action.yml
130
+ CacheAwareAction :: NotConfigurable (
131
+ Uses :: from_step( "Mozilla-Actions/sccache-action" ) . unwrap( ) ,
132
+ ) ,
110
133
]
111
134
} ) ;
112
135
@@ -145,6 +168,7 @@ enum CacheUsage {
145
168
ConditionalOptIn ,
146
169
DirectOptIn ,
147
170
DefaultActionBehaviour ,
171
+ AlwaysCache ,
148
172
}
149
173
150
174
enum PublishingArtifactsScenario < ' w > {
@@ -204,7 +228,7 @@ impl CachePoisoning {
204
228
) )
205
229
}
206
230
207
- fn evaluate_default_action_behaviour ( action : & CacheAwareAction ) -> Option < CacheUsage > {
231
+ fn evaluate_default_action_behaviour ( action : & ControllableCacheAction ) -> Option < CacheUsage > {
208
232
if action. caching_by_default {
209
233
Some ( CacheUsage :: DefaultActionBehaviour )
210
234
} else {
@@ -215,20 +239,20 @@ impl CachePoisoning {
215
239
fn evaluate_user_defined_opt_in (
216
240
cache_control_input : & str ,
217
241
env : & Env ,
218
- action : & CacheAwareAction ,
242
+ action : & ControllableCacheAction ,
219
243
) -> Option < CacheUsage > {
220
244
match env. get ( cache_control_input) {
221
245
None => None ,
222
246
Some ( value) => match value. to_string ( ) . as_str ( ) {
223
- "true" if matches ! ( action. control_value, ControlValue :: Boolean ) => {
247
+ "true" if matches ! ( action. control_value, CacheControlValue :: Boolean ) => {
224
248
Some ( CacheUsage :: DirectOptIn )
225
249
}
226
- "false" if matches ! ( action. control_value, ControlValue :: Boolean ) => {
250
+ "false" if matches ! ( action. control_value, CacheControlValue :: Boolean ) => {
227
251
// Explicitly opts out from caching
228
252
None
229
253
}
230
254
other => match ExplicitExpr :: from_curly ( other) {
231
- None if matches ! ( action. control_value, ControlValue :: String ) => {
255
+ None if matches ! ( action. control_value, CacheControlValue :: String ) => {
232
256
Some ( CacheUsage :: DirectOptIn )
233
257
}
234
258
None => None ,
@@ -238,44 +262,36 @@ impl CachePoisoning {
238
262
}
239
263
}
240
264
241
- fn evaluate_cache_usage ( & self , target_step : & str , env : & Env ) -> Option < CacheUsage > {
242
- let known_action = KNOWN_CACHE_AWARE_ACTIONS . iter ( ) . find ( |action| {
243
- let Uses :: Repository ( well_known_uses) = action. uses else {
244
- return false ;
245
- } ;
246
-
247
- let Some ( Uses :: Repository ( target_uses) ) = Uses :: from_step ( target_step) else {
248
- return false ;
249
- } ;
250
-
251
- target_uses. matches ( well_known_uses)
252
- } ) ?;
253
-
254
- let cache_control_input = env. keys ( ) . find ( |k| match known_action. cache_control {
255
- CacheControl :: OptIn ( inner) => * k == inner,
256
- CacheControl :: OptOut ( inner) => * k == inner,
265
+ fn usage_of_controllable_caching (
266
+ & self ,
267
+ env : & Env ,
268
+ controllable : & ControllableCacheAction ,
269
+ ) -> Option < CacheUsage > {
270
+ let cache_control_input = env. keys ( ) . find ( |k| match controllable. control_input {
271
+ CacheControlInput :: OptIn ( inner) => * k == inner,
272
+ CacheControlInput :: OptOut ( inner) => * k == inner,
257
273
} ) ;
258
274
259
275
match cache_control_input {
260
276
// when not using the specific Action input to control caching behaviour,
261
277
// we evaluate whether it uses caching by default
262
- None => CachePoisoning :: evaluate_default_action_behaviour ( known_action ) ,
278
+ None => CachePoisoning :: evaluate_default_action_behaviour ( controllable ) ,
263
279
264
280
// otherwise, we infer from the value assigned to the cache control input
265
281
Some ( key) => {
266
282
// first, we extract the value assigned to that input
267
283
let declared_usage =
268
- CachePoisoning :: evaluate_user_defined_opt_in ( key, env, known_action ) ;
284
+ CachePoisoning :: evaluate_user_defined_opt_in ( key, env, controllable ) ;
269
285
270
286
// we now evaluate the extracted value against the opt-in semantics
271
287
match & declared_usage {
272
288
Some ( CacheUsage :: DirectOptIn ) => {
273
- match known_action . cache_control {
289
+ match controllable . control_input {
274
290
// in this case, we just follow the opt-in
275
- CacheControl :: OptIn ( _) => declared_usage,
291
+ CacheControlInput :: OptIn ( _) => declared_usage,
276
292
// otherwise, the user opted for disabling the cache
277
293
// hence we don't return a CacheUsage
278
- CacheControl :: OptOut ( _) => None ,
294
+ CacheControlInput :: OptOut ( _) => None ,
279
295
}
280
296
}
281
297
// Because we can't evaluate expressions, there is nothing to do
@@ -286,6 +302,27 @@ impl CachePoisoning {
286
302
}
287
303
}
288
304
305
+ fn evaluate_cache_usage ( & self , target_step : & str , env : & Env ) -> Option < CacheUsage > {
306
+ let known_action = KNOWN_CACHE_AWARE_ACTIONS . iter ( ) . find ( |action| {
307
+ let Uses :: Repository ( well_known_uses) = action. uses ( ) else {
308
+ return false ;
309
+ } ;
310
+
311
+ let Some ( Uses :: Repository ( target_uses) ) = Uses :: from_step ( target_step) else {
312
+ return false ;
313
+ } ;
314
+
315
+ target_uses. matches ( well_known_uses)
316
+ } ) ?;
317
+
318
+ match known_action {
319
+ CacheAwareAction :: Configurable ( action) => {
320
+ self . usage_of_controllable_caching ( env, action)
321
+ }
322
+ CacheAwareAction :: NotConfigurable ( _) => Some ( CacheUsage :: AlwaysCache ) ,
323
+ }
324
+ }
325
+
289
326
fn uses_cache_aware_step < ' w > (
290
327
& self ,
291
328
step : & Step < ' w > ,
@@ -298,6 +335,7 @@ impl CachePoisoning {
298
335
let cache_usage = self . evaluate_cache_usage ( uses, with) ?;
299
336
300
337
let ( yaml_key, annotation) = match cache_usage {
338
+ CacheUsage :: AlwaysCache => ( "uses" , "caching always restored here" ) ,
301
339
CacheUsage :: DefaultActionBehaviour => ( "uses" , "cache enabled by default here" ) ,
302
340
CacheUsage :: DirectOptIn => ( "with" , "opt-in for caching here" ) ,
303
341
CacheUsage :: ConditionalOptIn => ( "with" , "opt-in for caching might happen here" ) ,
0 commit comments