feat: content addressable storage

This commit is contained in:
daimond113 2024-07-28 18:19:54 +02:00
parent d8cd78e7e2
commit 37cc86f028
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
36 changed files with 574 additions and 311 deletions

1
Cargo.lock generated
View file

@ -2736,6 +2736,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"sha2",
"tar", "tar",
"thiserror", "thiserror",
"threadpool", "threadpool",

View file

@ -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 }

View file

@ -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(())
} }

View file

@ -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(())
} }

View file

@ -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");

View file

@ -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),
} }
} }
} }

View file

@ -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");

View file

@ -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 => {

View file

@ -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(),
} }
} }
} }

View file

@ -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 => {

View file

@ -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())?;

View file

@ -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!(

View file

@ -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),
} }
} }
} }

View file

@ -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

View file

@ -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};

View file

@ -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)?;

View file

@ -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(())

View file

@ -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"));

View file

@ -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")?

View file

@ -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
}; };

View file

@ -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),
} }
} }

View file

@ -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)

View file

@ -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)?;
} }
} }
} }

View file

@ -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};

View file

@ -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()

View file

@ -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;

View file

@ -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)]

View file

@ -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
View 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(())
}
}

View file

@ -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),
}
} }

View file

@ -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
View 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
View 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
View 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
View 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),
}
}

View file

@ -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())
}