Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ foo:
| `export` | boolean | `false` | Export all variables as environment variables. |
| `fallback` | boolean | `false` | Search `justfile` in parent directory if the first recipe on the command line is not found. |
| `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. |
| `lazy`<sup>master</sup> | boolean | `false` | Don't evaluate unused variables. |
| `positional-arguments` | boolean | `false` | Pass positional arguments. |
| `quiet` | boolean | `false` | Disable echoing recipe lines before executing. |
| `script-interpreter`<sup>1.33.0</sup> | `[COMMAND, ARGS…]` | `['sh', '-eu']` | Set command used to invoke recipes with empty `[script]` attribute. |
Expand Down Expand Up @@ -1166,6 +1167,31 @@ hello
goodbye
```

#### Lazy

The `lazy` setting<sup>master</sup>, currently unstable, causes the evaluator
to skip evaluating unused variables. This can be beneficial when a `justfile`
contains variables that are expensive to evaluate but only sometimes used.

In the following `justfile`, `token` will be skipped when only invoking `bar`:

```just
set lazy
set unstable

token := `expensive-script-to-get-credentials`

foo:
curl -H "Authorization: Bearer {{ token }}" https://example.com/foo

bar:
cargo test
```

Because `just` cannot determine when exported variables are used, assignments
with `export` and assignments in a module with `set export` will always be
evaluated.

#### Positional Arguments

