feat: support configuration scripts
Signed-off-by: sisungo <[email protected]>
This commit is contained in:
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
@@ -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,5 +1,6 @@
|
||||
mod common;
|
||||
pub mod config;
|
||||
pub mod configure;
|
||||
pub mod install;
|
||||
pub mod link;
|
||||
pub mod local;
|
||||
|
||||
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user