Skip to content

Commit

Permalink
feat: add some code
Browse files Browse the repository at this point in the history
  • Loading branch information
acesyde committed Jan 25, 2025
1 parent 3bdb713 commit 887e33e
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 69 deletions.
6 changes: 1 addition & 5 deletions docs/tasks/toml-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,7 @@ Each task file is cached in the `MISE_CACHE_DIR` directory. If the file is updat
You can reset the cache by running `mise cache clear`.
:::

You can also choose to not use the cache during the run execution by adding `--no-cache` to the command.

```shell
mise run my-task-name --no-cache
```
You can use the `MISE_TASK_REMOTE_NO_CACHE` environment variable to disable caching of remote tasks.

## Arguments

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: 0 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ impl Cli {
keep_order_output: Default::default(),
task_prs: Default::default(),
timed_outputs: Default::default(),
no_cache: false,
}));
} else if let Some(cmd) = external::COMMANDS.get(&task) {
external::execute(
Expand Down
8 changes: 2 additions & 6 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,10 +194,6 @@ pub struct Run {

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

/// Don't cache the remote task file and always fetch it
#[clap(long, verbatim_doc_comment)]
pub no_cache: bool,
}

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

fn fetch_tasks(&self, tasks: &mut Vec<Task>) -> Result<()> {
let task_file_providers = TaskFileProviders::new(self.no_cache);
let task_file_providers = TaskFileProviders::new();

for t in tasks {
if let Some(file) = &t.file {
Expand Down
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,15 +1,16 @@
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::config::Settings;
use crate::dirs;

static CACHE_FILE_PROVIDERS: Lazy<PathBuf> = Lazy::new(|| dirs::CACHE.join("tasks-file-provider"));
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;
Expand All @@ -21,17 +22,18 @@ pub struct TaskFileProviders {
}

impl TaskFileProviders {
pub fn new(no_cache: bool) -> Self {
pub fn new() -> Self {
let no_cache = Settings::get().task_remote_no_cache.unwrap_or(false);
Self { no_cache }
}

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

Expand All @@ -47,26 +49,26 @@ mod tests {

#[test]
fn test_get_providers() {
let task_file_providers = TaskFileProviders::new(true);
let task_file_providers = TaskFileProviders::new();
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(true);
let task_file_providers = TaskFileProviders::new();
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(true);
fn test_http_file_match_http_remote_task_provider() {
let task_file_providers = TaskFileProviders::new();
let cases = vec![
"http://example.com/file.txt",
"https://example.com/file.txt",
Expand All @@ -76,7 +78,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"));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
use std::path::PathBuf;

use md5::Digest;
use sha2::Sha256;

use crate::{env, file, http::HTTP};
use crate::{env, file, hash, http::HTTP};

use super::TaskFileProvider;

#[derive(Debug)]
pub struct HttpTaskFileProvider {
pub struct RemoteTaskHttp {
cache_path: PathBuf,
no_cache: bool,
}

impl HttpTaskFileProvider {
impl RemoteTaskHttp {
pub fn new(cache_path: PathBuf, no_cache: bool) -> Self {
Self {
cache_path,
Expand All @@ -22,11 +19,9 @@ impl HttpTaskFileProvider {
}
}

impl HttpTaskFileProvider {
impl RemoteTaskHttp {
fn get_cache_key(&self, file: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(file);
format!("{:x}", hasher.finalize())
hash::hash_sha256_to_str(file)
}

fn download_file(
Expand All @@ -40,15 +35,24 @@ impl HttpTaskFileProvider {
}
}

impl TaskFileProvider for HttpTaskFileProvider {
impl TaskFileProvider for RemoteTaskHttp {
fn is_match(&self, file: &str) -> bool {
file.starts_with("http://") || file.starts_with("https://")
let url = url::Url::parse(file);

// Check if the URL is valid and the scheme is http or https
// and the path is not empty
// and the path is not a directory
url.is_ok_and(|url| {
(url.scheme() == "http" || url.scheme() == "https")
&& url.path().len() > 1
&& !url.path().ends_with('/')
})
}

fn get_local_path(&self, file: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
match self.no_cache {
false => {
debug!("Cache mode enabled");
trace!("Cache mode enabled");
let cache_key = self.get_cache_key(file);
let destination = self.cache_path.join(&cache_key);

Expand All @@ -62,7 +66,7 @@ impl TaskFileProvider for HttpTaskFileProvider {
Ok(destination)
}
true => {
debug!("Cache mode disabled");
trace!("Cache mode disabled");
let url = url::Url::parse(file)?;
let filename = url
.path_segments()
Expand All @@ -89,68 +93,77 @@ mod tests {

#[test]
fn test_is_match() {
let provider = HttpTaskFileProvider::new(env::temp_dir(), true);
assert!(provider.is_match("http://test.txt"));
assert!(provider.is_match("https://test.txt"));
let provider = RemoteTaskHttp::new(env::temp_dir(), true);

// Positive cases
assert!(provider.is_match("http://myhost.com/test.txt"));
assert!(provider.is_match("https://myhost.com/test.txt"));
assert!(provider.is_match("https://mydomain.com/myfile.py"));
assert!(provider.is_match("https://subdomain.mydomain.com/myfile.sh"));
assert!(provider.is_match("https://subdomain.mydomain.com/myfile.sh?query=1"));

// Negative cases
assert!(!provider.is_match("https://myhost.com/js/"));
assert!(!provider.is_match("https://myhost.com"));
assert!(!provider.is_match("https://myhost.com/"));
}

#[test]
fn test_http_task_file_provider_get_local_path_without_cache() {
fn test_http_remote_task_get_local_path_without_cache() {
let paths = vec![
"/myfile.py",
"/subpath/myfile.sh",
"/myfile.sh?query=1&sdfsdf=2",
("/myfile.py", "myfile.py"),
("/subpath/myfile.sh", "myfile.sh"),
("/myfile.sh?query=1&sdfsdf=2", "myfile.sh"),
];
let mut server = mockito::Server::new();

for path in paths {
let mocked_server = server
.mock("GET", path)
for (request_path, expected_file_name) in paths {
let mocked_server: mockito::Mock = server
.mock("GET", request_path)
.with_status(200)
.with_body("Random content")
.expect(2)
.create();

let provider = HttpTaskFileProvider::new(env::temp_dir(), true);
let mock = format!("{}{}", server.url(), path);
let provider = RemoteTaskHttp::new(env::temp_dir(), true);
let mock = format!("{}{}", server.url(), request_path);

for _ in 0..2 {
let path = provider.get_local_path(&mock).unwrap();
assert!(path.exists());
assert!(path.is_file());
let local_path = provider.get_local_path(&mock).unwrap();
assert!(local_path.exists());
assert!(local_path.is_file());
assert!(local_path.ends_with(expected_file_name));
}

mocked_server.assert();
}
}

#[test]
fn test_http_task_file_provider_get_local_path_with_cache() {
fn test_http_remote_task_get_local_path_with_cache() {
let paths = vec![
"/myfile.py",
"/subpath/myfile.sh",
"/myfile.sh?query=1&sdfsdf=2",
("/myfile.py", "myfile.py"),
("/subpath/myfile.sh", "myfile.sh"),
("/myfile.sh?query=1&sdfsdf=2", "myfile.sh"),
];
let mut server = mockito::Server::new();

for path in paths {
for (request_path, not_expected_file_name) in paths {
let mocked_server = server
.mock("GET", path)
.mock("GET", request_path)
.with_status(200)
.with_body("Random content")
.expect(1)
.create();

let provider = HttpTaskFileProvider::new(env::temp_dir(), false);
let mock = format!("{}{}", server.url(), path);
let provider = RemoteTaskHttp::new(env::temp_dir(), false);
let mock = format!("{}{}", server.url(), request_path);

for _ in 0..2 {
let path = provider.get_local_path(&mock).unwrap();
assert!(path.exists());
assert!(path.is_file());
assert!(!path.ends_with(not_expected_file_name));
}

mocked_server.assert();
Expand Down

0 comments on commit 887e33e

Please sign in to comment.