diff --git a/cli/src/common.rs b/cli/src/common.rs index a15cfde..9b391da 100644 --- a/cli/src/common.rs +++ b/cli/src/common.rs @@ -1,6 +1,7 @@ use chrono::{Local, TimeZone}; use console::{Term, style}; use indicatif::ProgressStyle; +use packie::{Packie, local::Installation, package::PkgSpec}; use rust_i18n::t; pub fn progress_style_multidownload() -> ProgressStyle { @@ -78,3 +79,20 @@ pub fn format_datetime(timestamp: i64) -> String { }; datetime.to_string() } + +pub fn search_unique_package( + packie: &mut Packie, + pkgspec: &PkgSpec, +) -> Result { + let mut found = packie.search_installation(&pkgspec)?; + if found.is_empty() { + return Err(crate::error!("common.package_not_found")); + } + if found.len() != 1 { + return Err(crate::Error { + message: rust_i18n::t!("common.error_pkgspec_not_unique").into(), + note: Some(rust_i18n::t!("common.note_pkgspec_not_unique").into()), + }); + } + Ok(found.remove(0)) +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 9c120e4..6f536ca 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,6 +4,7 @@ mod info; mod initdb; mod install; mod print; +mod reconfigure; mod remove; mod repo_admin; mod select; @@ -53,6 +54,9 @@ enum Subcommand { /// Select global link Select(select::Cli), + + /// Run configure scripts + Reconfigure(reconfigure::Cli), } fn main() { @@ -79,6 +83,7 @@ fn main() { Subcommand::RepoAdmin(cli) => repo_admin::main(cli), Subcommand::Update(cli) => update::main(cli), Subcommand::Select(cli) => select::main(cli), + Subcommand::Reconfigure(cli) => reconfigure::main(cli), }; if let Err(err) = result { eprintln!("{}", style(t!("main.error", error = err.message)).red()); diff --git a/cli/src/reconfigure.rs b/cli/src/reconfigure.rs new file mode 100644 index 0000000..46ded45 --- /dev/null +++ b/cli/src/reconfigure.rs @@ -0,0 +1,33 @@ +use crate::common::search_unique_package; +use clap::Parser; +use packie::{ + PackieBuilder, + configure::{Action, ConfigureOptions}, + package::PkgSpec, +}; + +#[derive(Debug, Parser)] +pub struct Cli { + #[arg(short = 'I', long)] + non_interactive: bool, + + pkgspec: PkgSpec, + + item: Option, +} + +pub fn main(cli: Cli) -> Result<(), crate::Error> { + let mut packie = PackieBuilder::new().build()?; + let pkg = search_unique_package(&mut packie, &cli.pkgspec)?; + packie.run_configure( + &pkg.pkg_manifest.pkg_ident(), + cli.item, + &ConfigureOptions { + action: Action::Configure, + interactive: !cli.non_interactive, + prefix: None, + extra_args: Vec::new(), + }, + )?; + Ok(()) +} diff --git a/cli/src/remove.rs b/cli/src/remove.rs index 3a144a6..0b6b1e2 100644 --- a/cli/src/remove.rs +++ b/cli/src/remove.rs @@ -1,7 +1,4 @@ -use crate::{ - common::{print_list, yesno}, - error, -}; +use crate::common::{print_list, search_unique_package, yesno}; use anyhow::anyhow; use clap::Parser; use console::style; @@ -20,14 +17,7 @@ pub fn main(cli: Cli) -> Result<(), crate::Error> { let mut packie = PackieBuilder::new().build()?; let mut packages = Vec::with_capacity(cli.items.len()); for pkgspec in cli.items { - let mut found = packie.search_installation(&pkgspec)?; - if found.is_empty() { - return Err(error!("remove.package_not_found")); - } - if found.len() != 1 { - return Err(error!("remove.pkgspec_not_unique")); - } - packages.push(found.remove(0)); + packages.push(search_unique_package(&mut packie, &pkgspec)?); } println!("{}", t!("remove.prompt_to_remove")); print_list( diff --git a/locales/packie-cli/main.yml b/locales/packie-cli/main.yml index 2b342ac..3588aba 100644 --- a/locales/packie-cli/main.yml +++ b/locales/packie-cli/main.yml @@ -91,14 +91,6 @@ remove.package_not_found: en: "The specified package \"%{pkgspec}\" was not found." zh-CN: "找不到指定的包 \"%{pkgspec}\"。" ja: "指定されたパッケージ \"%{pkgspec}\" が見つかりません。" -remove.error_pkgspec_not_unique: - en: "The specified package spec \"%{pkgspec}\" was not unique." - zh-CN: "指定的包规范 \"%{pkgspec}\" 匹配了多个包。" - ja: "指定されたパッケージ指定 \"%{pkgspec}\" が複数のパッケージに一致しました。" -remove.note_pkgspec_not_unique: - en: "The following packages were matched:" - zh-CN: "匹配到了下面的包:" - ja: "以下のパッケージが一致しました:" remove.action_failure: en: "Failed to remove the specified package: %{error}" zh-CN: "无法移除指定的包:%{error}" @@ -135,6 +127,18 @@ common.progress_style.eta: en: "ETA: " zh-CN: "剩余时间:" ja: "残り時間:" +common.package_not_found: + en: "The specified package \"%{pkgspec}\" was not found." + zh-CN: "找不到指定的包 \"%{pkgspec}\"。" + ja: "指定されたパッケージ \"%{pkgspec}\" が見つかりません。" +common.error_pkgspec_not_unique: + en: "The specified package spec \"%{pkgspec}\" was not unique." + zh-CN: "指定的包规范 \"%{pkgspec}\" 匹配了多个包。" + ja: "指定されたパッケージ指定 \"%{pkgspec}\" が複数のパッケージに一致しました。" +common.note_pkgspec_not_unique: + en: "The following packages were matched:" + zh-CN: "匹配到了下面的包:" + ja: "以下のパッケージが一致しました:" clean.warn_repos_dir: en: "warning: failed to read repository cache directory: %{error}" zh-CN: "警告:无法读取软件包仓库缓存目录:%{error}" diff --git a/src/configure.rs b/src/configure.rs new file mode 100644 index 0000000..b095832 --- /dev/null +++ b/src/configure.rs @@ -0,0 +1,93 @@ +use crate::package::PkgIdent; +use std::{ + fmt::Display, + path::{Path, PathBuf}, +}; + +impl super::Packie { + pub fn run_configure( + &self, + pkg: &PkgIdent, + item: Option, + options: &ConfigureOptions, + ) -> Result<(), ConfigureError> { + let mut options = options.clone(); + options.prefix = Some(self.profile.pkg_dir.join(pkg.prefix_name())); + if let Some(item) = item { + let path = self.local_data.configure_script(pkg, &item); + if !path.exists() { + return Err(ConfigureError::NoSuchItem); + } + run_script(path, &options)?; + return Ok(()); + } + let dirpath = self.local_data.pkg_configure_scripts_dir(pkg); + for i in std::fs::read_dir(dirpath) + .into_iter() + .flatten() + .filter_map(Result::ok) + { + run_script(i.path(), &options)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct ConfigureOptions { + pub action: Action, + pub interactive: bool, + pub prefix: Option, + pub extra_args: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub enum Action { + Install, + Remove, + Configure, +} +impl Display for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Self::Install => "install", + Self::Remove => "remove", + Self::Configure => "configure", + }; + write!(f, "{s}") + } +} + +pub fn run_script( + path: impl AsRef, + options: &ConfigureOptions, +) -> Result<(), ConfigureError> { + let mut command = std::process::Command::new(path.as_ref()); + command.env("ACTION", options.action.to_string()); + if options.interactive { + command.env("INTERACTIVE", "1"); + } + if let Some(prefix) = &options.prefix { + command.env("PREFIX", prefix); + } + command.args(&options.extra_args); + let mut child = command.spawn().map_err(ConfigureError::Spawn)?; + let wait = child.wait().map_err(|_| ConfigureError::Exited(i32::MIN))?; + if wait.success() { + Ok(()) + } else { + Err(ConfigureError::Exited(wait.code().unwrap_or(i32::MIN))) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigureError { + #[error("{0}")] + Spawn(std::io::Error), + + #[error("no such item")] + NoSuchItem, + + #[error("process exited with code {0}")] + Exited(i32), +} diff --git a/src/install.rs b/src/install.rs index 9b3a851..38fe6a4 100644 --- a/src/install.rs +++ b/src/install.rs @@ -1,4 +1,5 @@ use crate::{ + configure::ConfigureOptions, local::{InstallFlags, Installation}, package::{Package, PackageError, PkgIdent, PkgSpec}, repo::{RepoError, RepoPackage}, @@ -6,14 +7,25 @@ use crate::{ use rust_i18n::t; use std::path::Path; -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct InstallOptions { /// Package spec to record for this installation. pub install_pkgspec: Option, + + /// Whether we should run configure scripts in interactive mode. + pub interactive_configure: bool, } impl InstallOptions { pub fn new() -> Self { - Self::default() + Self { + install_pkgspec: None, + interactive_configure: true, + } + } +} +impl Default for InstallOptions { + fn default() -> Self { + Self::new() } } @@ -136,6 +148,18 @@ impl super::Packie { .map_err(|err| InstallError::Database(Box::new(err)))?; } + // Copy configure files + for i in std::fs::read_dir(package.configure_scripts()) + .into_iter() + .flatten() + .filter_map(Result::ok) + { + let dst = self + .local_data + .configure_script(&pkg_ident, &i.file_name().to_string_lossy()); + std::fs::copy(i.path(), dst).map_err(|err| InstallError::Database(Box::new(err)))?; + } + // Enable default links if let Some(links) = package.links() { for linkdes in links.iter().ok().into_iter().flatten() { @@ -149,6 +173,18 @@ impl super::Packie { } } + // Run configuration + _ = self.run_configure( + &pkg_ident, + None, + &ConfigureOptions { + action: crate::configure::Action::Install, + interactive: options.interactive_configure, + prefix: None, + extra_args: Vec::new(), + }, + ); + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index c5a3dbc..5ef0973 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod common; pub mod config; +pub mod configure; pub mod install; pub mod link; pub mod local; diff --git a/src/local.rs b/src/local.rs index d95012f..3aba9d4 100644 --- a/src/local.rs +++ b/src/local.rs @@ -51,6 +51,7 @@ impl super::Packie { pub struct DataDir(pub PathBuf); impl DataDir { const LINKS_FILES_DIR_NAME: &str = "links_files"; + const ALL_CONFIGURE_SCRIPTS_DIR_NAME: &str = "configure"; pub fn links_files_dir(&self) -> PathBuf { self.dir(&[Self::LINKS_FILES_DIR_NAME]) @@ -60,6 +61,18 @@ impl DataDir { self.links_files_dir().join(name.to_string()) } + pub fn all_configure_scripts_dir(&self) -> PathBuf { + self.dir(&[Self::ALL_CONFIGURE_SCRIPTS_DIR_NAME]) + } + + pub fn pkg_configure_scripts_dir(&self, name: &PkgIdent) -> PathBuf { + self.dir(&[Self::ALL_CONFIGURE_SCRIPTS_DIR_NAME, &name.to_string()]) + } + + pub fn configure_script(&self, pkg: &PkgIdent, item: &str) -> PathBuf { + self.pkg_configure_scripts_dir(pkg).join(item) + } + fn dir(&self, relative: &[&str]) -> PathBuf { let mut path = self.0.clone(); for i in relative { diff --git a/src/package.rs b/src/package.rs index 413f35c..0204e70 100644 --- a/src/package.rs +++ b/src/package.rs @@ -40,6 +40,7 @@ impl Package { .map_err(PackageError::Manifest) } + /// Returns links file of the package. pub fn links(&self) -> Option { let path = self.extracted_dir.path().join(LinksFile::FILENAME); if path.exists() { @@ -49,6 +50,11 @@ impl Package { } } + /// Returns path of configure script directory for this package. + pub fn configure_scripts(&self) -> PathBuf { + self.extracted_dir.path().join("configure") + } + /// Returns path of the bundle directory of this package. pub fn bundle_dir(&self) -> PathBuf { self.extracted_dir.path().join("bundle") @@ -197,7 +203,10 @@ impl FromStr for PkgSpec { } let name = name.trim_end().to_string(); - if name.chars().any(|ch| PKGNAME_FORBIDDEN_CHARS.contains(|x| x == ch)) { + if name + .chars() + .any(|ch| PKGNAME_FORBIDDEN_CHARS.contains(|x| x == ch)) + { return Err(ParseError::Pkgname); } @@ -265,7 +274,10 @@ impl FromStr for PkgIdent { } let name = name.trim().to_string(); - if name.chars().any(|ch| PKGNAME_FORBIDDEN_CHARS.contains(|x| x == ch)) { + if name + .chars() + .any(|ch| PKGNAME_FORBIDDEN_CHARS.contains(|x| x == ch)) + { return Err(ParseError::Pkgname); } diff --git a/src/remove.rs b/src/remove.rs index 87e36ce..a9ee249 100644 --- a/src/remove.rs +++ b/src/remove.rs @@ -1,4 +1,4 @@ -use crate::{link::LinksFile, package::PkgSpec}; +use crate::{configure::ConfigureOptions, link::LinksFile, package::PkgSpec}; use rust_i18n::t; impl super::Packie { @@ -17,6 +17,21 @@ impl super::Packie { let found = found.remove(0); let pkg_ident = found.pkg_manifest.pkg_ident(); + // Run configure scripts + _ = self.run_configure( + &pkg_ident, + None, + &ConfigureOptions { + action: crate::configure::Action::Remove, + interactive: false, + prefix: None, + extra_args: Vec::new(), + }, + ); + + // Remove configure scripts + _ = std::fs::remove_dir_all(&self.local_data.pkg_configure_scripts_dir(&pkg_ident)); + // Remove all links let links = LinksFile(self.local_data.links_file(&pkg_ident)); if let Ok(links) = links.iter() {