feat: support configuration scripts

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-09-10 13:00:27 +00:00
parent 55b40267ff
commit b5e91ce8b6
11 changed files with 245 additions and 25 deletions
+18
View File
@@ -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<Installation, crate::Error> {
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))
}
+5
View File
@@ -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());
+33
View File
@@ -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<String>,
}
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(())
}
+2 -12
View File
@@ -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(
+12 -8
View File
@@ -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}"
+93
View File
@@ -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<String>,
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<PathBuf>,
pub extra_args: Vec<String>,
}
#[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<Path>,
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),
}
+38 -2
View File
@@ -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<PkgSpec>,
/// 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(())
}
}
+1
View File
@@ -1,5 +1,6 @@
mod common;
pub mod config;
pub mod configure;
pub mod install;
pub mod link;
pub mod local;
+13
View File
@@ -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 {
+14 -2
View File
@@ -40,6 +40,7 @@ impl Package {
.map_err(PackageError::Manifest)
}
/// Returns links file of the package.
pub fn links(&self) -> Option<LinksFile> {
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);
}
+16 -1
View File
@@ -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() {