Skip to content

Commit 35b9e86

Browse files
committed
add setting no-cd
1 parent ad2678f commit 35b9e86

17 files changed

Lines changed: 228 additions & 13 deletions

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,12 @@ $ just bar
950950
/subdir
951951
```
952952

953+
To apply the same behavior to every recipe in a module, use `set no-cd := true`.
954+
This setting is module-local, so imported modules choose their own default, and
955+
it can't appear alongside `set working-directory` in the same `justfile`.
956+
Recipe-level attributes still take precedence: `[working-directory(...)]`
957+
overrides both, and `[no-cd]` on a recipe overrides `set working-directory`.
958+
953959
You can override the working directory for all recipes with
954960
`set working-directory := '…'`:
955961

@@ -1043,6 +1049,7 @@ foo:
10431049
| `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. |
10441050
| `no-exit-message`<sup>1.39.0</sup> | boolean | `false` | Don't print exit messages if recipes fail. |
10451051
| `lazy`<sup>1.47.0</sup> | boolean | `false` | Don't evaluate unused variables. |
1052+
| `no-cd` | boolean | `false` | Don't change directory before executing recipes and evaluating backticks, unless overridden by recipe attributes. |
10461053
| `positional-arguments` | boolean | `false` | Pass positional arguments. |
10471054
| `quiet` | boolean | `false` | Disable echoing recipe lines before executing. |
10481055
| `script-interpreter`<sup>1.33.0</sup> | `[COMMAND, ARGS…]` | `['sh', '-eu']` | Set command used to invoke recipes with empty `[script]` attribute. |

src/analyzer.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,30 @@ impl<'run, 'src> Analyzer<'run, 'src> {
417417
}));
418418
}
419419

420+
if let Some(keyword) = Keyword::from_lexeme(set.name.lexeme()) {
421+
match keyword {
422+
Keyword::NoCd => {
423+
if let Some(conflict) = self.sets.get(Keyword::WorkingDirectory.lexeme()) {
424+
return Err(set.name.error(NoCdAndWorkingDirectorySetting {
425+
first: Keyword::WorkingDirectory,
426+
first_line: conflict.name.line,
427+
second: keyword,
428+
}));
429+
}
430+
}
431+
Keyword::WorkingDirectory => {
432+
if let Some(conflict) = self.sets.get(Keyword::NoCd.lexeme()) {
433+
return Err(set.name.error(NoCdAndWorkingDirectorySetting {
434+
first: Keyword::NoCd,
435+
first_line: conflict.name.line,
436+
second: keyword,
437+
}));
438+
}
439+
}
440+
_ => {}
441+
}
442+
}
443+
420444
Ok(())
421445
}
422446

