Skip to content

Commit db0ab2e

Browse files
authored
Add lazy evaluation setting (#3083)
1 parent 9ba92d6 commit db0ab2e

25 files changed

Lines changed: 531 additions & 72 deletions

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ foo:
10161016
| `export` | boolean | `false` | Export all variables as environment variables. |
10171017
| `fallback` | boolean | `false` | Search `justfile` in parent directory if the first recipe on the command line is not found. |
10181018
| `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. |
1019+
| `lazy`<sup>master</sup> | boolean | `false` | Don't evaluate unused variables. |
10191020
| `positional-arguments` | boolean | `false` | Pass positional arguments. |
10201021
| `quiet` | boolean | `false` | Disable echoing recipe lines before executing. |
10211022
| `script-interpreter`<sup>1.33.0</sup> | `[COMMAND, ARGS…]` | `['sh', '-eu']` | Set command used to invoke recipes with empty `[script]` attribute. |
@@ -1166,6 +1167,31 @@ hello
11661167
goodbye
11671168
```
11681169

1170+
#### Lazy
1171+
1172+
The `lazy` setting<sup>master</sup>, currently unstable, causes the evaluator
1173+
to skip evaluating unused variables. This can be beneficial when a `justfile`
1174+
contains variables that are expensive to evaluate but only sometimes used.
1175+
1176+
In the following `justfile`, `token` will be skipped when only invoking `bar`:
1177+
1178+
```just
1179+
set lazy
1180+
set unstable
1181+
1182+
token := `expensive-script-to-get-credentials`
1183+
1184+
foo:
1185+
curl -H "Authorization: Bearer {{ token }}" https://example.com/foo
1186+
1187+
bar:
1188+
cargo test
1189+
```
1190+
1191+
Because `just` cannot determine when exported variables are used, assignments
1192+
with `export` and assignments in a module with `set export` will always be
1193+
evaluated.
1194+
11691195
#### Positional Arguments
11701196

11711197
If `positional-arguments` is `true`, recipe arguments will be passed as

src/binding.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub(crate) struct Binding<'src, V = String> {
88
pub(crate) file_depth: u32,
99
pub(crate) name: Name<'src>,
1010
#[serde(skip)]
11+
pub(crate) number: Number,
12+
#[serde(skip)]
1113
pub(crate) prelude: bool,
1214
pub(crate) private: bool,
1315
pub(crate) value: V,

src/compiler.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ impl Compiler {
1010
) -> RunResult<'src, Compilation<'src>> {
1111
let mut asts = HashMap::<PathBuf, Ast>::new();
1212
let mut loaded = Vec::new();
13+
let mut numerator = Numerator::new();
1314
let mut paths = HashMap::<PathBuf, PathBuf>::new();
1415
let mut stack = Vec::new();
1516
stack.push(Source::root(root));
@@ -21,7 +22,7 @@ impl Compiler {
2122

2223
let (relative, src) = loader.load(root, &current.path)?;
2324
loaded.push(relative.into());
24-
let mut ast = Parser::parse_source(relative, src, &current)?;
25+
let mut ast = Parser::parse_source(&mut numerator, relative, &current, src)?;
2526

2627
paths.insert(current.path.clone(), relative.into());
2728

@@ -208,7 +209,7 @@ impl Compiler {
208209
#[cfg(test)]
209210
pub(crate) fn test_compile(src: &str) -> RunResult<Justfile> {
210211
let tokens = Lexer::test_lex(src)?;
211-
let ast = Parser::parse(0, &[], None, &tokens, &PathBuf::new())?;
212+
let ast = Parser::parse_tokens(&mut Numerator::new(), &tokens)?;
212213
let root = PathBuf::from("justfile");
213214
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
214215
asts.insert(root.clone(), ast);

src/constants.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const CONSTANTS: &[(&str, &str, Option<&str>, &str)] = &[
3232
("BG_WHITE", "\x1b[47m", None, "1.37.0"),
3333
];
3434

35-
pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
36-
static MAP: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
35+
pub(crate) fn constants() -> &'static BTreeMap<&'static str, &'static str> {
36+
static MAP: LazyLock<BTreeMap<&str, &str>> = LazyLock::new(|| {
3737
CONSTANTS
3838
.iter()
3939
.copied()

src/evaluator.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
3535
export: assignment.export,
3636
file_depth: 0,
3737
name: assignment.name,
38+
number: assignment.number,
3839
prelude: false,
3940
private: assignment.private,
4041
value: value.clone(),
@@ -109,6 +110,9 @@ impl<'src, 'run> Evaluator<'src, 'run> {
109110
Setting::IgnoreComments(value) => {
110111
settings.ignore_comments = value;
111112
}
113+
Setting::Lazy(value) => {
114+
settings.lazy = value;
115+
}
112116
Setting::NoExitMessage(value) => {
113117
settings.no_exit_message = value;
114118
}
@@ -165,6 +169,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
165169
module: &'run Justfile<'src>,
166170
parent: &'run Scope<'src, 'run>,
167171
search: &'run Search,
172+
variable_references: Option<&HashSet<Number>>,
168173
) -> RunResult<'src, Scope<'src, 'run>>
169174
where
170175
'src: 'run,
@@ -187,6 +192,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
187192
export: assignment.export,
188193
file_depth: 0,
189194
name: assignment.name,
195+
number: assignment.number,
190196
prelude: false,
191197
private: assignment.private,
192198
value: value.clone(),
@@ -213,7 +219,13 @@ impl<'src, 'run> Evaluator<'src, 'run> {
213219
};
214220

215221
for assignment in module.assignments.values() {
216-
evaluator.evaluate_assignment(assignment)?;
222+
if module.settings.export
223+
|| assignment.export
224+
|| variable_references
225+
.is_none_or(|variable_references| variable_references.contains(&assignment.number))
226+
{
227+
evaluator.evaluate_assignment(assignment)?;
228+
}
217229
}
218230

219231
Ok(evaluator.scope)
@@ -228,6 +240,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
228240
export: assignment.export,
229241
file_depth: 0,
230242
name: assignment.name,
243+
number: assignment.number,
231244
prelude: false,
232245
private: assignment.private,
233246
value,
@@ -562,6 +575,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
562575
export: parameter.export,
563576
file_depth: 0,
564577
name: parameter.name,
578+
number: parameter.number,
565579
prelude: false,
566580
private: false,
567581
value: values.join(" "),

src/justfile.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,24 @@ impl<'src> Justfile<'src> {
8282
root: &'run Scope<'src, 'run>,
8383
scopes: &mut BTreeMap<String, (&'run Self, &'run Scope<'src, 'run>)>,
8484
search: &'run Search,
85+
variable_references: Option<&HashSet<Number>>,
8586
) -> RunResult<'src> {
86-
let scope = Evaluator::evaluate_assignments(config, dotenv, self, root, search)?;
87+
let scope =
88+
Evaluator::evaluate_assignments(config, dotenv, self, root, search, variable_references)?;
8789

8890
let scope = arena.alloc(scope);
8991
scopes.insert(self.module_path.clone(), (self, scope));
9092

9193
for module in self.modules.values() {
92-
module.evaluate_scopes(arena, config, dotenv, scope, scopes, search)?;
94+
module.evaluate_scopes(
95+
arena,
96+
config,
97+
dotenv,
98+
scope,
99+
scopes,
100+
search,
101+
variable_references,
102+
)?;
93103
}
94104

95105
Ok(())
@@ -123,9 +133,6 @@ impl<'src> Justfile<'src> {
123133
let root = Scope::root();
124134
let arena = Arena::new();
125135
let mut scopes = BTreeMap::new();
126-
self.evaluate_scopes(&arena, config, &dotenv, &root, &mut scopes, search)?;
127-
128-
let scope = scopes.get(&self.module_path).unwrap().1;
129136

130137
match &config.subcommand {
131138
Subcommand::Choose { .. } | Subcommand::Run { .. } => {
@@ -139,6 +146,37 @@ impl<'src> Justfile<'src> {
139146
});
140147
}
141148

149+
let variable_references = if self.settings.lazy {
150+
let mut variable_references = HashSet::new();
151+
152+
let mut stack = Vec::new();
153+
154+
for invocation in &invocations {
155+
stack.push(invocation.recipe);
156+
}
157+
158+
while let Some(recipe) = stack.pop() {
159+
variable_references.extend(&recipe.variable_references);
160+
for dependency in &recipe.dependencies {
161+
stack.push(&dependency.recipe);
162+
}
163+
}
164+
165+
Some(variable_references)
166+
} else {
167+
None
168+
};
169+
170+
self.evaluate_scopes(
171+
&arena,
172+
config,
173+
&dotenv,
174+
&root,
175+
&mut scopes,
176+
search,
177+
variable_references.as_ref(),
178+
)?;
179+
142180
let ran = Ran::default();
143181
for invocation in invocations {
144182
Self::run_recipe(
@@ -170,7 +208,8 @@ impl<'src> Justfile<'src> {
170208
.args(arguments)
171209
.current_dir(&search.working_directory);
172210

173-
let scope = scope.child();
211+
self.evaluate_scopes(&arena, config, &dotenv, &root, &mut scopes, search, None)?;
212+
let scope = scopes.get(&self.module_path).unwrap().1.child();
174213

175214
command.export(&self.settings, &dotenv, &scope, &self.unexports);
176215

@@ -197,6 +236,9 @@ impl<'src> Justfile<'src> {
197236
Ok(())
198237
}
199238
Subcommand::Evaluate { variable, .. } => {
239+
self.evaluate_scopes(&arena, config, &dotenv, &root, &mut scopes, search, None)?;
240+
let scope = scopes.get(&self.module_path).unwrap().1;
241+
200242
if let Some(variable) = variable {
201243
if let Some(value) = scope.value(variable) {
202244
print!("{value}");

src/keyword.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub(crate) enum Keyword {
2121
If,
2222
IgnoreComments,
2323
Import,
24+
Lazy,
2425
Mod,
2526
NoExitMessage,
2627
PositionalArguments,

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub(crate) use {
5858
module_path::ModulePath,
5959
name::Name,
6060
namepath::Namepath,
61+
number::Number,
62+
numerator::Numerator,
6163
ordinal::Ordinal,
6264
output_error::OutputError,
6365
parameter::Parameter,
@@ -244,6 +246,8 @@ mod loader;
244246
mod module_path;
245247
mod name;
246248
mod namepath;
249+
mod number;
250+
mod numerator;
247251
mod ordinal;
248252
mod output_error;
249253
mod parameter;

src/node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ impl<'src> Node<'src> for Set<'src> {
319319
| Setting::Fallback(value)
320320
| Setting::Guards(value)
321321
| Setting::IgnoreComments(value)
322+
| Setting::Lazy(value)
322323
| Setting::NoExitMessage(value)
323324
| Setting::PositionalArguments(value)
324325
| Setting::Quiet(value)

src/number.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2+
pub(crate) struct Number(pub(crate) u32);

0 commit comments

Comments
 (0)