If `positional-arguments` is `true`, recipe arguments will be passed as
Expand Down
2 changes: 2 additions & 0 deletions src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub(crate) struct Binding<'src, V = String> {
pub(crate) file_depth: u32,
pub(crate) name: Name<'src>,
#[serde(skip)]
pub(crate) number: Number,
#[serde(skip)]
pub(crate) prelude: bool,
pub(crate) private: bool,
pub(crate) value: V,
Expand Down
5 changes: 3 additions & 2 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ impl Compiler {
) -> RunResult<'src, Compilation<'src>> {
let mut asts = HashMap::<PathBuf, Ast>::new();
let mut loaded = Vec::new();
let mut numerator = Numerator::new();
let mut paths = HashMap::<PathBuf, PathBuf>::new();
let mut stack = Vec::new();
stack.push(Source::root(root));
Expand All @@ -21,7 +22,7 @@ impl Compiler {

let (relative, src) = loader.load(root, &current.path)?;
loaded.push(relative.into());
let mut ast = Parser::parse_source(relative, src, &current)?;
let mut ast = Parser::parse_source(&mut numerator, relative, &current, src)?;

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

Expand Down Expand Up @@ -208,7 +209,7 @@ impl Compiler {
#[cfg(test)]
pub(crate) fn test_compile(src: &str) -> RunResult<Justfile> {
let tokens = Lexer::test_lex(src)?;
let ast = Parser::parse(0, &[], None, &tokens, &PathBuf::new())?;
let ast = Parser::parse_tokens(&mut Numerator::new(), &tokens)?;
let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
asts.insert(root.clone(), ast);
Expand Down
4 changes: 2 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const CONSTANTS: &[(&str, &str, Option<&str>, &str)] = &[
("BG_WHITE", "\x1b[47m", None, "1.37.0"),
];

pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
static MAP: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
pub(crate) fn constants() -> &'static BTreeMap<&'static str, &'static str> {
static MAP: LazyLock<BTreeMap<&str, &str>> = LazyLock::new(|| {
CONSTANTS
.iter()
.copied()
Expand Down
16 changes: 15 additions & 1 deletion src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
export: assignment.export,
file_depth: 0,
name: assignment.name,
number: assignment.number,
prelude: false,
private: assignment.private,
value: value.clone(),
Expand Down Expand Up @@ -109,6 +110,9 @@ impl<'src, 'run> Evaluator<'src, 'run> {
Setting::IgnoreComments(value) => {
settings.ignore_comments = value;
}
Setting::Lazy(value) => {
settings.lazy = value;
}
Setting::NoExitMessage(value) => {
settings.no_exit_message = value;
}
Expand Down Expand Up @@ -165,6 +169,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
module: &'run Justfile<'src>,
parent: &'run Scope<'src, 'run>,
search: &'run Search,
variable_references: Option<&HashSet<Number>>,
) -> RunResult<'src, Scope<'src, 'run>>
where
'src: 'run,
Expand All @@ -187,6 +192,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
export: assignment.export,
file_depth: 0,
name: assignment.name,
number: assignment.number,
prelude: false,
private: assignment.private,
value: value.clone(),
Expand All @@ -213,7 +219,13 @@ impl<'src, 'run> Evaluator<'src, 'run> {
};

for assignment in module.assignments.values() {
evaluator.evaluate_assignment(assignment)?;
if module.settings.export
|| assignment.export
|| variable_references
.is_none_or(|variable_references| variable_references.contains(&assignment.number))
{
evaluator.evaluate_assignment(assignment)?;
}
}

Ok(evaluator.scope)
Expand All @@ -228,6 +240,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
export: assignment.export,
file_depth: 0,
name: assignment.name,
number: assignment.number,
prelude: false,
private: assignment.private,
value,
Expand Down Expand Up @@ -562,6 +575,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
export: parameter.export,
file_depth: 0,
name: parameter.name,
number: parameter.number,
prelude: false,
private: false,
value: values.join(" "),
Expand Down
54 changes: 48 additions & 6 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,24 @@ impl<'src> Justfile<'src> {
root: &'run Scope<'src, 'run>,
scopes: &mut BTreeMap<String, (&'run Self, &'run Scope<'src, 'run>)>,
search: &'run Search,
variable_references: Option<&HashSet<Number>>,
) -> RunResult<'src> {
let scope = Evaluator::evaluate_assignments(config, dotenv, self, root, search)?;
let scope =
Evaluator::evaluate_assignments(config, dotenv, self, root, search, variable_references)?;

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

for module in self.modules.values() {
module.evaluate_scopes(arena, config, dotenv, scope, scopes, search)?;
module.evaluate_scopes(
arena,
config,
dotenv,
scope,
scopes,
search,
variable_references,
)?;
}

Ok(())
Expand Down Expand Up @@ -123,9 +133,6 @@ impl<'src> Justfile<'src> {
let root = Scope::root();
let arena = Arena::new();
let mut scopes = BTreeMap::new();
self.evaluate_scopes(&arena, config, &dotenv, &root, &mut scopes, search)?;

let scope = scopes.get(&self.module_path).unwrap().1;

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

let variable_references = if self.settings.lazy {
let mut variable_references = HashSet::new();

let mut stack = Vec::new();

for invocation in &invocations {
stack.push(invocation.recipe);
}

while let Some(recipe) = stack.pop() {
variable_references.extend(&recipe.variable_references);
for dependency in &recipe.dependencies {
stack.push(&dependency.recipe);
}
}

Some(variable_references)
} else {
None
};

self.evaluate_scopes(
&arena,
config,
&dotenv,
&root,
&mut scopes,
search,
variable_references.as_ref(),
)?;

let ran = Ran::default();
for invocation in invocations {
Self::run_recipe(
Expand Down Expand Up @@ -170,7 +208,8 @@ impl<'src> Justfile<'src> {
.args(arguments)
.current_dir(&search.working_directory);

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

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

Expand All @@ -197,6 +236,9 @@ impl<'src> Justfile<'src> {
Ok(())
}
Subcommand::Evaluate { variable, .. } => {
self.evaluate_scopes(&arena, config, &dotenv, &root, &mut scopes, search, None)?;
let scope = scopes.get(&self.module_path).unwrap().1;

if let Some(variable) = variable {
if let Some(value) = scope.value(variable) {
print!("{value}");
Expand Down
1 change: 1 addition & 0 deletions src/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub(crate) enum Keyword {
If,
IgnoreComments,
Import,
Lazy,
Mod,
NoExitMessage,
PositionalArguments,
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub(crate) use {
module_path::ModulePath,
name::Name,
namepath::Namepath,
number::Number,
numerator::Numerator,
ordinal::Ordinal,
output_error::OutputError,
parameter::Parameter,
Expand Down Expand Up @@ -244,6 +246,8 @@ mod loader;
mod module_path;
mod name;
mod namepath;
mod number;
mod numerator;
mod ordinal;
mod output_error;
mod parameter;
Expand Down
1 change: 1 addition & 0 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ impl<'src> Node<'src> for Set<'src> {
| Setting::Fallback(value)
| Setting::Guards(value)
| Setting::IgnoreComments(value)
| Setting::Lazy(value)
| Setting::NoExitMessage(value)
| Setting::PositionalArguments(value)
| Setting::Quiet(value)
Expand Down
2 changes: 2 additions & 0 deletions src/number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct Number(pub(crate) u32);
20 changes: 20 additions & 0 deletions src/numerator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::*;

pub(crate) struct Numerator(u32);

impl Numerator {
pub(crate) fn new() -> Self {
Self(constants().len().try_into().unwrap())
}

pub(crate) fn next(&mut self) -> Number {
let id = self.0;
self.0 += 1;
Number(id)
}

pub(crate) fn constant(i: usize) -> Number {
assert!(i < constants().len());
Number(i.try_into().unwrap())
}
}
2 changes: 2 additions & 0 deletions src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub(crate) struct Parameter<'src> {
pub(crate) kind: ParameterKind,
pub(crate) long: Option<String>,
pub(crate) name: Name<'src>,
#[serde(skip)]
pub(crate) number: Number,
pub(crate) pattern: Option<Pattern<'src>>,
pub(crate) short: Option<char>,
pub(crate) value: Option<String>,
Expand Down
Loading