feat: support configuration scripts
Signed-off-by: sisungo <[email protected]>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use console::{Term, style};
|
use console::{Term, style};
|
||||||
use indicatif::ProgressStyle;
|
use indicatif::ProgressStyle;
|
||||||
|
use packie::{Packie, local::Installation, package::PkgSpec};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
||||||
pub fn progress_style_multidownload() -> ProgressStyle {
|
pub fn progress_style_multidownload() -> ProgressStyle {
|
||||||
@@ -78,3 +79,20 @@ pub fn format_datetime(timestamp: i64) -> String {
|
|||||||
};
|
};
|
||||||
datetime.to_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 initdb;
|
||||||
mod install;
|
mod install;
|
||||||
mod print;
|
mod print;
|
||||||
|
mod reconfigure;
|
||||||
mod remove;
|
mod remove;
|
||||||
mod repo_admin;
|
mod repo_admin;
|
||||||
mod select;
|
mod select;
|
||||||
@@ -53,6 +54,9 @@ enum Subcommand {
|
|||||||
|
|
||||||
/// Select global link
|
/// Select global link
|
||||||
Select(select::Cli),
|
Select(select::Cli),
|
||||||
|
|
||||||
|
/// Run configure scripts
|
||||||
|
Reconfigure(reconfigure::Cli),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -79,6 +83,7 @@ fn main() {
|
|||||||
Subcommand::RepoAdmin(cli) => repo_admin::main(cli),
|
Subcommand::RepoAdmin(cli) => repo_admin::main(cli),
|
||||||
Subcommand::Update(cli) => update::main(cli),
|
Subcommand::Update(cli) => update::main(cli),
|
||||||
Subcommand::Select(cli) => select::main(cli),
|
Subcommand::Select(cli) => select::main(cli),
|
||||||
|
Subcommand::Reconfigure(cli) => reconfigure::main(cli),
|
||||||
};
|
};
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
eprintln!("{}", style(t!("main.error", error = err.message)).red());
|
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::{
|
use crate::common::{print_list, search_unique_package, yesno};
|
||||||
common::{print_list, yesno},
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use console::style;
|
use console::style;
|
||||||
@@ -20,14 +17,7 @@ pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
|||||||
let mut packie = PackieBuilder::new().build()?;
|
let mut packie = PackieBuilder::new().build()?;
|
||||||
let mut packages = Vec::with_capacity(cli.items.len());
|
let mut packages = Vec::with_capacity(cli.items.len());
|
||||||
for pkgspec in cli.items {
|
for pkgspec in cli.items {
|
||||||
let mut found = packie.search_installation(&pkgspec)?;
|
packages.push(search_unique_package(&mut packie, &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));
|
|
||||||
}
|
}
|
||||||
println!("{}", t!("remove.prompt_to_remove"));
|
println!("{}", t!("remove.prompt_to_remove"));
|
||||||
print_list(
|
print_list(
|
||||||
|
|||||||
@@ -91,14 +91,6 @@ remove.package_not_found:
|
|||||||
en: "The specified package \"%{pkgspec}\" was not found."
|
en: "The specified package \"%{pkgspec}\" was not found."
|
||||||
zh-CN: "找不到指定的包 \"%{pkgspec}\"。"
|
zh-CN: "找不到指定的包 \"%{pkgspec}\"。"
|
||||||
ja: "指定されたパッケージ \"%{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:
|
remove.action_failure:
|
||||||
en: "Failed to remove the specified package: %{error}"
|
en: "Failed to remove the specified package: %{error}"
|
||||||
zh-CN: "无法移除指定的包:%{error}"
|
zh-CN: "无法移除指定的包:%{error}"
|
||||||
@@ -135,6 +127,18 @@ common.progress_style.eta:
|
|||||||
en: "ETA: "
|
en: "ETA: "
|
||||||
zh-CN: "剩余时间:"
|
zh-CN: "剩余时间:"
|
||||||
ja: "残り時間:"
|
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:
|
clean.warn_repos_dir:
|
||||||
en: "warning: failed to read repository cache directory: %{error}"
|
en: "warning: failed to read repository cache directory: %{error}"
|
||||||
zh-CN: "警告:无法读取软件包仓库缓存目录:%{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::{
|
use crate::{
|
||||||
|
configure::ConfigureOptions,
|
||||||
local::{InstallFlags, Installation},
|
local::{InstallFlags, Installation},
|
||||||
package::{Package, PackageError, PkgIdent, PkgSpec},
|
package::{Package, PackageError, PkgIdent, PkgSpec},
|
||||||
repo::{RepoError, RepoPackage},
|
repo::{RepoError, RepoPackage},
|
||||||
@@ -6,14 +7,25 @@ use crate::{
|
|||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InstallOptions {
|
pub struct InstallOptions {
|
||||||
/// Package spec to record for this installation.
|
/// Package spec to record for this installation.
|
||||||
pub install_pkgspec: Option<PkgSpec>,
|
pub install_pkgspec: Option<PkgSpec>,
|
||||||
|
|
||||||
|
/// Whether we should run configure scripts in interactive mode.
|
||||||
|
pub interactive_configure: bool,
|
||||||
}
|
}
|
||||||
impl InstallOptions {
|
impl InstallOptions {
|
||||||
pub fn new() -> Self {
|
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)))?;
|
.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
|
// Enable default links
|
||||||
if let Some(links) = package.links() {
|
if let Some(links) = package.links() {
|
||||||
for linkdes in links.iter().ok().into_iter().flatten() {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod common;
|
mod common;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod configure;
|
||||||
pub mod install;
|
pub mod install;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ impl super::Packie {
|
|||||||
pub struct DataDir(pub PathBuf);
|
pub struct DataDir(pub PathBuf);
|
||||||
impl DataDir {
|
impl DataDir {
|
||||||
const LINKS_FILES_DIR_NAME: &str = "links_files";
|
const LINKS_FILES_DIR_NAME: &str = "links_files";
|
||||||
|
const ALL_CONFIGURE_SCRIPTS_DIR_NAME: &str = "configure";
|
||||||
|
|
||||||
pub fn links_files_dir(&self) -> PathBuf {
|
pub fn links_files_dir(&self) -> PathBuf {
|
||||||
self.dir(&[Self::LINKS_FILES_DIR_NAME])
|
self.dir(&[Self::LINKS_FILES_DIR_NAME])
|
||||||
@@ -60,6 +61,18 @@ impl DataDir {
|
|||||||
self.links_files_dir().join(name.to_string())
|
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 {
|
fn dir(&self, relative: &[&str]) -> PathBuf {
|
||||||
let mut path = self.0.clone();
|
let mut path = self.0.clone();
|
||||||
for i in relative {
|
for i in relative {
|
||||||
|
|||||||
+14
-2
@@ -40,6 +40,7 @@ impl Package {
|
|||||||
.map_err(PackageError::Manifest)
|
.map_err(PackageError::Manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns links file of the package.
|
||||||
pub fn links(&self) -> Option<LinksFile> {
|
pub fn links(&self) -> Option<LinksFile> {
|
||||||
let path = self.extracted_dir.path().join(LinksFile::FILENAME);
|
let path = self.extracted_dir.path().join(LinksFile::FILENAME);
|
||||||
if path.exists() {
|
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.
|
/// Returns path of the bundle directory of this package.
|
||||||
pub fn bundle_dir(&self) -> PathBuf {
|
pub fn bundle_dir(&self) -> PathBuf {
|
||||||
self.extracted_dir.path().join("bundle")
|
self.extracted_dir.path().join("bundle")
|
||||||
@@ -197,7 +203,10 @@ impl FromStr for PkgSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = name.trim_end().to_string();
|
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);
|
return Err(ParseError::Pkgname);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +274,10 @@ impl FromStr for PkgIdent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = name.trim().to_string();
|
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);
|
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;
|
use rust_i18n::t;
|
||||||
|
|
||||||
impl super::Packie {
|
impl super::Packie {
|
||||||
@@ -17,6 +17,21 @@ impl super::Packie {
|
|||||||
let found = found.remove(0);
|
let found = found.remove(0);
|
||||||
let pkg_ident = found.pkg_manifest.pkg_ident();
|
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
|
// Remove all links
|
||||||
let links = LinksFile(self.local_data.links_file(&pkg_ident));
|
let links = LinksFile(self.local_data.links_file(&pkg_ident));
|
||||||
if let Ok(links) = links.iter() {
|
if let Ok(links) = links.iter() {
|
||||||
|
|||||||
Reference in New Issue
Block a user