|
|
|
@ -6,18 +6,115 @@ use crate::db::{is_executable, read_extension, read_or_none};
|
|
|
|
|
use crate::backend::{Repo, backend};
|
|
|
|
|
use crate::log;
|
|
|
|
|
|
|
|
|
|
/// Defines how tasks are selected to be run
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum Select {
|
|
|
|
|
/// Select all tasks (executable files) from a given basedir
|
|
|
|
|
All(PathBuf),
|
|
|
|
|
/// Select all tasks with a matching TASK.inbox from a basedir
|
|
|
|
|
Inbox(PathBuf),
|
|
|
|
|
/// Select all tasks from a basedir with a matching TASK.inbox
|
|
|
|
|
/// in a separate inbox directory.
|
|
|
|
|
InboxDir(PathBuf, String),
|
|
|
|
|
/// Select all tasks from a basedir that are also in a list
|
|
|
|
|
List(PathBuf, Vec<String>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Select {
|
|
|
|
|
/// Removes the inbox file for the given task
|
|
|
|
|
pub fn clean(&self, task: &str) {
|
|
|
|
|
match self {
|
|
|
|
|
Select::Inbox(basedir) => {
|
|
|
|
|
std::fs::remove_file(&basedir.join(&format!("{}.inbox", task))).unwrap();
|
|
|
|
|
},
|
|
|
|
|
Select::InboxDir(_basedir, inboxdir) => {
|
|
|
|
|
std::fs::remove_file(&PathBuf::from(inboxdir).join(&format!("{}.inbox", task))).unwrap()
|
|
|
|
|
},
|
|
|
|
|
_ => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find tasks matched by a selection
|
|
|
|
|
pub fn apply(&self, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
|
|
|
|
// TODO: Dedicated error type for specific IO errors:
|
|
|
|
|
// - missing basedir/select
|
|
|
|
|
// - permissions problem for basedir/select
|
|
|
|
|
match self {
|
|
|
|
|
// We load all executable entries from basedir
|
|
|
|
|
Select::All(basedir) => {
|
|
|
|
|
Ok(from_dir(basedir, self.clone(), context))
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// We load all entries ending with .inbox from basedir
|
|
|
|
|
Select::Inbox(basedir) => {
|
|
|
|
|
let inbox_entries = basedir.read_dir().unwrap().filter_map(|f| {
|
|
|
|
|
if f.is_err() {
|
|
|
|
|
// Dismiss individual file errors
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let f_string = f.unwrap().file_name().to_str().unwrap().to_string();
|
|
|
|
|
if ! f_string.ends_with(".inbox") || f_string.starts_with(".") {
|
|
|
|
|
// We're only looking for non-hidden *.inbox files
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
return Some(f_string.trim_end_matches(".inbox").to_string());
|
|
|
|
|
}).collect();
|
|
|
|
|
from_dir_and_list(basedir, inbox_entries, self.clone(), context)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// We load all entries ending with .inbox from inboxdir
|
|
|
|
|
Select::InboxDir(basedir, inboxdir) => {
|
|
|
|
|
let inbox_entries = PathBuf::from(inboxdir).read_dir().unwrap().filter_map(|f| {
|
|
|
|
|
if f.is_err() {
|
|
|
|
|
// Dismiss individual file errors
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let f_string = f.unwrap().file_name().to_str().unwrap().to_string();
|
|
|
|
|
if ! f_string.ends_with(".inbox") || f_string.starts_with(".") {
|
|
|
|
|
// We're only looking for non-hidden *.inbox files
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
return Some(f_string.trim_end_matches(".inbox").to_string());
|
|
|
|
|
}).collect();
|
|
|
|
|
from_dir_and_list(basedir, inbox_entries, self.clone(), context)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Load all entries from list
|
|
|
|
|
Select::List(basedir, list) => {
|
|
|
|
|
from_dir_and_list(basedir, list.clone(), self.clone(), context)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Maybe all source/DVCS information should be moved to Repo
|
|
|
|
|
// so that task structure is simpler.
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct Task {
|
|
|
|
|
/// The filename for the task
|
|
|
|
|
pub name: String,
|
|
|
|
|
/// Full path to the task
|
|
|
|
|
pub bin: PathBuf,
|
|
|
|
|
/// Potentially, a source repository to track for updates
|
|
|
|
|
pub source: Option<String>,
|
|
|
|
|
/// The full Repo information
|
|
|
|
|
pub repo: Option<Repo>,
|
|
|
|
|
/// Variables for task config
|
|
|
|
|
pub config: HashMap<String, String>,
|
|
|
|
|
/// Potentially, a branch/commit to track
|
|
|
|
|
pub branch: Option<String>,
|
|
|
|
|
/// List of hosts on which this task should run
|
|
|
|
|
pub hosts: Vec<String>,
|
|
|
|
|
/// Whether the source, if any, has been cloned already
|
|
|
|
|
pub cloned: bool,
|
|
|
|
|
/// Whether to track subrepository/submodule updates
|
|
|
|
|
pub subupdates: bool,
|
|
|
|
|
/// The context in which to store variables for translations
|
|
|
|
|
pub context: log::Context,
|
|
|
|
|
/// The selection context in which a task was created, so that running it can remove it from inbox
|
|
|
|
|
pub select: Select,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// config returns an option of (settings directory, ignored tasks) as
|
|
|
|
@ -48,7 +145,7 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec<String>) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl Task {
|
|
|
|
|
pub fn from_path(path: &Path, context: &log::Context) -> Option<Task> {
|
|
|
|
|
pub fn from_path(path: &Path, select: Select, context: &log::Context) -> Option<Task> {
|
|
|
|
|
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
|
|
|
|
|
|
|
|
|
// We don't return a task if:
|
|
|
|
@ -103,6 +200,7 @@ impl Task {
|
|
|
|
|
cloned,
|
|
|
|
|
subupdates: read_extension(path, "subupdates").is_some(),
|
|
|
|
|
context,
|
|
|
|
|
select,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -152,6 +250,9 @@ impl Task {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.info("run");
|
|
|
|
|
|
|
|
|
|
// TODO: debug message for removing inbox
|
|
|
|
|
self.select.clean(&self.name);
|
|
|
|
|
|
|
|
|
|
let cmd_out = Command::new("bash") // TODO: no need to call bash?
|
|
|
|
|
.arg(&self.bin)
|
|
|
|
@ -197,19 +298,6 @@ impl Task {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Loads a task list from a given base directory. Fails if the directory
|
|
|
|
|
/// is not readable with std::io::Error.
|
|
|
|
|
pub fn from_dir(basedir: &Path, context: &log::Context) -> Result<Vec<Task>, std::io::Error> {
|
|
|
|
|
Ok(
|
|
|
|
|
basedir.read_dir()?.filter_map(|f| {
|
|
|
|
|
if f.is_err() {
|
|
|
|
|
// Dismiss individual file errors
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
return Task::from_path(&f.unwrap().path(), context);
|
|
|
|
|
}).collect()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct MissingTask(pub String);
|
|
|
|
|
|
|
|
|
@ -259,19 +347,21 @@ impl SourceSet {
|
|
|
|
|
/// Loads a task list from a given base directory, taking only tasks that are in requested list.
|
|
|
|
|
/// Given tasks can be either a task name or a task URL. This function will panic if the basedir
|
|
|
|
|
/// does not exist, or error if a requested task/source does not exist.
|
|
|
|
|
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
|
|
|
|
// Safe unwrap because we checked that basedir existed before
|
|
|
|
|
// or maybe can crash for permission problem? TODO: write tests
|
|
|
|
|
pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, select: Select, context: &log::Context) -> Result<Vec<Task>, MissingTask> {
|
|
|
|
|
// TODO: Write tests for permissions problems
|
|
|
|
|
|
|
|
|
|
// If we're looking up specific tasks, maybe they're referenced by source
|
|
|
|
|
// and not by name. SourceSet allows for a source->name mapping.
|
|
|
|
|
let sourceset = SourceSet::from(basedir).unwrap();
|
|
|
|
|
let mut tasks = Vec::new();
|
|
|
|
|
for t in list {
|
|
|
|
|
if let Some(task) = Task::from_path(&basedir.join(&t), context) {
|
|
|
|
|
if let Some(task) = Task::from_path(&basedir.join(&t), select.clone(), context) {
|
|
|
|
|
tasks.push(task);
|
|
|
|
|
} else {
|
|
|
|
|
// Maybe it's not a task name, but a task URL?
|
|
|
|
|
if let Some(list) = sourceset.tasks_for(&t) {
|
|
|
|
|
// Hopefully safe unwrap (unless there's a source without a corresponding task?)
|
|
|
|
|
let task_list = list.iter().map(|t_name| Task::from_path(&basedir.join(&t_name), context).unwrap());
|
|
|
|
|
let task_list = list.iter().map(|t_name| Task::from_path(&basedir.join(&t_name), select.clone(), context).unwrap());
|
|
|
|
|
tasks.extend(task_list);
|
|
|
|
|
} else {
|
|
|
|
|
return Err(MissingTask(t));
|
|
|
|
@ -281,6 +371,19 @@ pub fn from_dir_and_list(basedir: &Path, list: Vec<String>, context: &log::Conte
|
|
|
|
|
Ok(tasks)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Loads a task list from a given base directory. Fails if the directory
|
|
|
|
|
/// is not readable with std::io::Error.
|
|
|
|
|
pub fn from_dir(basedir: &Path, select: Select, context: &log::Context) -> Vec<Task> {
|
|
|
|
|
basedir.read_dir().unwrap().filter_map(|f| {
|
|
|
|
|
if f.is_err() {
|
|
|
|
|
// Dismiss individual file errors
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
return Task::from_path(&f.unwrap().path(), select.clone(), context);
|
|
|
|
|
}).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Takes a &Path to a basedir, and a &str task_name, and return
|
|
|
|
|
/// the corresponding source directory as a PathBuf. Does not check
|
|
|
|
|
/// if the target exists.
|
|
|
|
|