Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add http cache #4160

Merged
merged 12 commits into from
Jan 25, 2025
2 changes: 2 additions & 0 deletions docs/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Change how tasks information is output when running tasks
- `quiet` - Don't show extra output
- `silent` - Don't show any output including stdout and stderr from the task except for errors

### `--no-cache`

Examples:

```
Expand Down
2 changes: 2 additions & 0 deletions docs/cli/tasks/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ Change how tasks information is output when running tasks
- `quiet` - Don't show extra output
- `silent` - Don't show any output including stdout and stderr from the task except for errors

### `--no-cache`

Examples:

```
Expand Down
9 changes: 7 additions & 2 deletions docs/tasks/toml-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,13 @@ Task files can be fetched via http:
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.
Each task file is cached in the `MISE_CACHE_DIR` directory. If the file is updated, it will not be re-downloaded unless the cache is cleared.

:::tip
You can reset the cache by running `mise cache clear`.
:::

You can use the `MISE_TASK_REMOTE_NO_CACHE` environment variable to disable caching of remote tasks.

## Arguments

Expand Down
2 changes: 2 additions & 0 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ cmd run help="Run task(s)" {
long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `replacing` - Stdout is replaced each time, stderr is printed as is\n- `timed` - Only show stdout lines if they are displayed for more than 1 second\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors"
arg <OUTPUT>
}
flag --no-cache
mount run="mise tasks --usage"
}
cmd self-update help="Updates mise itself." {
Expand Down Expand Up @@ -844,6 +845,7 @@ cmd tasks help="Manage tasks" {
long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `replacing` - Stdout is replaced each time, stderr is printed as is\n- `timed` - Only show stdout lines if they are displayed for more than 1 second\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors"
arg <OUTPUT>
}
flag --no-cache
arg "[TASK]" help="Tasks to run\nCan specify multiple tasks by separating with `:::`\ne.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2" required=#false default=default
arg "[ARGS]..." help="Arguments to pass to the tasks. Use \":::\" to separate tasks" required=#false var=#true
arg "[-- ARGS_LAST]..." help="Arguments to pass to the tasks. Use \":::\" to separate tasks" required=#false var=#true hide=#true
Expand Down
4 changes: 4 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,10 @@
"silent"
]
},
"task_remote_no_cache": {
"description": "Mise will always fetch the latest tasks from the remote, by default the cache is used.",
"type": "boolean"
},
"task_run_auto_install": {
"default": true,
"description": "Automatically install missing tools when executing tasks.",
Expand Down
6 changes: 6 additions & 0 deletions settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,12 @@ docs = """
Change output style when executing tasks. This controls the output of `mise run`.
"""

[task_remote_no_cache]
env = "MISE_TASK_REMOTE_NO_CACHE"
type = "Bool"
optional = true
description = "Mise will always fetch the latest tasks from the remote, by default the cache is used."

[task_run_auto_install]
env = "MISE_TASK_RUN_AUTO_INSTALL"
type = "Bool"
Expand Down
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ impl Cli {
keep_order_output: Default::default(),
task_prs: Default::default(),
timed_outputs: Default::default(),
no_cache: Default::default(),
}));
} else if let Some(cmd) = external::COMMANDS.get(&task) {
external::execute(
Expand Down
9 changes: 7 additions & 2 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::config::{Config, SETTINGS};
use crate::env_diff::EnvMap;
use crate::errors::Error;
use crate::file::display_path;
use crate::task::file_providers::TaskFileProviders;
use crate::task::task_file_providers::TaskFileProviders;
use crate::task::{Deps, GetMatchingExt, Task};
use crate::toolset::{InstallOptions, ToolsetBuilder};
use crate::ui::multi_progress_report::MultiProgressReport;
Expand Down Expand Up @@ -194,6 +194,10 @@ pub struct Run {

#[clap(skip)]
pub timed_outputs: Arc<Mutex<IndexMap<String, (SystemTime, String)>>>,

// Do not use cache on remote tasks
#[clap(long, verbatim_doc_comment, env = "MISE_TASK_REMOTE_NO_CACHE")]
pub no_cache: bool,
acesyde marked this conversation as resolved.
Show resolved Hide resolved
}

type KeepOrderOutputs = (Vec<(String, String)>, Vec<(String, String)>);
Expand Down Expand Up @@ -873,7 +877,8 @@ impl Run {
}

fn fetch_tasks(&self, tasks: &mut Vec<Task>) -> Result<()> {
let task_file_providers = TaskFileProviders::new(self.tmpdir.clone());
let no_cache = self.no_cache || SETTINGS.task_remote_no_cache.unwrap_or(false);
let task_file_providers = TaskFileProviders::new(no_cache);

for t in tasks {
if let Some(file) = &t.file {
Expand Down
78 changes: 0 additions & 78 deletions src/task/file_providers/http_file_provider.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use std::{ffi, fmt, path};
use xx::regex;

mod deps;
pub mod file_providers;
mod task_dep;
pub mod task_file_providers;
mod task_script_parser;
pub mod task_sources;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use std::path::{Path, PathBuf};
use super::TaskFileProvider;

#[derive(Debug)]
pub struct LocalTaskFileProvider;
pub struct LocalTask;

impl TaskFileProvider for LocalTaskFileProvider {
impl TaskFileProvider for LocalTask {
fn is_match(&self, file: &str) -> bool {
let path = Path::new(file);

Expand All @@ -24,7 +24,7 @@ mod tests {

#[test]
fn test_is_match() {
let provider = LocalTaskFileProvider;
let provider = LocalTask;
assert!(provider.is_match("filetask.bat"));
assert!(provider.is_match("filetask"));
assert!(provider.is_match("/test.txt"));
Expand All @@ -34,7 +34,7 @@ mod tests {

#[test]
fn test_get_local_path() {
let provider = LocalTaskFileProvider;
let provider = LocalTask;
assert_eq!(
provider.get_local_path("/test.txt").unwrap(),
PathBuf::from("/test.txt")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,72 @@
use std::sync::LazyLock as Lazy;
use std::{fmt::Debug, path::PathBuf};

mod http_file_provider;
mod local_file_provider;
mod local_task;
mod remote_task_http;

pub use http_file_provider::HttpTaskFileProvider;
pub use local_file_provider::LocalTaskFileProvider;
pub use local_task::LocalTask;
pub use remote_task_http::RemoteTaskHttp;

use crate::dirs;

static REMOTE_TASK_CACHE_DIR: Lazy<PathBuf> = Lazy::new(|| dirs::CACHE.join("remote-tasks-cache"));

pub trait TaskFileProvider: Debug {
fn is_match(&self, file: &str) -> bool;
fn get_local_path(&self, file: &str) -> Result<PathBuf, Box<dyn std::error::Error>>;
}

pub struct TaskFileProviders {
tmpdir: PathBuf,
no_cache: bool,
}

impl TaskFileProviders {
pub fn new(no_cache: bool) -> Self {
Self { no_cache }
}

fn get_providers(&self) -> Vec<Box<dyn TaskFileProvider>> {
vec![
Box::new(HttpTaskFileProvider::new(self.tmpdir.clone())),
Box::new(LocalTaskFileProvider), // Must be the last provider
Box::new(RemoteTaskHttp::new(
REMOTE_TASK_CACHE_DIR.clone(),
self.no_cache,
)),
Box::new(LocalTask), // Must be the last provider
]
}

pub fn new(tmpdir: PathBuf) -> Self {
Self { tmpdir }
}

pub fn get_provider(&self, file: &str) -> Option<Box<dyn TaskFileProvider>> {
self.get_providers().into_iter().find(|p| p.is_match(file))
}
}

#[cfg(test)]
mod tests {
use std::env;

use super::*;

#[test]
fn test_get_providers() {
let task_file_providers = TaskFileProviders::new(env::temp_dir());
let task_file_providers = TaskFileProviders::new(false);
let providers = task_file_providers.get_providers();
assert_eq!(providers.len(), 2);
}

#[test]
fn test_local_file_match_local_provider() {
let task_file_providers = TaskFileProviders::new(env::temp_dir());
let task_file_providers = TaskFileProviders::new(false);
let cases = vec!["file.txt", "./file.txt", "../file.txt", "/file.txt"];

for file in cases {
let provider = task_file_providers.get_provider(file);
assert!(provider.is_some());
assert!(format!("{:?}", provider.unwrap()).contains("LocalTaskFileProvider"));
assert!(format!("{:?}", provider.unwrap()).contains("LocalTask"));
}
}

#[test]
fn test_http_file_match_http_provider() {
let task_file_providers = TaskFileProviders::new(env::temp_dir());
fn test_http_file_match_http_remote_task_provider() {
let task_file_providers = TaskFileProviders::new(false);
let cases = vec![
"http://example.com/file.txt",
"https://example.com/file.txt",
Expand All @@ -69,7 +76,7 @@ mod tests {
for file in cases {
let provider = task_file_providers.get_provider(file);
assert!(provider.is_some());
assert!(format!("{:?}", provider.unwrap()).contains("HttpTaskFileProvider"));
assert!(format!("{:?}", provider.unwrap()).contains("RemoteTaskHttp"));
}
}
}
Loading
Loading