mod args;
mod deps;
mod emojis;
mod skill;
#[cfg(test)]
mod test;
use std::{
collections::BTreeMap,
env,
ffi::{OsStr, OsString},
fs,
io::Write as _,
path::{Path, PathBuf},
process::{Command, ExitCode},
};
use toml_edit::DocumentMut;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ToolchainOverride {
Nightly,
}
impl ToolchainOverride {
fn as_rustup_arg(self) -> &'static str {
match self {
Self::Nightly => "+nightly",
}
}
fn display_name(self) -> &'static str {
match self {
Self::Nightly => "nightly",
}
}
}
fn take_toolchain_override(args: &mut Vec<OsString>) -> Option<ToolchainOverride> {
let first = args.first()?;
match first.to_str() {
Some("+nightly") | Some("+n") => {
args.remove(0);
Some(ToolchainOverride::Nightly)
}
_ => None,
}
}
fn apply_toolchain_env(cmd: &mut Command, toolchain: Option<ToolchainOverride>) {
if let Some(toolchain) = toolchain {
match toolchain {
ToolchainOverride::Nightly => {
cmd.env("RUSTUP_TOOLCHAIN", "nightly");
}
}
}
}
#[derive(Debug, Clone)]
enum XtaskInvocation {
WorkspaceMember { package: String },
ManifestPath {
manifest_path: PathBuf,
package: String,
},
}
impl XtaskInvocation {
fn package_name(&self) -> &str {
match self {
XtaskInvocation::WorkspaceMember { package } => package,
XtaskInvocation::ManifestPath { package, .. } => package,
}
}
}
#[derive(Debug, Clone)]
struct Workspace {
path: PathBuf,
dir_name: String,
xtask_bin: String,
xtask: XtaskInvocation,
toolchain: Option<ToolchainOverride>,
}
impl Workspace {
fn with_toolchain(mut self, toolchain: Option<ToolchainOverride>) -> Self {
self.toolchain = toolchain;
self
}
}
#[derive(Debug, Clone)]
struct DispatchSummary {
entries: Vec<SubrepoExecutionResult>,
}
impl DispatchSummary {
fn new() -> Self {
Self {
entries: Vec::new(),
}
}
fn push(&mut self, entry: SubrepoExecutionResult) {
self.entries.push(entry);
}
fn success_count(&self) -> usize {
self.entries
.iter()
.filter(|entry| entry.is_success())
.count()
}
fn failure_count(&self) -> usize {
self.entries.len().saturating_sub(self.success_count())
}
fn has_failures(&self) -> bool {
self.failure_count() > 0
}
fn final_exit_code(&self) -> ExitCode {
if self.has_failures() {
ExitCode::from(1)
} else {
ExitCode::SUCCESS
}
}
fn print(&self) {
eprintln!();
eprintln!("==================== Execution summary ====================");
if !self.has_failures() {
eprintln!("✅ All {} subrepos succeeded!", self.entries.len());
return;
}
for entry in &self.entries {
match &entry.outcome {
RepoExecutionOutcome::Success => {
eprintln!("✅ {:<16} success", entry.repo);
}
RepoExecutionOutcome::Failure { code } => {
eprintln!("❌ {:<16} failed with exit code {}", entry.repo, code);
}
RepoExecutionOutcome::Error { message } => {
eprintln!("💥 {:<16} dispatch error: {}", entry.repo, message);
}
}
}
eprintln!();
eprintln!("Total subrepos : {}", self.entries.len());
eprintln!("Succeeded : {}", self.success_count());
eprintln!("Failed : {}", self.failure_count());
let failed = self
.entries
.iter()
.filter(|entry| !entry.is_success())
.map(|entry| entry.repo.as_str())
.collect::<Vec<_>>()
.join(", ");
eprintln!("Failed subrepos: {failed}");
eprintln!("===========================================================");
}
}
#[derive(Debug, Clone)]
struct SubrepoExecutionResult {
repo: String,
outcome: RepoExecutionOutcome,
}
impl SubrepoExecutionResult {
fn success(repo: impl Into<String>) -> Self {
Self {
repo: repo.into(),
outcome: RepoExecutionOutcome::Success,
}
}
fn failure(repo: impl Into<String>, code: u8) -> Self {
Self {
repo: repo.into(),
outcome: RepoExecutionOutcome::Failure { code },
}
}
fn error(repo: impl Into<String>, message: impl Into<String>) -> Self {
Self {
repo: repo.into(),
outcome: RepoExecutionOutcome::Error {
message: message.into(),
},
}
}
fn is_success(&self) -> bool {
matches!(self.outcome, RepoExecutionOutcome::Success)
}
}
#[derive(Debug, Clone)]
enum RepoExecutionOutcome {
Success,
Failure { code: u8 },
Error { message: String },
}
fn main() -> ExitCode {
let mut args: Vec<OsString> = env::args_os().skip(1).collect();
let toolchain = take_toolchain_override(&mut args);
if is_skill_invocation(&args) {
if args.len() > 1 {
eprintln!("xtask +skill does not accept additional arguments.");
return ExitCode::from(1);
}
skill::print();
return ExitCode::SUCCESS;
}
let git_root = match git_repo_root()
.map_err(|e| format!("xtask should run inside a git repository: {e}"))
{
Ok(root) => root,
Err(err) => {
eprintln!("{err}");
return ExitCode::from(1);
}
};
if is_cli_help_invocation(&args) {
match show_xtask_cli_help(&git_root, toolchain) {
Ok(code) => code,
Err(err) => {
eprintln!("{err}");
ExitCode::from(1)
}
}
} else if is_transparent_help_invocation(&args) {
match show_all_help(&git_root, &mut args, toolchain) {
Ok(code) => code,
Err(err) => {
eprintln!("{err}");
ExitCode::from(1)
}
}
} else {
match run(&git_root, &mut args, toolchain) {
Ok(code) => code,
Err(err) => {
eprintln!("{err}");
ExitCode::from(1)
}
}
}
}
fn run(
git_root: &Path,
args: &mut Vec<OsString>,
toolchain: Option<ToolchainOverride>,
) -> Result<ExitCode, String> {
let selector = args::take_subrepo_selector(args);
let cwd = env::current_dir().map_err(|e| format!("failed to read current directory: {e}"))?;
if let Some(sel) = selector {
if sel == "all" {
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
if subrepos.is_empty() {
return Err(format!(
"xtask :all requires at least one subrepo workspace under git root.\n\
Git root: {}",
git_root.display()
));
}
return exec_cargo_xtask_all(git_root, args, &subrepos);
} else {
let ws = select_subrepo_workspace(git_root, &sel, toolchain)?;
return exec_cargo_xtask(git_root, &ws, args).map(ExitCode::from);
}
}
let root_xtask = is_workspace(git_root)?;
if let Some(xtask) = root_xtask {
let xtask_bin = xtask.package_name().to_string();
let ws = Workspace {
path: git_root.to_path_buf(),
dir_name: "root".to_string(),
xtask_bin,
xtask,
toolchain,
};
exec_cargo_xtask(git_root, &ws, args).map(ExitCode::from)
} else {
if let Some(ws) = find_subrepo_workspace_root(&cwd, git_root, toolchain)? {
exec_cargo_xtask(git_root, &ws, args).map(ExitCode::from)
} else {
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
if subrepos.is_empty() {
return Err(format!(
"No xtask workspaces found under git root: {}",
git_root.display()
));
}
if !confirm_dispatch_all()? {
return Ok(ExitCode::SUCCESS);
}
exec_cargo_xtask_all(git_root, args, &subrepos)
}
}
}
fn sync_monorepo_dependencies(git_root: &Path, subrepos: &[Workspace]) -> Result<(), String> {
let deps_toml = git_root.join("Dependencies.toml");
if !deps_toml.exists() {
return Ok(());
}
eprintln!(
"🔗 Syncing dependencies from {}...",
deps_toml.file_name().unwrap().to_string_lossy()
);
let subrepo_roots: Vec<PathBuf> = subrepos.iter().map(|ws| ws.path.clone()).collect();
let report = deps::sync_subrepos(&deps_toml, &subrepo_roots)
.map_err(|e| format!("dependency sync should succeed: {e}"))?;
for (manifest, table_path, dep) in report.missing_canonical_dependencies {
eprintln!(
"warning: {} declares dependency '{}' in [{}] but it is missing from root [workspace.dependencies]",
manifest.display(),
dep,
table_path,
);
}
Ok(())
}
fn confirm_dispatch_all() -> Result<bool, String> {
eprintln!(
"⚠️ This will run the command in all subrepos (to suppress this prompt use the ':all' selector)"
);
eprint!("Continue? [y/N] ");
std::io::stderr().flush().ok();
let mut buf = String::new();
std::io::stdin()
.read_line(&mut buf)
.map_err(|e| format!("failed to read confirmation from stdin: {e}"))?;
let answer = buf.trim().to_ascii_lowercase();
Ok(answer == "y" || answer == "yes")
}
fn is_cli_help_invocation(args: &[OsString]) -> bool {
args.is_empty()
}
fn is_skill_invocation(args: &[OsString]) -> bool {
args.first() == Some(&OsString::from("+skill"))
}
fn is_transparent_help_invocation(args: &[OsString]) -> bool {
args.is_empty()
|| (args.len() == 1 && (args[0] == OsStr::new("-h") || args[0] == OsStr::new("--help")))
}
fn git_repo_root() -> Result<PathBuf, String> {
let out = Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()
.map_err(|e| format!("failed to execute git: {e}"))?;
if !out.status.success() {
return Err(
"git rev-parse --show-toplevel failed (are you inside a git repository?)".into(),
);
}
let s = String::from_utf8(out.stdout)
.map_err(|_| "git output should be valid UTF-8".to_string())?;
let p = s.trim();
if p.is_empty() {
return Err("git toplevel path is empty".into());
}
Ok(PathBuf::from(p))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WorkspaceEntryOrigin {
Members,
Exclude,
}
fn collect_workspace_dirs(
workspace_root: &Path,
items: &toml_edit::Item,
) -> Result<Vec<PathBuf>, String> {
let arr = items
.as_array()
.ok_or_else(|| "workspace members/exclude should be an array".to_string())?;
let mut out: Vec<PathBuf> = Vec::new();
for it in arr.iter() {
let s = it
.as_str()
.ok_or_else(|| "workspace entry should be a string".to_string())?;
if let Some((prefix, suffix)) = s.split_once('*') {
if suffix.is_empty() {
let base = workspace_root.join(prefix);
if base.is_dir() {
let entries = fs::read_dir(&base).map_err(|e| {
format!("failed to read directory listing {}: {e}", base.display())
})?;
for entry in entries {
let entry =
entry.map_err(|e| format!("failed to read directory entry: {e}"))?;
let p = entry.path();
if p.is_dir() {
out.push(p);
}
}
}
}
continue;
}
out.push(workspace_root.join(s));
}
Ok(out)
}
fn is_workspace(dir: &Path) -> Result<Option<XtaskInvocation>, String> {
let workspace_toml = dir.join("Cargo.toml");
if !workspace_toml.is_file() {
return Ok(None);
}
let root_src = fs::read_to_string(&workspace_toml)
.map_err(|e| format!("failed to read {}: {e}", workspace_toml.display()))?;
let root_doc = root_src
.parse::<DocumentMut>()
.map_err(|e| format!("failed to parse {}: {e}", workspace_toml.display()))?;
let Some(ws) = root_doc.get("workspace") else {
return Ok(None);
};
let Some(members_item) = ws.get("members") else {
return Ok(None);
};
let mut candidates: BTreeMap<PathBuf, WorkspaceEntryOrigin> = BTreeMap::new();
for p in collect_workspace_dirs(dir, members_item)? {
candidates.insert(p, WorkspaceEntryOrigin::Members);
}
if let Some(exclude_item) = ws.get("exclude") {
for p in collect_workspace_dirs(dir, exclude_item)? {
candidates.entry(p).or_insert(WorkspaceEntryOrigin::Exclude);
}
}
let mut matches: Vec<(String, WorkspaceEntryOrigin, PathBuf)> = Vec::new();
for (candidate_dir, origin) in candidates {
let candidate_manifest = candidate_dir.join("Cargo.toml");
if !candidate_manifest.is_file() {
continue;
}
let src = fs::read_to_string(&candidate_manifest)
.map_err(|e| format!("failed to read {}: {e}", candidate_manifest.display()))?;
let doc = src
.parse::<DocumentMut>()
.map_err(|e| format!("failed to parse {}: {e}", candidate_manifest.display()))?;
let package_name = doc
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str());
if let Some(name) = package_name
&& name.to_ascii_lowercase().starts_with("xtask")
{
matches.push((name.to_string(), origin, candidate_manifest));
}
}
if matches.is_empty() {
return Ok(None);
}
matches.sort_by(|a, b| a.0.cmp(&b.0));
let chosen = if let Some(idx) = matches
.iter()
.position(|(n, _, _)| n.eq_ignore_ascii_case("xtask"))
{
matches.remove(idx)
} else {
matches.remove(0)
};
let (package, origin, manifest_path) = chosen;
Ok(Some(match origin {
WorkspaceEntryOrigin::Members => XtaskInvocation::WorkspaceMember { package },
WorkspaceEntryOrigin::Exclude => XtaskInvocation::ManifestPath {
manifest_path,
package,
},
}))
}
fn find_subrepo_workspace_root(
start: &Path,
git_root: &Path,
toolchain: Option<ToolchainOverride>,
) -> Result<Option<Workspace>, String> {
let mut cur = start.to_path_buf();
loop {
if cur == *git_root {
return Ok(None);
}
if let Some(xtask) = is_workspace(&cur)? {
let rel = cur.strip_prefix(git_root).map_err(|_| {
format!(
"internal error: {} is not under git root {}",
cur.display(),
git_root.display()
)
})?;
let subrepo = rel
.components()
.next()
.ok_or_else(|| {
"internal error: workspace root has empty relative path".to_string()
})?
.as_os_str()
.to_string_lossy()
.to_string();
let xtask_bin = format!("xtask-{subrepo}");
return Ok(Some(
Workspace {
path: cur,
dir_name: subrepo,
xtask_bin,
xtask,
toolchain: None,
}
.with_toolchain(toolchain),
));
}
if !cur.pop() {
return Ok(None);
}
}
}
fn list_subrepo_workspaces(
git_root: &Path,
toolchain: Option<ToolchainOverride>,
) -> Result<Vec<Workspace>, String> {
let entries = fs::read_dir(git_root).map_err(|e| {
format!(
"failed to read git root directory listing {}: {e}",
git_root.display()
)
})?;
let mut subrepos = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| format!("failed to read directory entry: {e}"))?;
let path = entry.path();
if !path.is_dir() {
continue;
}
let dir_name = entry.file_name().to_string_lossy().to_string();
if let Some(xtask) = is_workspace(&path)? {
let xtask_bin = format!("xtask-{dir_name}");
subrepos.push(
Workspace {
path,
dir_name: dir_name.clone(),
xtask_bin,
xtask,
toolchain: None,
}
.with_toolchain(toolchain),
);
}
}
subrepos.sort_by(|a, b| a.dir_name.cmp(&b.dir_name));
Ok(subrepos)
}
fn select_subrepo_workspace(
git_root: &Path,
selector: &str,
toolchain: Option<ToolchainOverride>,
) -> Result<Workspace, String> {
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
if subrepos.is_empty() {
return Err(format!(
"No xtask workspaces found under git root: {}",
git_root.display()
));
}
select_subrepo_workspace_from_list(&subrepos, selector).cloned()
}
fn select_subrepo_workspace_from_list<'a>(
subrepos: &'a [Workspace],
selector: &str,
) -> Result<&'a Workspace, String> {
if let Some(ws) = subrepos.iter().find(|ws| ws.dir_name == selector) {
return Ok(ws);
}
let prefix_matches: Vec<&Workspace> = subrepos
.iter()
.filter(|ws| ws.dir_name.starts_with(selector))
.collect();
match prefix_matches.len() {
1 => return Ok(prefix_matches[0]),
n if n > 1 => {
let candidates = prefix_matches
.iter()
.map(|ws| ws.dir_name.as_str())
.collect::<Vec<_>>()
.join(", ");
return Err(format!(
"Ambiguous subrepo selector '{}'. Matching candidates: {}",
selector, candidates
));
}
_ => {}
}
let normalized_selector = selector.to_ascii_lowercase();
let shorthand_matches: Vec<&Workspace> = subrepos
.iter()
.filter(|ws| {
subrepo_shorthand(&ws.dir_name)
.as_deref()
.is_some_and(|shorthand| shorthand == normalized_selector)
})
.collect();
match shorthand_matches.len() {
0 => Err(format!("No subrepo matches selector '{}'.", selector)),
1 => Ok(shorthand_matches[0]),
_ => {
let candidates = shorthand_matches
.iter()
.map(|ws| {
let shorthand = subrepo_shorthand(&ws.dir_name)
.expect("subrepo shorthand should exist for a shorthand match");
format!("{} (:{})", ws.dir_name, shorthand)
})
.collect::<Vec<_>>()
.join(", ");
Err(format!(
"Ambiguous subrepo shorthand selector '{}'. Matching candidates: {}",
selector, candidates
))
}
}
}
fn subrepo_shorthand(name: &str) -> Option<String> {
let shorthand = name
.split(|c: char| !c.is_ascii_alphanumeric())
.filter_map(|part| part.chars().next())
.map(|c| c.to_ascii_lowercase())
.collect::<String>();
if shorthand.is_empty() {
None
} else {
Some(shorthand)
}
}
fn show_all_help(
git_root: &Path,
args: &mut Vec<OsString>,
toolchain: Option<ToolchainOverride>,
) -> Result<ExitCode, String> {
let selector = args::take_subrepo_selector(args);
let cwd = env::current_dir().map_err(|e| format!("failed to read current directory: {e}"))?;
if let Some(sel) = selector {
if sel == "all" {
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
if subrepos.is_empty() {
return Err(format!(
"xtask :all requires at least one subrepo workspace under git root.\n\
Git root: {}",
git_root.display()
));
}
run_help_all(&subrepos)
} else {
let ws = select_subrepo_workspace(git_root, &sel, toolchain)?;
run_help_one(&ws).map(ExitCode::from)
}
} else {
let root_xtask = is_workspace(git_root)?;
if let Some(xtask) = root_xtask {
let ws = Workspace {
path: git_root.to_path_buf(),
dir_name: "root".to_string(),
xtask_bin: xtask.package_name().to_string(),
xtask,
toolchain,
};
run_help_one(&ws).map(ExitCode::from)
} else {
if let Some(ws) = find_subrepo_workspace_root(&cwd, git_root, toolchain)? {
run_help_one(&ws).map(ExitCode::from)
} else {
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
if subrepos.is_empty() {
return Err(format!(
"No xtask workspaces found under git root: {}",
git_root.display()
));
}
if !confirm_dispatch_all()? {
return Ok(ExitCode::SUCCESS);
}
run_help_all(&subrepos)
}
}
}
}
fn run_help_all(subrepos: &[Workspace]) -> Result<ExitCode, String> {
let mut first_failure: Option<u8> = None;
for ws in subrepos {
let code = run_help_one(ws)?;
if code != 0 && first_failure.is_none() {
first_failure = Some(code);
}
eprintln!();
}
Ok(ExitCode::from(first_failure.unwrap_or(0)))
}
fn run_help_one(ws: &Workspace) -> Result<u8, String> {
let is_subrepo = ws.dir_name != "root";
let target_dir: &Path = if is_subrepo {
Path::new("../target/xtask")
} else {
Path::new("target/xtask")
};
if is_subrepo {
emojis::print_run_header(&emojis::format_repo_label(&ws.dir_name));
}
eprintln!("🔧 Compiling xtask:{}...", ws.dir_name);
let mut cmd = Command::new("cargo");
if let Some(toolchain) = ws.toolchain {
cmd.arg(toolchain.as_rustup_arg());
}
apply_toolchain_env(&mut cmd, ws.toolchain);
cmd.arg("run").arg("--target-dir").arg(target_dir);
match &ws.xtask {
XtaskInvocation::WorkspaceMember { package } => {
cmd.arg("--package").arg(package);
}
XtaskInvocation::ManifestPath { manifest_path, .. } => {
cmd.arg("--manifest-path").arg(manifest_path);
}
}
cmd.arg("--bin")
.arg(&ws.xtask_bin)
.arg("--")
.arg("--help")
.env("XTASK_CLI", "1")
.current_dir(&ws.path);
if is_subrepo {
cmd.env("XTASK_MONOREPO", "1");
}
let status = cmd.status().map_err(|e| {
format!(
"failed to execute cargo run ({} --help): {e}",
ws.path.display()
)
})?;
Ok(exit_code_u8_from_status(status))
}
fn exec_cargo_xtask_all(
git_root: &Path,
args: &[OsString],
subrepos: &[Workspace],
) -> Result<ExitCode, String> {
let mut summary = DispatchSummary::new();
for ws in subrepos {
match exec_cargo_xtask(git_root, ws, args) {
Ok(0) => {
summary.push(SubrepoExecutionResult::success(ws.dir_name.clone()));
}
Ok(code) => {
summary.push(SubrepoExecutionResult::failure(ws.dir_name.clone(), code));
}
Err(err) => {
eprintln!("error: {err}");
summary.push(SubrepoExecutionResult::error(ws.dir_name.clone(), err));
}
}
}
summary.print();
Ok(summary.final_exit_code())
}
fn exec_cargo_xtask(git_root: &Path, ws: &Workspace, args: &[OsString]) -> Result<u8, String> {
let is_subrepo = ws.dir_name != "root";
let target_path = format!("target/{}", ws.xtask.package_name());
let target_dir = Path::new(&target_path);
if is_subrepo {
emojis::print_run_header(&emojis::format_repo_label(&ws.dir_name));
};
sync_monorepo_dependencies(git_root, std::slice::from_ref(ws))?;
eprintln!("🔧 Compiling xtask:{}...", ws.dir_name);
let mut cmd = Command::new("cargo");
if let Some(toolchain) = ws.toolchain {
cmd.arg(toolchain.as_rustup_arg());
}
apply_toolchain_env(&mut cmd, ws.toolchain);
cmd.arg("run").arg("--target-dir").arg(target_dir);
match &ws.xtask {
XtaskInvocation::WorkspaceMember { package } => {
cmd.arg("--package").arg(package);
}
XtaskInvocation::ManifestPath { manifest_path, .. } => {
cmd.arg("--manifest-path").arg(manifest_path);
}
}
cmd.arg("--bin")
.arg(&ws.xtask_bin)
.arg("--")
.args(args)
.env("XTASK_CLI", "1")
.current_dir(&ws.path);
if is_subrepo {
cmd.env("XTASK_MONOREPO", "1");
}
let status = cmd
.status()
.map_err(|e| format!("failed to execute cargo run ({}): {e}", ws.path.display()))?;
Ok(exit_code_u8_from_status(status))
}
fn exit_code_u8_from_status(status: std::process::ExitStatus) -> u8 {
match status.code() {
Some(code) if (0..=255).contains(&code) => code as u8,
_ => 1,
}
}
fn cli_name() -> String {
std::env::args_os()
.next()
.and_then(|p| {
std::path::Path::new(&p)
.file_name()
.map(|s| s.to_string_lossy().to_string())
})
.unwrap_or_else(|| "xtask".to_string())
}
fn cli_help_header() {
let name = cli_name();
let version = env!("CARGO_PKG_VERSION");
let authors = env!("CARGO_PKG_AUTHORS");
let author = authors.split(',').next().unwrap_or(authors);
eprintln!("{name} v{version} by {author}");
}
fn cli_help_fooder() {
println!("LICENSE");
println!("-------");
println!(" This project is dual-licensed under the Apache 2.0 and MIT licenses.");
println!(" You may choose either license when using, modifying, or distributing it.");
println!();
println!(" Repository: https://github.com/tracel-ai/xtask");
println!(" See LICENSE-APACHE and LICENSE-MIT for full license texts.");
println!();
}
fn show_xtask_cli_help(
git_root: &Path,
toolchain: Option<ToolchainOverride>,
) -> Result<ExitCode, String> {
let cwd = env::current_dir().map_err(|e| format!("failed to read current directory: {e}"))?;
let cli_name = cli_name();
let root_xtask = is_workspace(git_root)?;
let is_monorepo = root_xtask.is_none();
cli_help_header();
println!();
println!("A transparent wrapper around `cargo xtask` alias for standard repos and monorepos.");
println!("It discovers xtask workspaces and dispatches your command to the right place.");
println!();
println!("USAGE");
println!("-----");
println!(" {cli_name} [+nightly|+n] [:<subrepo>|:all] [<xtask args...>]");
println!(" {cli_name} +skill");
println!();
println!("BEHAVIOR");
println!("--------");
println!(" - With a selector:");
println!(" :<subrepo> Runs xtask in that subrepo workspace.");
println!(" :all Runs xtask in all subrepos.");
println!(" - Without a selector:");
println!(" Standard repo: runs xtask at the git root.");
println!(" Monorepo: if you're inside a subrepo, runs in that subrepo context,");
println!(" otherwise prompts then run the command in all the subrepos.");
println!();
println!("TOOLCHAIN");
println!("---------");
println!(" - `+nightly` Runs the underlying xtask with `cargo +nightly run ...`.");
println!(" - `+n` Short alias for `+nightly`.");
match toolchain {
Some(toolchain) => {
println!(" - Current override: {}", toolchain.display_name());
}
None => {
println!(" - Current override: none");
}
}
println!();
println!("HELP");
println!("----");
println!(" - `{cli_name}` Shows this screen.");
println!(" - `{cli_name} --help` Shows underlying xtask help (transparent mode).");
println!(" - `{cli_name} <command> --help` Shows help of <command>.");
println!(" - `{cli_name} +skill` Prints agent-oriented instructions for xtask.");
println!();
if !is_monorepo {
let xtask_pkg = root_xtask
.as_ref()
.map(|x| x.package_name().to_string())
.unwrap_or_else(|| "xtask".to_string());
println!("CONTEXT");
println!("-------");
println!(" Current Repository mode: standard repository");
println!(" Git root: {}", git_root.display());
println!(" Xtask package: {xtask_pkg}");
println!();
println!("EXAMPLES");
println!("--------");
println!(" {cli_name} build");
println!(" Run the `build` xtask command at the repository root.");
println!(" Equivalent to `cargo xtask build`.");
println!();
println!(" {cli_name} test all");
println!(" Run the `test` xtask command with argument `all`.");
println!(" Arguments are forwarded transparently to xtask.");
println!();
println!(" {cli_name} fix -y all");
println!(" Run the `fix` xtask command, auto-confirming prompts (`-y`),");
println!(" and applying fixes to all supported targets.");
println!();
println!(" {cli_name} +nightly test --miri");
println!(" Run the `test` xtask command through the nightly toolchain.");
println!();
cli_help_fooder();
return Ok(ExitCode::SUCCESS);
}
let subrepos = list_subrepo_workspaces(git_root, toolchain)?;
let located = find_subrepo_workspace_root(&cwd, git_root, toolchain)?;
let ex1 = subrepos
.first()
.map(|ws| ws.dir_name.as_str())
.unwrap_or("backend");
let ex2 = subrepos
.get(1)
.map(|ws| ws.dir_name.as_str())
.unwrap_or("frontend");
println!("CONTEXT");
println!("-------");
println!(" Git root: {}", git_root.display());
println!(" Current Repository mode: monorepo");
match located {
Some(ws) => {
println!(" Current location: inside subrepo `{}`", ws.dir_name);
println!(" Current xtask package: {}", ws.xtask.package_name());
}
None => {
if cwd == git_root {
println!(" Current location: monorepo root");
} else {
println!(" Current location: outside a recognized subrepo workspace");
}
}
}
println!();
println!("SUBREPOS");
println!("--------");
if subrepos.is_empty() {
println!(" (none found)");
} else {
for ws in &subrepos {
let shorthand = subrepo_shorthand(&ws.dir_name)
.map(|shorthand| format!(":{shorthand}"))
.unwrap_or_else(|| "-".to_string());
println!(
" - {:<16} shorthand: {:<8} xtask: {:<12}",
ws.dir_name,
shorthand,
ws.xtask.package_name(),
);
}
}
println!();
println!("EXAMPLES");
println!("--------");
println!(" {cli_name} :{ex1} build");
println!(" Run `build` in the `{ex1}` subrepo, regardless of current directory within");
println!(" the monorepo.");
println!();
println!(" {cli_name} :{ex2} test all");
println!(" Run both unit and integration tests scoped to the `{ex2}` subrepo only.");
println!();
println!(" {cli_name} +n :{ex1} test --miri");
println!(" Run xtask in `{ex1}` through the nightly toolchain.");
println!();
println!(" {cli_name} :all fix -y all");
println!(" Run all available fixes (lint, format, audit, ...) across all subrepos,");
println!(" auto-confirming prompts and applying fixes everywhere.");
println!();
println!(" {cli_name} :all build");
println!(" Run `build` xtask command in every subrepo, regardless of current");
println!(" directory within the monorepo. Useful to easily sync the dependencies");
println!(" of `Dependencies.toml` with all the subrepos and verify that they all");
println!(" still build without errors.");
println!();
println!("NOTES");
println!("-----");
println!(
" - If `Dependencies.toml` exists at the monorepo root, xtask will sync dependency specs"
);
println!(" before running subrepo commands.");
println!(
" - This wrapper is designed to remain transparent: it forwards your arguments to the"
);
println!(" underlying xtask binary in the selected workspace(s).");
println!(" - Subrepo selectors also support unambiguous shorthands, for example");
println!(" `product-backend` can be selected with `:pb`.");
println!();
cli_help_fooder();
Ok(ExitCode::SUCCESS)
}