use std::ffi::{OsStr, OsString}; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use super::release::{Layout, MachineManager, Version, platform_contract}; const USAGE: &str = "Usage:\n dever install \n dever update\n dever target add linux-aarch64\n dever use \n dever uninstall \n dever version [project-root]\n dever cache status\n dever cache clean\n dever lib add|list|update|install|remove|doctor [--target linux-x86_64|linux-aarch64] [spec ...]\n dever package add|list|update|remove|doctor [spec ...]\n dever check \n dever api [--output ]\n dever fmt [--check]\n dever test \n dever run [-- .. '']\n dever build --output [--target linux-x86_64|linux-aarch64]\n dever clean \n dever tenant ..."; #[derive(Debug)] pub enum CommandResult { Exited(i32), Message(String), } pub fn execute(executable: &Path, arguments: &[OsString]) -> Result { if platform_contract(std::env::consts::OS).is_none() { return Err(format!( "Dever does not support platform '{}'", std::env::consts::OS )); } let layout = Layout::from_launcher(executable)?; execute_in(&layout, arguments) } pub fn execute_in(layout: &Layout, arguments: &[OsString]) -> Result { layout.validate_machine_permissions()?; #[cfg(target_os = "linux")] super::bootstrap::require_no_pending(layout)?; let manager = MachineManager::new(layout.clone()); match arguments { [command, operation, target] if command == "target" && operation == "add" => { let target = utf8(target, "target")?.parse::()?; if target != super::BuildTarget::LinuxAarch64 { return Err("target add supports linux-aarch64".into()); } let version = manager .active_version()? .ok_or("no active Dever version; run 'dever install latest'")?; super::ensure_extension(layout, &version, super::ExtensionKind::Target, target)?; Ok(CommandResult::Message(format!( "prepared {} for Dever {version}", target.platform() ))) } [command, requested] if command == "install" => { let requested = utf8(requested, "version")?; super::release_source::prepare(layout, requested)?; let version = manager.install_requested(requested)?; Ok(CommandResult::Message(format!("installed Dever {version}"))) } [command] if command == "update" => { super::release_source::prepare(layout, "latest")?; let version = manager.update()?; Ok(CommandResult::Message(format!( "updated Dever and dever-language skill to {version}" ))) } [command, version] if command == "use" => { let version = Version::parse(utf8(version, "version")?)?; manager.activate(&version)?; Ok(CommandResult::Message(format!("active Dever {version}"))) } [command, version] if command == "uninstall" => { let version = Version::parse(utf8(version, "version")?)?; manager.uninstall(&version)?; Ok(CommandResult::Message(format!( "uninstalled Dever {version}" ))) } [command] if command == "version" => { let version = manager .active_version()? .ok_or("no active Dever version; run 'dever install latest'")?; manager.resolve_core(&version)?; Ok(CommandResult::Message(version.to_string())) } [command, project] if command == "version" => { let root = PathBuf::from(project); let version = select_version(&manager, &root)?; Ok(CommandResult::Message(version.to_string())) } [cache, status] if cache == "cache" && status == "status" => { let status = crate::toolchain::cache_status(layout)?; Ok(CommandResult::Message( serde_json::to_string(&status).map_err(|error| error.to_string())?, )) } [cache, clean] if cache == "cache" && clean == "clean" => { let status = crate::toolchain::cache_clean( layout, &manager.installed_versions()?.into_iter().collect(), )?; Ok(CommandResult::Message( serde_json::to_string(&status).map_err(|error| error.to_string())?, )) } [skill, path] if skill == "skill" && path == "path" => Ok(CommandResult::Message( super::skill::path(&manager)?.display().to_string(), )), [skill, path, project] if skill == "skill" && path == "path" => { let version = select_version(&manager, Path::new(project))?; Ok(CommandResult::Message( manager.resolve_skill(&version)?.display().to_string(), )) } [skill, install, directory] if skill == "skill" && install == "install" => { super::skill::install(&manager, Path::new(directory))?; Ok(CommandResult::Message(format!( "installed dever-language skill entry in '{}'", Path::new(directory).display() ))) } [command, ..] if command == "new" => { let version = manager .active_version()? .ok_or("no active Dever version; run 'dever install latest'")?; dispatch_core(&manager, &version, arguments) } [help] if help == "--help" => Ok(CommandResult::Message(format!( "{USAGE}\n dever new [--markdown]\n dever skill path [project-root]\n dever skill install " ))), [] => Err(USAGE.into()), _ => dispatch_project_command(&manager, arguments), } } pub fn project_version(project_root: &Path) -> Result, String> { let path = project_root.join("config/setting.json"); let text = fs::read_to_string(&path) .map_err(|error| format!("cannot read '{}': {error}", path.display()))?; let root = dever_runtime::wire::parse(&text) .map_err(|error| format!("invalid '{}': {error}", path.display()))?; let fields = root .object() .map_err(|_| format!("invalid '{}': expected a JSON object", path.display()))?; let Some(dever) = fields.get("dever") else { return Ok(None); }; let fields = dever .fields(&["version"]) .map_err(|error| format!("invalid '{}': dever: {error}", path.display()))?; let version = fields .get("version") .ok_or_else(|| format!("invalid '{}': dever.version is required", path.display()))? .text() .map_err(|error| format!("invalid '{}': dever.version: {error}", path.display()))?; Version::parse(version) .map(Some) .map_err(|error| format!("invalid '{}': {error}", path.display())) } fn dispatch_project_command( manager: &MachineManager, arguments: &[OsString], ) -> Result { let root = project_root(arguments).ok_or_else(|| USAGE.to_owned())?; let version = select_version(manager, &root)?; dispatch_core(manager, &version, arguments) } fn dispatch_core( manager: &MachineManager, version: &Version, arguments: &[OsString], ) -> Result { let core = manager.resolve_core(version)?; let status = Command::new(&core) .args(arguments) .env_clear() .status() .map_err(|error| format!("cannot run Dever {version} '{}': {error}", core.display()))?; Ok(CommandResult::Exited(status.code().unwrap_or(1))) } fn select_version(manager: &MachineManager, project_root: &Path) -> Result { let version = match project_version(project_root)? { Some(version) => version, None => manager .active_version()? .ok_or("no active Dever version; run 'dever install latest'")?, }; manager.resolve_core(&version)?; Ok(version) } fn project_root(arguments: &[OsString]) -> Option { match arguments { [command, _operation, root, ..] if command == "lib" || command == "package" => { Some(root.into()) } [command, root, ..] if matches!( command.to_str(), Some("check" | "api" | "fmt" | "test" | "run" | "build" | "clean") ) => { Some(root.into()) } [tenant, operation, root, ..] if tenant == "tenant" && matches!(operation.to_str(), Some("migrate" | "owner")) => { Some(root.into()) } [tenant, component, _operation, root, ..] if tenant == "tenant" && component == "component" => { Some(root.into()) } _ => None, } } fn utf8<'a>(value: &'a OsStr, label: &str) -> Result<&'a str, String> { value .to_str() .ok_or_else(|| format!("{label} must be UTF-8")) }