mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: content addressable storage
This commit is contained in:
parent
d8cd78e7e2
commit
37cc86f028
36 changed files with 574 additions and 311 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2736,6 +2736,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
"sha2",
|
||||||
"tar",
|
"tar",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"threadpool",
|
"threadpool",
|
||||||
|
|
|
@ -61,6 +61,7 @@ url = { version = "2.5.2", features = ["serde"] }
|
||||||
# TODO: reevaluate whether to use this
|
# TODO: reevaluate whether to use this
|
||||||
# secrecy = "0.8.0"
|
# secrecy = "0.8.0"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
|
||||||
# TODO: remove this when gitoxide adds support for: committing, pushing, adding
|
# TODO: remove this when gitoxide adds support for: committing, pushing, adding
|
||||||
git2 = { version = "0.19.0", optional = true }
|
git2 = { version = "0.19.0", optional = true }
|
||||||
|
|
|
@ -2,16 +2,15 @@ use crate::cli::config::{read_config, write_config};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
pub fn get_token<P: AsRef<Path>>(data_dir: P) -> anyhow::Result<Option<String>> {
|
pub fn get_token() -> anyhow::Result<Option<String>> {
|
||||||
match std::env::var("PESDE_TOKEN") {
|
match std::env::var("PESDE_TOKEN") {
|
||||||
Ok(token) => return Ok(Some(token)),
|
Ok(token) => return Ok(Some(token)),
|
||||||
Err(std::env::VarError::NotPresent) => {}
|
Err(std::env::VarError::NotPresent) => {}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = read_config(data_dir)?;
|
let config = read_config()?;
|
||||||
if let Some(token) = config.token {
|
if let Some(token) = config.token {
|
||||||
return Ok(Some(token));
|
return Ok(Some(token));
|
||||||
}
|
}
|
||||||
|
@ -29,7 +28,7 @@ pub fn get_token<P: AsRef<Path>>(data_dir: P) -> anyhow::Result<Option<String>>
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_token<P: AsRef<Path>>(data_dir: P, token: Option<&str>) -> anyhow::Result<()> {
|
pub fn set_token(token: Option<&str>) -> anyhow::Result<()> {
|
||||||
let entry = match Entry::new("token", env!("CARGO_PKG_NAME")) {
|
let entry = match Entry::new("token", env!("CARGO_PKG_NAME")) {
|
||||||
Ok(entry) => entry,
|
Ok(entry) => entry,
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
|
@ -47,9 +46,9 @@ pub fn set_token<P: AsRef<Path>>(data_dir: P, token: Option<&str>) -> anyhow::Re
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = read_config(&data_dir)?;
|
let mut config = read_config()?;
|
||||||
config.token = token.map(|s| s.to_string());
|
config.token = token.map(|s| s.to_string());
|
||||||
write_config(data_dir, &config)?;
|
write_config(&config)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use clap::Args;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use pesde::{
|
use pesde::{
|
||||||
errors::ManifestReadError,
|
errors::ManifestReadError,
|
||||||
source::{pesde::PesdePackageSource, PackageSource},
|
source::{pesde::PesdePackageSource, traits::PackageSource},
|
||||||
Project,
|
Project,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -71,7 +71,7 @@ impl LoginCommand {
|
||||||
},
|
},
|
||||||
None => match manifest {
|
None => match manifest {
|
||||||
Some(_) => None,
|
Some(_) => None,
|
||||||
None => Some(read_config(project.data_dir())?.default_index),
|
None => Some(read_config()?.default_index),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ impl LoginCommand {
|
||||||
|
|
||||||
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold());
|
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold());
|
||||||
|
|
||||||
set_token(project.data_dir(), Some(&token))?;
|
set_token(Some(&token))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::cli::auth::set_token;
|
use crate::cli::auth::set_token;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct LogoutCommand {}
|
pub struct LogoutCommand {}
|
||||||
|
|
||||||
impl LogoutCommand {
|
impl LogoutCommand {
|
||||||
pub fn run(self, project: Project) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
set_token(project.data_dir(), None)?;
|
set_token(None)?;
|
||||||
|
|
||||||
println!("logged out");
|
println!("logged out");
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ impl AuthCommands {
|
||||||
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
AuthCommands::Login(login) => login.run(project, reqwest),
|
AuthCommands::Login(login) => login.run(project, reqwest),
|
||||||
AuthCommands::Logout(logout) => logout.run(project),
|
AuthCommands::Logout(logout) => logout.run(),
|
||||||
AuthCommands::WhoAmI(whoami) => whoami.run(project, reqwest),
|
AuthCommands::WhoAmI(whoami) => whoami.run(reqwest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::cli::{auth::get_token_login, get_token};
|
use crate::cli::{auth::get_token_login, get_token};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct WhoAmICommand {}
|
pub struct WhoAmICommand {}
|
||||||
|
|
||||||
impl WhoAmICommand {
|
impl WhoAmICommand {
|
||||||
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
||||||
let token = match get_token(project.data_dir())? {
|
let token = match get_token()? {
|
||||||
Some(token) => token,
|
Some(token) => token,
|
||||||
None => {
|
None => {
|
||||||
println!("not logged in");
|
println!("not logged in");
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::cli::config::{read_config, write_config, CliConfig};
|
use crate::cli::config::{read_config, write_config, CliConfig};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct DefaultIndexCommand {
|
pub struct DefaultIndexCommand {
|
||||||
|
@ -14,8 +13,8 @@ pub struct DefaultIndexCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultIndexCommand {
|
impl DefaultIndexCommand {
|
||||||
pub fn run(self, project: Project) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
let mut config = read_config(project.data_dir())?;
|
let mut config = read_config()?;
|
||||||
|
|
||||||
let index = if self.reset {
|
let index = if self.reset {
|
||||||
Some(CliConfig::default().default_index)
|
Some(CliConfig::default().default_index)
|
||||||
|
@ -26,7 +25,7 @@ impl DefaultIndexCommand {
|
||||||
match index {
|
match index {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
config.default_index = index.clone();
|
config.default_index = index.clone();
|
||||||
write_config(project.data_dir(), &config)?;
|
write_config(&config)?;
|
||||||
println!("default index set to: {index}");
|
println!("default index set to: {index}");
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
mod default_index;
|
mod default_index;
|
||||||
mod scripts_repo;
|
mod scripts_repo;
|
||||||
|
@ -14,10 +13,10 @@ pub enum ConfigCommands {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigCommands {
|
impl ConfigCommands {
|
||||||
pub fn run(self, project: Project) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
ConfigCommands::DefaultIndex(default_index) => default_index.run(project),
|
ConfigCommands::DefaultIndex(default_index) => default_index.run(),
|
||||||
ConfigCommands::ScriptsRepo(scripts_repo) => scripts_repo.run(project),
|
ConfigCommands::ScriptsRepo(scripts_repo) => scripts_repo.run(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::cli::config::{read_config, write_config, CliConfig};
|
use crate::cli::config::{read_config, write_config, CliConfig};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct ScriptsRepoCommand {
|
pub struct ScriptsRepoCommand {
|
||||||
|
@ -14,8 +13,8 @@ pub struct ScriptsRepoCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptsRepoCommand {
|
impl ScriptsRepoCommand {
|
||||||
pub fn run(self, project: Project) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
let mut config = read_config(project.data_dir())?;
|
let mut config = read_config()?;
|
||||||
|
|
||||||
let repo = if self.reset {
|
let repo = if self.reset {
|
||||||
Some(CliConfig::default().scripts_repo)
|
Some(CliConfig::default().scripts_repo)
|
||||||
|
@ -26,7 +25,7 @@ impl ScriptsRepoCommand {
|
||||||
match repo {
|
match repo {
|
||||||
Some(repo) => {
|
Some(repo) => {
|
||||||
config.scripts_repo = repo.clone();
|
config.scripts_repo = repo.clone();
|
||||||
write_config(project.data_dir(), &config)?;
|
write_config(&config)?;
|
||||||
println!("scripts repo set to: {repo}");
|
println!("scripts repo set to: {repo}");
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -144,12 +144,8 @@ impl InitCommand {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest["indices"][DEFAULT_INDEX_NAME] = toml_edit::value(
|
manifest["indices"][DEFAULT_INDEX_NAME] =
|
||||||
read_config(project.data_dir())?
|
toml_edit::value(read_config()?.default_index.to_bstring().to_string());
|
||||||
.default_index
|
|
||||||
.to_bstring()
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
project.write_manifest(manifest.to_string())?;
|
project.write_manifest(manifest.to_string())?;
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@ fn bin_link_file(alias: &str) -> String {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(not(unix))]
|
||||||
let prefix = String::new();
|
let prefix = String::new();
|
||||||
#[cfg(not(windows))]
|
#[cfg(unix)]
|
||||||
let prefix = "#!/usr/bin/env -S lune run\n";
|
let prefix = "#!/usr/bin/env -S lune run\n";
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -60,7 +60,7 @@ impl Subcommand {
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Subcommand::Auth(auth) => auth.run(project, reqwest),
|
Subcommand::Auth(auth) => auth.run(project, reqwest),
|
||||||
Subcommand::Config(config) => config.run(project),
|
Subcommand::Config(config) => config.run(),
|
||||||
Subcommand::Init(init) => init.run(project),
|
Subcommand::Init(init) => init.run(project),
|
||||||
Subcommand::Run(run) => run.run(project),
|
Subcommand::Run(run) => run.run(project),
|
||||||
Subcommand::Install(install) => install.run(project, multi, reqwest),
|
Subcommand::Install(install) => install.run(project, multi, reqwest),
|
||||||
|
@ -70,7 +70,7 @@ impl Subcommand {
|
||||||
Subcommand::Patch(patch) => patch.run(project, reqwest),
|
Subcommand::Patch(patch) => patch.run(project, reqwest),
|
||||||
#[cfg(feature = "patches")]
|
#[cfg(feature = "patches")]
|
||||||
Subcommand::PatchCommit(patch_commit) => patch_commit.run(project),
|
Subcommand::PatchCommit(patch_commit) => patch_commit.run(project),
|
||||||
Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(project, reqwest),
|
Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use clap::Args;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use pesde::{
|
use pesde::{
|
||||||
patches::setup_patches_repo,
|
patches::setup_patches_repo,
|
||||||
source::{PackageRef, PackageSource},
|
source::traits::{PackageRef, PackageSource},
|
||||||
Project, MANIFEST_FILE_NAME,
|
Project, MANIFEST_FILE_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,11 @@ impl PatchCommand {
|
||||||
.join(chrono::Utc::now().timestamp().to_string());
|
.join(chrono::Utc::now().timestamp().to_string());
|
||||||
std::fs::create_dir_all(&directory)?;
|
std::fs::create_dir_all(&directory)?;
|
||||||
|
|
||||||
source.download(&node.node.pkg_ref, &directory, &project, &reqwest)?;
|
source
|
||||||
|
.download(&node.node.pkg_ref, &project, &reqwest)?
|
||||||
|
.0
|
||||||
|
.write_to(&directory, project.cas_dir(), false)
|
||||||
|
.context("failed to write package contents")?;
|
||||||
|
|
||||||
// TODO: if MANIFEST_FILE_NAME does not exist, try to convert it
|
// TODO: if MANIFEST_FILE_NAME does not exist, try to convert it
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::cli::IsUpToDate;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pesde::{
|
use pesde::{
|
||||||
manifest::Manifest, names::PackageNames, patches::create_patch, source::VersionId, Project,
|
manifest::Manifest, names::PackageNames, patches::create_patch, source::version_id::VersionId,
|
||||||
MANIFEST_FILE_NAME,
|
Project, MANIFEST_FILE_NAME,
|
||||||
};
|
};
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::cli::{config::read_config, version::get_or_download_version};
|
use crate::cli::{config::read_config, version::get_or_download_version};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pesde::Project;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct SelfUpgradeCommand {
|
pub struct SelfUpgradeCommand {
|
||||||
|
@ -10,8 +9,8 @@ pub struct SelfUpgradeCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelfUpgradeCommand {
|
impl SelfUpgradeCommand {
|
||||||
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
|
||||||
let config = read_config(project.data_dir())?;
|
let config = read_config()?;
|
||||||
|
|
||||||
get_or_download_version(&reqwest, &config.last_checked_updates.unwrap().1)?;
|
get_or_download_version(&reqwest, &config.last_checked_updates.unwrap().1)?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::Path;
|
|
||||||
|
use crate::cli::home_dir;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CliConfig {
|
pub struct CliConfig {
|
||||||
|
@ -37,8 +38,8 @@ impl Default for CliConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_config<P: AsRef<Path>>(data_dir: P) -> anyhow::Result<CliConfig> {
|
pub fn read_config() -> anyhow::Result<CliConfig> {
|
||||||
let config_string = match std::fs::read_to_string(data_dir.as_ref().join("config.toml")) {
|
let config_string = match std::fs::read_to_string(home_dir()?.join("config.toml")) {
|
||||||
Ok(config_string) => config_string,
|
Ok(config_string) => config_string,
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
return Ok(CliConfig::default());
|
return Ok(CliConfig::default());
|
||||||
|
@ -51,9 +52,9 @@ pub fn read_config<P: AsRef<Path>>(data_dir: P) -> anyhow::Result<CliConfig> {
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_config<P: AsRef<Path>>(data_dir: P, config: &CliConfig) -> anyhow::Result<()> {
|
pub fn write_config(config: &CliConfig) -> anyhow::Result<()> {
|
||||||
let config_string = toml::to_string(config).context("failed to serialize config")?;
|
let config_string = toml::to_string(config).context("failed to serialize config")?;
|
||||||
std::fs::write(data_dir.as_ref().join("config.toml"), config_string)
|
std::fs::write(home_dir()?.join("config.toml"), config_string)
|
||||||
.context("failed to write config file")?;
|
.context("failed to write config file")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -7,7 +7,9 @@ pub mod version;
|
||||||
|
|
||||||
use crate::cli::auth::get_token;
|
use crate::cli::auth::get_token;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use pesde::{lockfile::DownloadedGraph, names::PackageNames, source::VersionId, Project};
|
use pesde::{
|
||||||
|
lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project,
|
||||||
|
};
|
||||||
use std::{collections::HashSet, str::FromStr};
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME"));
|
pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME"));
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> {
|
||||||
} else {
|
} else {
|
||||||
std::fs::create_dir_all(&scripts_dir).context("failed to create scripts directory")?;
|
std::fs::create_dir_all(&scripts_dir).context("failed to create scripts directory")?;
|
||||||
|
|
||||||
let cli_config = read_config(project.data_dir())?;
|
let cli_config = read_config()?;
|
||||||
|
|
||||||
gix::prepare_clone(cli_config.scripts_repo, &scripts_dir)
|
gix::prepare_clone(cli_config.scripts_repo, &scripts_dir)
|
||||||
.context("failed to prepare scripts repository clone")?
|
.context("failed to prepare scripts repository clone")?
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
use std::{
|
use std::{fs::create_dir_all, io::Read, path::PathBuf};
|
||||||
fs::create_dir_all,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
@ -42,13 +38,10 @@ fn get_repo() -> (String, String) {
|
||||||
|
|
||||||
const CHECK_INTERVAL: chrono::Duration = chrono::Duration::seconds(30);
|
const CHECK_INTERVAL: chrono::Duration = chrono::Duration::seconds(30);
|
||||||
|
|
||||||
pub fn check_for_updates<P: AsRef<Path>>(
|
pub fn check_for_updates(reqwest: &reqwest::blocking::Client) -> anyhow::Result<()> {
|
||||||
reqwest: &reqwest::blocking::Client,
|
|
||||||
data_dir: P,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (owner, repo) = get_repo();
|
let (owner, repo) = get_repo();
|
||||||
|
|
||||||
let config = read_config(&data_dir)?;
|
let config = read_config()?;
|
||||||
|
|
||||||
let version = if let Some((_, version)) = config
|
let version = if let Some((_, version)) = config
|
||||||
.last_checked_updates
|
.last_checked_updates
|
||||||
|
@ -71,13 +64,10 @@ pub fn check_for_updates<P: AsRef<Path>>(
|
||||||
.max()
|
.max()
|
||||||
.context("failed to find latest version")?;
|
.context("failed to find latest version")?;
|
||||||
|
|
||||||
write_config(
|
write_config(&CliConfig {
|
||||||
&data_dir,
|
last_checked_updates: Some((chrono::Utc::now(), version.clone())),
|
||||||
&CliConfig {
|
..config
|
||||||
last_checked_updates: Some((chrono::Utc::now(), version.clone())),
|
})?;
|
||||||
..config
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
version
|
version
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
use crate::{
|
||||||
|
lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph},
|
||||||
|
source::{
|
||||||
|
traits::{PackageRef, PackageSource},
|
||||||
|
PackageSources,
|
||||||
|
},
|
||||||
|
Project, PACKAGES_CONTAINER_NAME,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs::create_dir_all,
|
fs::create_dir_all,
|
||||||
sync::{mpsc::Receiver, Arc, Mutex},
|
sync::{mpsc::Receiver, Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph},
|
|
||||||
source::{PackageRef, PackageSource, PackageSources},
|
|
||||||
Project, PACKAGES_CONTAINER_NAME,
|
|
||||||
};
|
|
||||||
|
|
||||||
type MultithreadedGraph = Arc<Mutex<DownloadedGraph>>;
|
type MultithreadedGraph = Arc<Mutex<DownloadedGraph>>;
|
||||||
|
|
||||||
type MultithreadDownloadJob = (
|
type MultithreadDownloadJob = (
|
||||||
|
@ -65,18 +67,25 @@ impl Project {
|
||||||
|
|
||||||
log::debug!("downloading {name}@{version_id}");
|
log::debug!("downloading {name}@{version_id}");
|
||||||
|
|
||||||
let target =
|
let (fs, target) = match source.download(&node.pkg_ref, &project, &reqwest) {
|
||||||
match source.download(&node.pkg_ref, &container_folder, &project, &reqwest)
|
Ok(target) => target,
|
||||||
{
|
Err(e) => {
|
||||||
Ok(target) => target,
|
tx.send(Err(e.into())).unwrap();
|
||||||
Err(e) => {
|
return;
|
||||||
tx.send(Err(e.into())).unwrap();
|
}
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("downloaded {name}@{version_id}");
|
log::debug!("downloaded {name}@{version_id}");
|
||||||
|
|
||||||
|
match fs.write_to(container_folder, project.cas_dir(), true) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
tx.send(Err(errors::DownloadGraphError::WriteFailed(e)))
|
||||||
|
.unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut downloaded_graph = downloaded_graph.lock().unwrap();
|
let mut downloaded_graph = downloaded_graph.lock().unwrap();
|
||||||
downloaded_graph
|
downloaded_graph
|
||||||
.entry(name)
|
.entry(name)
|
||||||
|
@ -109,5 +118,8 @@ pub mod errors {
|
||||||
|
|
||||||
#[error("failed to download package")]
|
#[error("failed to download package")]
|
||||||
DownloadFailed(#[from] crate::source::errors::DownloadError),
|
DownloadFailed(#[from] crate::source::errors::DownloadError),
|
||||||
|
|
||||||
|
#[error("failed to write package contents")]
|
||||||
|
WriteFailed(std::io::Error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,18 +62,21 @@ pub struct Project {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
data_dir: PathBuf,
|
data_dir: PathBuf,
|
||||||
auth_config: AuthConfig,
|
auth_config: AuthConfig,
|
||||||
|
cas_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn new<P: AsRef<Path>, Q: AsRef<Path>>(
|
pub fn new<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
data_dir: Q,
|
data_dir: Q,
|
||||||
|
cas_dir: R,
|
||||||
auth_config: AuthConfig,
|
auth_config: AuthConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Project {
|
Project {
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
data_dir: data_dir.as_ref().to_path_buf(),
|
data_dir: data_dir.as_ref().to_path_buf(),
|
||||||
auth_config,
|
auth_config,
|
||||||
|
cas_dir: cas_dir.as_ref().to_path_buf(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +92,10 @@ impl Project {
|
||||||
&self.auth_config
|
&self.auth_config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cas_dir(&self) -> &Path {
|
||||||
|
&self.cas_dir
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_manifest(&self) -> Result<String, errors::ManifestReadError> {
|
pub fn read_manifest(&self) -> Result<String, errors::ManifestReadError> {
|
||||||
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
|
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
|
||||||
Ok(string)
|
Ok(string)
|
||||||
|
|
|
@ -4,21 +4,30 @@ use crate::{
|
||||||
manifest::target::Target,
|
manifest::target::Target,
|
||||||
names::PackageNames,
|
names::PackageNames,
|
||||||
scripts::{execute_script, ScriptName},
|
scripts::{execute_script, ScriptName},
|
||||||
source::{PackageRef, VersionId},
|
source::{fs::store_in_cas, traits::PackageRef, version_id::VersionId},
|
||||||
|
util::hash,
|
||||||
Project, PACKAGES_CONTAINER_NAME,
|
Project, PACKAGES_CONTAINER_NAME,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeMap, fs::create_dir_all};
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fs::create_dir_all,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
|
|
||||||
fn create_and_canonicalize<P: AsRef<std::path::Path>>(
|
fn create_and_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
|
||||||
path: P,
|
|
||||||
) -> std::io::Result<std::path::PathBuf> {
|
|
||||||
let p = path.as_ref();
|
let p = path.as_ref();
|
||||||
create_dir_all(p)?;
|
create_dir_all(p)?;
|
||||||
p.canonicalize()
|
p.canonicalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_cas(destination: PathBuf, cas_dir: &Path, contents: &str) -> std::io::Result<()> {
|
||||||
|
let cas_path = store_in_cas(cas_dir, contents)?.1;
|
||||||
|
|
||||||
|
std::fs::hard_link(cas_path, destination)
|
||||||
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> {
|
pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> {
|
||||||
let manifest = self.deser_manifest()?;
|
let manifest = self.deser_manifest()?;
|
||||||
|
@ -117,34 +126,34 @@ impl Project {
|
||||||
.and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types)))
|
.and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types)))
|
||||||
{
|
{
|
||||||
if let Some(lib_file) = node.target.lib_path() {
|
if let Some(lib_file) = node.target.lib_path() {
|
||||||
let linker_file = base_folder.join(format!("{alias}.luau"));
|
write_cas(
|
||||||
|
base_folder.join(format!("{alias}.luau")),
|
||||||
let module = generator::generate_lib_linking_module(
|
self.cas_dir(),
|
||||||
&generator::get_lib_require_path(
|
&generator::generate_lib_linking_module(
|
||||||
&node.target.kind(),
|
&generator::get_lib_require_path(
|
||||||
&base_folder,
|
&node.target.kind(),
|
||||||
lib_file,
|
&base_folder,
|
||||||
&container_folder,
|
lib_file,
|
||||||
node.node.pkg_ref.use_new_structure(),
|
&container_folder,
|
||||||
|
node.node.pkg_ref.use_new_structure(),
|
||||||
|
),
|
||||||
|
types,
|
||||||
),
|
),
|
||||||
types,
|
)?;
|
||||||
);
|
|
||||||
|
|
||||||
std::fs::write(linker_file, module)?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(bin_file) = node.target.bin_path() {
|
if let Some(bin_file) = node.target.bin_path() {
|
||||||
let linker_file = base_folder.join(format!("{alias}.bin.luau"));
|
write_cas(
|
||||||
|
base_folder.join(format!("{alias}.bin.luau")),
|
||||||
let module = generator::generate_bin_linking_module(
|
self.cas_dir(),
|
||||||
&generator::get_bin_require_path(
|
&generator::generate_bin_linking_module(
|
||||||
&base_folder,
|
&generator::get_bin_require_path(
|
||||||
bin_file,
|
&base_folder,
|
||||||
&container_folder,
|
bin_file,
|
||||||
|
&container_folder,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
)?;
|
||||||
|
|
||||||
std::fs::write(linker_file, module)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,27 +178,28 @@ impl Project {
|
||||||
container_folder
|
container_folder
|
||||||
.join(dependency_node.node.base_folder(node.target.kind(), false)),
|
.join(dependency_node.node.base_folder(node.target.kind(), false)),
|
||||||
)?;
|
)?;
|
||||||
let linker_file = linker_folder.join(format!("{dependency_alias}.luau"));
|
|
||||||
|
|
||||||
let module = generator::generate_lib_linking_module(
|
write_cas(
|
||||||
&generator::get_lib_require_path(
|
linker_folder.join(format!("{dependency_alias}.luau")),
|
||||||
&dependency_node.target.kind(),
|
self.cas_dir(),
|
||||||
&linker_folder,
|
&generator::generate_lib_linking_module(
|
||||||
lib_file,
|
&generator::get_lib_require_path(
|
||||||
&dependency_node.node.container_folder(
|
&dependency_node.target.kind(),
|
||||||
&packages_container_folder,
|
&linker_folder,
|
||||||
dependency_name,
|
lib_file,
|
||||||
dependency_version_id.version(),
|
&dependency_node.node.container_folder(
|
||||||
|
&packages_container_folder,
|
||||||
|
dependency_name,
|
||||||
|
dependency_version_id.version(),
|
||||||
|
),
|
||||||
|
node.node.pkg_ref.use_new_structure(),
|
||||||
),
|
),
|
||||||
node.node.pkg_ref.use_new_structure(),
|
package_types
|
||||||
|
.get(dependency_name)
|
||||||
|
.and_then(|v| v.get(dependency_version_id))
|
||||||
|
.unwrap(),
|
||||||
),
|
),
|
||||||
package_types
|
)?;
|
||||||
.get(dependency_name)
|
|
||||||
.and_then(|v| v.get(dependency_version_id))
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
std::fs::write(linker_file, module)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ use crate::{
|
||||||
DependencyType,
|
DependencyType,
|
||||||
},
|
},
|
||||||
names::{PackageName, PackageNames},
|
names::{PackageName, PackageNames},
|
||||||
source::{DependencySpecifiers, PackageRef, PackageRefs, VersionId},
|
source::{
|
||||||
|
refs::PackageRefs, specifiers::DependencySpecifiers, traits::PackageRef,
|
||||||
|
version_id::VersionId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
51
src/main.rs
51
src/main.rs
|
@ -2,6 +2,7 @@ use crate::cli::{
|
||||||
auth::get_token,
|
auth::get_token,
|
||||||
home_dir,
|
home_dir,
|
||||||
version::{check_for_updates, current_version, get_or_download_version, max_installed_version},
|
version::{check_for_updates, current_version, get_or_download_version, max_installed_version},
|
||||||
|
HOME_DIR,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
@ -9,7 +10,7 @@ use colored::Colorize;
|
||||||
use indicatif::MultiProgress;
|
use indicatif::MultiProgress;
|
||||||
use indicatif_log_bridge::LogWrapper;
|
use indicatif_log_bridge::LogWrapper;
|
||||||
use pesde::{AuthConfig, Project};
|
use pesde::{AuthConfig, Project};
|
||||||
use std::fs::create_dir_all;
|
use std::{fs::create_dir_all, path::PathBuf};
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -26,6 +27,39 @@ struct Cli {
|
||||||
subcommand: cli::commands::Subcommand,
|
subcommand: cli::commands::Subcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn get_root(path: &std::path::Path) -> PathBuf {
|
||||||
|
match path.components().next().unwrap() {
|
||||||
|
std::path::Component::Prefix(prefix) => {
|
||||||
|
let mut string = prefix.as_os_str().to_string_lossy().to_string();
|
||||||
|
if string.ends_with(':') {
|
||||||
|
string.push(std::path::MAIN_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::path::PathBuf::from(&string)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn get_root(path: &std::path::Path) -> PathBuf {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
let path = std::fs::canonicalize(path).unwrap();
|
||||||
|
let mut current = path.as_path();
|
||||||
|
|
||||||
|
while let Some(parent) = current.parent() {
|
||||||
|
if std::fs::metadata(parent).unwrap().dev() != std::fs::metadata(current).unwrap().dev() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
fn run() -> anyhow::Result<()> {
|
fn run() -> anyhow::Result<()> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
'scripts: {
|
'scripts: {
|
||||||
|
@ -73,11 +107,20 @@ fn run() -> anyhow::Result<()> {
|
||||||
let data_dir = home_dir()?.join("data");
|
let data_dir = home_dir()?.join("data");
|
||||||
create_dir_all(&data_dir).expect("failed to create data directory");
|
create_dir_all(&data_dir).expect("failed to create data directory");
|
||||||
|
|
||||||
let token = get_token(&data_dir)?;
|
let token = get_token()?;
|
||||||
|
|
||||||
|
let home_cas_dir = data_dir.join("cas");
|
||||||
|
let project_root = get_root(&cwd);
|
||||||
|
let cas_dir = if get_root(&home_cas_dir) == project_root {
|
||||||
|
home_cas_dir
|
||||||
|
} else {
|
||||||
|
project_root.join(HOME_DIR).join("cas")
|
||||||
|
};
|
||||||
|
|
||||||
let project = Project::new(
|
let project = Project::new(
|
||||||
cwd,
|
cwd,
|
||||||
&data_dir,
|
data_dir,
|
||||||
|
cas_dir,
|
||||||
AuthConfig::new().with_pesde_token(token.as_ref()),
|
AuthConfig::new().with_pesde_token(token.as_ref()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -109,7 +152,7 @@ fn run() -> anyhow::Result<()> {
|
||||||
.build()?
|
.build()?
|
||||||
};
|
};
|
||||||
|
|
||||||
check_for_updates(&reqwest, &data_dir)?;
|
check_for_updates(&reqwest)?;
|
||||||
|
|
||||||
let target_version = project
|
let target_version = project
|
||||||
.deser_manifest()
|
.deser_manifest()
|
||||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
manifest::{overrides::OverrideKey, target::Target},
|
manifest::{overrides::OverrideKey, target::Target},
|
||||||
names::{PackageName, PackageNames},
|
names::{PackageName, PackageNames},
|
||||||
source::{DependencySpecifiers, VersionId},
|
source::{specifiers::DependencySpecifiers, version_id::VersionId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod overrides;
|
pub mod overrides;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{lockfile::DownloadedGraph, Project, MANIFEST_FILE_NAME, PACKAGES_CONTAINER_NAME};
|
use crate::{lockfile::DownloadedGraph, Project, MANIFEST_FILE_NAME, PACKAGES_CONTAINER_NAME};
|
||||||
use git2::{ApplyLocation, Diff, DiffFormat, DiffLineType, Repository, Signature};
|
use git2::{ApplyLocation, ApplyOptions, Diff, DiffFormat, DiffLineType, Repository, Signature};
|
||||||
|
use relative_path::RelativePathBuf;
|
||||||
use std::{fs::read, path::Path};
|
use std::{fs::read, path::Path};
|
||||||
|
|
||||||
pub fn setup_patches_repo<P: AsRef<Path>>(dir: P) -> Result<Repository, git2::Error> {
|
pub fn setup_patches_repo<P: AsRef<Path>>(dir: P) -> Result<Repository, git2::Error> {
|
||||||
|
@ -94,7 +95,37 @@ impl Project {
|
||||||
|
|
||||||
{
|
{
|
||||||
let repo = setup_patches_repo(&container_folder)?;
|
let repo = setup_patches_repo(&container_folder)?;
|
||||||
repo.apply(&patch, ApplyLocation::Both, None)?;
|
let mut apply_opts = ApplyOptions::new();
|
||||||
|
apply_opts.delta_callback(|delta| {
|
||||||
|
let Some(delta) = delta else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !matches!(delta.status(), git2::Delta::Modified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = delta.new_file();
|
||||||
|
let Some(relative_path) = file.path() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
let relative_path = RelativePathBuf::from_path(relative_path).unwrap();
|
||||||
|
let path = relative_path.to_path(&container_folder);
|
||||||
|
|
||||||
|
if !path.is_file() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no way (as far as I know) to check if it's hardlinked
|
||||||
|
// so, we always unlink it
|
||||||
|
let content = read(&path).unwrap();
|
||||||
|
std::fs::remove_file(&path).unwrap();
|
||||||
|
std::fs::write(path, content).unwrap();
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
|
repo.apply(&patch, ApplyLocation::Both, Some(&mut apply_opts))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("patch applied to {name}@{version_id}, removing .git directory");
|
log::debug!("patch applied to {name}@{version_id}, removing .git directory");
|
||||||
|
@ -112,7 +143,7 @@ impl Project {
|
||||||
pub mod errors {
|
pub mod errors {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{names::PackageNames, source::VersionId};
|
use crate::{names::PackageNames, source::version_id::VersionId};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
@ -3,8 +3,11 @@ use crate::{
|
||||||
manifest::DependencyType,
|
manifest::DependencyType,
|
||||||
names::PackageNames,
|
names::PackageNames,
|
||||||
source::{
|
source::{
|
||||||
pesde::PesdePackageSource, DependencySpecifiers, PackageRef, PackageSource, PackageSources,
|
pesde::PesdePackageSource,
|
||||||
VersionId,
|
specifiers::DependencySpecifiers,
|
||||||
|
traits::{PackageRef, PackageSource},
|
||||||
|
version_id::VersionId,
|
||||||
|
PackageSources,
|
||||||
},
|
},
|
||||||
Project, DEFAULT_INDEX_NAME,
|
Project, DEFAULT_INDEX_NAME,
|
||||||
};
|
};
|
||||||
|
|
72
src/source/fs.rs
Normal file
72
src/source/fs.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::util::hash;
|
||||||
|
use relative_path::RelativePathBuf;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum FSEntry {
|
||||||
|
#[serde(rename = "f")]
|
||||||
|
File(String),
|
||||||
|
#[serde(rename = "d")]
|
||||||
|
Directory,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct PackageFS(pub(crate) BTreeMap<RelativePathBuf, FSEntry>);
|
||||||
|
|
||||||
|
pub(crate) fn store_in_cas<P: AsRef<Path>>(
|
||||||
|
cas_dir: P,
|
||||||
|
contents: &str,
|
||||||
|
) -> std::io::Result<(String, PathBuf)> {
|
||||||
|
let hash = hash(contents.as_bytes());
|
||||||
|
let (prefix, rest) = hash.split_at(2);
|
||||||
|
|
||||||
|
let folder = cas_dir.as_ref().join(prefix);
|
||||||
|
std::fs::create_dir_all(&folder)?;
|
||||||
|
|
||||||
|
let cas_path = folder.join(rest);
|
||||||
|
if !cas_path.exists() {
|
||||||
|
std::fs::write(&cas_path, contents)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((hash, cas_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageFS {
|
||||||
|
pub fn write_to<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
destination: P,
|
||||||
|
cas_path: Q,
|
||||||
|
link: bool,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
for (path, entry) in &self.0 {
|
||||||
|
let path = path.to_path(destination.as_ref());
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
FSEntry::File(hash) => {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (prefix, rest) = hash.split_at(2);
|
||||||
|
let cas_file_path = cas_path.as_ref().join(prefix).join(rest);
|
||||||
|
|
||||||
|
if link {
|
||||||
|
std::fs::hard_link(cas_file_path, path)?;
|
||||||
|
} else {
|
||||||
|
std::fs::copy(cas_file_path, path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FSEntry::Directory => {
|
||||||
|
std::fs::create_dir_all(path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,127 +1,21 @@
|
||||||
|
use std::{collections::BTreeMap, fmt::Debug};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
manifest::{
|
manifest::target::{Target, TargetKind},
|
||||||
target::{Target, TargetKind},
|
|
||||||
DependencyType,
|
|
||||||
},
|
|
||||||
names::PackageNames,
|
names::PackageNames,
|
||||||
|
source::{
|
||||||
|
fs::PackageFS, refs::PackageRefs, specifiers::DependencySpecifiers, traits::*,
|
||||||
|
version_id::VersionId,
|
||||||
|
},
|
||||||
Project,
|
Project,
|
||||||
};
|
};
|
||||||
use semver::Version;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
|
||||||
use std::{
|
|
||||||
collections::BTreeMap,
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
path::Path,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub mod fs;
|
||||||
pub mod pesde;
|
pub mod pesde;
|
||||||
|
pub mod refs;
|
||||||
pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String {
|
pub mod specifiers;
|
||||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
pub mod traits;
|
||||||
|
pub mod version_id;
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
struc.hash(&mut hasher);
|
|
||||||
hasher.finish().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum DependencySpecifiers {
|
|
||||||
Pesde(pesde::specifier::PesdeDependencySpecifier),
|
|
||||||
}
|
|
||||||
pub trait DependencySpecifier: Debug + Display {}
|
|
||||||
impl DependencySpecifier for DependencySpecifiers {}
|
|
||||||
|
|
||||||
impl Display for DependencySpecifiers {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
DependencySpecifiers::Pesde(specifier) => write!(f, "{specifier}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "snake_case", tag = "ref_ty")]
|
|
||||||
pub enum PackageRefs {
|
|
||||||
Pesde(pesde::pkg_ref::PesdePackageRef),
|
|
||||||
}
|
|
||||||
pub trait PackageRef: Debug {
|
|
||||||
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>;
|
|
||||||
fn use_new_structure(&self) -> bool;
|
|
||||||
fn target_kind(&self) -> TargetKind;
|
|
||||||
fn source(&self) -> PackageSources;
|
|
||||||
}
|
|
||||||
impl PackageRef for PackageRefs {
|
|
||||||
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
|
|
||||||
match self {
|
|
||||||
PackageRefs::Pesde(pkg_ref) => pkg_ref.dependencies(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn use_new_structure(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
PackageRefs::Pesde(pkg_ref) => pkg_ref.use_new_structure(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn target_kind(&self) -> TargetKind {
|
|
||||||
match self {
|
|
||||||
PackageRefs::Pesde(pkg_ref) => pkg_ref.target_kind(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self) -> PackageSources {
|
|
||||||
match self {
|
|
||||||
PackageRefs::Pesde(pkg_ref) => pkg_ref.source(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Debug, SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
|
|
||||||
)]
|
|
||||||
pub struct VersionId(Version, TargetKind);
|
|
||||||
|
|
||||||
impl VersionId {
|
|
||||||
pub fn new(version: Version, target: TargetKind) -> Self {
|
|
||||||
VersionId(version, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn version(&self) -> &Version {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn target(&self) -> &TargetKind {
|
|
||||||
&self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn escaped(&self) -> String {
|
|
||||||
format!("{}+{}", self.0, self.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for VersionId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{} {}", self.0, self.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for VersionId {
|
|
||||||
type Err = errors::VersionIdParseError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let Some((version, target)) = s.split_once(' ') else {
|
|
||||||
return Err(errors::VersionIdParseError::Malformed(s.to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let version = version.parse()?;
|
|
||||||
let target = target.parse()?;
|
|
||||||
|
|
||||||
Ok(VersionId(version, target))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
|
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
|
||||||
|
|
||||||
|
@ -129,32 +23,7 @@ pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
|
||||||
pub enum PackageSources {
|
pub enum PackageSources {
|
||||||
Pesde(pesde::PesdePackageSource),
|
Pesde(pesde::PesdePackageSource),
|
||||||
}
|
}
|
||||||
pub trait PackageSource: Debug {
|
|
||||||
type Ref: PackageRef;
|
|
||||||
type Specifier: DependencySpecifier;
|
|
||||||
type RefreshError: std::error::Error;
|
|
||||||
type ResolveError: std::error::Error;
|
|
||||||
type DownloadError: std::error::Error;
|
|
||||||
|
|
||||||
fn refresh(&self, _project: &Project) -> Result<(), Self::RefreshError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve(
|
|
||||||
&self,
|
|
||||||
specifier: &Self::Specifier,
|
|
||||||
project: &Project,
|
|
||||||
project_target: TargetKind,
|
|
||||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError>;
|
|
||||||
|
|
||||||
fn download(
|
|
||||||
&self,
|
|
||||||
pkg_ref: &Self::Ref,
|
|
||||||
destination: &Path,
|
|
||||||
project: &Project,
|
|
||||||
reqwest: &reqwest::blocking::Client,
|
|
||||||
) -> Result<Target, Self::DownloadError>;
|
|
||||||
}
|
|
||||||
impl PackageSource for PackageSources {
|
impl PackageSource for PackageSources {
|
||||||
type Ref = PackageRefs;
|
type Ref = PackageRefs;
|
||||||
type Specifier = DependencySpecifiers;
|
type Specifier = DependencySpecifiers;
|
||||||
|
@ -195,13 +64,12 @@ impl PackageSource for PackageSources {
|
||||||
fn download(
|
fn download(
|
||||||
&self,
|
&self,
|
||||||
pkg_ref: &Self::Ref,
|
pkg_ref: &Self::Ref,
|
||||||
destination: &Path,
|
|
||||||
project: &Project,
|
project: &Project,
|
||||||
reqwest: &reqwest::blocking::Client,
|
reqwest: &reqwest::blocking::Client,
|
||||||
) -> Result<Target, Self::DownloadError> {
|
) -> Result<(PackageFS, Target), Self::DownloadError> {
|
||||||
match (self, pkg_ref) {
|
match (self, pkg_ref) {
|
||||||
(PackageSources::Pesde(source), PackageRefs::Pesde(pkg_ref)) => source
|
(PackageSources::Pesde(source), PackageRefs::Pesde(pkg_ref)) => source
|
||||||
.download(pkg_ref, destination, project, reqwest)
|
.download(pkg_ref, project, reqwest)
|
||||||
.map_err(Into::into),
|
.map_err(Into::into),
|
||||||
|
|
||||||
_ => Err(errors::DownloadError::Mismatch),
|
_ => Err(errors::DownloadError::Mismatch),
|
||||||
|
@ -238,17 +106,4 @@ pub mod errors {
|
||||||
#[error("error downloading pesde package")]
|
#[error("error downloading pesde package")]
|
||||||
Pesde(#[from] crate::source::pesde::errors::DownloadError),
|
Pesde(#[from] crate::source::pesde::errors::DownloadError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum VersionIdParseError {
|
|
||||||
#[error("malformed entry key {0}")]
|
|
||||||
Malformed(String),
|
|
||||||
|
|
||||||
#[error("malformed version")]
|
|
||||||
Version(#[from] semver::Error),
|
|
||||||
|
|
||||||
#[error("malformed target")]
|
|
||||||
Target(#[from] crate::manifest::target::errors::TargetKindFromStr),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::Path};
|
|
||||||
|
|
||||||
use gix::remote::Direction;
|
use gix::remote::Direction;
|
||||||
|
use relative_path::RelativePathBuf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::BTreeMap, fmt::Debug, hash::Hash, io::Read};
|
||||||
|
|
||||||
use pkg_ref::PesdePackageRef;
|
use pkg_ref::PesdePackageRef;
|
||||||
use specifier::PesdeDependencySpecifier;
|
use specifier::PesdeDependencySpecifier;
|
||||||
|
@ -12,8 +12,11 @@ use crate::{
|
||||||
DependencyType,
|
DependencyType,
|
||||||
},
|
},
|
||||||
names::{PackageName, PackageNames},
|
names::{PackageName, PackageNames},
|
||||||
source::{hash, DependencySpecifiers, PackageSource, ResolveResult, VersionId},
|
source::{
|
||||||
util::authenticate_conn,
|
fs::{store_in_cas, FSEntry, PackageFS},
|
||||||
|
DependencySpecifiers, PackageSource, ResolveResult, VersionId,
|
||||||
|
},
|
||||||
|
util::{authenticate_conn, hash},
|
||||||
Project,
|
Project,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,8 +35,12 @@ impl PesdePackageSource {
|
||||||
Self { repo_url }
|
Self { repo_url }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Vec<u8> {
|
||||||
|
self.repo_url.to_bstring().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn path(&self, project: &Project) -> std::path::PathBuf {
|
pub fn path(&self, project: &Project) -> std::path::PathBuf {
|
||||||
project.data_dir.join("indices").join(hash(self))
|
project.data_dir.join("indices").join(hash(self.as_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tree<'a>(
|
pub(crate) fn tree<'a>(
|
||||||
|
@ -349,11 +356,30 @@ impl PackageSource for PesdePackageSource {
|
||||||
fn download(
|
fn download(
|
||||||
&self,
|
&self,
|
||||||
pkg_ref: &Self::Ref,
|
pkg_ref: &Self::Ref,
|
||||||
destination: &Path,
|
|
||||||
project: &Project,
|
project: &Project,
|
||||||
reqwest: &reqwest::blocking::Client,
|
reqwest: &reqwest::blocking::Client,
|
||||||
) -> Result<Target, Self::DownloadError> {
|
) -> Result<(PackageFS, Target), Self::DownloadError> {
|
||||||
let config = self.config(project)?;
|
let config = self.config(project)?;
|
||||||
|
let index_file = project
|
||||||
|
.cas_dir
|
||||||
|
.join("index")
|
||||||
|
.join(pkg_ref.name.escaped())
|
||||||
|
.join(pkg_ref.version.to_string())
|
||||||
|
.join(pkg_ref.target.to_string());
|
||||||
|
|
||||||
|
match std::fs::read_to_string(&index_file) {
|
||||||
|
Ok(s) => {
|
||||||
|
log::debug!(
|
||||||
|
"using cached index file for package {}@{} {}",
|
||||||
|
pkg_ref.name,
|
||||||
|
pkg_ref.version,
|
||||||
|
pkg_ref.target
|
||||||
|
);
|
||||||
|
return Ok((toml::from_str::<PackageFS>(&s)?, pkg_ref.target.clone()));
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(e) => return Err(errors::DownloadError::ReadIndex(e)),
|
||||||
|
}
|
||||||
|
|
||||||
let (scope, name) = pkg_ref.name.as_str();
|
let (scope, name) = pkg_ref.name.as_str();
|
||||||
let url = config
|
let url = config
|
||||||
|
@ -375,9 +401,35 @@ impl PackageSource for PesdePackageSource {
|
||||||
let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref());
|
let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref());
|
||||||
let mut archive = tar::Archive::new(&mut decoder);
|
let mut archive = tar::Archive::new(&mut decoder);
|
||||||
|
|
||||||
archive.unpack(destination)?;
|
let mut entries = BTreeMap::new();
|
||||||
|
|
||||||
Ok(pkg_ref.target.clone())
|
for entry in archive.entries()? {
|
||||||
|
let mut entry = entry?;
|
||||||
|
let path = RelativePathBuf::from_path(entry.path()?).unwrap();
|
||||||
|
|
||||||
|
if entry.header().entry_type().is_dir() {
|
||||||
|
entries.insert(path, FSEntry::Directory);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
entry.read_to_string(&mut contents)?;
|
||||||
|
|
||||||
|
let hash = store_in_cas(&project.cas_dir, &contents)?.0;
|
||||||
|
entries.insert(path, FSEntry::File(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fs = PackageFS(entries);
|
||||||
|
|
||||||
|
if let Some(parent) = index_file.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(&index_file, toml::to_string(&fs)?)
|
||||||
|
.map_err(errors::DownloadError::WriteIndex)?;
|
||||||
|
|
||||||
|
Ok((fs, pkg_ref.target.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,5 +627,17 @@ pub mod errors {
|
||||||
|
|
||||||
#[error("error unpacking package")]
|
#[error("error unpacking package")]
|
||||||
Unpack(#[from] std::io::Error),
|
Unpack(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("error writing index file")]
|
||||||
|
WriteIndex(#[source] std::io::Error),
|
||||||
|
|
||||||
|
#[error("error serializing index file")]
|
||||||
|
SerializeIndex(#[from] toml::ser::Error),
|
||||||
|
|
||||||
|
#[error("error deserializing index file")]
|
||||||
|
DeserializeIndex(#[from] toml::de::Error),
|
||||||
|
|
||||||
|
#[error("error reading index file")]
|
||||||
|
ReadIndex(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
src/source/refs.rs
Normal file
38
src/source/refs.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::{
|
||||||
|
manifest::{target::TargetKind, DependencyType},
|
||||||
|
source::{pesde, specifiers::DependencySpecifiers, traits::PackageRef, PackageSources},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case", tag = "ref_ty")]
|
||||||
|
pub enum PackageRefs {
|
||||||
|
Pesde(pesde::pkg_ref::PesdePackageRef),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageRef for PackageRefs {
|
||||||
|
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
|
||||||
|
match self {
|
||||||
|
PackageRefs::Pesde(pkg_ref) => pkg_ref.dependencies(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_new_structure(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
PackageRefs::Pesde(pkg_ref) => pkg_ref.use_new_structure(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_kind(&self) -> TargetKind {
|
||||||
|
match self {
|
||||||
|
PackageRefs::Pesde(pkg_ref) => pkg_ref.target_kind(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self) -> PackageSources {
|
||||||
|
match self {
|
||||||
|
PackageRefs::Pesde(pkg_ref) => pkg_ref.source(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/source/specifiers.rs
Normal file
18
src/source/specifiers.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::source::{pesde, traits::DependencySpecifier};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum DependencySpecifiers {
|
||||||
|
Pesde(pesde::specifier::PesdeDependencySpecifier),
|
||||||
|
}
|
||||||
|
impl DependencySpecifier for DependencySpecifiers {}
|
||||||
|
|
||||||
|
impl Display for DependencySpecifiers {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
DependencySpecifiers::Pesde(specifier) => write!(f, "{specifier}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/source/traits.rs
Normal file
47
src/source/traits.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::{
|
||||||
|
manifest::{
|
||||||
|
target::{Target, TargetKind},
|
||||||
|
DependencyType,
|
||||||
|
},
|
||||||
|
source::{DependencySpecifiers, PackageFS, PackageSources, ResolveResult},
|
||||||
|
Project,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait DependencySpecifier: Debug + Display {}
|
||||||
|
|
||||||
|
pub trait PackageRef: Debug {
|
||||||
|
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>;
|
||||||
|
fn use_new_structure(&self) -> bool;
|
||||||
|
fn target_kind(&self) -> TargetKind;
|
||||||
|
fn source(&self) -> PackageSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PackageSource: Debug {
|
||||||
|
type Ref: PackageRef;
|
||||||
|
type Specifier: DependencySpecifier;
|
||||||
|
type RefreshError: std::error::Error;
|
||||||
|
type ResolveError: std::error::Error;
|
||||||
|
type DownloadError: std::error::Error;
|
||||||
|
|
||||||
|
fn refresh(&self, _project: &Project) -> Result<(), Self::RefreshError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(
|
||||||
|
&self,
|
||||||
|
specifier: &Self::Specifier,
|
||||||
|
project: &Project,
|
||||||
|
project_target: TargetKind,
|
||||||
|
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError>;
|
||||||
|
|
||||||
|
fn download(
|
||||||
|
&self,
|
||||||
|
pkg_ref: &Self::Ref,
|
||||||
|
project: &Project,
|
||||||
|
reqwest: &reqwest::blocking::Client,
|
||||||
|
) -> Result<(PackageFS, Target), Self::DownloadError>;
|
||||||
|
}
|
65
src/source/version_id.rs
Normal file
65
src/source/version_id.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::manifest::target::TargetKind;
|
||||||
|
use semver::Version;
|
||||||
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
|
||||||
|
)]
|
||||||
|
pub struct VersionId(pub(crate) Version, pub(crate) TargetKind);
|
||||||
|
|
||||||
|
impl VersionId {
|
||||||
|
pub fn new(version: Version, target: TargetKind) -> Self {
|
||||||
|
VersionId(version, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> &Version {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> &TargetKind {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn escaped(&self) -> String {
|
||||||
|
format!("{}+{}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VersionId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} {}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for VersionId {
|
||||||
|
type Err = errors::VersionIdParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let Some((version, target)) = s.split_once(' ') else {
|
||||||
|
return Err(errors::VersionIdParseError::Malformed(s.to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let version = version.parse()?;
|
||||||
|
let target = target.parse()?;
|
||||||
|
|
||||||
|
Ok(VersionId(version, target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod errors {
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum VersionIdParseError {
|
||||||
|
#[error("malformed entry key {0}")]
|
||||||
|
Malformed(String),
|
||||||
|
|
||||||
|
#[error("malformed version")]
|
||||||
|
Version(#[from] semver::Error),
|
||||||
|
|
||||||
|
#[error("malformed target")]
|
||||||
|
Target(#[from] crate::manifest::target::errors::TargetKindFromStr),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::AuthConfig;
|
use crate::AuthConfig;
|
||||||
use gix::bstr::BStr;
|
use gix::bstr::BStr;
|
||||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
|
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
pub fn authenticate_conn(
|
pub fn authenticate_conn(
|
||||||
|
@ -59,3 +60,9 @@ pub fn deserialize_gix_url_map<'de, D: Deserializer<'de>>(
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash<S: AsRef<[u8]>>(struc: S) -> String {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(struc.as_ref());
|
||||||
|
format!("{:x}", hasher.finalize())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue