use crate::channel::Channel;
use crate::component;
use crate::config::{self, Settings};
use crate::dist;
use crate::doc;
use crate::env;
use crate::error::{CjvError, Result};
use crate::init_cmd;
use crate::override_cmd;
use crate::resolve;
use crate::self_update;
use crate::toolchain::Toolchain;
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "cjv",
version = "0.3.0-beta",
about = "Cangjie SDK toolchain manager"
)]
#[command(propagate_version = true)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Install {
toolchain: String,
#[arg(short = 't', long = "target", value_delimiter = ',')]
target: Vec<String>,
#[arg(short = 'c', long = "component", value_delimiter = ',')]
component: Vec<String>,
#[arg(long = "force")]
force: bool,
},
Uninstall {
toolchain: String,
#[arg(short = 'y', long = "yes")]
yes: bool,
},
Update {
toolchain: Option<String>,
#[arg(long = "no-self-update")]
no_self_update: bool,
},
Check,
Show {
#[command(subcommand)]
subcommand: Option<ShowSubcommand>,
},
Run {
toolchain: String,
command: String,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
#[arg(long = "install")]
install: bool,
},
Exec {
#[arg(value_name = "+toolchain")]
plus_toolchain: Option<String>,
command: String,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Envsetup {
#[arg(value_name = "+toolchain")]
plus_toolchain: Option<String>,
#[arg(long = "shell")]
shell: Option<String>,
#[arg(long = "target")]
target: Option<String>,
},
Which {
command: Option<String>,
},
#[command(alias = "docs")]
Doc {
topic: Option<String>,
#[arg(long = "path")]
show_path: bool,
#[arg(long = "toolchain")]
toolchain: Option<String>,
},
Default {
toolchain: Option<String>,
},
Override {
#[command(subcommand)]
subcommand: OverrideSubcommand,
},
Component {
#[command(subcommand)]
subcommand: ComponentSubcommand,
},
Toolchain {
#[command(subcommand)]
subcommand: ToolchainSubcommand,
},
Set {
#[command(subcommand)]
subcommand: SetSubcommand,
},
Self_ {
#[command(subcommand)]
subcommand: SelfSubcommand,
},
Downloads {
#[command(subcommand)]
subcommand: DownloadsSubcommand,
},
Init {
#[arg(short = 'y', long = "yes")]
yes: bool,
#[arg(long = "default-toolchain")]
default_toolchain: Option<String>,
#[arg(short = 'c', long = "component", value_delimiter = ',')]
component: Vec<String>,
#[arg(long = "no-modify-path")]
no_modify_path: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum ShowSubcommand {
Active,
Installed,
Home,
}
#[derive(Subcommand, Debug)]
pub enum OverrideSubcommand {
Set {
toolchain: String,
#[arg(long = "path")]
path: Option<String>,
},
Unset {
#[arg(long = "path")]
path: Option<String>,
#[arg(long = "nonexistent")]
nonexistent: bool,
},
List,
}
#[derive(Subcommand, Debug)]
pub enum ComponentSubcommand {
Add {
names: Vec<String>,
#[arg(long = "toolchain")]
toolchain: Option<String>,
#[arg(long = "force")]
force: bool,
},
Link {
name: String,
path: String,
#[arg(long = "toolchain")]
toolchain: Option<String>,
#[arg(long = "force")]
force: bool,
},
#[command(alias = "uninstall", alias = "rm", alias = "delete", alias = "del")]
Remove {
names: Vec<String>,
#[arg(long = "toolchain")]
toolchain: Option<String>,
},
List {
#[arg(long = "toolchain")]
toolchain: Option<String>,
#[arg(long = "installed")]
installed: bool,
#[arg(short = 'q', long = "quiet")]
quiet: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum ToolchainSubcommand {
List,
Link {
name: String,
source: String,
#[arg(long = "sha256")]
sha256: Option<String>,
#[arg(long = "force")]
force: bool,
#[arg(long = "no-stdx")]
no_stdx: bool,
},
Uninstall {
name: String,
#[arg(short = 'y', long = "yes")]
yes: bool,
},
#[command(name = "list-remote")]
ListRemote,
}
#[derive(Subcommand, Debug)]
pub enum SetSubcommand {
#[command(name = "auto-self-update")]
AutoSelfUpdate { value: String },
#[command(name = "auto-install")]
AutoInstall { value: String },
#[command(name = "default-host")]
DefaultHost { value: String },
#[command(name = "gitcode-api-key")]
GitcodeApiKey { key: String },
#[command(name = "manifest-url")]
ManifestUrl { url: String },
#[command(name = "home")]
Home { path: Option<String> },
}
#[derive(Subcommand, Debug)]
pub enum SelfSubcommand {
Update,
Uninstall {
#[arg(short = 'y', long = "yes")]
yes: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum DownloadsSubcommand {
Purge,
}
pub async fn execute(cli: &Cli) -> Result<()> {
match &cli.command {
Commands::Install {
toolchain,
target,
component: components,
force,
} => cmd_install(toolchain, target, components, *force).await,
Commands::Uninstall { toolchain, yes } => cmd_uninstall(toolchain, *yes),
Commands::Update {
toolchain,
no_self_update,
} => cmd_update(toolchain.as_deref(), *no_self_update).await,
Commands::Check => cmd_check().await,
Commands::Show { subcommand } => cmd_show(subcommand.as_ref()),
Commands::Run {
toolchain,
command,
args,
install,
} => cmd_run(toolchain, command, args, *install).await,
Commands::Exec {
plus_toolchain,
command,
args,
} => cmd_exec(plus_toolchain, command, args),
Commands::Envsetup {
plus_toolchain,
shell,
target,
} => cmd_envsetup(plus_toolchain, shell.as_deref(), target.as_deref()),
Commands::Which { command } => cmd_which(command.as_deref()),
Commands::Doc {
topic,
show_path,
toolchain,
} => cmd_doc(topic.as_deref(), *show_path, toolchain.as_deref()),
Commands::Default { toolchain } => cmd_default(toolchain.as_deref()),
Commands::Override { subcommand } => cmd_override(subcommand),
Commands::Component { subcommand } => cmd_component(subcommand).await,
Commands::Toolchain { subcommand } => cmd_toolchain(subcommand).await,
Commands::Set { subcommand } => cmd_set(subcommand),
Commands::Self_ { subcommand } => cmd_self(subcommand),
Commands::Downloads { subcommand } => cmd_downloads(subcommand),
Commands::Init {
yes,
default_toolchain,
component,
no_modify_path,
} => {
init_cmd::run_init(
*yes,
default_toolchain.as_deref(),
component,
*no_modify_path,
)
.await
}
}
}
async fn cmd_install(
toolchain: &str,
targets: &[String],
components: &[String],
force: bool,
) -> Result<()> {
let normalized_targets =
crate::target::normalize_target_list(targets).map_err(CjvError::InvalidArgument)?;
let tc_name = resolve::resolve_toolchain_name(toolchain);
if Toolchain::is_installed(&tc_name) && !force {
return Err(CjvError::AlreadyExists(format!(
"Toolchain '{}' is already installed. Use --force to reinstall.",
tc_name
)));
}
let settings = Settings::load()?;
let distribution = dist::resolve_toolchain_distribution(toolchain, &settings).await?;
let target_base_url = distribution
.url
.rsplit_once('/')
.map(|(base, _)| base.to_string())
.unwrap_or_else(|| "https://download.cangjie-lang.cn".to_string());
dist::download_and_install(
&distribution.install_name,
&distribution.url,
distribution.checksum.as_deref(),
)
.await?;
let tc_name = distribution.install_name;
for comp in components {
component::install_component(&tc_name, comp, false).await?;
}
for target in &normalized_targets {
println!("Installing target '{}'...", target);
let target_url = format!("{}/targets/{}/{}.tar.gz", target_base_url, tc_name, target);
let target_path = Toolchain::get_path(&tc_name).join("target").join(target);
if let Some(parent) = target_path.parent() {
crate::utils::ensure_dir(parent)?;
}
let archive_path = crate::utils::cjv_home()
.join("downloads")
.join(format!("target-{}-{}.tar.gz", tc_name, target));
match dist::download_file(&target_url, &archive_path).await {
Ok(_) => {
crate::toolchain::extract_archive_to(&archive_path, &target_path)?;
component::update_target_manifest(&tc_name, target, true)?;
let _ = std::fs::remove_file(&archive_path);
println!("Target '{}' installed.", target);
}
Err(e) => eprintln!("Warning: Failed to install target '{}': {}", target, e),
}
}
Ok(())
}
fn cmd_uninstall(toolchain: &str, yes: bool) -> Result<()> {
let tc_name = resolve::resolve_toolchain_name(toolchain);
if !Toolchain::is_installed(&tc_name) {
return Err(CjvError::ToolchainNotInstalled(tc_name));
}
if !yes {
use dialoguer::Confirm;
let confirmed = Confirm::new()
.with_prompt(format!("Uninstall toolchain '{}'?", tc_name))
.default(false)
.interact()
.map_err(|e| CjvError::Other(format!("Failed to get confirmation: {}", e)))?;
if !confirmed {
println!("Uninstall cancelled.");
return Ok(());
}
}
let mut settings = Settings::load()?;
if settings.get_default_toolchain().as_deref() == Some(&tc_name) {
settings.set_default_toolchain(None)?;
}
let overrides = settings.get_overrides();
for (path, tc) in &overrides {
if tc == &tc_name {
settings.remove_override(path)?;
}
}
Toolchain::uninstall(&tc_name)?;
println!("Toolchain '{}' has been uninstalled.", tc_name);
Ok(())
}
async fn cmd_update(toolchain: Option<&str>, no_self_update: bool) -> Result<()> {
let settings = Settings::load()?;
if !no_self_update && settings.auto_self_update.as_deref() == Some("enable") {
eprintln!(
"Skipping self update: 'cjv self update' is not implemented in this beta. Use --no-self-update to suppress this notice."
);
}
let manifest_url = settings.get_manifest_url();
let manifest = dist::fetch_manifest(&manifest_url).await;
let installed = Toolchain::list_installed()?;
let platform = crate::utils::current_platform()?;
if let Some(tc) = toolchain {
let tc_name = resolve::resolve_toolchain_name(tc);
if !Toolchain::is_installed(&tc_name) {
return Err(CjvError::ToolchainNotInstalled(tc_name));
}
if let Some(ref m) = manifest {
let parsed = crate::toolchain_name::ToolchainName::parse(tc)
.map_err(CjvError::InvalidArgument)?;
let ch = match parsed.channel {
crate::toolchain_name::Channel::Lts => Channel::Lts,
crate::toolchain_name::Channel::Sts => Channel::Sts,
crate::toolchain_name::Channel::Nightly => Channel::Nightly,
crate::toolchain_name::Channel::Unknown => Channel::Unknown,
};
if let Some(latest) = m.latest_for_channel(ch.as_str()) {
let current = installed.iter().find(|t| t.name == tc_name);
if let Some(c) = current {
if !c.version.is_empty() && c.version == latest.version {
println!(
"Toolchain '{}' is already at the latest version ({}).",
tc_name, latest.version
);
return Ok(());
}
}
println!("Updating '{}' to version {}...", tc_name, latest.version);
}
}
install_update_candidate(tc, &tc_name, &settings, manifest.as_ref(), &platform).await?;
println!("Toolchain '{}' updated successfully.", tc_name);
} else {
for tc in &installed {
if tc.is_linked {
println!("Skipping linked toolchain '{}' (cannot update)", tc.name);
continue;
}
if tc.channel == Channel::Unknown || tc.is_custom {
println!("Skipping custom toolchain '{}' (cannot update)", tc.name);
continue;
}
if let Some(ref m) = manifest {
let ch = Channel::parse(&tc.name);
if let Some(latest) = m.latest_for_channel(ch.as_str()) {
if !tc.version.is_empty() && tc.version == latest.version {
println!("'{}' is already up to date ({}).", tc.name, tc.version);
continue;
}
}
}
println!("Updating '{}'...", tc.name);
let tc_name = tc.name.clone();
install_update_candidate(
tc.channel.as_str(),
&tc_name,
&settings,
manifest.as_ref(),
&platform,
)
.await?;
println!("Toolchain '{}' updated successfully.", tc_name);
}
}
Ok(())
}
async fn install_update_candidate(
requested: &str,
install_name: &str,
settings: &Settings,
manifest: Option<&crate::manifest::Manifest>,
platform: &str,
) -> Result<()> {
let parsed = crate::toolchain_name::ToolchainName::parse(requested)
.map_err(CjvError::InvalidArgument)?;
if parsed.is_custom() {
return Err(CjvError::InvalidArgument(format!(
"Cannot update custom toolchain '{}'",
requested
)));
}
let channel = match parsed.channel {
crate::toolchain_name::Channel::Lts => Channel::Lts,
crate::toolchain_name::Channel::Sts => Channel::Sts,
crate::toolchain_name::Channel::Nightly => Channel::Nightly,
crate::toolchain_name::Channel::Unknown => Channel::Unknown,
};
let version = parsed.version.as_str();
if let Some(manifest) = manifest {
if channel != Channel::Unknown {
if let Some(download) = manifest.get_download(channel.as_str(), version, platform) {
return dist::download_and_install(
install_name,
&download.url,
download.sha256.as_deref(),
)
.await;
}
}
}
let registry = crate::channel::Registry::new(settings.get_gitcode_api_key());
let url = registry.get_download_url(&channel, version, platform)?;
let checksum = registry.get_checksum(&channel, version, platform).await?;
dist::download_and_install(install_name, &url, checksum.as_deref()).await
}
async fn cmd_check() -> Result<()> {
let toolchains = Toolchain::list_installed()?;
if toolchains.is_empty() {
println!("No toolchains installed.");
return Ok(());
}
let settings = Settings::load()?;
let manifest_url = settings.get_manifest_url();
let manifest = dist::fetch_manifest(&manifest_url).await;
println!("Checking for updates...");
for tc in &toolchains {
print!("{}: ", tc.name);
if tc.is_linked {
println!("linked (skipped)");
continue;
}
if let Some(ref m) = manifest {
let ch = tc.channel;
if let Some(latest) = m.latest_for_channel(ch.as_str()) {
if tc.version.is_empty() || tc.version != latest.version {
if tc.version.is_empty() {
println!("{} available (current version unknown)", latest.version);
} else {
println!("{} → {} (update available)", tc.version, latest.version);
}
} else {
println!("✓ up to date ({})", tc.version);
}
} else if ch == Channel::Unknown {
println!("custom (skipped)");
} else {
println!("no release info");
}
} else {
println!("no manifest (cannot check)");
}
}
println!();
println!("cjv version: 0.3.0-beta");
Ok(())
}
fn cmd_show(subcommand: Option<&ShowSubcommand>) -> Result<()> {
match subcommand {
Some(ShowSubcommand::Active) => {
let settings = Settings::load()?;
let active = config::resolve_active_toolchain_with_source(&settings);
match active {
Some((tc, source)) => {
println!("Active toolchain: {}", tc);
println!("Source: {}", source.as_str());
}
None => {
println!("No active toolchain configured.");
println!("Install one with 'cjv install lts' and set it as default with 'cjv default lts'");
}
}
}
Some(ShowSubcommand::Installed) => {
let toolchains = Toolchain::list_installed()?;
if toolchains.is_empty() {
println!("No toolchains installed.");
} else {
println!("Installed toolchains:");
for tc in &toolchains {
let link_marker = if tc.is_linked { " (linked)" } else { "" };
print!(" {} {}", tc.name, if tc.is_linked { "🔗" } else { "✓" });
print!("{}", link_marker);
if !tc.components.is_empty() {
print!(" [components: {}]", tc.components.join(", "));
}
if !tc.version.is_empty() {
print!(" (v{})", tc.version);
}
println!();
}
}
}
Some(ShowSubcommand::Home) => {
let home = crate::utils::cjv_home();
let source = if std::env::var("CJV_HOME").is_ok() {
"CJV_HOME environment variable"
} else {
"default"
};
println!("{}", home.display());
println!("Source: {}", source);
}
None => {
let settings = Settings::load()?;
let active = config::resolve_active_toolchain(&settings);
let platform = crate::utils::current_platform()?;
println!("Active toolchain:");
match active {
Some(ref tc) => {
let installed = Toolchain::is_installed(tc);
let status = if installed {
"✓ installed"
} else {
"✗ not installed"
};
println!(" {} ({})", tc, status);
}
None => println!(" (none configured)"),
}
println!();
println!("Default host: {}", platform);
let toolchains = Toolchain::list_installed()?;
println!();
println!("Installed toolchains:");
if toolchains.is_empty() {
println!(" (none)");
} else {
for tc in &toolchains {
let link_marker = if tc.is_linked { " 🔗" } else { "" };
let active_marker = if active.as_deref() == Some(&tc.name) {
" ← active"
} else {
""
};
println!(" {}{}{}", tc.name, link_marker, active_marker);
}
}
}
}
Ok(())
}
async fn cmd_run(toolchain: &str, command: &str, args: &[String], install: bool) -> Result<()> {
let tc_name = resolve::resolve_toolchain_name(toolchain);
if !Toolchain::is_installed(&tc_name) {
if install {
let settings = Settings::load()?;
dist::download_and_install_toolchain(toolchain, &settings).await?;
} else {
return Err(CjvError::ToolchainNotInstalled(tc_name));
}
}
let tool_path = resolve::find_tool_path(&tc_name, command)?;
let mut cmd = std::process::Command::new(&tool_path);
cmd.args(args);
env::inject_runtime_env(&mut cmd, &tc_name);
let mut child = cmd
.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.spawn()
.map_err(|e| {
CjvError::Proxy(format!(
"Failed to execute '{}': {}",
tool_path.display(),
e
))
})?;
let status = child
.wait()
.map_err(|e| CjvError::Proxy(format!("Failed to wait for process: {}", e)))?;
std::process::exit(status.code().unwrap_or(1));
}
fn cmd_exec(plus_toolchain: &Option<String>, command: &str, args: &[String]) -> Result<()> {
let tc = plus_toolchain.as_deref().and_then(|s| s.strip_prefix('+'));
let exit_code = env::exec_in_env(tc, command, args)?;
std::process::exit(exit_code);
}
fn cmd_envsetup(
plus_toolchain: &Option<String>,
shell: Option<&str>,
target: Option<&str>,
) -> Result<()> {
let tc = plus_toolchain.as_deref().and_then(|s| s.strip_prefix('+'));
let output = env::envsetup(tc, shell, target)?;
println!("{}", output);
Ok(())
}
fn cmd_which(command: Option<&str>) -> Result<()> {
let settings = Settings::load()?;
let active_tc = config::resolve_active_toolchain(&settings)
.ok_or_else(|| CjvError::Other("No active toolchain configured".to_string()))?;
match command {
Some(cmd) => {
let path = resolve::find_tool_path(&active_tc, cmd)?;
println!("{}", path.display());
}
None => {
let path = Toolchain::get_path(&active_tc);
println!("{}", path.display());
}
}
Ok(())
}
fn cmd_doc(topic: Option<&str>, show_path: bool, toolchain: Option<&str>) -> Result<()> {
doc::open_doc(topic, show_path, toolchain)
}
fn cmd_default(toolchain: Option<&str>) -> Result<()> {
let mut settings = Settings::load()?;
match toolchain {
Some("none") => {
settings.set_default_toolchain(None)?;
println!("Default toolchain cleared.");
}
Some(tc) => {
settings.set_default_toolchain(Some(tc.to_string()))?;
println!("Default toolchain set to '{}'.", tc);
}
None => match settings.get_default_toolchain() {
Some(tc) => println!("{}", tc),
None => println!("No default toolchain configured."),
},
}
Ok(())
}
fn cmd_override(subcommand: &OverrideSubcommand) -> Result<()> {
match subcommand {
OverrideSubcommand::Set { toolchain, path } => {
override_cmd::set_override(path.as_deref(), toolchain)
}
OverrideSubcommand::Unset { path, nonexistent } => {
if *nonexistent {
override_cmd::clean_nonexistent_overrides()
} else {
override_cmd::unset_override(path.as_deref())
}
}
OverrideSubcommand::List => {
let overrides = override_cmd::list_overrides()?;
if overrides.is_empty() {
println!("No directory overrides configured.");
} else {
println!("Directory overrides:");
for (path, tc) in &overrides {
println!(" {} -> {}", path, tc);
}
}
Ok(())
}
}
}
async fn cmd_component(subcommand: &ComponentSubcommand) -> Result<()> {
match subcommand {
ComponentSubcommand::Add {
names,
toolchain,
force,
} => {
let tc = selected_component_toolchain(toolchain)?;
for name in names {
component::install_component(&tc, name, *force).await?;
}
Ok(())
}
ComponentSubcommand::Link {
name,
path,
toolchain,
force,
} => {
let tc = selected_component_toolchain(toolchain)?;
component::link_component(&tc, name, &std::path::PathBuf::from(path), *force)
}
ComponentSubcommand::Remove { names, toolchain } => {
let tc = selected_component_toolchain(toolchain)?;
for name in names {
component::remove_component(&tc, name)?;
}
Ok(())
}
ComponentSubcommand::List {
toolchain,
installed,
quiet,
} => {
let tc = selected_component_toolchain(toolchain)?;
let components = component::list_components(&tc, *installed)?;
if *quiet {
for c in &components {
println!("{}", c);
}
} else {
println!("Components for '{}':", tc);
if components.is_empty() {
println!(" (none)");
} else {
for c in &components {
println!(" {}", c);
}
}
}
Ok(())
}
}
}
fn selected_component_toolchain(toolchain: &Option<String>) -> Result<String> {
toolchain
.clone()
.or_else(Toolchain::active_toolchain_name)
.ok_or_else(|| {
CjvError::Other(
"No active toolchain configured. Pass --toolchain <tc> or set a default toolchain."
.to_string(),
)
})
}
async fn cmd_toolchain(subcommand: &ToolchainSubcommand) -> Result<()> {
match subcommand {
ToolchainSubcommand::List => {
let toolchains = Toolchain::list_installed()?;
if toolchains.is_empty() {
println!("No toolchains installed.");
} else {
println!("Installed toolchains:");
for tc in &toolchains {
println!(
" {}{}",
tc.name,
if tc.is_linked { " (linked)" } else { "" }
);
}
}
Ok(())
}
ToolchainSubcommand::Link {
name,
source,
sha256,
force,
no_stdx,
} => {
if crate::utils::is_reserved_name(name) {
return Err(CjvError::InvalidArgument(format!(
"Cannot use reserved name '{}' for a custom toolchain",
name
)));
}
if Toolchain::is_installed(name) && !force {
return Err(CjvError::AlreadyExists(format!(
"Toolchain '{}' already exists. Use --force to overwrite.",
name
)));
}
if source.starts_with("http://") || source.starts_with("https://") {
let downloads_dir = crate::utils::cjv_home().join("downloads");
crate::utils::ensure_dir(&downloads_dir)?;
let archive_name = format!("{}.tar.gz", name);
let archive_path = downloads_dir.join(&archive_name);
println!("Downloading from {}...", source);
dist::download_file(source, &archive_path).await?;
if let Some(hash) = sha256 {
dist::verify_checksum(&archive_path, hash)?;
}
if *force && Toolchain::is_installed(name) {
Toolchain::uninstall(name)?;
}
Toolchain::install_from_archive(name, &archive_path)?;
let _ = std::fs::remove_file(&archive_path);
if !no_stdx {
println!("stdx component can be installed with 'cjv component add stdx --toolchain {}'", name);
}
println!("Toolchain '{}' installed from URL.", name);
} else {
let source_path = std::path::PathBuf::from(source);
if !source_path.exists() {
return Err(CjvError::NotFound(format!("Path '{}' not found", source)));
}
if source_path.is_dir() {
if *force && Toolchain::is_installed(name) {
Toolchain::uninstall(name)?;
}
Toolchain::link(name, &source_path)?;
println!("Toolchain '{}' linked to '{}'", name, source_path.display());
} else {
if *force && Toolchain::is_installed(name) {
Toolchain::uninstall(name)?;
}
if let Some(hash) = sha256 {
dist::verify_checksum(&source_path, hash)?;
}
Toolchain::install_from_archive(name, &source_path)?;
println!(
"Toolchain '{}' installed from '{}'",
name,
source_path.display()
);
}
}
Ok(())
}
ToolchainSubcommand::ListRemote => cmd_toolchain_list_remote().await,
ToolchainSubcommand::Uninstall { name, yes } => cmd_uninstall(name, *yes),
}
}
async fn cmd_toolchain_list_remote() -> Result<()> {
let settings = Settings::load()?;
let manifest_url = settings.get_manifest_url();
println!("Fetching remote toolchains from {}...", manifest_url);
match dist::fetch_manifest(&manifest_url).await {
Some(manifest) => {
println!();
println!("Available remote toolchains:");
for entry in &manifest.releases {
let platforms: Vec<String> =
entry.downloads.iter().map(|d| d.platform.clone()).collect();
println!(
" {} {} [{}]",
entry.channel,
entry.version,
platforms.join(", ")
);
}
return Ok(());
}
None => println!("Cannot reach distribution server. Showing default channels:"),
}
println!(" lts - Long Term Support (latest)");
println!(" sts - Short Term Support (latest)");
println!(" nightly - Nightly builds (requires GitCode API key)");
Ok(())
}
fn cmd_set(subcommand: &SetSubcommand) -> Result<()> {
let mut settings = Settings::load()?;
match subcommand {
SetSubcommand::AutoSelfUpdate { value } => {
settings.set_auto_self_update(value.clone())?;
println!("Setting 'auto-self-update' updated to '{}'", value);
}
SetSubcommand::AutoInstall { value } => {
let val = match value.as_str() {
"true" => true,
"false" => false,
_ => {
return Err(CjvError::InvalidArgument(format!(
"Invalid value '{}'. Must be 'true' or 'false'",
value
)))
}
};
settings.set_auto_install(val)?;
println!("Setting 'auto-install' updated to '{}'", value);
}
SetSubcommand::DefaultHost { value } => {
settings.set_default_host(value.clone())?;
println!("Setting 'default-host' updated to '{}'", value);
}
SetSubcommand::GitcodeApiKey { key } => {
settings.set_gitcode_api_key(key.clone())?;
println!("Setting 'gitcode-api-key' updated.");
}
SetSubcommand::ManifestUrl { url } => {
settings.set_manifest_url(url.clone())?;
println!("Setting 'manifest-url' updated to '{}'", url);
}
SetSubcommand::Home { path } => {
settings.set_home(path.clone())?;
match &path {
Some(p) if !p.is_empty() => println!("Setting 'home' updated to '{}'", p),
_ => println!("Setting 'home' cleared (reset to default)."),
}
}
}
Ok(())
}
fn cmd_self(subcommand: &SelfSubcommand) -> Result<()> {
match subcommand {
SelfSubcommand::Update => self_update::self_update(),
SelfSubcommand::Uninstall { yes } => self_update::self_uninstall(*yes),
}
}
fn cmd_downloads(subcommand: &DownloadsSubcommand) -> Result<()> {
match subcommand {
DownloadsSubcommand::Purge => {
let downloads_dir = crate::utils::cjv_home().join("downloads");
if downloads_dir.exists() {
std::fs::remove_dir_all(&downloads_dir)?;
crate::utils::ensure_dir(&downloads_dir)?;
println!("Downloads cache cleared.");
} else {
println!("Downloads cache is empty.");
}
Ok(())
}
}
}