src/compile_error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,17 @@ impl Display for CompileError<'_> {
262262
f,
263263
"Recipe `{recipe}` has both `[no-cd]` and `[working-directory]` attributes"
264264
),
265+
NoCdAndWorkingDirectorySetting {
266+
first,
267+
first_line,
268+
second,
269+
} => write!(
270+
f,
271+
"Setting `{}` first set on line {} is incompatible with setting `{}`",
272+
first.lexeme(),
273+
first_line.ordinal(),
274+
second.lexeme()
275+
),
265276
OptionNameContainsEqualSign { parameter } => {
266277
write!(
267278
f,

src/compile_error_kind.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ pub(crate) enum CompileErrorKind<'src> {
120120
OptionNameEmpty {
121121
parameter: String,
122122
},
123+
NoCdAndWorkingDirectorySetting {
124+
first: Keyword,
125+
first_line: usize,
126+
second: Keyword,
127+
},
123128
ParameterFollowsVariadicParameter {
124129
parameter: &'src str,
125130
},

src/evaluator.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ impl<'src, 'run> Evaluator<'src, 'run> {
8686
Setting::Lazy(value) => {
8787
settings.lazy = value;
8888
}
89+
Setting::NoCd(value) => {
90+
settings.no_cd = value;
91+
}
8992
Setting::NoExitMessage(value) => {
9093
settings.no_exit_message = value;
9194
}

src/execution_context.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,20 @@ impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
4141

4242
pub(crate) fn working_directory(&self) -> PathBuf {
4343
let base = if self.module.is_submodule() {
44-
&self.module.working_directory
44+
self
45+
.module
46+
.source
47+
.parent()
48+
.map(Path::to_path_buf)
49+
.unwrap_or_else(|| self.search.working_directory.clone())
4550
} else {
46-
&self.search.working_directory
51+
self.search.working_directory.clone()
4752
};
4853

4954
if let Some(setting) = &self.module.settings.working_directory {
5055
base.join(setting)
5156
} else {
52-
base.into()
57+
base
5358
}
5459
}
5560
}

src/keyword.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub(crate) enum Keyword {
2424
Import,
2525
Lazy,
2626
Mod,
27+
NoCd,
2728
NoExitMessage,
2829
PositionalArguments,
2930
Quiet,

src/node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ impl<'src> Node<'src> for Set<'src> {
283283
| Setting::Guards(value)
284284
| Setting::IgnoreComments(value)
285285
| Setting::Lazy(value)
286+
| Setting::NoCd(value)
286287
| Setting::NoExitMessage(value)
287288
| Setting::PositionalArguments(value)
288289
| Setting::Quiet(value)

src/parser.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,7 @@ impl<'run, 'src> Parser<'run, 'src> {
14161416
Keyword::Guards => Some(Setting::Guards(self.parse_set_bool()?)),
14171417
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
14181418
Keyword::Lazy => Some(Setting::Lazy(self.parse_set_bool()?)),
1419+
Keyword::NoCd => Some(Setting::NoCd(self.parse_set_bool()?)),
14191420
Keyword::NoExitMessage => Some(Setting::NoExitMessage(self.parse_set_bool()?)),
14201421
Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
14211422
Keyword::Quiet => Some(Setting::Quiet(self.parse_set_bool()?)),
@@ -2650,6 +2651,12 @@ mod tests {
26502651
tree: (justfile (set quiet false)),
26512652
}
26522653

2654+
test! {
2655+
name: set_no_cd,
2656+
text: "set no-cd := true",
2657+
tree: (justfile (set no_cd true)),
2658+
}
2659+
26532660
test! {
26542661
name: set_positional_arguments_false,
26552662
text: "set positional-arguments := false",

src/recipe.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,19 @@ impl<'src> Recipe<'src> {
169169
.contains(AttributeDiscriminant::PositionalArguments)
170170
}
171171

172-
pub(crate) fn change_directory(&self) -> bool {
173-
!self.attributes.contains(AttributeDiscriminant::NoCd)
172+
pub(crate) fn change_directory(&self, settings: &Settings) -> bool {
173+
if self
174+
.attributes
175+
.contains(AttributeDiscriminant::WorkingDirectory)
176+
{
177+
return true;
178+
}
179+
180+
if self.attributes.contains(AttributeDiscriminant::NoCd) {
181+
return false;
182+
}
183+
184+
!settings.no_cd
174185
}
175186

176187
fn print_exit_message(&self, settings: &Settings) -> bool {
@@ -185,20 +196,20 @@ impl<'src> Recipe<'src> {
185196
}
186197
}
187198

188-
fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
189-
if !self.change_directory() {
190-
return None;
191-
}
192-
193-
let working_directory = context.working_directory();
199+
pub(crate) fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
200+
let module_working_directory = context.working_directory();
194201

195202
for attribute in &self.attributes {
196203
if let Attribute::WorkingDirectory(dir) = attribute {
197-
return Some(working_directory.join(&dir.cooked));
204+
return Some(module_working_directory.join(&dir.cooked));
198205
}
199206
}
200207

201-
Some(working_directory)
208+
if !self.change_directory(&context.module.settings) {
209+
return None;
210+
}
211+
212+
Some(module_working_directory)
202213
}
203214

204215
fn no_quiet(&self) -> bool {

0 commit comments

Comments
 (0)