Allow enabling unstable features with set unstable (#2237)

This commit is contained in:
Casey Rodarmor 2024-07-07 20:45:03 -07:00 committed by GitHub
parent 564814208f
commit d6669e0b97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 136 additions and 46 deletions

View File

@ -379,11 +379,11 @@ There will never be a `just` 2.0. Any desirable backwards-incompatible changes
will be opt-in on a per-`justfile` basis, so users may migrate at their
leisure.
Features that aren't yet ready for stabilization are gated behind the
`--unstable` flag. Features enabled by `--unstable` may change in backwards
incompatible ways at any time. Unstable features can also be enabled by setting
the environment variable `JUST_UNSTABLE` to any value other than `false`, `0`,
or the empty string.
Features that aren't yet ready for stabilization are marked as unstable and may
be changed or removed at any time. Using unstable features produces an error by
default, which can be suppressed with by passing the `--unstable` flag,
`set unstable`, or setting the environment variable `JUST_UNSTABLE`, to any
value other than `false`, `0`, or the empty string.
Editor Support
--------------
@ -820,6 +820,7 @@ foo:
| `positional-arguments` | boolean | `false` | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. |
| `tempdir` | string | - | Create temporary directories in `tempdir` instead of the system default temporary directory. |
| `unstable`<sup>master</sup> | boolean | `false` | Enable unstable features. |
| `windows-powershell` | boolean | `false` | Use PowerShell on Windows as default shell. (Deprecated. Use `windows-shell` instead. |
| `windows-shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. |
@ -3154,8 +3155,8 @@ Missing source files for optional imports do not produce an error.
### Modules<sup>1.19.0</sup>
A `justfile` can declare modules using `mod` statements. `mod` statements are
currently unstable, so you'll need to use the `--unstable` flag, or set the
`JUST_UNSTABLE` environment variable to use them.
currently unstable, so you'll need to use the `--unstable` flag,
`set unstable`, or set the `JUST_UNSTABLE` environment variable to use them.
If you have the following `justfile`:

View File

@ -37,6 +37,8 @@ impl<'src> Analyzer<'src> {
let mut warnings = Vec::new();
let mut unstable = BTreeSet::new();
let mut modules: Table<Justfile> = Table::new();
let mut unexports: HashSet<String> = HashSet::new();
@ -92,6 +94,8 @@ impl<'src> Analyzer<'src> {
doc,
..
} => {
unstable.insert(Unstable::Modules);
if let Some(absolute) = absolute {
define(*name, "module", false)?;
modules.insert(Self::analyze(
@ -194,6 +198,7 @@ impl<'src> Analyzer<'src> {
settings,
source: root.into(),
unexports,
unstable,
warnings,
})
}

View File

@ -252,7 +252,7 @@ mod tests {
fs::write(&path, "mod foo").unwrap();
fs::create_dir(tempdir.path().join("foo")).unwrap();
fs::write(tempdir.path().join("foo/mod.just"), "bar:").unwrap();
let compilation = Compiler::compile(true, &loader, &path).unwrap();
let compilation = Compiler::compile(&loader, &path).unwrap();
assert_eq!(
ArgumentParser::parse_arguments(&compilation.justfile, &["foo", "bar"]).unwrap(),
@ -271,7 +271,7 @@ mod tests {
fs::write(&path, "mod foo").unwrap();
fs::create_dir(tempdir.path().join("foo")).unwrap();
fs::write(tempdir.path().join("foo/mod.just"), "bar:").unwrap();
let compilation = Compiler::compile(true, &loader, &path).unwrap();
let compilation = Compiler::compile(&loader, &path).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &["foo", "zzz"]).unwrap_err(),
@ -289,7 +289,7 @@ mod tests {
tempdir.write("foo.just", "bar:");
let loader = Loader::new();
let compilation = Compiler::compile(true, &loader, &tempdir.path().join("justfile")).unwrap();
let compilation = Compiler::compile(&loader, &tempdir.path().join("justfile")).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &["foo::zzz"]).unwrap_err(),
@ -307,7 +307,7 @@ mod tests {
tempdir.write("foo.just", "bar:");
let loader = Loader::new();
let compilation = Compiler::compile(true, &loader, &tempdir.path().join("justfile")).unwrap();
let compilation = Compiler::compile(&loader, &tempdir.path().join("justfile")).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &["foo::bar::baz"]).unwrap_err(),
@ -323,7 +323,7 @@ mod tests {
tempdir.write("justfile", "");
let loader = Loader::new();
let compilation = Compiler::compile(true, &loader, &tempdir.path().join("justfile")).unwrap();
let compilation = Compiler::compile(&loader, &tempdir.path().join("justfile")).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &[]).unwrap_err(),
@ -337,7 +337,7 @@ mod tests {
tempdir.write("justfile", "foo bar:");
let loader = Loader::new();
let compilation = Compiler::compile(true, &loader, &tempdir.path().join("justfile")).unwrap();
let compilation = Compiler::compile(&loader, &tempdir.path().join("justfile")).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &[]).unwrap_err(),
@ -355,7 +355,7 @@ mod tests {
tempdir.write("foo.just", "bar:");
let loader = Loader::new();
let compilation = Compiler::compile(true, &loader, &tempdir.path().join("justfile")).unwrap();
let compilation = Compiler::compile(&loader, &tempdir.path().join("justfile")).unwrap();
assert_matches!(
ArgumentParser::parse_arguments(&compilation.justfile, &[]).unwrap_err(),

View File

@ -4,14 +4,13 @@ pub(crate) struct Compiler;
impl Compiler {
pub(crate) fn compile<'src>(
unstable: bool,
loader: &'src Loader,
root: &Path,
) -> RunResult<'src, Compilation<'src>> {
let mut asts = HashMap::<PathBuf, Ast>::new();
let mut loaded = Vec::new();
let mut paths = HashMap::<PathBuf, PathBuf>::new();
let mut srcs = HashMap::<PathBuf, &str>::new();
let mut loaded = Vec::new();
let mut stack = Vec::new();
stack.push(Source::root(root));
@ -42,12 +41,6 @@ impl Compiler {
relative,
..
} => {
if !unstable {
return Err(Error::Unstable {
message: "Modules are currently unstable.".into(),
});
}
let parent = current.path.parent().unwrap();
let import = if let Some(relative) = relative {
@ -112,9 +105,9 @@ impl Compiler {
Ok(Compilation {
asts,
srcs,
justfile,
root: root.into(),
srcs,
})
}
@ -225,7 +218,7 @@ recipe_b: recipe_c
let loader = Loader::new();
let justfile_a_path = tmp.path().join("justfile");
let compilation = Compiler::compile(false, &loader, &justfile_a_path).unwrap();
let compilation = Compiler::compile(&loader, &justfile_a_path).unwrap();
assert_eq!(compilation.root_src(), justfile_a);
}
@ -242,7 +235,7 @@ recipe_b: recipe_c
let loader = Loader::new();
let justfile_a_path = tmp.path().join("justfile");
let loader_output = Compiler::compile(false, &loader, &justfile_a_path).unwrap_err();
let loader_output = Compiler::compile(&loader, &justfile_a_path).unwrap_err();
assert_matches!(loader_output, Error::CircularImport { current, import }
if current == tmp.path().join("subdir").join("b").lexiclean() &&

View File

@ -460,7 +460,7 @@ impl<'src> ColorDisplay for Error<'src> {
}
}
Unstable { message } => {
write!(f, "{message} Invoke `just` with the `--unstable` flag to enable unstable features.")?;
write!(f, "{message} Invoke `just` with `--unstable`, set the `JUST_UNSTABLE` environment variable, or add `set unstable` to your `justfile` to enable unstable features.")?;
}
WriteJustfile { justfile, io_error } => {
let justfile = justfile.display();

View File

@ -27,6 +27,8 @@ pub(crate) struct Justfile<'src> {
pub(crate) source: PathBuf,
pub(crate) unexports: HashSet<String>,
pub(crate) warnings: Vec<Warning>,
#[serde(skip)]
pub(crate) unstable: BTreeSet<Unstable>,
}
impl<'src> Justfile<'src> {
@ -225,6 +227,22 @@ impl<'src> Justfile<'src> {
Ok(())
}
pub(crate) fn check_unstable(&self, config: &Config) -> RunResult<'src> {
if !config.unstable && !self.settings.unstable {
if let Some(unstable) = self.unstable.iter().next() {
return Err(Error::Unstable {
message: unstable.message(),
});
}
}
for module in self.modules.values() {
module.check_unstable(config)?;
}
Ok(())
}
pub(crate) fn get_alias(&self, name: &str) -> Option<&Alias<'src>> {
self.aliases.get(name)
}

View File

@ -26,6 +26,7 @@ pub(crate) enum Keyword {
Tempdir,
True,
Unexport,
Unstable,
WindowsPowershell,
WindowsShell,
X,

View File

@ -42,8 +42,8 @@ pub(crate) use {
shell::Shell, show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
verbosity::Verbosity, warning::Warning,
unresolved_recipe::UnresolvedRecipe, unstable::Unstable, use_color::UseColor,
variables::Variables, verbosity::Verbosity, warning::Warning,
},
camino::Utf8Path,
clap::ValueEnum,
@ -204,6 +204,7 @@ mod token_kind;
mod unindent;
mod unresolved_dependency;
mod unresolved_recipe;
mod unstable;
mod use_color;
mod variables;
mod verbosity;

View File

@ -294,6 +294,7 @@ impl<'src> Node<'src> for Set<'src> {
| Setting::Fallback(value)
| Setting::PositionalArguments(value)
| Setting::Quiet(value)
| Setting::Unstable(value)
| Setting::WindowsPowerShell(value)
| Setting::IgnoreComments(value) => {
set.push_mut(value.to_string());

View File

@ -936,6 +936,7 @@ impl<'run, 'src> Parser<'run, 'src> {
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
Keyword::Quiet => Some(Setting::Quiet(self.parse_set_bool()?)),
Keyword::Unstable => Some(Setting::Unstable(self.parse_set_bool()?)),
Keyword::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)),
_ => None,
};

View File

@ -15,6 +15,7 @@ pub(crate) enum Setting<'src> {
Quiet(bool),
Shell(Shell<'src>),
Tempdir(String),
Unstable(bool),
WindowsPowerShell(bool),
WindowsShell(Shell<'src>),
}
@ -31,6 +32,7 @@ impl<'src> Display for Setting<'src> {
| Self::IgnoreComments(value)
| Self::PositionalArguments(value)
| Self::Quiet(value)
| Self::Unstable(value)
| Self::WindowsPowerShell(value) => write!(f, "{value}"),
Self::Shell(shell) | Self::WindowsShell(shell) => write!(f, "{shell}"),
Self::DotenvFilename(value) | Self::DotenvPath(value) | Self::Tempdir(value) => {

View File

@ -20,6 +20,7 @@ pub(crate) struct Settings<'src> {
pub(crate) quiet: bool,
pub(crate) shell: Option<Shell<'src>>,
pub(crate) tempdir: Option<String>,
pub(crate) unstable: bool,
pub(crate) windows_powershell: bool,
pub(crate) windows_shell: Option<Shell<'src>>,
}
@ -66,6 +67,9 @@ impl<'src> Settings<'src> {
Setting::Shell(shell) => {
settings.shell = Some(shell);
}
Setting::Unstable(unstable) => {
settings.unstable = unstable;
}
Setting::WindowsPowerShell(windows_powershell) => {
settings.windows_powershell = windows_powershell;
}

View File

@ -190,7 +190,9 @@ impl Subcommand {
loader: &'src Loader,
search: &Search,
) -> RunResult<'src, Compilation<'src>> {
let compilation = Compiler::compile(config.unstable, loader, &search.justfile)?;
let compilation = Compiler::compile(loader, &search.justfile)?;
compilation.justfile.check_unstable(config)?;
if config.verbosity.loud() {
for warning in &compilation.justfile.warnings {

View File

@ -28,7 +28,7 @@ mod full {
pub fn summary(path: &Path) -> io::Result<Result<Summary, String>> {
let loader = Loader::new();
match Compiler::compile(false, &loader, path) {
match Compiler::compile(&loader, path) {
Ok(compilation) => Ok(Ok(Summary::new(&compilation.justfile))),
Err(error) => Ok(Err(if let Error::Compile { compile_error } = error {
compile_error.to_string()

12
src/unstable.rs Normal file
View File

@ -0,0 +1,12 @@
#[derive(Copy, Clone, Debug, PartialEq, Ord, Eq, PartialOrd)]
pub(crate) enum Unstable {
Modules,
}
impl Unstable {
pub(crate) fn message(self) -> String {
match self {
Self::Modules => "Modules are currently unstable.".into(),
}
}
}

View File

@ -4,10 +4,7 @@ test! {
name: unstable_not_passed,
justfile: "",
args: ("--fmt"),
stderr: "
error: The `--fmt` command is currently unstable. \
Invoke `just` with the `--unstable` flag to enable unstable features.
",
stderr_regex: "error: The `--fmt` command is currently unstable..*",
status: EXIT_FAILURE,
}

View File

@ -56,7 +56,9 @@ fn alias() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"ignore_comments": false,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -98,6 +100,7 @@ fn assignment() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -153,6 +156,7 @@ fn body() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -220,6 +224,7 @@ fn dependencies() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -325,6 +330,7 @@ fn dependency_argument() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -392,6 +398,7 @@ fn duplicate_recipes() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -437,6 +444,7 @@ fn duplicate_variables() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -485,6 +493,7 @@ fn doc_comment() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -519,6 +528,7 @@ fn empty_justfile() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -674,6 +684,7 @@ fn parameters() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -762,6 +773,7 @@ fn priors() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -810,6 +822,7 @@ fn private() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -858,6 +871,7 @@ fn quiet() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -921,6 +935,7 @@ fn settings() {
"command": "a",
},
"tempdir": null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -972,6 +987,7 @@ fn shebang() {
"quiet": false,
"shell": null,
"tempdir": null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -1020,6 +1036,7 @@ fn simple() {
"quiet": false,
"shell": null,
"tempdir": null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -1070,6 +1087,7 @@ fn attribute() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"ignore_comments": false,
"windows_powershell": false,
"windows_shell": null,
@ -1136,6 +1154,7 @@ fn module() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"ignore_comments": false,
"windows_powershell": false,
"windows_shell": null,
@ -1158,6 +1177,7 @@ fn module() {
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"ignore_comments": false,
"windows_powershell": false,
"windows_shell": null,

View File

@ -8,10 +8,8 @@ fn modules_are_unstable() {
mod foo
",
)
.stderr(
"error: Modules are currently unstable. \
Invoke `just` with the `--unstable` flag to enable unstable features.\n",
)
.write("foo.just", "")
.stderr_regex("error: Modules are currently unstable..*")
.status(EXIT_FAILURE)
.run();
}

View File

@ -150,7 +150,7 @@ impl Test {
}
pub(crate) fn stderr_regex(mut self, stderr_regex: impl AsRef<str>) -> Self {
self.stderr_regex = Some(Regex::new(&format!("^{}$", stderr_regex.as_ref())).unwrap());
self.stderr_regex = Some(Regex::new(&format!("^(?s){}$", stderr_regex.as_ref())).unwrap());
self
}

View File

@ -26,12 +26,12 @@ default:
"#;
for val in ["0", "", "false"] {
Test::new()
.justfile(justfile)
.args(["--fmt"])
.env("JUST_UNSTABLE", val)
.status(EXIT_FAILURE)
.stderr("error: The `--fmt` command is currently unstable. Invoke `just` with the `--unstable` flag to enable unstable features.\n")
.run();
.justfile(justfile)
.args(["--fmt"])
.env("JUST_UNSTABLE", val)
.status(EXIT_FAILURE)
.stderr_regex("error: The `--fmt` command is currently unstable.*")
.run();
}
}
@ -45,6 +45,40 @@ default:
.justfile(justfile)
.args(["--fmt"])
.status(EXIT_FAILURE)
.stderr("error: The `--fmt` command is currently unstable. Invoke `just` with the `--unstable` flag to enable unstable features.\n")
.stderr_regex("error: The `--fmt` command is currently unstable.*")
.run();
}
#[test]
fn set_unstable_with_setting() {
Test::new()
.justfile(
"
set unstable
mod foo
",
)
.write("foo.just", "@bar:\n echo BAR")
.args(["foo", "bar"])
.stdout("BAR\n")
.run();
}
#[test]
fn unstable_setting_does_not_affect_submodules() {
Test::new()
.justfile(
"
set unstable
mod foo
",
)
.write("foo.just", "mod bar")
.write("bar.just", "baz:\n echo hello")
.args(["foo", "bar"])
.stderr_regex("error: Modules are currently unstable.*")
.status(EXIT_FAILURE)
.run();
}