mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
308 lines
9.4 KiB
Rust
308 lines
9.4 KiB
Rust
#![deny(missing_docs)]
|
|
//! A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune.
|
|
//! pesde has its own registry, however it can also use Wally, and Git repositories as package sources.
|
|
//! It has been designed with multiple targets in mind, namely Roblox, Lune, and Luau.
|
|
|
|
use crate::{
|
|
lockfile::Lockfile,
|
|
manifest::Manifest,
|
|
source::{traits::PackageSource, PackageSources},
|
|
};
|
|
use async_stream::stream;
|
|
use fs_err::tokio as fs;
|
|
use futures::{future::try_join_all, Stream};
|
|
use gix::sec::identity::Account;
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
/// Downloading packages
|
|
pub mod download;
|
|
/// Linking packages
|
|
pub mod linking;
|
|
/// Lockfile
|
|
pub mod lockfile;
|
|
/// Manifest
|
|
pub mod manifest;
|
|
/// Package names
|
|
pub mod names;
|
|
/// Patching packages
|
|
#[cfg(feature = "patches")]
|
|
pub mod patches;
|
|
/// Resolving packages
|
|
pub mod resolver;
|
|
/// Running scripts
|
|
pub mod scripts;
|
|
/// Package sources
|
|
pub mod source;
|
|
pub(crate) mod util;
|
|
|
|
/// The name of the manifest file
|
|
pub const MANIFEST_FILE_NAME: &str = "pesde.toml";
|
|
/// The name of the lockfile
|
|
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";
|
|
/// The name of the default index
|
|
pub const DEFAULT_INDEX_NAME: &str = "default";
|
|
/// The name of the packages container
|
|
pub const PACKAGES_CONTAINER_NAME: &str = ".pesde";
|
|
pub(crate) const LINK_LIB_NO_FILE_FOUND: &str = "____pesde_no_export_file_found";
|
|
|
|
/// Struct containing the authentication configuration
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct AuthConfig {
|
|
tokens: HashMap<gix::Url, String>,
|
|
git_credentials: Option<Account>,
|
|
}
|
|
|
|
impl AuthConfig {
|
|
/// Create a new `AuthConfig`
|
|
pub fn new() -> Self {
|
|
AuthConfig::default()
|
|
}
|
|
|
|
/// Set the tokens
|
|
pub fn with_tokens<I: IntoIterator<Item = (gix::Url, S)>, S: AsRef<str>>(
|
|
mut self,
|
|
tokens: I,
|
|
) -> Self {
|
|
self.tokens = tokens
|
|
.into_iter()
|
|
.map(|(url, s)| (url, s.as_ref().to_string()))
|
|
.collect();
|
|
self
|
|
}
|
|
|
|
/// Set the git credentials
|
|
pub fn with_git_credentials(mut self, git_credentials: Option<Account>) -> Self {
|
|
self.git_credentials = git_credentials;
|
|
self
|
|
}
|
|
|
|
/// Get the tokens
|
|
pub fn tokens(&self) -> &HashMap<gix::Url, String> {
|
|
&self.tokens
|
|
}
|
|
|
|
/// Get the git credentials
|
|
pub fn git_credentials(&self) -> Option<&Account> {
|
|
self.git_credentials.as_ref()
|
|
}
|
|
}
|
|
|
|
/// The main struct of the pesde library, representing a project
|
|
#[derive(Debug, Clone)]
|
|
pub struct Project {
|
|
package_dir: PathBuf,
|
|
workspace_dir: Option<PathBuf>,
|
|
data_dir: PathBuf,
|
|
auth_config: AuthConfig,
|
|
cas_dir: PathBuf,
|
|
}
|
|
|
|
impl Project {
|
|
/// Create a new `Project`
|
|
pub fn new<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>, S: AsRef<Path>>(
|
|
package_dir: P,
|
|
workspace_dir: Option<Q>,
|
|
data_dir: R,
|
|
cas_dir: S,
|
|
auth_config: AuthConfig,
|
|
) -> Self {
|
|
Project {
|
|
package_dir: package_dir.as_ref().to_path_buf(),
|
|
workspace_dir: workspace_dir.map(|d| d.as_ref().to_path_buf()),
|
|
data_dir: data_dir.as_ref().to_path_buf(),
|
|
auth_config,
|
|
cas_dir: cas_dir.as_ref().to_path_buf(),
|
|
}
|
|
}
|
|
|
|
/// The directory of the package
|
|
pub fn package_dir(&self) -> &Path {
|
|
&self.package_dir
|
|
}
|
|
|
|
/// The directory of the workspace this package belongs to, if any
|
|
pub fn workspace_dir(&self) -> Option<&Path> {
|
|
self.workspace_dir.as_deref()
|
|
}
|
|
|
|
/// The directory to store general-purpose data
|
|
pub fn data_dir(&self) -> &Path {
|
|
&self.data_dir
|
|
}
|
|
|
|
/// The authentication configuration
|
|
pub fn auth_config(&self) -> &AuthConfig {
|
|
&self.auth_config
|
|
}
|
|
|
|
/// The CAS (content-addressable storage) directory
|
|
pub fn cas_dir(&self) -> &Path {
|
|
&self.cas_dir
|
|
}
|
|
|
|
/// Read the manifest file
|
|
pub async fn read_manifest(&self) -> Result<String, errors::ManifestReadError> {
|
|
let string = fs::read_to_string(self.package_dir.join(MANIFEST_FILE_NAME)).await?;
|
|
Ok(string)
|
|
}
|
|
|
|
/// Deserialize the manifest file
|
|
pub async fn deser_manifest(&self) -> Result<Manifest, errors::ManifestReadError> {
|
|
let string = fs::read_to_string(self.package_dir.join(MANIFEST_FILE_NAME)).await?;
|
|
Ok(toml::from_str(&string)?)
|
|
}
|
|
|
|
/// Write the manifest file
|
|
pub async fn write_manifest<S: AsRef<[u8]>>(&self, manifest: S) -> Result<(), std::io::Error> {
|
|
fs::write(self.package_dir.join(MANIFEST_FILE_NAME), manifest.as_ref()).await
|
|
}
|
|
|
|
/// Deserialize the lockfile
|
|
pub async fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
|
|
let string = fs::read_to_string(self.package_dir.join(LOCKFILE_FILE_NAME)).await?;
|
|
Ok(toml::from_str(&string)?)
|
|
}
|
|
|
|
/// Write the lockfile
|
|
pub async fn write_lockfile(
|
|
&self,
|
|
lockfile: Lockfile,
|
|
) -> Result<(), errors::LockfileWriteError> {
|
|
let string = toml::to_string(&lockfile)?;
|
|
fs::write(self.package_dir.join(LOCKFILE_FILE_NAME), string).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the workspace members
|
|
pub async fn workspace_members<P: AsRef<Path>>(
|
|
&self,
|
|
dir: P,
|
|
) -> Result<
|
|
impl Stream<Item = Result<(PathBuf, Manifest), errors::WorkspaceMembersError>>,
|
|
errors::WorkspaceMembersError,
|
|
> {
|
|
let dir = dir.as_ref().to_path_buf();
|
|
let manifest = fs::read_to_string(dir.join(MANIFEST_FILE_NAME))
|
|
.await
|
|
.map_err(errors::WorkspaceMembersError::ManifestMissing)?;
|
|
let manifest = toml::from_str::<Manifest>(&manifest).map_err(|e| {
|
|
errors::WorkspaceMembersError::ManifestDeser(dir.to_path_buf(), Box::new(e))
|
|
})?;
|
|
|
|
let members = manifest
|
|
.workspace_members
|
|
.into_iter()
|
|
.map(|glob| dir.join(glob))
|
|
.map(|path| glob::glob(&path.as_os_str().to_string_lossy()))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.flat_map(|paths| paths.into_iter())
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(stream! {
|
|
for path in members {
|
|
let manifest = fs::read_to_string(path.join(MANIFEST_FILE_NAME))
|
|
.await
|
|
.map_err(errors::WorkspaceMembersError::ManifestMissing)?;
|
|
let manifest = toml::from_str::<Manifest>(&manifest).map_err(|e| {
|
|
errors::WorkspaceMembersError::ManifestDeser(path.clone(), Box::new(e))
|
|
})?;
|
|
|
|
yield Ok((path, manifest));
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Refreshes the sources asynchronously
|
|
pub async fn refresh_sources<I: Iterator<Item = PackageSources>>(
|
|
project: &Project,
|
|
sources: I,
|
|
refreshed_sources: &mut HashSet<PackageSources>,
|
|
) -> Result<(), Box<source::errors::RefreshError>> {
|
|
try_join_all(sources.map(|source| {
|
|
let needs_refresh = refreshed_sources.insert(source.clone());
|
|
async move {
|
|
if needs_refresh {
|
|
source.refresh(project).await.map_err(Box::new)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}))
|
|
.await
|
|
.map(|_| ())
|
|
}
|
|
|
|
/// Errors that can occur when using the pesde library
|
|
pub mod errors {
|
|
use std::path::PathBuf;
|
|
use thiserror::Error;
|
|
|
|
/// Errors that can occur when reading the manifest file
|
|
#[derive(Debug, Error)]
|
|
#[non_exhaustive]
|
|
pub enum ManifestReadError {
|
|
/// An IO error occurred
|
|
#[error("io error reading manifest file")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// An error occurred while deserializing the manifest file
|
|
#[error("error deserializing manifest file")]
|
|
Serde(#[from] toml::de::Error),
|
|
}
|
|
|
|
/// Errors that can occur when reading the lockfile
|
|
#[derive(Debug, Error)]
|
|
#[non_exhaustive]
|
|
pub enum LockfileReadError {
|
|
/// An IO error occurred
|
|
#[error("io error reading lockfile")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// An error occurred while deserializing the lockfile
|
|
#[error("error deserializing lockfile")]
|
|
Serde(#[from] toml::de::Error),
|
|
}
|
|
|
|
/// Errors that can occur when writing the lockfile
|
|
#[derive(Debug, Error)]
|
|
#[non_exhaustive]
|
|
pub enum LockfileWriteError {
|
|
/// An IO error occurred
|
|
#[error("io error writing lockfile")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// An error occurred while serializing the lockfile
|
|
#[error("error serializing lockfile")]
|
|
Serde(#[from] toml::ser::Error),
|
|
}
|
|
|
|
/// Errors that can occur when finding workspace members
|
|
#[derive(Debug, Error)]
|
|
#[non_exhaustive]
|
|
pub enum WorkspaceMembersError {
|
|
/// The manifest file could not be found
|
|
#[error("missing manifest file")]
|
|
ManifestMissing(#[source] std::io::Error),
|
|
|
|
/// An error occurred deserializing the manifest file
|
|
#[error("error deserializing manifest file at {0}")]
|
|
ManifestDeser(PathBuf, #[source] Box<toml::de::Error>),
|
|
|
|
/// An error occurred interacting with the filesystem
|
|
#[error("error interacting with the filesystem")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// An invalid glob pattern was found
|
|
#[error("invalid glob pattern")]
|
|
Glob(#[from] glob::PatternError),
|
|
|
|
/// An error occurred while globbing
|
|
#[error("error globbing")]
|
|
Globbing(#[from] glob::GlobError),
|
|
}
|
|
}
|