Merge branch 'master' into module_deps1

This commit is contained in:
Casey Rodarmor 2025-07-03 15:41:47 -07:00 committed by GitHub
commit 665dff3989
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 624 additions and 404 deletions

View file

@ -1,6 +1,43 @@
Changelog
=========
[1.41.0](https://github.com/casey/just/releases/tag/1.41.0) - 2025-07-01
------------------------------------------------------------------------
### Changed
- Treat SIGINFO as non-fatal ([#2788](https://github.com/casey/just/pull/2788) by [casey](https://github.com/casey))
- Improve signal handling ([#2488](https://github.com/casey/just/pull/2488) by [casey](https://github.com/casey))
### Added
- Add `dotenv-override` setting ([#2785](https://github.com/casey/just/pull/2785) by [Lun4m](https://github.com/Lun4m))
- Add `PATH_SEP` and `PATH_VAR_SEP` constants ([#2679](https://github.com/casey/just/pull/2679) by [casey](https://github.com/casey))
- Add `--tempdir` command-line option ([#2798](https://github.com/casey/just/pull/2798) by [casey](https://github.com/casey))
### Fixed
- Pin `clap_complete` to last compatible version ([#2800](https://github.com/casey/just/pull/2800) by [casey](https://github.com/casey))
### Misc
- Add `arkade` to readme ([#2700](https://github.com/casey/just/pull/2700) by [rgee0](https://github.com/rgee0))
- Link to pipx instead of pypi in readme ([#2799](https://github.com/casey/just/pull/2799) by [casey](https://github.com/casey))
- Add reasons to `#[ignore]` attributes ([#2797](https://github.com/casey/just/pull/2797) by [casey](https://github.com/casey))
- Mention that command-line environment variables are inherited ([#2783](https://github.com/casey/just/pull/2783) by [philipmgrant](https://github.com/philipmgrant))
- Fix attribute grammar and update documentation ([#2790](https://github.com/casey/just/pull/2790) by [casey](https://github.com/casey))
- Tweak prose in groups section of readme ([#2767](https://github.com/casey/just/pull/2767) by [offby1](https://github.com/offby1))
- Update `extractions/setup-just` version in readme ([#2735](https://github.com/casey/just/pull/2735) by [esadek](https://github.com/esadek))
- Remove `return` in `Recipe::confirm` ([#2789](https://github.com/casey/just/pull/2789) by [casey](https://github.com/casey))
- Add `mise` to alternatives in readem ([#2758](https://github.com/casey/just/pull/2758) by [koppor](https://github.com/koppor))
- Update `softprops/action-gh-release` ([#2781](https://github.com/casey/just/pull/2781) by [app/dependabot](https://github.com/app/dependabot))
- Use `default` as name of `--init` justfile default recipe ([#2777](https://github.com/casey/just/pull/2777) by [casey](https://github.com/casey))
- Add `just.systems` link to `--init` justfile ([#2776](https://github.com/casey/just/pull/2776) by [casey](https://github.com/casey))
- Fix `kitchen-sink.just` comment ([#2738](https://github.com/casey/just/pull/2738) by [azarmadr](https://github.com/azarmadr))
- Update `softprops/action-gh-release` ([#2716](https://github.com/casey/just/pull/2716) by [app/dependabot](https://github.com/app/dependabot))
- Fix clippy lints ([#2768](https://github.com/casey/just/pull/2768) by [casey](https://github.com/casey))
- Add back-to-the-top link to readme ([#2707](https://github.com/casey/just/pull/2707) by [bravesasha](https://github.com/bravesasha))
- Placate clippy lints for 1.86 ([#2708](https://github.com/casey/just/pull/2708) by [casey](https://github.com/casey))
- Use `-S` in `uv` example ([#2696](https://github.com/casey/just/pull/2696) by [rmoorman](https://github.com/rmoorman))
- Handle `--request` without parsing justfile ([#2683](https://github.com/casey/just/pull/2683) by [casey](https://github.com/casey))
- Bump MSRV to 1.77 and enforce on CI ([#2674](https://github.com/casey/just/pull/2674) by [casey](https://github.com/casey))
[1.40.0](https://github.com/casey/just/releases/tag/1.40.0) - 2025-03-09
------------------------------------------------------------------------

501
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "just"
version = "1.40.0"
version = "1.41.0"
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
autotests = false
categories = ["command-line-utilities", "development-tools"]
@ -23,7 +23,7 @@ blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
camino = "1.0.4"
chrono = "0.4.38"
clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.0.0"
clap_complete = "=4.5.48"
clap_mangen = "0.2.20"
ctrlc = { version = "3.1.1", features = ["termination"] }
derive-where = "1.2.7"
@ -55,7 +55,7 @@ unicode-width = "0.2.0"
uuid = { version = "1.0.0", features = ["v4"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.29.0", features = ["user"] }
nix = { version = "0.30.1", features = ["user"] }
[target.'cfg(windows)'.dependencies]
ctrlc = { version = "3.1.1", features = ["termination"] }
@ -64,7 +64,7 @@ ctrlc = { version = "3.1.1", features = ["termination"] }
executable-path = "1.0.0"
pretty_assertions = "1.0.0"
temptree = "0.2.0"
which = "7.0.0"
which = "8.0.0"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
@ -73,6 +73,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
all = { level = "deny", priority = -1 }
arbitrary-source-item-ordering = "deny"
enum_glob_use = "allow"
ignore_without_reason = "allow"
needless_pass_by_value = "allow"
pedantic = { level = "deny", priority = -1 }
similar_names = "allow"

141
README.md
View file

@ -139,6 +139,11 @@ most Windows users.)
</tr>
</thead>
<tbody>
<tr>
<td><a href=https://github.com/alexellis/arkade>arkade</a></td>
<td>just</td>
<td><code>arkade get just</code></td>
</tr>
<tr>
<td><a href=https://asdf-vm.com>asdf</a></td>
<td><a href=https://github.com/olofvndrhr/asdf-just>just</a></td>
@ -173,7 +178,7 @@ most Windows users.)
<td><code>npm install -g rust-just</code></td>
</tr>
<tr>
<td><a href=https://pypi.org/>PyPI</a></td>
<td><a href=https://pipx.pypa.io/stable/>pipx</a></td>
<td><a href=https://pypi.org/project/rust-just/>rust-just</a></td>
<td><code>pipx install rust-just</code></td>
</tr>
@ -992,6 +997,7 @@ foo:
| `allow-duplicate-variables` | boolean | `false` | Allow variables appearing later in a `justfile` to override earlier variables with the same name. |
| `dotenv-filename` | string | - | Load a `.env` file with a custom name, if present. |
| `dotenv-load` | boolean | `false` | Load a `.env` file, if present. |
| `dotenv-override` | boolean | `false` | Override existing environment variables with values from the `.env` file. |
| `dotenv-path` | string | - | Load a `.env` file from a custom path and error if not present. Overrides `dotenv-filename`. |
| `dotenv-required` | boolean | `false` | Error if a `.env` file isn't found. |
| `export` | boolean | `false` | Export all variables as environment variables. |
@ -1063,8 +1069,9 @@ bar
#### Dotenv Settings
If any of `dotenv-load`, `dotenv-filename`, `dotenv-path`, or `dotenv-required`
are set, `just` will try to load environment variables from a file.
If any of `dotenv-load`, `dotenv-filename`, `dotenv-override`, `dotenv-path`,
or `dotenv-required` are set, `just` will try to load environment variables
from a file.
If `dotenv-path` is set, `just` will look for a file at the given path, which
may be absolute, or relative to the working directory.
@ -1089,6 +1096,9 @@ It is not an error if an environment file is not found, unless
The loaded variables are environment variables, not `just` variables, and so
must be accessed using `$VARIABLE_NAME` in recipes and backticks.
If `dotenv-override` is set, variables from the environment file will override
existing environment variables.
For example, if your `.env` file contains:
```console
@ -2014,35 +2024,37 @@ xdg_config_dir := if env('XDG_CONFIG_HOME', '') =~ '^/' {
A number of constants are predefined:
| Name | Value |
|------|-------------|
| `HEX`<sup>1.27.0</sup> | `"0123456789abcdef"` |
| `HEXLOWER`<sup>1.27.0</sup> | `"0123456789abcdef"` |
| `HEXUPPER`<sup>1.27.0</sup> | `"0123456789ABCDEF"` |
| `CLEAR`<sup>1.37.0</sup> | `"\ec"` |
| `NORMAL`<sup>1.37.0</sup> | `"\e[0m"` |
| `BOLD`<sup>1.37.0</sup> | `"\e[1m"` |
| `ITALIC`<sup>1.37.0</sup> | `"\e[3m"` |
| `UNDERLINE`<sup>1.37.0</sup> | `"\e[4m"` |
| `INVERT`<sup>1.37.0</sup> | `"\e[7m"` |
| `HIDE`<sup>1.37.0</sup> | `"\e[8m"` |
| `STRIKETHROUGH`<sup>1.37.0</sup> | `"\e[9m"` |
| `BLACK`<sup>1.37.0</sup> | `"\e[30m"` |
| `RED`<sup>1.37.0</sup> | `"\e[31m"` |
| `GREEN`<sup>1.37.0</sup> | `"\e[32m"` |
| `YELLOW`<sup>1.37.0</sup> | `"\e[33m"` |
| `BLUE`<sup>1.37.0</sup> | `"\e[34m"` |
| `MAGENTA`<sup>1.37.0</sup> | `"\e[35m"` |
| `CYAN`<sup>1.37.0</sup> | `"\e[36m"` |
| `WHITE`<sup>1.37.0</sup> | `"\e[37m"` |
| `BG_BLACK`<sup>1.37.0</sup> | `"\e[40m"` |
| `BG_RED`<sup>1.37.0</sup> | `"\e[41m"` |
| `BG_GREEN`<sup>1.37.0</sup> | `"\e[42m"` |
| `BG_YELLOW`<sup>1.37.0</sup> | `"\e[43m"` |
| `BG_BLUE`<sup>1.37.0</sup> | `"\e[44m"` |
| `BG_MAGENTA`<sup>1.37.0</sup> | `"\e[45m"` |
| `BG_CYAN`<sup>1.37.0</sup> | `"\e[46m"` |
| `BG_WHITE`<sup>1.37.0</sup> | `"\e[47m"` |
| Name | Value | Value on Windows |
|---|---|---|
| `HEX`<sup>1.27.0</sup> | `"0123456789abcdef"` | |
| `HEXLOWER`<sup>1.27.0</sup> | `"0123456789abcdef"` | |
| `HEXUPPER`<sup>1.27.0</sup> | `"0123456789ABCDEF"` | |
| `PATH_SEP`<sup>1.41.0</sup> | `"/"` | "\" |
| `PATH_VAR_SEP`<sup>1.41.0</sup> | `":"` | ";" |
| `CLEAR`<sup>1.37.0</sup> | `"\ec"` | |
| `NORMAL`<sup>1.37.0</sup> | `"\e[0m"` | |
| `BOLD`<sup>1.37.0</sup> | `"\e[1m"` | |
| `ITALIC`<sup>1.37.0</sup> | `"\e[3m"` | |
| `UNDERLINE`<sup>1.37.0</sup> | `"\e[4m"` | |
| `INVERT`<sup>1.37.0</sup> | `"\e[7m"` | |
| `HIDE`<sup>1.37.0</sup> | `"\e[8m"` | |
| `STRIKETHROUGH`<sup>1.37.0</sup> | `"\e[9m"` | |
| `BLACK`<sup>1.37.0</sup> | `"\e[30m"` | |
| `RED`<sup>1.37.0</sup> | `"\e[31m"` | |
| `GREEN`<sup>1.37.0</sup> | `"\e[32m"` | |
| `YELLOW`<sup>1.37.0</sup> | `"\e[33m"` | |
| `BLUE`<sup>1.37.0</sup> | `"\e[34m"` | |
| `MAGENTA`<sup>1.37.0</sup> | `"\e[35m"` | |
| `CYAN`<sup>1.37.0</sup> | `"\e[36m"` | |
| `WHITE`<sup>1.37.0</sup> | `"\e[37m"` | |
| `BG_BLACK`<sup>1.37.0</sup> | `"\e[40m"` | |
| `BG_RED`<sup>1.37.0</sup> | `"\e[41m"` | |
| `BG_GREEN`<sup>1.37.0</sup> | `"\e[42m"` | |
| `BG_YELLOW`<sup>1.37.0</sup> | `"\e[43m"` | |
| `BG_BLUE`<sup>1.37.0</sup> | `"\e[44m"` | |
| `BG_MAGENTA`<sup>1.37.0</sup> | `"\e[45m"` | |
| `BG_CYAN`<sup>1.37.0</sup> | `"\e[46m"` | |
| `BG_WHITE`<sup>1.37.0</sup> | `"\e[47m"` | |
```just
@foo:
@ -2899,6 +2911,46 @@ the final argument. For example, on Windows, if a recipe starts with `#! py`,
the final command the OS runs will be something like
`py C:\Temp\PATH_TO_SAVED_RECIPE_BODY`.
### Script Recipes
Recipes with a `[script(COMMAND)]`<sup>1.32.0</sup> attribute are run as
scripts interpreted by `COMMAND`. This avoids some of the issues with shebang
recipes, such as the use of `cygpath` on Windows, the need to use
`/usr/bin/env`, inconsistencies in shebang line splitting across Unix OSs, and
requiring a temporary directory from which files can be executed.
Recipes with an empty `[script]` attribute are executed with the value of `set
script-interpreter := […]`<sup>1.33.0</sup>, defaulting to `sh -eu`, and *not*
the value of `set shell`.
The body of the recipe is evaluated, written to disk in the temporary
directory, and run by passing its path as an argument to `COMMAND`.
The `[script(…)]` attribute is unstable, so you'll need to use `set unstable`,
set the `JUST_UNSTABLE` environment variable, or pass `--unstable` on the
command line.
### Script and Shebang Recipe Temporary Files
Both script and shebang recipes write the recipe body to a temporary file for
execution. Script recipes execute that file by passing it to a command, while
shebang recipes execute the file directly. Shebang recipe execution will fail
if the filesystem containing the temporary file is mounted with `noexec` or is
otherwise non-executable.
The directory that `just` writes temporary files to may be configured in a
number of ways, from highest to lowest precedence:
- Globally with the `--tempdir` command-line option or the `JUST_TEMPDIR`
environment variable<sup>1.41.0</sup>.
- On a per-module basis with the `tempdir` setting.
- Globally on Linux with the `XDG_RUNTIME_DIR` environment variable.
- Falling back to the directory returned by
[std::env::temp_dir](https://doc.rust-lang.org/std/env/fn.temp_dir.html).
### Python Recipes with `uv`
[`uv`](https://github.com/astral-sh/uv) is an excellent cross-platform python
@ -2934,23 +2986,6 @@ hello:
print("Hello from Python!")
```
### Script Recipes
Recipes with a `[script(COMMAND)]`<sup>1.32.0</sup> attribute are run as
scripts interpreted by `COMMAND`. This avoids some of the issues with shebang
recipes, such as the use of `cygpath` on Windows, the need to use
`/usr/bin/env`, and inconsistencies in shebang line splitting across Unix OSs.
Recipes with an empty `[script]` attribute are executed with the value of `set
script-interpreter := […]`<sup>1.33.0</sup>, defaulting to `sh -eu`, and *not*
the value of `set shell`.
The body of the recipe is evaluated, written to disk in the temporary
directory, and run by passing its path as an argument to `COMMAND`.
The `[script(…)]` attribute is unstable, so you'll need to use `set unstable`,
set the `JUST_UNSTABLE` environment variable, or pass `--unstable` on the
command line.
### Safer Bash Shebang Recipes
@ -4029,7 +4064,7 @@ When a child process *is* running, `just` will wait until it terminates, to
avoid leaving it behind.
Additionally, on receipt of `SIGTERM`, `just` will forward `SIGTERM` to any
running children<sup>master</sup>, since unlike other fatal signals, `SIGTERM`,
running children<sup>1.41.0</sup>, since unlike other fatal signals, `SIGTERM`,
was likely sent to `just` alone.
Regardless of whether a child process terminates successfully after `just`
@ -4043,7 +4078,7 @@ user types `ctrl-t` on
operating systems, including MacOS, but not Linux.
`just` responds by printing a list of all child process IDs and
commands<sup>master</sup>.
commands<sup>1.41.0</sup>.
#### Windows
@ -4430,7 +4465,9 @@ and checking the program's stdout, stderr, and exit code .
5. Implement the feature.
6. Run `just ci` to make sure that all tests, lints, and checks pass.
6. Run `just ci` to make sure that all tests, lints, and checks pass. Requires
[mdBook](https://github.com/rust-lang/mdBook) and
[mdbook-linkcheck](https://github.com/Michael-F-Bryan/mdbook-linkcheck).
7. Open a PR with the new code that is editable by maintainers. PRs often
require rebasing and minor tweaks. If the PR is not editable by maintainers,

View file

@ -36,6 +36,7 @@ pub(crate) struct Config {
pub(crate) shell_args: Option<Vec<String>>,
pub(crate) shell_command: bool,
pub(crate) subcommand: Subcommand,
pub(crate) tempdir: Option<PathBuf>,
pub(crate) timestamp: bool,
pub(crate) timestamp_format: String,
pub(crate) unsorted: bool,
@ -116,6 +117,7 @@ mod arg {
pub(crate) const SHELL: &str = "SHELL";
pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
pub(crate) const TEMPDIR: &str = "TEMPDIR";
pub(crate) const TIMESTAMP: &str = "TIMESTAMP";
pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP-FORMAT";
pub(crate) const UNSORTED: &str = "UNSORTED";
@ -373,6 +375,14 @@ impl Config {
.action(ArgAction::SetTrue)
.help("Invoke <COMMAND> with the shell used to run recipe lines and backticks"),
)
.arg(
Arg::new(arg::TEMPDIR)
.action(ArgAction::Set)
.env("JUST_TEMPDIR")
.long("tempdir")
.value_parser(value_parser!(PathBuf))
.help("Save temporary files to <TEMPDIR>."),
)
.arg(
Arg::new(arg::TIMESTAMP)
.action(ArgAction::SetTrue)
@ -792,6 +802,7 @@ impl Config {
},
shell_command: matches.get_flag(arg::SHELL_COMMAND),
subcommand,
tempdir: matches.get_one::<PathBuf>(arg::TEMPDIR).map(Into::into),
timestamp: matches.get_flag(arg::TIMESTAMP),
timestamp_format: matches
.get_one::<String>(arg::TIMESTAMP_FORMAT)

View file

@ -1,41 +1,53 @@
use super::*;
const CONSTANTS: [(&str, &str, &str); 27] = [
("HEX", "0123456789abcdef", "1.27.0"),
("HEXLOWER", "0123456789abcdef", "1.27.0"),
("HEXUPPER", "0123456789ABCDEF", "1.27.0"),
("CLEAR", "\x1bc", "master"),
("NORMAL", "\x1b[0m", "master"),
("BOLD", "\x1b[1m", "master"),
("ITALIC", "\x1b[3m", "master"),
("UNDERLINE", "\x1b[4m", "master"),
("INVERT", "\x1b[7m", "master"),
("HIDE", "\x1b[8m", "master"),
("STRIKETHROUGH", "\x1b[9m", "master"),
("BLACK", "\x1b[30m", "master"),
("RED", "\x1b[31m", "master"),
("GREEN", "\x1b[32m", "master"),
("YELLOW", "\x1b[33m", "master"),
("BLUE", "\x1b[34m", "master"),
("MAGENTA", "\x1b[35m", "master"),
("CYAN", "\x1b[36m", "master"),
("WHITE", "\x1b[37m", "master"),
("BG_BLACK", "\x1b[40m", "master"),
("BG_RED", "\x1b[41m", "master"),
("BG_GREEN", "\x1b[42m", "master"),
("BG_YELLOW", "\x1b[43m", "master"),
("BG_BLUE", "\x1b[44m", "master"),
("BG_MAGENTA", "\x1b[45m", "master"),
("BG_CYAN", "\x1b[46m", "master"),
("BG_WHITE", "\x1b[47m", "master"),
const CONSTANTS: &[(&str, &str, Option<&str>, &str)] = &[
("HEX", "0123456789abcdef", None, "1.27.0"),
("HEXLOWER", "0123456789abcdef", None, "1.27.0"),
("HEXUPPER", "0123456789ABCDEF", None, "1.27.0"),
("PATH_SEP", "/", Some("\\"), "1.41.0"),
("PATH_VAR_SEP", ":", Some(";"), "1.41.0"),
("CLEAR", "\x1bc", None, "1.37.0"),
("NORMAL", "\x1b[0m", None, "1.37.0"),
("BOLD", "\x1b[1m", None, "1.37.0"),
("ITALIC", "\x1b[3m", None, "1.37.0"),
("UNDERLINE", "\x1b[4m", None, "1.37.0"),
("INVERT", "\x1b[7m", None, "1.37.0"),
("HIDE", "\x1b[8m", None, "1.37.0"),
("STRIKETHROUGH", "\x1b[9m", None, "1.37.0"),
("BLACK", "\x1b[30m", None, "1.37.0"),
("RED", "\x1b[31m", None, "1.37.0"),
("GREEN", "\x1b[32m", None, "1.37.0"),
("YELLOW", "\x1b[33m", None, "1.37.0"),
("BLUE", "\x1b[34m", None, "1.37.0"),
("MAGENTA", "\x1b[35m", None, "1.37.0"),
("CYAN", "\x1b[36m", None, "1.37.0"),
("WHITE", "\x1b[37m", None, "1.37.0"),
("BG_BLACK", "\x1b[40m", None, "1.37.0"),
("BG_RED", "\x1b[41m", None, "1.37.0"),
("BG_GREEN", "\x1b[42m", None, "1.37.0"),
("BG_YELLOW", "\x1b[43m", None, "1.37.0"),
("BG_BLUE", "\x1b[44m", None, "1.37.0"),
("BG_MAGENTA", "\x1b[45m", None, "1.37.0"),
("BG_CYAN", "\x1b[46m", None, "1.37.0"),
("BG_WHITE", "\x1b[47m", None, "1.37.0"),
];
pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
static MAP: OnceLock<HashMap<&str, &str>> = OnceLock::new();
MAP.get_or_init(|| {
CONSTANTS
.into_iter()
.map(|(name, value, _version)| (name, value))
.iter()
.copied()
.map(|(name, unix, windows, _version)| {
(
name,
if cfg!(windows) {
windows.unwrap_or(unix)
} else {
unix
},
)
})
.collect()
})
}
@ -46,13 +58,26 @@ mod tests {
#[test]
fn readme_table() {
println!("| Name | Value |");
println!("|------|-------------|");
for (name, value, version) in CONSTANTS {
println!(
"| `{name}`<sup>{version}</sup> | `\"{}\"` |",
value.replace('\x1b', "\\e")
);
let mut table = Vec::<String>::new();
table.push("| Name | Value | Value on Windows |".into());
table.push("|---|---|---|".into());
for (name, unix, windows, version) in CONSTANTS {
table.push(format!(
"| `{name}`<sup>{version}</sup> | `\"{}\"` | {} |",
unix.replace('\x1b', "\\e"),
windows
.map(|value| format!("\"{value}\""))
.unwrap_or_default(),
));
}
let table = table.join("\n");
let readme = fs::read_to_string("README.md").unwrap();
assert!(
readme.contains(&table),
"could not find table in readme:\n{table}",
);
}
}

View file

@ -10,6 +10,36 @@ pub(crate) struct ExecutionContext<'src: 'run, 'run> {
}
impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
pub(crate) fn tempdir<D>(&self, recipe: &Recipe<'src, D>) -> RunResult<'src, TempDir> {
let mut tempdir_builder = tempfile::Builder::new();
tempdir_builder.prefix("just-");
if let Some(tempdir) = &self.config.tempdir {
tempdir_builder.tempdir_in(self.search.working_directory.join(tempdir))
} else {
match &self.module.settings.tempdir {
Some(tempdir) => tempdir_builder.tempdir_in(self.search.working_directory.join(tempdir)),
None => {
if let Some(runtime_dir) = dirs::runtime_dir() {
let path = runtime_dir.join("just");
fs::create_dir_all(&path).map_err(|io_error| Error::RuntimeDirIo {
io_error,
path: path.clone(),
})?;
tempdir_builder.tempdir_in(path)
} else {
tempdir_builder.tempdir()
}
}
}
}
.map_err(|error| Error::TempdirIo {
recipe: recipe.name(),
io_error: error,
})
}
pub(crate) fn working_directory(&self) -> PathBuf {
let base = if self.module.is_submodule() {
&self.module.working_directory

View file

@ -9,6 +9,7 @@ pub(crate) enum Keyword {
Assert,
DotenvFilename,
DotenvLoad,
DotenvOverride,
DotenvPath,
DotenvRequired,
Else,

View file

@ -133,7 +133,7 @@ pub(crate) use {
vec,
},
strum::{Display, EnumDiscriminants, EnumString, IntoStaticStr},
tempfile::tempfile,
tempfile::{tempfile, TempDir},
typed_arena::Arena,
unicode_width::{UnicodeWidthChar, UnicodeWidthStr},
};

View file

@ -16,9 +16,10 @@ pub(crate) fn load_dotenv(
.or(settings.dotenv_path.as_ref());
if !settings.dotenv_load
&& !settings.dotenv_override
&& !settings.dotenv_required
&& dotenv_filename.is_none()
&& dotenv_path.is_none()
&& !settings.dotenv_required
{
return Ok(BTreeMap::new());
}
@ -26,7 +27,7 @@ pub(crate) fn load_dotenv(
if let Some(path) = dotenv_path {
let path = working_directory.join(path);
if path.is_file() {
return load_from_file(&path);
return load_from_file(&path, settings);
}
}
@ -35,7 +36,7 @@ pub(crate) fn load_dotenv(
for directory in working_directory.ancestors() {
let path = directory.join(filename);
if path.is_file() {
return load_from_file(&path);
return load_from_file(&path, settings);
}
}
@ -46,12 +47,15 @@ pub(crate) fn load_dotenv(
}
}
fn load_from_file(path: &Path) -> RunResult<'static, BTreeMap<String, String>> {
fn load_from_file(
path: &Path,
settings: &Settings,
) -> RunResult<'static, BTreeMap<String, String>> {
let iter = dotenvy::from_path_iter(path)?;
let mut dotenv = BTreeMap::new();
for result in iter {
let (key, value) = result?;
if env::var_os(&key).is_none() {
if settings.dotenv_override || env::var_os(&key).is_none() {
dotenv.insert(key, value);
}
}

View file

@ -303,6 +303,7 @@ impl<'src> Node<'src> for Set<'src> {
Setting::AllowDuplicateRecipes(value)
| Setting::AllowDuplicateVariables(value)
| Setting::DotenvLoad(value)
| Setting::DotenvOverride(value)
| Setting::DotenvRequired(value)
| Setting::Export(value)
| Setting::Fallback(value)

View file

@ -1119,6 +1119,7 @@ impl<'run, 'src> Parser<'run, 'src> {
Some(Setting::AllowDuplicateVariables(self.parse_set_bool()?))
}
Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)),
Keyword::DotenvOverride => Some(Setting::DotenvOverride(self.parse_set_bool()?)),
Keyword::DotenvRequired => Some(Setting::DotenvRequired(self.parse_set_bool()?)),
Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)),
Keyword::Fallback => Some(Setting::Fallback(self.parse_set_bool()?)),

View file

@ -390,27 +390,8 @@ impl<'src, D> Recipe<'src, D> {
Executor::Shebang(shebang)
};
let mut tempdir_builder = tempfile::Builder::new();
tempdir_builder.prefix("just-");
let tempdir = match &context.module.settings.tempdir {
Some(tempdir) => tempdir_builder.tempdir_in(context.search.working_directory.join(tempdir)),
None => {
if let Some(runtime_dir) = dirs::runtime_dir() {
let path = runtime_dir.join("just");
fs::create_dir_all(&path).map_err(|io_error| Error::RuntimeDirIo {
io_error,
path: path.clone(),
})?;
tempdir_builder.tempdir_in(path)
} else {
tempdir_builder.tempdir()
}
}
}
.map_err(|error| Error::TempdirIo {
recipe: self.name(),
io_error: error,
})?;
let tempdir = context.tempdir(self)?;
let mut path = tempdir.path().to_path_buf();
let extension = self.attributes.iter().find_map(|attribute| {

View file

@ -11,23 +11,18 @@ pub(crate) struct Search {
}
impl Search {
fn global_justfile_paths() -> Vec<PathBuf> {
fn global_justfile_paths() -> Vec<(PathBuf, &'static str)> {
let mut paths = Vec::new();
if let Some(config_dir) = dirs::config_dir() {
paths.push(config_dir.join("just").join(DEFAULT_JUSTFILE_NAME));
paths.push((config_dir.join("just"), DEFAULT_JUSTFILE_NAME));
}
if let Some(home_dir) = dirs::home_dir() {
paths.push(
home_dir
.join(".config")
.join("just")
.join(DEFAULT_JUSTFILE_NAME),
);
paths.push((home_dir.join(".config").join("just"), DEFAULT_JUSTFILE_NAME));
for justfile_name in JUSTFILE_NAMES {
paths.push(home_dir.join(justfile_name));
paths.push((home_dir.clone(), justfile_name));
}
}
@ -51,11 +46,7 @@ impl Search {
})
}
SearchConfig::GlobalJustfile => Ok(Self {
justfile: Self::global_justfile_paths()
.iter()
.find(|path| path.exists())
.cloned()
.ok_or(SearchError::GlobalJustfileNotFound)?,
justfile: Self::find_global_justfile()?,
working_directory: Self::project_root(invocation_directory)?,
}),
SearchConfig::WithJustfile { justfile } => {
@ -76,6 +67,26 @@ impl Search {
}
}
fn find_global_justfile() -> SearchResult<PathBuf> {
for (directory, filename) in Self::global_justfile_paths() {
if let Ok(read_dir) = fs::read_dir(&directory) {
for entry in read_dir {
let entry = entry.map_err(|io_error| SearchError::Io {
io_error,
directory: directory.clone(),
})?;
if let Some(candidate) = entry.file_name().to_str() {
if candidate.eq_ignore_ascii_case(filename) {
return Ok(entry.path());
}
}
}
}
}
Err(SearchError::GlobalJustfileNotFound)
}
/// Find justfile starting from parent directory of current justfile
pub(crate) fn search_parent_directory(&self) -> SearchResult<Self> {
let parent = self
@ -150,6 +161,7 @@ impl Search {
io_error,
directory: directory.to_owned(),
})?;
for entry in entries {
let entry = entry.map_err(|io_error| SearchError::Io {
io_error,

View file

@ -6,6 +6,7 @@ pub(crate) enum Setting<'src> {
AllowDuplicateVariables(bool),
DotenvFilename(StringLiteral<'src>),
DotenvLoad(bool),
DotenvOverride(bool),
DotenvPath(StringLiteral<'src>),
DotenvRequired(bool),
Export(bool),
@ -29,6 +30,7 @@ impl Display for Setting<'_> {
Self::AllowDuplicateRecipes(value)
| Self::AllowDuplicateVariables(value)
| Self::DotenvLoad(value)
| Self::DotenvOverride(value)
| Self::DotenvRequired(value)
| Self::Export(value)
| Self::Fallback(value)

View file

@ -11,6 +11,7 @@ pub(crate) struct Settings<'src> {
pub(crate) allow_duplicate_variables: bool,
pub(crate) dotenv_filename: Option<String>,
pub(crate) dotenv_load: bool,
pub(crate) dotenv_override: bool,
pub(crate) dotenv_path: Option<PathBuf>,
pub(crate) dotenv_required: bool,
pub(crate) export: bool,
@ -50,6 +51,9 @@ impl<'src> Settings<'src> {
Setting::DotenvPath(path) => {
settings.dotenv_path = Some(PathBuf::from(path.cooked));
}
Setting::DotenvOverride(dotenv_overrride) => {
settings.dotenv_override = dotenv_overrride;
}
Setting::DotenvRequired(dotenv_required) => {
settings.dotenv_required = dotenv_required;
}

View file

@ -134,7 +134,6 @@ fn no_choosable_recipes() {
}
#[test]
#[ignore]
fn multiple_recipes() {
Test::new()
.arg("--choose")

View file

@ -5,6 +5,12 @@ fn constants_are_defined() {
assert_eval_eq("HEX", "0123456789abcdef");
}
#[test]
fn constants_can_have_different_values_on_windows() {
assert_eval_eq("PATH_SEP", if cfg!(windows) { "\\" } else { "/" });
assert_eval_eq("PATH_VAR_SEP", if cfg!(windows) { ";" } else { ":" });
}
#[test]
fn constants_are_defined_in_recipe_bodies() {
Test::new()

View file

@ -362,7 +362,7 @@ fn no_dotenv() {
}
#[test]
fn dotenv_env_var_override() {
fn dotenv_env_var_default_no_override() {
Test::new()
.justfile(
"
@ -377,6 +377,41 @@ fn dotenv_env_var_override() {
.run();
}
#[test]
fn dotenv_env_var_override() {
Test::new()
.justfile(
"
set dotenv-load
set dotenv-override := true
echo:
echo $DOTENV_KEY
",
)
.write(".env", "DOTENV_KEY=dotenv-value")
.env("DOTENV_KEY", "not-the-dotenv-value")
.stdout("dotenv-value\n")
.stderr("echo $DOTENV_KEY\n")
.run();
}
#[test]
fn dotenv_env_var_override_no_load() {
Test::new()
.justfile(
"
set dotenv-override := true
echo:
echo $DOTENV_KEY
",
)
.write(".env", "DOTENV_KEY=dotenv-value")
.env("DOTENV_KEY", "not-the-dotenv-value")
.stdout("dotenv-value\n")
.stderr("echo $DOTENV_KEY\n")
.run();
}
#[test]
fn dotenv_path_usable_from_subdir() {
Test::new()

View file

@ -63,3 +63,20 @@ fn unix() {
.stdout("bar\n")
.run();
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn case_insensitive() {
let tempdir = tempdir();
let path = tempdir.path().to_owned();
Test::with_tempdir(tempdir)
.no_justfile()
.test_round_trip(false)
.write("just/JUSTFILE", "@default:\n echo foo")
.env("XDG_CONFIG_HOME", path.to_str().unwrap())
.args(["--global-justfile"])
.stdout("foo\n")
.run();
}

View file

@ -79,6 +79,7 @@ struct Settings<'a> {
allow_duplicate_variables: bool,
dotenv_filename: Option<&'a str>,
dotenv_load: bool,
dotenv_override: bool,
dotenv_path: Option<&'a str>,
dotenv_required: bool,
export: bool,

View file

@ -86,9 +86,9 @@ fn interrupt_command() {
interrupt_test(&["--command", "sleep", "1"], "");
}
/// This test is sensitive to the process signal mask. Programs like
/// `watchexec` and `cargo-watch` change the signal mask to ignore `SIGHUP`,
/// which causes this test to fail.
// This test is ignored because it is sensitive to the process signal mask.
// Programs like `watchexec` and `cargo-watch` change the signal mask to ignore
// `SIGHUP`, which causes this test to fail.
#[test]
#[ignore]
fn forwarding() {
@ -155,8 +155,6 @@ fn forwarding() {
}
}
/// This test is ignored because it includes a 500ms wait, and because signal
/// tests are often flakey.
#[test]
#[ignore]
#[cfg(any(

View file

@ -16,7 +16,7 @@ pub(crate) fn tempdir() -> TempDir {
}
#[test]
fn test_tempdir_is_set() {
fn setting() {
Test::new()
.justfile(
"
@ -28,10 +28,46 @@ fn test_tempdir_is_set() {
)
.shell(false)
.tree(tree! {
foo: {
bar: {
}
})
.current_dir("foo")
.current_dir("bar")
.stdout(if cfg!(windows) {
"
cat just*/foo
"
} else {
"
#!/usr/bin/env bash
cat just*/foo
"
})
.run();
}
#[test]
fn argument_overrides_setting() {
Test::new()
.args(["--tempdir", "."])
.justfile(
"
set tempdir := 'hello'
foo:
#!/usr/bin/env bash
cat just*/foo
",
)
.shell(false)
.tree(tree! {
bar: {
}
})
.current_dir("bar")
.stdout(if cfg!(windows) {
"