Skip to content

Commit

Permalink
feat: standalone tasks (#3240)
Browse files Browse the repository at this point in the history
Allows running tasks with `mise run ./foo`
  • Loading branch information
jdx authored Nov 27, 2024
1 parent 04f6c4b commit aba8c04
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 3 deletions.
22 changes: 22 additions & 0 deletions docs/tasks/file-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,25 @@ Also, the original working directory is available in the `MISE_ORIGINAL_CWD` env
#!/usr/bin/env bash
cd "$MISE_ORIGINAL_CWD"
```

## Running tasks directly

Tasks don't need to be configured as part of a config, you can just run them directly by passing the path to the script:

```bash
mise run ./path/to/script.sh
```

Note that the path must start with `/` or `./` to be considered a file path. (On Windows it can be `C:\` or `.\`)

## Remote tasks

Task files can be fetched via http:

```toml
[tasks.build]
file = "https://example.com/build.sh"
```

Currently, they're fetched everytime they're executed, but we may add some cache support later.
This could be extended with other protocols like mentioned in [this ticket](https://github.com/jdx/mise/issues/2488) if there were interest.
24 changes: 24 additions & 0 deletions e2e/tasks/test_task_standalone
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash

cat <<EOF >mytask
#!/usr/bin/env bash
echo "running mytask"
EOF
chmod +x mytask

assert "mise run ./mytask" "running mytask"

cat <<EOF >mise.toml
[tasks.mytask]
file = "./mytask"
EOF

mkdir -p subdir
cd subdir || exit 1
assert "mise run mytask" "running mytask"
cd .. || exit 1

cat <<EOF >mise.toml
tasks.mytask.file = "https://mise.jdx.dev/test/mytask"
EOF
assert "mise run mytask" "running mytask"
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ impl Cli {
raw: self.raw,
timings: self.timings,
tool: Default::default(),
tmpdir: Default::default(),
}));
} else if let Some(cmd) = external::COMMANDS.get(&task) {
external::execute(
Expand Down
50 changes: 47 additions & 3 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::cmd::CmdLineRunner;
use crate::config::{CONFIG, SETTINGS};
use crate::errors::Error;
use crate::file::display_path;
use crate::http::HTTP;
use crate::task::{Deps, EitherIntOrBool, GetMatchingExt, Task};
use crate::toolset::{InstallOptions, ToolsetBuilder};
use crate::ui::{ctrlc, prompt, style, time};
Expand All @@ -26,6 +27,7 @@ use glob::glob;
use itertools::Itertools;
#[cfg(unix)]
use nix::sys::signal::SIGTERM;
use xx::regex;

/// Run task(s)
///
Expand Down Expand Up @@ -130,10 +132,13 @@ pub struct Run {

#[clap(skip)]
pub output: TaskOutput,

#[clap(skip)]
pub tmpdir: PathBuf,
}

impl Run {
pub fn run(self) -> Result<()> {
pub fn run(mut self) -> Result<()> {
if self.task == "-h" {
self.get_clap_command().print_help()?;
return Ok(());
Expand All @@ -143,6 +148,8 @@ impl Run {
return Ok(());
}
time!("run init");
let tmpdir = tempfile::tempdir()?;
self.tmpdir = tmpdir.path().to_path_buf();
let task_list = self.get_task_lists()?;
time!("run get_task_lists");
self.parallelize_tasks(task_list)?;
Expand Down Expand Up @@ -173,6 +180,25 @@ impl Run {
})
.flat_map(|args| args.split_first().map(|(t, a)| (t.clone(), a.to_vec())))
.map(|(t, args)| {
// can be any of the following:
// - ./path/to/script
// - ~/path/to/script
// - /path/to/script
// - ../path/to/script
// - C:\path\to\script
// - .\path\to\script
if regex!(r#"^((\.*|~)(/|\\)|\w:\\)"#).is_match(&t) {
let path = PathBuf::from(&t);
if path.exists() {
let config_root = CONFIG
.project_root
.clone()
.or_else(|| dirs::CWD.clone())
.unwrap_or_default();
let task = Task::from_path(&path, &PathBuf::new(), &config_root)?;
return Ok(vec![task.with_args(args)]);
}
}
let tasks = CONFIG
.tasks_with_aliases()?
.get_matching(&t)?
Expand All @@ -196,7 +222,7 @@ impl Run {
.collect()
}

fn parallelize_tasks(mut self, tasks: Vec<Task>) -> Result<()> {
fn parallelize_tasks(mut self, mut tasks: Vec<Task>) -> Result<()> {
time!("paralellize_tasks start");

ctrlc::exit_on_ctrl_c(false);
Expand All @@ -216,6 +242,7 @@ impl Run {
env.insert("root".into(), root.display().to_string());
}

self.fetch_tasks(&mut tasks)?;
let tasks = Deps::new(tasks)?;
for task in tasks.all() {
self.validate_task(task)?;
Expand Down Expand Up @@ -606,7 +633,7 @@ impl Run {

fn validate_task(&self, task: &Task) -> Result<()> {
if let Some(path) = &task.file {
if !file::is_executable(path) {
if path.exists() && !file::is_executable(path) {
let dp = display_path(path);
let msg = format!("Script `{dp}` is not executable. Make it executable?");
if ui::confirm(msg)? {
Expand Down Expand Up @@ -732,6 +759,23 @@ impl Run {
.task_timings
.unwrap_or(self.output == TaskOutput::Prefix)
}

fn fetch_tasks(&self, tasks: &mut Vec<Task>) -> Result<()> {
let http_re = regex!("https?://");
for t in tasks {
if let Some(file) = t.file.clone() {
let source = file.to_string_lossy().to_string();
if http_re.is_match(&source) {
let filename = file.file_name().unwrap().to_string_lossy().to_string();
let tmp_path = self.tmpdir.join(&filename);
HTTP.download_file(&source, &tmp_path, None)?;
file::make_executable(&tmp_path)?;
t.file = Some(tmp_path);
}
}
}
Ok(())
}
}

fn is_glob_pattern(path: &str) -> bool {
Expand Down

0 comments on commit aba8c04

Please sign in to comment.