diff --git a/stage0/src/pkg/dep.rs b/stage0/src/pkg/dep.rs index 1221ba5..5909fd8 100644 --- a/stage0/src/pkg/dep.rs +++ b/stage0/src/pkg/dep.rs @@ -1,7 +1,8 @@ use super::{ Package, PackageName, PackageNameError, PackageOpenError, PackageUnpackError, PackageVersion, + TargetResolver, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; @@ -32,7 +33,11 @@ impl DependencyResolver { } } - pub fn resolve(&self, id: &Dependency) -> Result, DependencyResolveError> { + pub fn resolve( + &self, + id: &Dependency, + targets: &TargetResolver, + ) -> Result, DependencyResolveError> { // Check if already loaded. let mut loaded = self.loaded.borrow_mut(); @@ -46,7 +51,7 @@ impl DependencyResolver { let cache = self.cache.join(format!("{}-{}", id.name, id.version)); match cache.symlink_metadata() { - Ok(_) => match Package::open(&cache) { + Ok(_) => match Package::open(&cache, targets) { Ok(v) => { let pkg = Rc::new(v); assert!(loaded.insert(id.clone(), pkg.clone()).is_none()); @@ -75,7 +80,7 @@ impl DependencyResolver { Package::unpack(pkg, &cache).map_err(|e| DependencyResolveError::UnpackPackageFailed(e))?; // Open the package. - match Package::open(&cache) { + match Package::open(&cache, targets) { Ok(v) => { let pkg = Rc::new(v); assert!(loaded.insert(id.clone(), pkg.clone()).is_none()); @@ -87,7 +92,7 @@ impl DependencyResolver { } /// A package dependency. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Dependency { name: PackageName, version: PackageVersion, diff --git a/stage0/src/pkg/lib.rs b/stage0/src/pkg/lib.rs index 2ec0c6f..cb4e1d4 100644 --- a/stage0/src/pkg/lib.rs +++ b/stage0/src/pkg/lib.rs @@ -32,6 +32,51 @@ impl Library { Self { bin, types } } + pub fn open(bin: B, types: T) -> Result + where + B: AsRef, + T: AsRef, + { + // Read binary magic. + let bin = bin.as_ref(); + let mut file = + File::open(bin).map_err(|e| LibraryError::OpenFileFailed(bin.to_owned(), e))?; + let mut magic = [0u8; 4]; + + file.read_exact(&mut magic) + .map_err(|e| LibraryError::ReadFileFailed(bin.to_owned(), e))?; + + // Check binary type. + let bin = if &magic == b"\x7FNLS" { + let mut name = String::new(); + file.read_to_string(&mut name) + .map_err(|e| LibraryError::ReadFileFailed(bin.to_owned(), e))?; + LibraryBinary::System(name) + } else { + LibraryBinary::Bundle(bin.to_owned()) + }; + + // Load types. + let path = types.as_ref(); + let mut file = + File::open(path).map_err(|e| LibraryError::OpenFileFailed(path.to_owned(), e))?; + let mut types = HashSet::new(); + + loop { + let ty = match TypeDeclaration::deserialize(&mut file) { + Ok(v) => v, + Err(TypeDeserializeError::EmptyData) => break, + Err(e) => return Err(LibraryError::ReadTypeFailed(path.to_owned(), e)), + }; + + if !types.insert(ty) { + return Err(LibraryError::DuplicatedType(path.to_owned())); + } + } + + Ok(Self { bin, types }) + } + pub fn bin(&self) -> &LibraryBinary { &self.bin } @@ -161,6 +206,22 @@ pub enum LibraryBinary { System(String), } +/// Represents an error when [`Library`] is failed to construct. +#[derive(Debug, Error)] +pub enum LibraryError { + #[error("cannot open {0}")] + OpenFileFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot read {0}")] + ReadFileFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot read type declaration from {0}")] + ReadTypeFailed(PathBuf, #[source] TypeDeserializeError), + + #[error("duplicated type declaration in {0}")] + DuplicatedType(PathBuf), +} + /// Represents an error when [`Library`] is failed to unpack from a serialized data. #[derive(Debug, Error)] pub enum LibraryUnpackError { diff --git a/stage0/src/pkg/mod.rs b/stage0/src/pkg/mod.rs index daa8f93..0039e9c 100644 --- a/stage0/src/pkg/mod.rs +++ b/stage0/src/pkg/mod.rs @@ -3,12 +3,10 @@ pub use self::lib::*; pub use self::meta::*; pub use self::target::*; pub use self::ty::*; - use crate::zstd::{ZstdReader, ZstdWriter}; use std::collections::{HashMap, HashSet}; -use std::fs::File; -use std::io::Read; -use std::io::{Seek, SeekFrom, Write}; +use std::fs::{read_dir, File}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::time::SystemTime; use thiserror::Error; @@ -353,8 +351,100 @@ impl Package { Ok(()) } - pub fn open>(path: P) -> Result { - todo!() + pub fn open( + path: impl AsRef, + targets: &TargetResolver, + ) -> Result { + let root = path.as_ref(); + + // Open metadata. + let path = root.join("meta.yml"); + let meta = match File::open(&path) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::OpenFileFailed(path, e)), + }; + + // Read metadata. + let meta = match serde_yaml::from_reader(meta) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::ReadPackageMetaFailed(path, e)), + }; + + // Enumerate libraries. + let mut libs = HashMap::new(); + let path = root.join("libs"); + let items = match read_dir(&path) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::OpenDirectoryFailed(path, e)), + }; + + for item in items { + let item = match item { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::OpenDirectoryFailed(path, e)), + }; + + // Check if directory. + let path = item.path(); + let meta = match std::fs::metadata(&path) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::GetFileMetaFailed(path, e)), + }; + + if !meta.is_dir() { + continue; + } + + // Check if directory name is UTF-8. + let name = match path.file_name().unwrap().to_str() { + Some(v) => v, + None => return Err(PackageOpenError::InvalidLibraryDirectory(path)), + }; + + // Get target ID. + let target: Uuid = match name.parse() { + Ok(v) => v, + Err(_) => return Err(PackageOpenError::InvalidLibraryDirectory(path)), + }; + + // Resolve target. + let target = match targets.resolve(&target) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::ResolveTargetFailed(target, e)), + }; + + // Load library. + let bin = match Library::open(path.join("bin"), path.join("types")) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::OpenLibraryFailed(e)), + }; + + // Open dependencies. + let path = path.join("deps.yml"); + let deps = match File::open(&path) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::OpenFileFailed(path, e)), + }; + + // Read dependencies. + let list: Vec = match serde_yaml::from_reader(deps) { + Ok(v) => v, + Err(e) => return Err(PackageOpenError::ReadDependenciesFailed(path, e)), + }; + + // Build dependency list. + let mut deps = HashSet::with_capacity(list.len()); + + for dep in list { + if let Some(dep) = deps.replace(dep) { + return Err(PackageOpenError::DuplicatedDependency(path, dep)); + } + } + + assert!(libs.insert(target, Binary::new(bin, deps)).is_none()); + } + + Ok(Self::new(meta, HashMap::new(), libs)) } } @@ -376,7 +466,34 @@ impl Binary { /// Represents an error when a package is failed to open. #[derive(Debug, Error)] -pub enum PackageOpenError {} +pub enum PackageOpenError { + #[error("cannot open {0}")] + OpenFileFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot open {0}")] + OpenDirectoryFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot get metadata of {0}")] + GetFileMetaFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot read package metadata from {0}")] + ReadPackageMetaFailed(PathBuf, #[source] serde_yaml::Error), + + #[error("name of {0} is not a valid name for library directory")] + InvalidLibraryDirectory(PathBuf), + + #[error("cannot resolve target {0}")] + ResolveTargetFailed(Uuid, #[source] TargetResolveError), + + #[error("cannot open library")] + OpenLibraryFailed(#[source] LibraryError), + + #[error("cannot read dependencies from {0}")] + ReadDependenciesFailed(PathBuf, #[source] serde_yaml::Error), + + #[error("multiple definition of {1} in {0}")] + DuplicatedDependency(PathBuf, Dependency), +} /// Represents an error when a package is failed to pack. #[derive(Debug, Error)] diff --git a/stage0/src/pkg/target.rs b/stage0/src/pkg/target.rs index b12ed67..0ed3b5f 100644 --- a/stage0/src/pkg/target.rs +++ b/stage0/src/pkg/target.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use thiserror::Error; use uuid::{uuid, Uuid}; -/// A struct to resolve primitive target. +/// Struct to resolve [`Target`] from identifier. pub struct TargetResolver {} impl TargetResolver { @@ -14,6 +14,15 @@ impl TargetResolver { Self {} } + pub fn resolve(&self, id: &Uuid) -> Result { + // Check if primitive target. + if let Some(v) = PrimitiveTarget::ALL.iter().find(|&t| t.id == *id) { + return Ok(Target::Primitive(v)); + } + + todo!() + } + pub fn primitive( &self, target: &Target, diff --git a/stage0/src/pkg/ty.rs b/stage0/src/pkg/ty.rs index 034a726..2e873e0 100644 --- a/stage0/src/pkg/ty.rs +++ b/stage0/src/pkg/ty.rs @@ -66,12 +66,21 @@ impl TypeDeclaration { let mut struc = false; let mut class = false; let mut funcs = HashSet::new(); + let mut entries = 0; loop { // Read entry type. - let mut entry = 0; + let mut entry = 0u8; - r.read_exact(std::slice::from_mut(&mut entry))?; + if let Err(e) = r.read_exact(std::slice::from_mut(&mut entry)) { + if e.kind() == std::io::ErrorKind::UnexpectedEof { + break; + } else { + return Err(TypeDeserializeError::ReadDataFailed(e)); + } + } + + entries += 1; // Process the entry. match entry { @@ -110,6 +119,10 @@ impl TypeDeclaration { } } + if entries == 0 { + return Err(TypeDeserializeError::EmptyData); + } + // Construct type. let name = name.ok_or(TypeDeserializeError::TypeNameNotFound)?; let ty = match (struc, class) { @@ -704,6 +717,9 @@ pub enum Representation { /// Represents an error when [`TypeDeclaration`] is failed to deserialize from the data. #[derive(Debug, Error)] pub enum TypeDeserializeError { + #[error("no data to read")] + EmptyData, + #[error("cannot read data")] ReadDataFailed(#[source] std::io::Error), diff --git a/stage0/src/project/mod.rs b/stage0/src/project/mod.rs index d9730d8..6ba6434 100644 --- a/stage0/src/project/mod.rs +++ b/stage0/src/project/mod.rs @@ -111,7 +111,7 @@ impl<'a> Project<'a> { let version = env!("CARGO_PKG_VERSION").parse().unwrap(); let id = Dependency::new("nitro".parse().unwrap(), version); - match self.deps.resolve(&id) { + match self.deps.resolve(&id, self.targets) { Ok(v) => deps.push(v), Err(e) => return Err(ProjectBuildError::ResolveDependencyFailed(id, e)), };