feat: implement linking

This commit is contained in:
daimond113 2024-07-17 19:38:01 +02:00
parent fdad8995a4
commit 10ca24a0cc
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
14 changed files with 630 additions and 101 deletions

View file

@ -1,12 +1,24 @@
use anyhow::Context;
use clap::Args; use clap::Args;
use pesde::Project; use pesde::Project;
use std::collections::HashSet;
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct InstallCommand {} pub struct InstallCommand {}
impl InstallCommand { impl InstallCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> { pub fn run(self, project: Project) -> anyhow::Result<()> {
dbg!(project.dependency_graph(None)?); let mut refreshed_sources = HashSet::new();
let graph = project
.dependency_graph(None, &mut refreshed_sources)
.context("failed to build dependency graph")?;
let downloaded_graph = project
.download_graph(&graph, &mut refreshed_sources)
.context("failed to download dependencies")?;
project
.link_dependencies(&downloaded_graph)
.context("failed to link dependencies")?;
Ok(()) Ok(())
} }

79
src/download.rs Normal file
View file

@ -0,0 +1,79 @@
use std::{
collections::{BTreeMap, HashSet},
fs::create_dir_all,
};
use crate::{
lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph},
source::{pesde::PesdePackageSource, PackageRefs, PackageSource, PackageSources},
Project, PACKAGES_CONTAINER_NAME,
};
impl Project {
pub fn download_graph(
&self,
graph: &DependencyGraph,
refreshed_sources: &mut HashSet<PackageSources>,
) -> Result<DownloadedGraph, errors::DownloadGraphError> {
let manifest = self.deser_manifest()?;
let mut downloaded_graph: DownloadedGraph = BTreeMap::new();
for (name, versions) in graph {
for (version, node) in versions {
let source = match &node.pkg_ref {
PackageRefs::Pesde(pkg_ref) => {
PackageSources::Pesde(PesdePackageSource::new(pkg_ref.index_url.clone()))
}
};
if refreshed_sources.insert(source.clone()) {
source.refresh(self).map_err(Box::new)?;
}
let container_folder = node.container_folder(
&self
.path()
.join(node.base_folder(manifest.target.kind(), true))
.join(PACKAGES_CONTAINER_NAME),
name,
version,
);
create_dir_all(&container_folder)?;
let target = source.download(&node.pkg_ref, &container_folder, self)?;
downloaded_graph.entry(name.clone()).or_default().insert(
version.clone(),
DownloadedDependencyGraphNode {
node: node.clone(),
target,
},
);
}
}
Ok(downloaded_graph)
}
}
pub mod errors {
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DownloadGraphError {
#[error("error deserializing project manifest")]
ManifestDeserializationFailed(#[from] crate::errors::ManifestReadError),
#[error("failed to refresh package source")]
RefreshFailed(#[from] Box<crate::source::errors::RefreshError>),
#[error("error interacting with filesystem")]
Io(#[from] std::io::Error),
#[error("failed to download package")]
DownloadFailed(#[from] crate::source::errors::DownloadError),
}
}

View file

@ -7,6 +7,8 @@ use crate::lockfile::Lockfile;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub mod download;
pub mod linking;
pub mod lockfile; pub mod lockfile;
pub mod manifest; pub mod manifest;
pub mod names; pub mod names;
@ -17,6 +19,7 @@ pub mod source;
pub const MANIFEST_FILE_NAME: &str = "pesde.yaml"; pub const MANIFEST_FILE_NAME: &str = "pesde.yaml";
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock"; pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";
pub const DEFAULT_INDEX_NAME: &str = "default"; pub const DEFAULT_INDEX_NAME: &str = "default";
pub const PACKAGES_CONTAINER_NAME: &str = ".pesde";
pub(crate) static REQWEST_CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| { pub(crate) static REQWEST_CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| {
reqwest::blocking::Client::builder() reqwest::blocking::Client::builder()

128
src/linking/generator.rs Normal file
View file

@ -0,0 +1,128 @@
use std::path::{Component, Path};
use full_moon::{ast::luau::ExportedTypeDeclaration, visitors::Visitor};
use crate::manifest::Target;
struct TypeVisitor {
types: Vec<String>,
}
impl Visitor for TypeVisitor {
fn visit_exported_type_declaration(&mut self, node: &ExportedTypeDeclaration) {
let name = node.type_declaration().type_name().to_string();
let (declaration_generics, generics) =
if let Some(declaration) = node.type_declaration().generics() {
let mut declaration_generics = vec![];
let mut generics = vec![];
for generic in declaration.generics().iter() {
declaration_generics.push(generic.to_string());
if generic.default_type().is_some() {
generics.push(generic.parameter().to_string())
} else {
generics.push(generic.to_string())
}
}
(
format!("<{}>", declaration_generics.join(", ")),
format!("<{}>", generics.join(", ")),
)
} else {
("".to_string(), "".to_string())
};
self.types.push(format!(
"export type {name}{declaration_generics} = module.{name}{generics}\n"
));
}
}
pub fn get_file_types(file: &str) -> Result<Vec<String>, Vec<full_moon::Error>> {
let ast = full_moon::parse(file)?;
let mut visitor = TypeVisitor { types: vec![] };
visitor.visit_ast(&ast);
Ok(visitor.types)
}
pub fn generate_linking_module<I: IntoIterator<Item = S>, S: AsRef<str>>(
path: &str,
types: I,
) -> String {
let mut output = format!("local module = require({path})\n");
for ty in types {
output.push_str(ty.as_ref());
}
output.push_str("return module");
output
}
pub fn get_require_path(
target: &Target,
base_dir: &Path,
destination_dir: &Path,
use_new_structure: bool,
) -> Result<String, errors::GetRequirePathError> {
let Some(lib_file) = target.lib_path() else {
return Err(errors::GetRequirePathError::NoLibPath);
};
let path = pathdiff::diff_paths(destination_dir, base_dir).unwrap();
let path = if !use_new_structure {
lib_file.to_path(path)
} else {
path
};
#[cfg(feature = "roblox")]
if matches!(target, Target::Roblox { .. }) {
let path = path
.components()
.filter_map(|component| match component {
Component::ParentDir => Some(".Parent".to_string()),
Component::Normal(part) if part != "init.lua" && part != "init.luau" => {
Some(format!(
"[{:?}]",
part.to_string_lossy()
.trim_end_matches(".lua")
.trim_end_matches(".luau")
))
}
_ => None,
})
.collect::<Vec<_>>()
.join("");
return Ok(format!("script{path}"));
};
let path = path
.components()
.filter_map(|ct| match ct {
Component::ParentDir => Some("..".to_string()),
Component::Normal(part) => Some(format!("{}", part.to_string_lossy())),
_ => None,
})
.collect::<Vec<_>>()
.join("/");
Ok(format!("./{path}"))
}
pub mod errors {
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GetRequirePathError {
#[error("get require path called for target without a lib path")]
NoLibPath,
}
}

177
src/linking/mod.rs Normal file
View file

@ -0,0 +1,177 @@
use crate::{
linking::generator::get_file_types, lockfile::DownloadedGraph, manifest::Manifest,
names::PackageNames, source::PackageRef, Project, MANIFEST_FILE_NAME, PACKAGES_CONTAINER_NAME,
};
use semver::Version;
use std::{collections::BTreeMap, fs::create_dir_all};
pub mod generator;
fn read_manifest(path: &std::path::Path) -> Result<Manifest, errors::LinkingError> {
let manifest = std::fs::read_to_string(path.join(MANIFEST_FILE_NAME))?;
serde_yaml::from_str(&manifest)
.map_err(|e| errors::LinkingError::DependencyManifest(path.display().to_string(), e))
}
impl Project {
pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> {
let manifest = self.deser_manifest()?;
let mut package_types = BTreeMap::<&PackageNames, BTreeMap<&Version, Vec<String>>>::new();
for (name, versions) in graph {
for (version, node) in versions {
let Some(lib_file) = node.target.lib_path() else {
continue;
};
let container_folder = node.node.container_folder(
&self
.path()
.join(node.node.base_folder(manifest.target.kind(), true))
.join(PACKAGES_CONTAINER_NAME),
name,
version,
);
let lib_file = lib_file.to_path(container_folder);
let contents = match std::fs::read_to_string(&lib_file) {
Ok(contents) => contents,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Err(errors::LinkingError::LibFileNotFound(
lib_file.display().to_string(),
));
}
Err(e) => return Err(e.into()),
};
let types = match get_file_types(&contents) {
Ok(types) => types,
Err(e) => {
return Err(errors::LinkingError::FullMoon(
lib_file.display().to_string(),
e,
))
}
};
package_types
.entry(name)
.or_default()
.insert(version, types);
}
}
for (name, versions) in graph {
for (version, node) in versions {
let base_folder = self.path().join(
self.path()
.join(node.node.base_folder(manifest.target.kind(), true)),
);
create_dir_all(&base_folder)?;
let base_folder = base_folder.canonicalize()?;
let packages_container_folder = base_folder.join(PACKAGES_CONTAINER_NAME);
let container_folder =
node.node
.container_folder(&packages_container_folder, name, version);
let node_manifest = read_manifest(&container_folder)?;
if let Some((alias, types)) = package_types
.get(name)
.and_then(|v| v.get(version))
.and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types)))
{
let module = generator::generate_linking_module(
&generator::get_require_path(
&node_manifest.target,
&base_folder,
&container_folder,
node.node.pkg_ref.use_new_structure(),
)?,
types,
);
std::fs::write(base_folder.join(format!("{alias}.luau")), module)?;
}
for (dependency_name, (dependency_version, dependency_alias)) in
&node.node.dependencies
{
let Some(dependency_node) = graph
.get(dependency_name)
.and_then(|v| v.get(dependency_version))
else {
return Err(errors::LinkingError::DependencyNotFound(
dependency_name.to_string(),
dependency_version.to_string(),
));
};
let dependency_container_folder = dependency_node.node.container_folder(
&packages_container_folder,
dependency_name,
dependency_version,
);
let dependency_manifest = read_manifest(&dependency_container_folder)?;
let linker_folder = container_folder
.join(dependency_node.node.base_folder(node.target.kind(), false));
create_dir_all(&linker_folder)?;
let linker_folder = linker_folder.canonicalize()?;
let linker_file = linker_folder.join(format!("{dependency_alias}.luau"));
let module = generator::generate_linking_module(
&generator::get_require_path(
&dependency_manifest.target,
&linker_file,
&dependency_container_folder,
node.node.pkg_ref.use_new_structure(),
)?,
package_types
.get(dependency_name)
.and_then(|v| v.get(dependency_version))
.unwrap(),
);
std::fs::write(linker_file, module)?;
}
}
}
Ok(())
}
}
pub mod errors {
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LinkingError {
#[error("error deserializing project manifest")]
Manifest(#[from] crate::errors::ManifestReadError),
#[error("error deserializing manifest at {0}")]
DependencyManifest(String, #[source] serde_yaml::Error),
#[error("error interacting with filesystem")]
Io(#[from] std::io::Error),
#[error("dependency not found: {0}@{1}")]
DependencyNotFound(String, String),
#[error("library file at {0} not found")]
LibFileNotFound(String),
#[error("error parsing Luau script at {0}")]
FullMoon(String, Vec<full_moon::Error>),
#[error("error generating require path")]
GetRequirePath(#[from] crate::linking::generator::errors::GetRequirePathError),
}
}

View file

@ -1,11 +1,16 @@
use crate::{ use crate::{
manifest::{DependencyType, OverrideKey}, manifest::{DependencyType, OverrideKey, Target, TargetKind},
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::{DependencySpecifiers, PackageRefs}, source::{DependencySpecifiers, PackageRef, PackageRefs},
}; };
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{btree_map::Entry, BTreeMap}; use std::{
collections::{btree_map::Entry, BTreeMap},
path::{Path, PathBuf},
};
pub type Graph<Node> = BTreeMap<PackageNames, BTreeMap<Version, Node>>;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DependencyGraphNode { pub struct DependencyGraphNode {
@ -16,7 +21,29 @@ pub struct DependencyGraphNode {
pub ty: DependencyType, pub ty: DependencyType,
} }
pub type DependencyGraph = BTreeMap<PackageNames, BTreeMap<Version, DependencyGraphNode>>; impl DependencyGraphNode {
pub fn base_folder(&self, project_target: TargetKind, is_top_level: bool) -> String {
if is_top_level || self.pkg_ref.use_new_structure() {
project_target.packages_folder(&self.pkg_ref.target_kind())
} else {
"..".to_string()
}
}
pub fn container_folder<P: AsRef<Path>>(
&self,
path: &P,
name: &PackageNames,
version: &Version,
) -> PathBuf {
path.as_ref()
.join(name.escaped())
.join(version.to_string())
.join(name.as_str().1)
}
}
pub type DependencyGraph = Graph<DependencyGraphNode>;
pub fn insert_node( pub fn insert_node(
graph: &mut DependencyGraph, graph: &mut DependencyGraph,
@ -50,6 +77,14 @@ pub fn insert_node(
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DownloadedDependencyGraphNode {
pub node: DependencyGraphNode,
pub target: Target,
}
pub type DownloadedGraph = Graph<DownloadedDependencyGraphNode>;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lockfile { pub struct Lockfile {
pub name: PackageName, pub name: PackageName,

View file

@ -2,6 +2,7 @@ use crate::cli::get_token;
use clap::Parser; use clap::Parser;
use colored::Colorize; use colored::Colorize;
use pesde::{AuthConfig, Project}; use pesde::{AuthConfig, Project};
use std::fs::create_dir_all;
mod cli; mod cli;
@ -27,6 +28,7 @@ fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let data_dir = project_dirs.data_dir(); let data_dir = project_dirs.data_dir();
create_dir_all(data_dir).expect("failed to create data directory");
if let Err(err) = get_token(data_dir).and_then(|token| { if let Err(err) = get_token(data_dir).and_then(|token| {
cli.subcommand.run(Project::new( cli.subcommand.run(Project::new(
@ -35,16 +37,33 @@ fn main() {
AuthConfig::new().with_pesde_token(token), AuthConfig::new().with_pesde_token(token),
)) ))
}) { }) {
eprintln!("{}: {}\n", "error".red().bold(), err.to_string().bold()); eprintln!("{}: {err}\n", "error".red().bold());
let cause = err.chain().skip(1).collect::<Vec<_>>(); let cause = err.chain().skip(1).collect::<Vec<_>>();
if !cause.is_empty() { if !cause.is_empty() {
eprintln!("{}:", "caused by".red().bold()); eprintln!("{}:", "caused by".red().bold());
for err in cause { for err in cause {
eprintln!(" - {}", err.to_string().bold()); eprintln!(" - {err}");
} }
} }
let backtrace = err.backtrace();
match backtrace.status() {
std::backtrace::BacktraceStatus::Disabled => {
eprintln!(
"\n{}: set RUST_BACKTRACE=1 for a backtrace",
"help".yellow().bold()
);
}
std::backtrace::BacktraceStatus::Captured => {
eprintln!("\n{}:\n{backtrace}", "backtrace".yellow().bold());
}
_ => {
eprintln!("\n{}: not captured", "backtrace".yellow().bold());
}
}
std::process::exit(1); std::process::exit(1);
} }
} }

View file

@ -1,6 +1,6 @@
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use semver::Version; use semver::Version;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeFromStr, SerializeDisplay}; use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
@ -58,7 +58,7 @@ impl TargetKind {
} }
} }
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case", tag = "environment", remote = "Self")] #[serde(rename_all = "snake_case", tag = "environment", remote = "Self")]
pub enum Target { pub enum Target {
#[cfg(feature = "roblox")] #[cfg(feature = "roblox")]
@ -75,6 +75,39 @@ pub enum Target {
}, },
} }
impl Target {
pub fn kind(&self) -> TargetKind {
match self {
#[cfg(feature = "roblox")]
Target::Roblox { .. } => TargetKind::Roblox,
#[cfg(feature = "lune")]
Target::Lune { .. } => TargetKind::Lune,
#[cfg(feature = "luau")]
Target::Luau { .. } => TargetKind::Luau,
}
}
pub fn lib_path(&self) -> Option<&RelativePathBuf> {
match self {
#[cfg(feature = "roblox")]
Target::Roblox { lib } => Some(lib),
#[cfg(feature = "lune")]
Target::Lune { lib, .. } => lib.as_ref(),
#[cfg(feature = "luau")]
Target::Luau { lib, .. } => lib.as_ref(),
}
}
}
impl Serialize for Target {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Self::serialize(self, serializer)
}
}
impl<'de> Deserialize<'de> for Target { impl<'de> Deserialize<'de> for Target {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -115,19 +148,6 @@ impl Display for Target {
} }
} }
impl Target {
pub fn kind(&self) -> TargetKind {
match self {
#[cfg(feature = "roblox")]
Target::Roblox { .. } => TargetKind::Roblox,
#[cfg(feature = "lune")]
Target::Lune { .. } => TargetKind::Lune,
#[cfg(feature = "luau")]
Target::Luau { .. } => TargetKind::Luau,
}
}
}
#[derive( #[derive(
Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
)] )]
@ -189,10 +209,10 @@ pub struct Manifest {
pub scripts: BTreeMap<String, RelativePathBuf>, pub scripts: BTreeMap<String, RelativePathBuf>,
#[serde(default)] #[serde(default)]
pub indices: BTreeMap<String, url::Url>, pub indices: BTreeMap<String, url::Url>,
#[cfg(feature = "wally")] #[cfg(feature = "wally-compat")]
#[serde(default)] #[serde(default)]
pub wally_indices: BTreeMap<String, url::Url>, pub wally_indices: BTreeMap<String, url::Url>,
#[cfg(all(feature = "wally", feature = "roblox"))] #[cfg(all(feature = "wally-compat", feature = "roblox"))]
#[serde(default)] #[serde(default)]
pub sourcemap_generator: Option<String>, pub sourcemap_generator: Option<String>,
#[serde(default)] #[serde(default)]

View file

@ -63,6 +63,10 @@ impl PackageName {
pub fn as_str(&self) -> (&str, &str) { pub fn as_str(&self) -> (&str, &str) {
(&self.0, &self.1) (&self.0, &self.1)
} }
pub fn escaped(&self) -> String {
format!("{}+{}", self.0, self.1)
}
} }
#[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
@ -70,6 +74,20 @@ pub enum PackageNames {
Pesde(PackageName), Pesde(PackageName),
} }
impl PackageNames {
pub fn as_str(&self) -> (&str, &str) {
match self {
PackageNames::Pesde(name) => name.as_str(),
}
}
pub fn escaped(&self) -> String {
match self {
PackageNames::Pesde(name) => name.escaped(),
}
}
}
impl Display for PackageNames { impl Display for PackageNames {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {

View file

@ -11,19 +11,17 @@ use semver::Version;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
impl Project { impl Project {
// TODO: implement dependency overrides // TODO: implement dependency overrides, account for targets using the is_compatible_with method
pub fn dependency_graph( pub fn dependency_graph(
&self, &self,
previous_graph: Option<&DependencyGraph>, previous_graph: Option<&DependencyGraph>,
refreshed_sources: &mut HashSet<PackageSources>,
) -> Result<DependencyGraph, Box<errors::DependencyGraphError>> { ) -> Result<DependencyGraph, Box<errors::DependencyGraphError>> {
let manifest = self.deser_manifest().map_err(|e| Box::new(e.into()))?; let manifest = self.deser_manifest().map_err(|e| Box::new(e.into()))?;
let mut all_dependencies = manifest let mut all_specifiers = manifest
.all_dependencies() .all_dependencies()
.map_err(|e| Box::new(e.into()))?; .map_err(|e| Box::new(e.into()))?
let mut all_specifiers = all_dependencies
.clone()
.into_iter() .into_iter()
.map(|(alias, (spec, ty))| ((spec, ty), alias)) .map(|(alias, (spec, ty))| ((spec, ty), alias))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
@ -38,14 +36,12 @@ impl Project {
continue; continue;
}; };
match all_specifiers.remove(&(specifier.clone(), node.ty)) { if all_specifiers
Some(alias) => { .remove(&(specifier.clone(), node.ty))
all_dependencies.remove(&alias); .is_none()
} {
None => { // this dependency is no longer in the manifest, or it's type has changed
// this dependency is no longer in the manifest, or it's type has changed continue;
continue;
}
} }
log::debug!("resolved {}@{} from old dependency graph", name, version); log::debug!("resolved {}@{} from old dependency graph", name, version);
@ -96,10 +92,9 @@ impl Project {
} }
} }
let mut refreshed_sources = HashSet::new(); let mut queue = all_specifiers
let mut queue = all_dependencies
.into_iter() .into_iter()
.map(|(alias, (spec, ty))| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize)) .map(|((spec, ty), alias)| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize))
.collect::<VecDeque<_>>(); .collect::<VecDeque<_>>();
while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() { while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() {

View file

@ -1,12 +1,18 @@
use crate::{manifest::DependencyType, names::PackageNames, Project};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
fmt::{Debug, Display}, fmt::{Debug, Display},
path::Path, path::Path,
}; };
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::{
manifest::{DependencyType, Target, TargetKind},
names::PackageNames,
Project,
};
pub mod pesde; pub mod pesde;
pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String { pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String {
@ -20,7 +26,7 @@ pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)] #[serde(untagged)]
pub enum DependencySpecifiers { pub enum DependencySpecifiers {
Pesde(pesde::PesdeDependencySpecifier), Pesde(pesde::specifier::PesdeDependencySpecifier),
} }
pub trait DependencySpecifier: Debug + Display {} pub trait DependencySpecifier: Debug + Display {}
impl DependencySpecifier for DependencySpecifiers {} impl DependencySpecifier for DependencySpecifiers {}
@ -35,10 +41,12 @@ impl Display for DependencySpecifiers {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub enum PackageRefs { pub enum PackageRefs {
Pesde(pesde::PesdePackageRef), Pesde(pesde::pkg_ref::PesdePackageRef),
} }
pub trait PackageRef: Debug { pub trait PackageRef: Debug {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>; fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>;
fn use_new_structure(&self) -> bool;
fn target_kind(&self) -> TargetKind;
} }
impl PackageRef for PackageRefs { impl PackageRef for PackageRefs {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> { fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
@ -46,6 +54,18 @@ impl PackageRef for PackageRefs {
PackageRefs::Pesde(pkg_ref) => pkg_ref.dependencies(), 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(),
}
}
} }
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<Version, Ref>); pub type ResolveResult<Ref> = (PackageNames, BTreeMap<Version, Ref>);
@ -76,7 +96,7 @@ pub trait PackageSource: Debug {
pkg_ref: &Self::Ref, pkg_ref: &Self::Ref,
destination: &Path, destination: &Path,
project: &Project, project: &Project,
) -> Result<(), Self::DownloadError>; ) -> Result<Target, Self::DownloadError>;
} }
impl PackageSource for PackageSources { impl PackageSource for PackageSources {
type Ref = PackageRefs; type Ref = PackageRefs;
@ -119,7 +139,7 @@ impl PackageSource for PackageSources {
pkg_ref: &Self::Ref, pkg_ref: &Self::Ref,
destination: &Path, destination: &Path,
project: &Project, project: &Project,
) -> Result<(), Self::DownloadError> { ) -> Result<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) .download(pkg_ref, destination, project)

View file

@ -1,63 +1,22 @@
use std::{ use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::Path};
collections::BTreeMap,
fmt::{Debug, Display},
hash::Hash,
path::Path,
};
use gix::remote::Direction; use gix::remote::Direction;
use semver::{Version, VersionReq}; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use pkg_ref::PesdePackageRef;
use specifier::PesdeDependencySpecifier;
use crate::{ use crate::{
authenticate_conn, authenticate_conn,
manifest::{DependencyType, TargetKind}, manifest::{DependencyType, Target},
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::{ source::{hash, DependencySpecifiers, PackageSource, ResolveResult},
hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource, ResolveResult,
},
Project, REQWEST_CLIENT, Project, REQWEST_CLIENT,
}; };
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] pub mod pkg_ref;
pub struct PesdeDependencySpecifier { pub mod specifier;
pub name: PackageName,
pub version: VersionReq,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<String>,
}
impl DependencySpecifier for PesdeDependencySpecifier {}
impl Display for PesdeDependencySpecifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}@{}", self.name, self.version)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
pub struct PesdePackageRef {
name: PackageName,
version: Version,
index_url: gix::Url,
dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
}
impl PackageRef for PesdePackageRef {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
&self.dependencies
}
}
impl Ord for PesdePackageRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.version.cmp(&other.version)
}
}
impl PartialOrd for PesdePackageRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct PesdePackageSource { pub struct PesdePackageSource {
@ -359,6 +318,7 @@ impl PackageSource for PesdePackageSource {
version: entry.version, version: entry.version,
index_url: self.repo_url.clone(), index_url: self.repo_url.clone(),
dependencies: entry.dependencies, dependencies: entry.dependencies,
target: entry.target,
}, },
) )
}) })
@ -371,7 +331,7 @@ impl PackageSource for PesdePackageSource {
pkg_ref: &Self::Ref, pkg_ref: &Self::Ref,
destination: &Path, destination: &Path,
project: &Project, project: &Project,
) -> Result<(), Self::DownloadError> { ) -> Result<Target, Self::DownloadError> {
let config = self.config(project)?; let config = self.config(project)?;
let (scope, name) = pkg_ref.name.as_str(); let (scope, name) = pkg_ref.name.as_str();
@ -397,7 +357,7 @@ impl PackageSource for PesdePackageSource {
archive.unpack(destination)?; archive.unpack(destination)?;
Ok(()) Ok(pkg_ref.target.clone())
} }
} }
@ -432,7 +392,7 @@ impl IndexConfig {
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct IndexFileEntry { pub struct IndexFileEntry {
pub version: Version, pub version: Version,
pub target: TargetKind, pub target: Target,
#[serde(default = "chrono::Utc::now")] #[serde(default = "chrono::Utc::now")]
pub published_at: chrono::DateTime<chrono::Utc>, pub published_at: chrono::DateTime<chrono::Utc>,

View file

@ -0,0 +1,44 @@
use std::collections::BTreeMap;
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::{
manifest::{DependencyType, Target, TargetKind},
names::PackageName,
source::{DependencySpecifiers, PackageRef},
};
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
pub struct PesdePackageRef {
pub name: PackageName,
pub version: Version,
pub index_url: gix::Url,
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
pub target: Target,
}
impl PackageRef for PesdePackageRef {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
&self.dependencies
}
fn use_new_structure(&self) -> bool {
true
}
fn target_kind(&self) -> TargetKind {
self.target.kind()
}
}
impl Ord for PesdePackageRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.version.cmp(&other.version)
}
}
impl PartialOrd for PesdePackageRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

View file

@ -0,0 +1,19 @@
use crate::{names::PackageName, source::DependencySpecifier};
use semver::VersionReq;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct PesdeDependencySpecifier {
pub name: PackageName,
pub version: VersionReq,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<String>,
}
impl DependencySpecifier for PesdeDependencySpecifier {}
impl Display for PesdeDependencySpecifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}@{}", self.name, self.version)
}
}