Skip to content

Commit 6621758

Browse files
committed
keep no-cd as is, introduce new attr/setting
1 parent f540d51 commit 6621758

File tree

13 files changed

+99
-59
lines changed

13 files changed

+99
-59
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,11 @@ it can't appear alongside `set working-directory` in the same `justfile`.
941941
Recipe-level attributes still take precedence: `[working-directory(...)]`
942942
overrides both, and `[no-cd]` on a recipe overrides `set working-directory`.
943943

944+
Path resolution remains the same by default: backticks, functions like
945+
`read()`, and shell() calls use the module's working directory even if `no-cd`
946+
is set. To also resolve these relative to the invocation directory when
947+
skipping `cd`, turn on `set no-cd-strict := true`.
948+
944949
You can override the working directory for all recipes with
945950
`set working-directory := '…'`:
946951

@@ -1033,6 +1038,7 @@ foo:
10331038
| `fallback` | boolean | `false` | Search `justfile` in parent directory if the first recipe on the command line is not found. |
10341039
| `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. |
10351040
| `no-cd` | boolean | `false` | Don't change directory before executing recipes and evaluating backticks, unless overridden by recipe attributes. |
1041+
| `no-cd-strict` | boolean | `false` | When `no-cd` is set, also resolve backticks, shell functions, and path helpers relative to the invocation directory instead of the module directory. |
10361042
| `positional-arguments` | boolean | `false` | Pass positional arguments. |
10371043
| `quiet` | boolean | `false` | Disable echoing recipe lines before executing. |
10381044
| `script-interpreter`<sup>1.33.0</sup> | `[COMMAND, ARGS…]` | `['sh', '-eu']` | Set command used to invoke recipes with empty `[script]` attribute. |

src/evaluator.rs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1-
use {super::*, std::path::Path};
1+
use super::*;
22

33
pub(crate) struct Evaluator<'src: 'run, 'run> {
44
pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>,
55
pub(crate) context: ExecutionContext<'src, 'run>,
66
pub(crate) is_dependency: bool,
77
pub(crate) scope: Scope<'src, 'run>,
8-
pub(crate) working_directory: Option<PathBuf>,
98
}
109

1110
impl<'src, 'run> Evaluator<'src, 'run> {
12-
pub(crate) fn working_directory(&self) -> Option<&Path> {
13-
self.working_directory.as_deref()
14-
}
15-
16-
pub(crate) fn working_directory_or_invocation(&self) -> PathBuf {
17-
self
18-
.working_directory
19-
.clone()
20-
.unwrap_or_else(|| self.context.config.invocation_directory.clone())
21-
}
22-
2311
pub(crate) fn evaluate_assignments(
2412
config: &'run Config,
2513
dotenv: &'run BTreeMap<String, String>,
@@ -67,7 +55,6 @@ impl<'src, 'run> Evaluator<'src, 'run> {
6755
assignments: Some(&module.assignments),
6856
scope,
6957
is_dependency: false,
70-
working_directory: context.module_default_working_directory(),
7158
};
7259

7360
for assignment in module.assignments.values() {
@@ -288,9 +275,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
288275

289276
cmd.arg(command).args(args);
290277

291-
if let Some(working_directory) = self.working_directory() {
292-
cmd.current_dir(working_directory);
293-
}
278+
cmd.current_dir(self.context.path_working_directory());
294279

295280
cmd
296281
.export(
@@ -341,9 +326,8 @@ impl<'src, 'run> Evaluator<'src, 'run> {
341326
arguments: &[String],
342327
parameters: &[Parameter<'src>],
343328
scope: &'run Scope<'src, 'run>,
344-
working_directory: Option<PathBuf>,
345329
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
346-
let mut evaluator = Self::new(context, is_dependency, scope, working_directory);
330+
let mut evaluator = Self::new(context, is_dependency, scope);
347331

348332
let mut positional = Vec::new();
349333

@@ -391,14 +375,12 @@ impl<'src, 'run> Evaluator<'src, 'run> {
391375
context: &ExecutionContext<'src, 'run>,
392376
is_dependency: bool,
393377
scope: &'run Scope<'src, 'run>,
394-
working_directory: Option<PathBuf>,
395378
) -> Self {
396379
Self {
397380
assignments: None,
398381
context: *context,
399382
is_dependency,
400383
scope: scope.child(),
401-
working_directory: working_directory.or_else(|| context.module_default_working_directory()),
402384
}
403385
}
404386
}

src/execution_context.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
3939
})
4040
}
4141

42-
pub(crate) fn module_base_directory(&self) -> PathBuf {
43-
if self.module.is_submodule() {
42+
pub(crate) fn working_directory(&self) -> PathBuf {
43+
let base = if self.module.is_submodule() {
4444
self
4545
.module
4646
.source
@@ -49,11 +49,7 @@ impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
4949
.unwrap_or_else(|| self.search.working_directory.clone())
5050
} else {
5151
self.search.working_directory.clone()
52-
}
53-
}
54-
55-
pub(crate) fn module_working_directory(&self) -> PathBuf {
56-
let base = self.module_base_directory();
52+
};
5753

5854
if let Some(setting) = &self.module.settings.working_directory {
5955
base.join(setting)
@@ -62,11 +58,11 @@ impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
6258
}
6359
}
6460

65-
pub(crate) fn module_default_working_directory(&self) -> Option<PathBuf> {
66-
if self.module.settings.no_cd {
67-
None
61+
pub(crate) fn path_working_directory(&self) -> PathBuf {
62+
if self.module.settings.no_cd && self.module.settings.no_cd_strict {
63+
self.config.invocation_directory.clone()
6864
} else {
69-
Some(self.module_working_directory())
65+
self.working_directory()
7066
}
7167
}
7268
}

src/function.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl Function {
134134
}
135135

136136
fn absolute_path(context: Context, path: &str) -> FunctionResult {
137-
let working_directory = context.evaluator.working_directory_or_invocation();
137+
let working_directory = context.evaluator.context.path_working_directory();
138138
let abs_path_unchecked = working_directory.join(path).lexiclean();
139139
match abs_path_unchecked.to_str() {
140140
Some(absolute_path) => Ok(absolute_path.to_owned()),
@@ -165,7 +165,8 @@ fn blake3(_context: Context, s: &str) -> FunctionResult {
165165
fn blake3_file(context: Context, path: &str) -> FunctionResult {
166166
let path = context
167167
.evaluator
168-
.working_directory_or_invocation()
168+
.context
169+
.path_working_directory()
169170
.join(path);
170171
let mut hasher = blake3::Hasher::new();
171172
hasher
@@ -178,7 +179,8 @@ fn canonicalize(context: Context, path: &str) -> FunctionResult {
178179
let canonical = std::fs::canonicalize(
179180
context
180181
.evaluator
181-
.working_directory_or_invocation()
182+
.context
183+
.path_working_directory()
182184
.join(path),
183185
)
184186
.map_err(|err| format!("I/O error canonicalizing path: {err}"))?;
@@ -499,7 +501,8 @@ fn path_exists(context: Context, path: &str) -> FunctionResult {
499501
Ok(
500502
context
501503
.evaluator
502-
.working_directory_or_invocation()
504+
.context
505+
.path_working_directory()
503506
.join(path)
504507
.exists()
505508
.to_string(),
@@ -514,7 +517,8 @@ fn read(context: Context, filename: &str) -> FunctionResult {
514517
fs::read_to_string(
515518
context
516519
.evaluator
517-
.working_directory_or_invocation()
520+
.context
521+
.path_working_directory()
518522
.join(filename),
519523
)
520524
.map_err(|err| format!("I/O error reading `{filename}`: {err}"))
@@ -549,7 +553,8 @@ fn sha256_file(context: Context, path: &str) -> FunctionResult {
549553
use sha2::{Digest, Sha256};
550554
let path = context
551555
.evaluator
552-
.working_directory_or_invocation()
556+
.context
557+
.path_working_directory()
553558
.join(path);
554559
let mut hasher = Sha256::new();
555560
let mut file =

src/justfile.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,20 +343,17 @@ impl<'src> Justfile<'src> {
343343
search,
344344
};
345345

346-
let recipe_working_directory = recipe.working_directory(&context);
347-
348346
let (outer, positional) = Evaluator::evaluate_parameters(
349347
&context,
350348
is_dependency,
351349
arguments,
352350
&recipe.parameters,
353351
scope,
354-
recipe_working_directory.clone(),
355352
)?;
356353

357354
let scope = outer.child();
358355

359-
let mut evaluator = Evaluator::new(&context, true, &scope, recipe_working_directory.clone());
356+
let mut evaluator = Evaluator::new(&context, true, &scope);
360357

361358
Self::run_dependencies(
362359
config,

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
Import,
2222
Mod,
2323
NoCd,
24+
NoCdStrict,
2425
NoExitMessage,
2526
PositionalArguments,
2627
Quiet,

src/node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ impl<'src> Node<'src> for Set<'src> {
308308
| Setting::Export(value)
309309
| Setting::Fallback(value)
310310
| Setting::NoCd(value)
311+
| Setting::NoCdStrict(value)
311312
| Setting::NoExitMessage(value)
312313
| Setting::PositionalArguments(value)
313314
| Setting::Quiet(value)

src/parser.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,7 @@ impl<'run, 'src> Parser<'run, 'src> {
11271127
Keyword::Fallback => Some(Setting::Fallback(self.parse_set_bool()?)),
11281128
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
11291129
Keyword::NoCd => Some(Setting::NoCd(self.parse_set_bool()?)),
1130+
Keyword::NoCdStrict => Some(Setting::NoCdStrict(self.parse_set_bool()?)),
11301131
Keyword::NoExitMessage => Some(Setting::NoExitMessage(self.parse_set_bool()?)),
11311132
Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
11321133
Keyword::Quiet => Some(Setting::Quiet(self.parse_set_bool()?)),
@@ -2308,6 +2309,12 @@ mod tests {
23082309
tree: (justfile (set no_cd true)),
23092310
}
23102311

2312+
test! {
2313+
name: set_no_cd_strict,
2314+
text: "set no-cd-strict := true",
2315+
tree: (justfile (set no_cd_strict true)),
2316+
}
2317+
23112318
test! {
23122319
name: set_positional_arguments_false,
23132320
text: "set positional-arguments := false",

src/recipe.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use {super::*, std::path::Path};
1+
use super::*;
22

33
/// Return a `Error::Signal` if the process was terminated by a signal,
44
/// otherwise return an `Error::UnknownFailure`
@@ -173,11 +173,7 @@ impl<'src, D> Recipe<'src, D> {
173173
}
174174

175175
pub(crate) fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
176-
let module_default = context.module_default_working_directory();
177-
let module_working_directory = module_default
178-
.as_deref()
179-
.map(Path::to_path_buf)
180-
.unwrap_or_else(|| context.module_working_directory());
176+
let module_working_directory = context.working_directory();
181177

182178
for attribute in &self.attributes {
183179
if let Attribute::WorkingDirectory(dir) = attribute {
@@ -189,7 +185,7 @@ impl<'src, D> Recipe<'src, D> {
189185
return None;
190186
}
191187

192-
module_default
188+
Some(module_working_directory)
193189
}
194190

195191
fn no_quiet(&self) -> bool {
@@ -217,14 +213,12 @@ impl<'src, D> Recipe<'src, D> {
217213
}
218214
}
219215

220-
let working_directory = self.working_directory(context);
221-
222-
let evaluator = Evaluator::new(context, is_dependency, scope, working_directory.clone());
216+
let evaluator = Evaluator::new(context, is_dependency, scope);
223217

224218
if self.is_script() {
225-
self.run_script(context, scope, positional, evaluator, working_directory)
219+
self.run_script(context, scope, positional, evaluator)
226220
} else {
227-
self.run_linewise(context, scope, positional, evaluator, working_directory)
221+
self.run_linewise(context, scope, positional, evaluator)
228222
}
229223
}
230224

@@ -234,7 +228,6 @@ impl<'src, D> Recipe<'src, D> {
234228
scope: &Scope<'src, 'run>,
235229
positional: &[String],
236230
mut evaluator: Evaluator<'src, 'run>,
237-
working_directory: Option<PathBuf>,
238231
) -> RunResult<'src, ()> {
239232
let config = &context.config;
240233

@@ -316,7 +309,7 @@ impl<'src, D> Recipe<'src, D> {
316309

317310
let mut cmd = context.module.settings.shell_command(config);
318311

319-
if let Some(working_directory) = working_directory.as_deref() {
312+
if let Some(working_directory) = self.working_directory(context) {
320313
cmd.current_dir(working_directory);
321314
}
322315

@@ -382,7 +375,6 @@ impl<'src, D> Recipe<'src, D> {
382375
scope: &Scope<'src, 'run>,
383376
positional: &[String],
384377
mut evaluator: Evaluator<'src, 'run>,
385-
working_directory: Option<PathBuf>,
386378
) -> RunResult<'src, ()> {
387379
let config = &context.config;
388380

@@ -453,7 +445,12 @@ impl<'src, D> Recipe<'src, D> {
453445
io_error: error,
454446
})?;
455447

456-
let mut command = executor.command(config, &path, self.name(), working_directory.as_deref())?;
448+
let mut command = executor.command(
449+
config,
450+
&path,
451+
self.name(),
452+
self.working_directory(context).as_deref(),
453+
)?;
457454

458455
if self.takes_positional_arguments(&context.module.settings) {
459456
command.args(positional);

src/setting.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub(crate) enum Setting<'src> {
1313
Fallback(bool),
1414
IgnoreComments(bool),
1515
NoCd(bool),
16+
NoCdStrict(bool),
1617
NoExitMessage(bool),
1718
PositionalArguments(bool),
1819
Quiet(bool),
@@ -37,6 +38,7 @@ impl Display for Setting<'_> {
3738
| Self::Fallback(value)
3839
| Self::IgnoreComments(value)
3940
| Self::NoCd(value)
41+
| Self::NoCdStrict(value)
4042
| Self::NoExitMessage(value)
4143
| Self::PositionalArguments(value)
4244
| Self::Quiet(value)

0 commit comments

Comments
 (0)