From dd8fb0e3787ec5dd6fb1056d26d82857a5e4c2dd Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Mon, 27 May 2024 23:25:25 +0400 Subject: [PATCH 01/24] metadata refactoring, add cargo-targets tables that overrides main manifest' fields --- Cargo.lock | 13 +- Cargo.toml | 2 +- support/build/Cargo.toml | 15 +- support/build/README.md | 65 +- support/build/src/assets/plan.rs | 73 +- support/build/src/lib.rs | 2 +- support/build/src/manifest/format.rs | 121 +-- support/build/src/manifest/mod.rs | 214 +---- support/build/src/metadata/format.rs | 1298 +++++++++++++++++++++++--- support/build/src/metadata/mod.rs | 4 +- support/build/src/metadata/source.rs | 584 ++++++++++++ support/build/src/value.rs | 98 -- 12 files changed, 1925 insertions(+), 564 deletions(-) create mode 100644 support/build/src/metadata/source.rs delete mode 100644 support/build/src/value.rs diff --git a/Cargo.lock b/Cargo.lock index 4479f5b3..b706b0e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,16 +1101,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crate-metadata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a4577b4d5dd1ba39e4c8b9ece7eb83fa4fb2e504c883504925a12f85b14126" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "crates-io" version = "0.40.1" @@ -4112,9 +4102,8 @@ dependencies = [ [[package]] name = "playdate-build" -version = "0.3.0" +version = "0.4.0-pre1" dependencies = [ - "crate-metadata", "dirs", "fs_extra", "log", diff --git a/Cargo.toml b/Cargo.toml index 2c5da288..1fc08570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ system = { version = "0.3", path = "api/system", package = "playdate-system", de sys = { version = "0.4", path = "api/sys", package = "playdate-sys", default-features = false } tool = { version = "0.1", path = "support/tool", package = "playdate-tool" } -build = { version = "0.3", path = "support/build", package = "playdate-build", default-features = false } +build = { version = "=0.4.0-pre1", path = "support/build", package = "playdate-build", default-features = false } utils = { version = "0.3", path = "support/utils", package = "playdate-build-utils", default-features = false } device = { version = "0.2", path = "support/device", package = "playdate-device" } simulator = { version = "0.1", path = "support/sim-ctrl", package = "playdate-simulator-utils", default-features = false } diff --git a/support/build/Cargo.toml b/support/build/Cargo.toml index e729dd39..f9167daa 100644 --- a/support/build/Cargo.toml +++ b/support/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-build" -version = "0.3.0" +version = "0.4.0-pre1" readme = "README.md" description = "Utils that help to build package for Playdate" keywords = ["playdate", "package", "encoding", "manifest", "assets"] @@ -14,18 +14,19 @@ repository.workspace = true [dependencies] log.workspace = true -serde = { workspace = true, features = ["derive"], optional = true } -serde_json = { workspace = true, optional = true } -toml = { workspace = true, optional = true } dirs.workspace = true fs_extra.workspace = true regex.workspace = true wax = "0.6" symlink = "0.1" -[dependencies.crate-metadata] -version = "0.1" +toml = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } + +[dependencies.serde] +workspace = true optional = true +features = ["derive"] [dependencies.utils] workspace = true @@ -36,8 +37,8 @@ features = ["log"] [features] default = [] toml = ["serde", "dep:toml"] +json = ["serde_json"] serde_json = ["serde", "dep:serde_json"] -crate-metadata = ["serde_json", "dep:crate-metadata"] assets-report = [] diff --git a/support/build/README.md b/support/build/README.md index a3b78b6d..7f4417d9 100644 --- a/support/build/README.md +++ b/support/build/README.md @@ -12,23 +12,60 @@ Here is the metadata format explanation in examples ### Package Info +The following fields are used to generate the package manifest: + ```toml # Playdate Package Info # official doc: https://sdk.play.date/#pdxinfo [package.metadata.playdate] -name = "{name}" # optional, default is package.name -author = "{author}" # optional, default is package.authors -version = "{version}" # optional, default is package.version -description = "{description}" # optional, default is package.description -bundle-id = "com.yourcompany.{bundle_id}" +bundle-id = "com.yourcompany.game" +name = "My Game" # default is package.name +author = "Alex" # default is package.authors +version = "0.0" # default is package.version +description = "short about" # default is package.description + +image-path = "img/system" +launch-sound-path = "sfx/jump" + +content-warning = "This game contains mild realistic, violence and bloodshed." +content-warning2 = "Really scary game." -image-path = "img/system" # optional -launch-sound-path = "sfx/jump" # optional +build-number = 42 # also can be string, e.g "42" -content-warning = "This game contains mild realistic, violence and bloodshed." # optional -content-warning2 = "Really scary game." # optional +# also extra fields are supported +# acceptable types of values: string, number, boolean +foo = "bar" ``` +_Note, only `bundle-id` is required, other fields are optional._ + + +#### Target-specific Package Info + +Main [Package Info](#package-info) can be overridden with special _table_ for a `bin` or `example`. +All manifest fields are acceptable, but optional. + +Two formats are supported. +First is like a cargo's targets: + +```toml +[[package.metadata.playdate.example]] +target = "existing-example-name" # pointing to cargo-target name +# next is same as for main manifest fields, all are optional: +bundle-id = "com.yourcompany.game.example" +name = "My Example" +``` + +Second if just a table: + +```toml +[package.metadata.playdate.example.existing-example-name] +bundle-id = "com.yourcompany.game.example" +content-warning = "Scary experimental stuff." +``` + +_Important: you should not mix these two formats in the same document._ + ### Assets @@ -103,15 +140,11 @@ Also this way supports simple include and exclude instructions: #### Assets Options -There is some options where to set asset options: -- `[package.metadata.playdate.assets.options]` -- `[package.metadata.playdate.options.assets]` - -Both are equal but should not be both in one crate. +This is how assets will be collected for your package. ```toml -[package.metadata.playdate.assets.options] -dependencies = true # allow to build assets for dependencies (default is `true`) +[package.metadata.playdate.options.assets] +dependencies = true # allow to build assets for dependencies (default is `false`) overwrite = true # overwrite existing assets in build dir (default is `true`) method = "link" # "copy" or "link" (default is `link`) - how assets should be collected, make symlinks or copy files follow-symlinks = true # follow symlinks (default is `true`) diff --git a/support/build/src/assets/plan.rs b/support/build/src/assets/plan.rs index d7aafd57..0945c1b0 100644 --- a/support/build/src/assets/plan.rs +++ b/support/build/src/assets/plan.rs @@ -6,21 +6,17 @@ use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use wax::{Glob, Pattern}; use crate::config::Env; -use crate::metadata::format::AssetsOptions; -use crate::metadata::format::PlayDateMetadataAssets; -use crate::value::Value; +use crate::metadata::format::{AssetsOptions, AssetsRules, RuleValue}; use super::resolver::*; /// Create build plan for assets. -pub fn build_plan<'l, 'r, 'c: 'l, V>(env: &'c Env, - assets: &PlayDateMetadataAssets, - options: &AssetsOptions, - crate_root: Option<&'c Path>) - -> Result, super::Error> - where V: Value -{ +pub fn build_plan<'l, 'r, 'c: 'l /* V */>(env: &'c Env, + assets: &AssetsRules, + options: &AssetsOptions, + crate_root: Option<&'c Path>) + -> Result, super::Error> { // copy_unresolved => get all files with glob // include_unresolved => same // exclude_unresolved => @@ -54,7 +50,7 @@ pub fn build_plan<'l, 'r, 'c: 'l, V>(env: &'c Env, }; match assets { - PlayDateMetadataAssets::List(vec) => { + AssetsRules::List(vec) => { include_unresolved.extend( vec.iter() .map(to_relative) @@ -62,18 +58,19 @@ pub fn build_plan<'l, 'r, 'c: 'l, V>(env: &'c Env, .map(|e| enver.expr(e, env)), ) }, - PlayDateMetadataAssets::Map(map) => { + AssetsRules::Map(map) => { for (k, v) in map { let k = to_relative(k); - if let Some(v) = v.as_bool() { - match v { - true => include_unresolved.push(enver.expr(Expr::from(k), env)), - false => exclude_exprs.push(enver.expr(Expr::from(k), env)), - } - } else if let Some(from) = v.as_str() { - map_unresolved.push((enver.expr(Expr::from(k), env), enver.expr(Expr::from(from), env))) - } else { - return Err(format!("not supported type of value: {v} for key: {k}").into()); + match v { + RuleValue::Boolean(v) => { + match v { + true => include_unresolved.push(enver.expr(Expr::from(k), env)), + false => exclude_exprs.push(enver.expr(Expr::from(k), env)), + } + }, + RuleValue::String(from) => { + map_unresolved.push((enver.expr(Expr::from(k), env), enver.expr(Expr::from(from), env))) + }, } } }, @@ -467,9 +464,9 @@ mod tests { use std::path::{PathBuf, Path}; use crate::config::Env; - use crate::value::default::Value; use crate::assets::resolver::Expr; - use crate::metadata::format::PlayDateMetadataAssets; + use crate::metadata::format::RuleValue; + use crate::metadata::format::AssetsRules; use super::*; @@ -607,7 +604,7 @@ mod tests { let tests: HashSet<_> = vec!["Cargo.toml", "src/lib.rs"].into_iter().collect(); let exprs = tests.iter().map(|s| s.to_string()).collect(); - let assets = PlayDateMetadataAssets::List::(exprs); + let assets = AssetsRules::List(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -654,7 +651,7 @@ mod tests { }; let exprs = tests.keys().map(|s| s.to_string()).collect(); - let assets = PlayDateMetadataAssets::List::(exprs); + let assets = AssetsRules::List(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -686,7 +683,7 @@ mod tests { let tests: HashMap<_, _> = { vec![("${SRC}/lib.rs", "src/lib.rs"),].into_iter().collect() }; let exprs = tests.keys().map(|s| s.to_string()).collect(); - let assets = PlayDateMetadataAssets::List::(exprs); + let assets = AssetsRules::List(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -732,7 +729,7 @@ mod tests { }; let exprs = tests.keys().map(|s| s.to_string()).collect(); - let assets = PlayDateMetadataAssets::List::(exprs); + let assets = AssetsRules::List(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -771,7 +768,7 @@ mod tests { let exprs = ["${TMP}/*.txt", "${SUB}/*.txt"]; - let assets = PlayDateMetadataAssets::List::(exprs.iter().map(|s| s.to_string()).collect()); + let assets = AssetsRules::List(exprs.iter().map(|s| s.to_string()).collect()); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -825,9 +822,10 @@ mod tests { let tests: HashSet<_> = vec!["Cargo.toml", "src/lib.rs"].into_iter().collect(); let exprs = tests.iter() - .map(|s| (s.to_string(), Value::Boolean(true))) + .map(|s| (s.to_string(), RuleValue::Boolean(true))) .collect(); - let assets = PlayDateMetadataAssets::Map::(exprs); + + let assets = AssetsRules::Map(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -864,9 +862,10 @@ mod tests { let stripped_trg = &trg.replace('/', "").trim().to_owned(); let exprs = tests.iter() - .map(|s| (trg.to_string(), Value::String(s.to_string()))) + .map(|s| (trg.to_string(), RuleValue::String(s.to_string()))) .collect(); - let assets = PlayDateMetadataAssets::Map::(exprs); + + let assets = AssetsRules::Map(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -909,9 +908,10 @@ mod tests { for trg in targets { let exprs = tests.iter() - .map(|s| (trg.to_string(), Value::String(s.to_string()))) + .map(|s| (trg.to_string(), RuleValue::String(s.to_string()))) .collect(); - let assets = PlayDateMetadataAssets::Map::(exprs); + + let assets = AssetsRules::Map(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); @@ -955,9 +955,10 @@ mod tests { for trg in targets { let exprs = tests.iter() - .map(|s| (trg.to_string(), Value::String(s.to_string()))) + .map(|s| (trg.to_string(), RuleValue::String(s.to_string()))) .collect(); - let assets = PlayDateMetadataAssets::Map::(exprs); + + let assets = AssetsRules::Map(exprs); let plan = build_plan(&env, &assets, &opts, root).unwrap(); diff --git a/support/build/src/lib.rs b/support/build/src/lib.rs index a62a574c..619e5229 100644 --- a/support/build/src/lib.rs +++ b/support/build/src/lib.rs @@ -1,5 +1,6 @@ #![feature(extract_if)] #![feature(io_error_more)] +#![cfg_attr(test, feature(assert_matches))] #[macro_use] extern crate log; @@ -8,7 +9,6 @@ pub use utils::*; pub mod fs; -pub mod value; pub mod layout; pub mod config; pub mod assets; diff --git a/support/build/src/manifest/format.rs b/support/build/src/manifest/format.rs index 13c257ce..ec7ea9df 100644 --- a/support/build/src/manifest/format.rs +++ b/support/build/src/manifest/format.rs @@ -1,73 +1,78 @@ -use std::collections::HashMap; +pub use crate::metadata::format::Manifest; +use crate::metadata::source::ManifestSourceOptExt; -#[cfg(feature = "serde")] -use serde::{Serialize, Deserialize}; +pub trait ManifestFmt { + fn write_to(&self, to: &mut W) -> std::io::Result<()> + where Self: ManifestSourceOptExt { + let mut buf = String::new(); + self.write_to_fmt(&mut buf).map_err(std::io::Error::other)?; + to.write_all(buf.as_bytes()) + } -#[derive(Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "Value: Deserialize<'de>")))] -pub struct Manifest { - pub name: String, - pub author: String, - pub description: String, - #[cfg_attr(feature = "serde", serde(rename = "bundleID"))] - pub bundle_id: String, - pub version: String, - pub build_number: Option, - pub image_path: Option, - pub launch_sound_path: Option, - pub content_warning: Option, - pub content_warning2: Option, - /// Manifest extra fields, e.g: `pdxversion=20000` - #[cfg_attr(feature = "serde", serde(flatten))] - pub extra: HashMap, -} + fn write_to_fmt(&self, to: &mut W) -> std::fmt::Result + where Self: ManifestSourceOptExt { + let data = self; + let is_not_empty = |s: &&str| !s.trim().is_empty(); -impl Manifest { - pub fn to_manifest_string(&self) -> String { - let mut result = String::new(); + { + let mut write_fmt = |k, v| to.write_fmt(format_args!("{}={}\n", k, v)); - fn to_row, V: AsRef>(key: K, value: V) -> String { - if !value.as_ref().trim().is_empty() { - format!("{}={}\n", key.as_ref(), value.as_ref()) - } else { - String::with_capacity(0) + if let Some(s) = data.name().filter(is_not_empty) { + write_fmt("name", s)?; + } + if let Some(s) = data.author().filter(is_not_empty) { + write_fmt("author", s)? + } + if let Some(s) = data.bundle_id().filter(is_not_empty) { + write_fmt("bundleID", s)? + } + if let Some(s) = data.version().filter(is_not_empty) { + write_fmt("version", s)? + } + if let Some(s) = data.description().filter(is_not_empty) { + write_fmt("description", s)? + } + if let Some(s) = data.image_path().filter(is_not_empty) { + write_fmt("imagePath", s)? + } + if let Some(s) = data.launch_sound_path().filter(is_not_empty) { + write_fmt("launchSoundPath", s)? + } + if let Some(s) = data.content_warning().filter(is_not_empty) { + write_fmt("contentWarning", s)? + } + if let Some(s) = data.content_warning2().filter(is_not_empty) { + write_fmt("contentWarning2", s)? } } - result.push_str(&to_row("name", &self.name)); - result.push_str(&to_row("author", &self.author)); - result.push_str(&to_row("description", &self.description)); - result.push_str(&to_row("bundleID", &self.bundle_id)); - result.push_str(&to_row("version", &self.version)); - if let Some(value) = self.build_number { - result.push_str(&to_row("buildNumber", value.to_string())); - } - if let Some(ref value) = self.image_path { - result.push_str(&to_row("imagePath", value)); + if let Some(v) = data.build_number() { + to.write_fmt(format_args!("{}={}\n", "buildNumber", v))? } - if let Some(ref value) = self.launch_sound_path { - result.push_str(&to_row("launchSoundPath", value)); - } - if let Some(ref value) = self.content_warning { - result.push_str(&to_row("contentWarning", value)); - if let Some(ref value) = self.content_warning2 { - result.push_str(&to_row("contentWarning2", value)); - } - } - for (key, value) in &self.extra { - if let Some(value) = value.as_str() { - result.push_str(&to_row(key, value)); - } else if let Some(value) = value.as_bool() { - result.push_str(&to_row(key, format!("{value}"))); - } else { - warn!("Manifest extra field `{key}={value}` has unsupported type"); + + if let Some(extra) = data.iter_extra() { + for (key, value) in extra.into_iter() { + let (key, value) = (key.as_ref(), value.as_ref()); + if is_not_empty(&key) && !value.is_empty() { + to.write_fmt(format_args!("{}={}\n", key, value))? + } } } - result + + Ok(()) + } + + + fn to_manifest_string(&self) -> Result + where Self: ManifestSourceOptExt { + let mut buf = String::new(); + self.write_to_fmt(&mut buf)?; + Ok(buf) } } + + +impl ManifestFmt for T {} diff --git a/support/build/src/manifest/mod.rs b/support/build/src/manifest/mod.rs index bc833aed..adc434aa 100644 --- a/support/build/src/manifest/mod.rs +++ b/support/build/src/manifest/mod.rs @@ -1,215 +1,5 @@ -use std::borrow::Cow; - pub use crate::compile::PDX_PKG_MANIFEST_FILENAME; -use crate::metadata::format::PlayDateMetadata; -use self::format::Manifest; - +pub use crate::metadata::source::CrateInfoSource; +pub use crate::metadata::source::{ManifestSourceOpt, ManifestSourceOptExt}; pub mod format; - - -pub trait ManifestDataSource { - type Value: crate::value::Value; - - fn name(&self) -> &str; - fn authors(&self) -> &[String]; - fn version(&self) -> Cow; - fn description(&self) -> Option<&str>; - fn metadata(&self) -> Option<&PlayDateMetadata>; -} - - -impl<'t, T> TryFrom> for Manifest where T: ManifestDataSource { - type Error = &'static str; - - fn try_from(source: SourceRef<'t, T>) -> Result { - let metadata = source.metadata() - .ok_or("[package.metadata.playdate] not found in the manifest file Cargo.toml")?; - - let description = metadata.description - .to_owned() - .or(source.description().map(ToOwned::to_owned)) - .ok_or("description not found")?; - let manifest = Manifest { name: metadata.name.to_owned().unwrap_or(source.name().to_owned()), - author: metadata.author.to_owned().unwrap_or(source.authors().join(", ")), - description, - bundle_id: metadata.bundle_id.to_owned(), - version: metadata.version - .to_owned() - .unwrap_or(source.version().to_string()), - build_number: metadata.build_number.as_ref().and_then(|v| v.parse().ok()), - image_path: metadata.image_path.to_owned(), - launch_sound_path: metadata.launch_sound_path.to_owned(), - content_warning: metadata.content_warning.to_owned(), - content_warning2: metadata.content_warning2.to_owned(), - extra: metadata.extra.to_owned() }; - Ok(manifest) - } -} - -impl Manifest { - pub fn try_from_source(source: T) -> Result - where T: ManifestDataSource { - SourceRef(&source).try_into() - } -} - - -struct SourceRef<'t, T: ManifestDataSource>(pub &'t T); -impl<'t, T: ManifestDataSource> SourceRef<'t, T> { - #![allow(dead_code)] - pub fn inner(&self) -> &'t T { self.0 } - pub fn into_inner(self) -> &'t T { self.0 } -} - -impl<'t, T> From<&'t T> for SourceRef<'t, T> where T: ManifestDataSource { - fn from(value: &'t T) -> Self { Self(value) } -} - -impl<'t, T> ManifestDataSource for SourceRef<'t, T> where T: ManifestDataSource { - type Value = T::Value; - fn name(&self) -> &str { self.inner().name() } - fn authors(&self) -> &[String] { self.inner().authors() } - fn version(&self) -> Cow { self.inner().version() } - fn description(&self) -> Option<&str> { self.inner().description() } - fn metadata(&self) -> Option<&PlayDateMetadata> { self.inner().metadata() } -} - - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - use std::ops::Deref; - use crate::metadata::format::PlayDateMetadata; - use super::*; - - #[cfg(feature = "serde_json")] - use serde_json::Value; - #[cfg(all(feature = "toml", not(feature = "serde_json")))] - use toml::Value; - #[cfg(all(not(feature = "toml"), not(feature = "serde_json")))] - use crate::value::default::Value; - - - struct ManifestSource { - name: Name, - authors: Vec, - version: Ver, - description: Option, - metadata: Option>, - } - - impl ManifestDataSource for ManifestSource - where N: AsRef, - V: AsRef, - D: Deref - { - type Value = Value; - - fn name(&self) -> &str { self.name.as_ref() } - fn authors(&self) -> &[String] { &self.authors } - fn version(&self) -> Cow { self.version.as_ref().into() } - fn description(&self) -> Option<&str> { self.description.as_deref() } - fn metadata(&self) -> Option<&PlayDateMetadata> { self.metadata.as_ref() } - } - - - fn metadata_minimal() -> PlayDateMetadata { - PlayDateMetadata { bundle_id: "bundle.id".to_owned(), - name: Default::default(), - version: Default::default(), - author: Default::default(), - description: Default::default(), - image_path: Default::default(), - launch_sound_path: Default::default(), - content_warning: Default::default(), - content_warning2: Default::default(), - build_number: Default::default(), - assets: Default::default(), - dev_assets: Default::default(), - options: Default::default(), - support: Default::default(), - extra: Default::default() } - } - fn metadata_maximal() -> PlayDateMetadata { - PlayDateMetadata { bundle_id: "bundle.id".to_owned(), - name: Some("name".to_owned()), - version: Some("0.42.0".to_owned()), - author: Some("author".to_owned()), - description: Some("description".to_owned()), - image_path: Some("image_path".to_owned()), - launch_sound_path: Some("launch_sound_path".to_owned()), - content_warning: Some("content_warning".to_owned()), - content_warning2: Some("content_warning2".to_owned()), - build_number: Some("42".to_owned()), - assets: Default::default(), - dev_assets: Default::default(), - options: Default::default(), - support: Default::default(), - extra: Default::default() } - } - fn metadata_extra() -> PlayDateMetadata { - let mut data = metadata_minimal(); - data.extra = HashMap::new(); - data.extra.insert("key".to_owned(), "value".to_string().into()); - data - } - - - #[test] - fn from_data_source_minimal() { - let source = ManifestSource { name: "name", - authors: vec!["author".to_owned()], - version: "0.42.0", - description: Some("description"), - metadata: Some(metadata_minimal()) }; - let manifest = Manifest::try_from_source(source).expect("manifest"); - - assert_eq!(&manifest.name, "name"); - assert_eq!(&manifest.author, "author"); - assert_eq!(&manifest.version, "0.42.0"); - assert_eq!(&manifest.description, "description"); - assert_eq!(&manifest.bundle_id, "bundle.id"); - - assert!(manifest.image_path.is_none()); - assert!(manifest.launch_sound_path.is_none()); - assert!(manifest.content_warning.is_none()); - assert!(manifest.content_warning2.is_none()); - assert!(manifest.build_number.is_none()); - } - - #[test] - fn from_data_source_maximal() { - let source = ManifestSource { name: "crate-name", - authors: vec!["crate-author".to_owned()], - version: "0.0.0", - description: Some("crate-description"), - metadata: Some(metadata_maximal()) }; - let manifest = Manifest::try_from_source(source).expect("manifest"); - - assert_eq!(&manifest.name, "name"); - assert_eq!(&manifest.author, "author"); - assert_eq!(&manifest.version, "0.42.0"); - assert_eq!(&manifest.description, "description"); - assert_eq!(&manifest.bundle_id, "bundle.id"); - - assert_eq!(manifest.image_path.as_deref(), Some("image_path")); - assert_eq!(manifest.launch_sound_path.as_deref(), Some("launch_sound_path")); - assert_eq!(manifest.content_warning.as_deref(), Some("content_warning")); - assert_eq!(manifest.content_warning2.as_deref(), Some("content_warning2")); - assert_eq!(manifest.build_number, Some(42)); - } - - #[test] - fn from_data_source_extra() { - let source = ManifestSource { name: "-", - version: "0.0.0", - description: Some("-"), - authors: vec!["-".to_owned()], - metadata: Some(metadata_extra()) }; - let manifest = Manifest::try_from_source(source).expect("manifest"); - - assert_eq!(Some(&Value::from("value".to_string())), manifest.extra.get("key")); - assert_eq!(1, manifest.extra.len()); - } -} diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 21b04dc7..40ead947 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -1,160 +1,593 @@ -use super::Value; -use super::error::Error; +use std::ops::Deref; +use std::cmp::Eq; use std::borrow::Cow; use std::collections::HashMap; + +#[cfg(feature = "serde")] +use std::hash::Hash; + #[cfg(feature = "serde")] -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; + +use super::source::*; + + +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct CrateMetadata { + #[cfg_attr(feature = "serde", serde(rename = "playdate"))] + pub inner: Option, +} + +/// Just ensure that `METADATA_FIELD` is not changed and something missed. +#[cfg(test)] +#[cfg_attr(test, test)] +fn eq_metadata_field() { + assert_eq!(super::METADATA_FIELD, "playdate"); +} + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(bound(deserialize = "Main: Deserialize<'de>")))] +pub struct Ext
{ + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) main: Main, + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) extra: ExtraFields, +} + +impl Ext { + pub fn inner(&self) -> &T { &self.main } + pub fn extra(&self) -> &ExtraFields { &self.extra } +} + +impl Ext> where S: ToOwned { + pub fn clone_owned(self) -> Ext::Owned>> { + Ext { main: self.main.clone_owned(), + extra: self.extra.to_owned() } + } +} + + +pub type ExtraFields = HashMap; + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum ExtraValue { + Boolean(bool), + String(String), + Int(i64), +} + +impl ExtraValue { + pub fn is_empty(&self) -> bool { + match self { + Self::String(s) => s.trim().is_empty(), + _ => false, + } + } +} + +impl std::fmt::Display for ExtraValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Boolean(v) => v.fmt(f), + Self::String(v) => v.fmt(f), + Self::Int(v) => v.fmt(f), + } + } +} + +impl From for ExtraValue { + fn from(value: bool) -> Self { Self::Boolean(value.into()) } +} +impl From for ExtraValue { + fn from(value: i64) -> Self { Self::Int(value) } +} +impl From for ExtraValue { + fn from(value: isize) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: u64) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: usize) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: String) -> Self { Self::String(value) } +} +impl From<&str> for ExtraValue { + fn from(value: &str) -> Self { Self::String(value.to_string()) } +} +impl<'t> From> for ExtraValue { + fn from(value: Cow<'t, str>) -> Self { Self::String(value.into_owned()) } +} + +impl AsRef for ExtraValue { + fn as_ref(&self) -> &ExtraValue { self } +} +impl AsMut for ExtraValue { + fn as_mut(&mut self) -> &mut ExtraValue { self } +} + + +/// Package Playdate Metadata, contains: +/// - Package Manifest fields +/// - Assets tables - `assets` & `dev-assets` +/// - Configuration table - `options` +#[derive(Debug, Clone, PartialEq)] +pub struct Metadata { + pub(super) inner: MetadataInner, +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Metadata { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + let meta = MetadataInner::deserialize(deserializer)?; + // TODO: validate fields, just critical retirements - bundle_id + Ok(Self { inner: meta }) + } +} + + +impl MetadataSource for Metadata { + type Manifest = Ext>; + type TargetManifest = Override; + + + fn manifest(&self) -> impl ManifestSourceOptExt { &self.inner.manifest } + + fn bins<'t>(&'t self) -> &'t [Self::TargetManifest] { self.inner.bins.as_slice() } + fn examples<'t>(&'t self) -> &'t [Self::TargetManifest] { self.inner.examples.as_slice() } + + fn bin_targets(&self) -> impl IntoIterator { self.inner.bins.iter().map(|o| o.target.as_str()) } + fn example_targets(&self) -> impl IntoIterator { + self.inner.examples.iter().map(|o| o.target.as_str()) + } + + fn assets(&self) -> &AssetsRules { &self.inner.assets } + fn dev_assets(&self) -> &AssetsRules { &self.inner.dev_assets } + + + fn options(&self) -> &Options { &self.inner.options } + + fn assets_options(&self) -> Cow<'_, AssetsOptions> { + self.options() + .assets + .as_ref() + .map_or_else(Default::default, Cow::Borrowed) + } + + fn support(&self) -> &Support { &self.inner.support } +} /// Package Metadata, contains: /// - Package Manifest fields /// - Assets tables - `assets` & `dev-assets` /// - Configuration table - `options` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", + serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] +pub(super) struct MetadataInner { + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) manifest: Ext>, + + #[cfg_attr(feature = "serde", serde(default))] + pub(super) assets: AssetsRules, + #[cfg_attr(feature = "serde", serde(default, alias = "dev-assets"))] + pub(super) dev_assets: AssetsRules, + + #[cfg_attr(feature = "serde", serde(default))] + pub(super) options: Options, + #[cfg_attr(feature = "serde", serde(default))] + pub(super) support: Support, + + #[cfg_attr(feature = "serde", serde(default, alias = "bin", rename = "bin"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_targets_overrides"))] + pub(super) bins: Vec>, + #[cfg_attr(feature = "serde", serde(default, alias = "example", rename = "example"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_targets_overrides"))] + pub(super) examples: Vec>, +} + + +#[cfg(feature = "serde")] +fn deserialize_targets_overrides<'de, D, S>(deserializer: D) -> Result>, D::Error> + where D: Deserializer<'de>, + S: Deserialize<'de> + Default + Eq + Hash { + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "serde", derive(Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged))] + pub enum Targets { + List(Vec>), + Map(HashMap>>), + } + + let result = match Targets::::deserialize(deserializer)? { + Targets::List(vec) => vec, + Targets::Map(map) => { + map.into_iter() + .map(|(k, v)| { + Override:: { target: k, + manifest: v } + }) + .collect() + }, + }; + + Ok(result) +} + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de> +Default")))] +pub struct Override { + /// Associated cargo-target name + #[cfg_attr(feature = "serde", serde(rename = "id", alias = "target"))] + pub(super) target: S, + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) manifest: Ext>, +} + +impl> Override { + pub fn into_parts(self) -> (S, Ext>) { + let Override { target, manifest } = self; + (target, manifest) + } + + pub fn as_parts(&self) -> (&S, &Ext>) { + let Override { target, manifest } = self; + (target, manifest) + } +} + + +impl> TargetId for Override { + fn target(&self) -> &str { self.target.as_ref() } +} + + +impl<'t> IntoOwned> for Override> { + fn into_owned(self) -> Override { + Override { target: self.target.into_owned(), + manifest: self.manifest.into_owned() } + } +} + +impl Override where S: ToOwned { + pub fn clone_owned(self) -> Override<::Owned> { + Override { target: self.target.to_owned(), + manifest: self.manifest.clone_owned() } + } +} + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] +pub enum AssetsRules { + /// List of paths to include. + List(Vec), + /// Rules & queries used to resolve paths to include. + Map(HashMap), +} + +impl Default for AssetsRules { + fn default() -> Self { Self::List(Vec::with_capacity(0)) } +} + +impl AssetsRules { + pub fn is_empty(&self) -> bool { + match self { + Self::List(list) => list.is_empty(), + Self::Map(map) => map.is_empty(), + } + } +} + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum RuleValue { + Boolean(bool), + String(String), +} + +impl Default for RuleValue { + fn default() -> Self { Self::Boolean(true) } +} + + +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "T: Deserialize<'de>")))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -pub struct PlayDateMetadata { - pub name: Option, - pub version: Option, - pub author: Option, +#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))] +pub struct Manifest { + pub name: Option, + pub version: Option, + pub author: Option, #[cfg_attr(feature = "serde", serde(alias = "bundle-id"))] - pub bundle_id: String, - pub description: Option, + pub bundle_id: Option, + pub description: Option, #[cfg_attr(feature = "serde", serde(alias = "image-path"))] - pub image_path: Option, + pub image_path: Option, #[cfg_attr(feature = "serde", serde(alias = "launch-sound-path"))] - pub launch_sound_path: Option, + pub launch_sound_path: Option, #[cfg_attr(feature = "serde", serde(alias = "content-warning"))] - pub content_warning: Option, + pub content_warning: Option, #[cfg_attr(feature = "serde", serde(alias = "content-warning2"))] - pub content_warning2: Option, - #[cfg_attr(feature = "serde", serde(alias = "build-number"))] - pub build_number: Option, - #[cfg_attr(feature = "serde", serde(default = "PlayDateMetadataAssets::::default"))] - pub assets: PlayDateMetadataAssets, - #[cfg_attr(feature = "serde", serde(alias = "dev-assets"))] - pub dev_assets: Option>, - #[cfg_attr(feature = "serde", serde(default))] - pub options: Options, - #[cfg_attr(feature = "serde", serde(default))] - pub support: Support, + pub content_warning2: Option, + #[cfg_attr(feature = "serde", serde(default, alias = "build-number"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_num_compat"))] + pub build_number: Option, +} - /// Package Manifest extra fields. - // It could be `serde::flatten`, but if so we should remove `deny_unknown_fields` from entire struct - // and break other fields validation, so it isn't good idea. - #[cfg_attr(feature = "serde", serde(default))] - pub extra: HashMap, +impl<'t, S: 't> Manifest where &'t S: Into> { + fn as_borrowed(&'t self) -> Manifest> { + Manifest { name: self.name.as_ref().map(Into::into), + version: self.version.as_ref().map(Into::into), + author: self.author.as_ref().map(Into::into), + bundle_id: self.bundle_id.as_ref().map(Into::into), + description: self.description.as_ref().map(Into::into), + image_path: self.image_path.as_ref().map(Into::into), + launch_sound_path: self.launch_sound_path.as_ref().map(Into::into), + content_warning: self.content_warning.as_ref().map(Into::into), + content_warning2: self.content_warning2.as_ref().map(Into::into), + build_number: self.build_number.clone() } + } +} + +impl IntoOwned::Owned>> for Manifest> { + fn into_owned(self) -> Manifest<::Owned> { + Manifest { name: self.name.map(|s| s.into_owned()), + version: self.version.map(|s| s.into_owned()), + author: self.author.map(|s| s.into_owned()), + bundle_id: self.bundle_id.map(|s| s.into_owned()), + description: self.description.map(|s| s.into_owned()), + image_path: self.image_path.map(|s| s.into_owned()), + launch_sound_path: self.launch_sound_path.map(|s| s.into_owned()), + content_warning: self.content_warning.map(|s| s.into_owned()), + content_warning2: self.content_warning2.map(|s| s.into_owned()), + build_number: self.build_number.clone() } + } +} + +impl Manifest where S: ToOwned { + pub fn clone_owned(self) -> Manifest<::Owned> { + Manifest { name: self.name.map(|s| s.to_owned()), + version: self.version.map(|s| s.to_owned()), + author: self.author.map(|s| s.to_owned()), + bundle_id: self.bundle_id.map(|s| s.to_owned()), + description: self.description.map(|s| s.to_owned()), + image_path: self.image_path.map(|s| s.to_owned()), + launch_sound_path: self.launch_sound_path.map(|s| s.to_owned()), + content_warning: self.content_warning.map(|s| s.to_owned()), + content_warning2: self.content_warning2.map(|s| s.to_owned()), + build_number: self.build_number.clone() } + } +} + + +#[cfg(feature = "serde")] +fn deserialize_num_compat<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + #[derive(Debug, Clone, PartialEq, Deserialize)] + #[serde(untagged)] + pub enum Value { + Num(usize), + Str(String), + } + let result = match Option::::deserialize(deserializer)? { + Some(Value::Num(value)) => Some(value), + Some(Value::Str(s)) => Some(s.parse().map_err(serde::de::Error::custom)?), + None => None, + }; + Ok(result) +} + + +impl ManifestSourceOpt for Manifest where S: Deref { + const MAY_BE_INCOMPLETE: bool = true; + + fn name(&self) -> Option<&str> { self.name.as_deref() } + fn version(&self) -> Option<&str> { self.version.as_deref() } + fn author(&self) -> Option<&str> { self.author.as_deref() } + fn bundle_id(&self) -> Option<&str> { self.bundle_id.as_deref() } + fn description(&self) -> Option<&str> { self.description.as_deref() } + fn image_path(&self) -> Option<&str> { self.image_path.as_deref() } + fn launch_sound_path(&self) -> Option<&str> { self.launch_sound_path.as_deref() } + fn content_warning(&self) -> Option<&str> { self.content_warning.as_deref() } + fn content_warning2(&self) -> Option<&str> { self.content_warning2.as_deref() } + fn build_number(&self) -> Option { self.build_number.clone() } } +impl ManifestSourceOpt for Ext { + const MAY_BE_INCOMPLETE: bool = Manifest::::MAY_BE_INCOMPLETE; + + fn name(&self) -> Option<&str> { self.inner().name() } + fn version(&self) -> Option<&str> { self.inner().version() } + fn author(&self) -> Option<&str> { self.inner().author() } + fn bundle_id(&self) -> Option<&str> { self.inner().bundle_id() } + fn description(&self) -> Option<&str> { self.inner().description() } + fn image_path(&self) -> Option<&str> { self.inner().image_path() } + fn launch_sound_path(&self) -> Option<&str> { self.inner().launch_sound_path() } + fn content_warning(&self) -> Option<&str> { self.inner().content_warning() } + fn content_warning2(&self) -> Option<&str> { self.inner().content_warning2() } + fn build_number(&self) -> Option { self.inner().build_number() } +} +impl ManifestSourceOpt for &Ext { + const MAY_BE_INCOMPLETE: bool = T::MAY_BE_INCOMPLETE; + + fn name(&self) -> Option<&str> { (*self).name() } + fn version(&self) -> Option<&str> { (*self).version() } + fn author(&self) -> Option<&str> { (*self).author() } + fn bundle_id(&self) -> Option<&str> { (*self).bundle_id() } + fn description(&self) -> Option<&str> { (*self).description() } + fn image_path(&self) -> Option<&str> { (*self).image_path() } + fn launch_sound_path(&self) -> Option<&str> { (*self).launch_sound_path() } + fn content_warning(&self) -> Option<&str> { (*self).content_warning() } + fn content_warning2(&self) -> Option<&str> { (*self).content_warning2() } + fn build_number(&self) -> Option { (*self).build_number() } +} + + +impl ManifestSourceOptExt for Ext { + const MAY_HAVE_EXTRA: bool = true; -impl PlayDateMetadata where Error: From<>::Error> { - pub fn merge_opts(&mut self) -> Result<(), Error> { - let opts = if let Some(res) = self.assets.extract_options() { - Some(res?) + fn has_extra(&self) -> bool { !self.extra.is_empty() } + fn iter_extra(&self) -> Option, impl AsRef)>> { + if self.extra.is_empty() { + None } else { - Default::default() - }; - - match (self.options.assets.is_some(), opts) { - (_, None) => Ok(()), - (true, Some(_)) => { - Err(concat!( - "[package.metadata.playdate.assets.options]", - " conflicts with ", - "[package.metadata.playdate.options.assets]" - ).into()) - }, - (false, Some(opts)) => { - let _ = self.options.assets.insert(opts); - Ok(()) - }, + Some(self.extra.iter()) } } } -impl PlayDateMetadata { - pub fn assets_options(&self) -> Cow<'_, crate::metadata::format::AssetsOptions> { - self.options - .assets - .as_ref() - .map_or_else(Default::default, Cow::Borrowed) +impl ManifestSourceOptExt for Manifest where S: Deref { + const MAY_HAVE_EXTRA: bool = false; + + fn has_extra(&self) -> bool { false } + fn iter_extra(&self) -> Option, impl AsRef)>> { + None::> } } +impl<'s, T: ManifestSourceOpt, S: From<&'s str>> From<&'s T> for Manifest { + fn from(source: &'s T) -> Self { + Self { name: source.name().map(Into::into), + version: source.version().map(Into::into), + author: source.author().map(Into::into), + bundle_id: source.bundle_id().map(Into::into), + description: source.description().map(Into::into), + image_path: source.image_path().map(Into::into), + launch_sound_path: source.launch_sound_path().map(Into::into), + content_warning: source.content_warning().map(Into::into), + content_warning2: source.content_warning2().map(Into::into), + build_number: source.build_number().clone() } + } +} -impl PlayDateMetadataAssets where Error: From<>::Error> { - fn extract_options(&mut self) -> Option> { - match self { - PlayDateMetadataAssets::Map(map) => { - // Remove only value that have `table/map` (not bool or str) type: - if map.get("options") - .filter(|v| v.as_str().is_none() && v.as_bool().is_none()) - .is_some() - { - map.remove("options") - .map(|v| v.try_into()) - .map(|res| res.map_err(Into::into)) - } else { - None - } - }, - _ => None, - } + +impl From<&T> for Ext> { + fn from(source: &T) -> Self { + let main = Manifest::from(source); + Ext { main, + extra: source.iter_extra() + .map(|i| { + i.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } } } -#[cfg(feature = "serde_json")] -impl TryFrom for AssetsOptions { - type Error = serde_json::error::Error; - fn try_from(value: serde_json::Value) -> std::result::Result { - serde_json::from_value(value) +impl<'t, T: ManifestSourceOptExt> From<&'t T> for Ext>> { + fn from(source: &'t T) -> Self { + Ext { main: Manifest::from(source), + extra: source.iter_extra() + .map(|i| { + i.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } } } -#[cfg(feature = "toml")] -impl TryFrom for AssetsOptions { - type Error = toml::de::Error; - fn try_from(value: toml::Value) -> std::result::Result { - toml::Value::try_into::(value) +impl<'t, T: ManifestSourceOptExt + 't> IntoOwned>> for T { + fn into_owned(self) -> Ext> { + Ext { main: Manifest::from(&self).into_owned(), + extra: self.iter_extra() + .map(|i| { + i.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } + } +} + + +impl ManifestSourceOpt for Override where Manifest: ManifestSourceOpt { + const MAY_BE_INCOMPLETE: bool = Manifest::::MAY_BE_INCOMPLETE; + + fn name(&self) -> Option<&str> { self.manifest.name() } + fn version(&self) -> Option<&str> { self.manifest.version() } + fn author(&self) -> Option<&str> { self.manifest.author() } + fn bundle_id(&self) -> Option<&str> { self.manifest.bundle_id() } + fn description(&self) -> Option<&str> { self.manifest.description() } + fn image_path(&self) -> Option<&str> { self.manifest.image_path() } + fn launch_sound_path(&self) -> Option<&str> { self.manifest.launch_sound_path() } + fn content_warning(&self) -> Option<&str> { self.manifest.content_warning() } + fn content_warning2(&self) -> Option<&str> { self.manifest.content_warning2() } + fn build_number(&self) -> Option { self.manifest.build_number() } +} + +impl ManifestSourceOptExt for Override where Manifest: ManifestSourceOpt { + const MAY_HAVE_EXTRA: bool = Ext::>::MAY_HAVE_EXTRA; + + fn iter_extra(&self) -> Option, impl AsRef)>> { + self.manifest.iter_extra() } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Options { pub assets: Option, - // Output layout ctrl, temporary removed: - // #[serde(alias = "cross-target", default)] - // pub cross_target: bool, + // Output layout ctrl, temporary removed. } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct AssetsOptions { - #[cfg_attr(feature = "serde", serde(alias = "override", default = "bool::"))] + #[cfg_attr(feature = "serde", serde(alias = "override"))] + #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_overwrite"))] pub overwrite: bool, - #[cfg_attr(feature = "serde", serde(alias = "follow-symlinks", default = "bool::"))] + #[cfg_attr(feature = "serde", serde(alias = "follow-symlinks"))] + #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_follow_symlinks"))] pub follow_symlinks: bool, #[cfg_attr(feature = "serde", serde(alias = "build-method", default))] pub method: AssetsBuildMethod, /// Allow building assets for dependencies - #[cfg_attr(feature = "serde", serde(default = "bool::"))] + #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_dependencies"))] pub dependencies: bool, } -#[cfg(feature = "serde")] -const fn bool() -> bool { V } +impl AssetsOptions { + const fn default_overwrite() -> bool { true } + const fn default_follow_symlinks() -> bool { true } + const fn default_dependencies() -> bool { false } +} + +impl Default for AssetsOptions { + fn default() -> Self { + Self { overwrite: Self::default_overwrite(), + follow_symlinks: Self::default_follow_symlinks(), + dependencies: Self::default_dependencies(), + method: Default::default() } + } +} -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum AssetsBuildMethod { @@ -167,37 +600,662 @@ impl Default for AssetsBuildMethod { } -#[derive(Debug, Clone)] +/// Compatibility options. +/// e.g. Crank manifest path. +#[derive(Debug, Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "T: Deserialize<'de>")))] -#[cfg_attr(feature = "serde", serde(untagged))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -pub enum PlayDateMetadataAssets { - /// List of paths to include. - List(Vec), - /// Rules & queries used to resolve paths to include. - Map(HashMap), +pub struct Support { + // #[serde(alias = "crank-manifest")] + // pub crank_manifest: Option } -impl Default for PlayDateMetadataAssets { - fn default() -> Self { Self::List(Vec::with_capacity(0)) } -} -impl PlayDateMetadataAssets { - pub fn is_empty(&self) -> bool { - match self { - PlayDateMetadataAssets::List(list) => list.is_empty(), - PlayDateMetadataAssets::Map(map) => map.is_empty(), +#[cfg(test)] +#[cfg(feature = "toml")] +mod tests { + use super::*; + use crate::manifest::format::ManifestFmt; + + use std::assert_matches::assert_matches; + + + type ManifestWithAny = Ext>; + type ManifestStrict = Manifest; + type ManifestStrictRef<'t> = Manifest>; + + + #[test] + fn minimal_strict() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + "#; + let m: ManifestStrict = toml::from_str(src).unwrap(); + assert!(m.bundle_id.is_some()); + let m: ManifestStrictRef = toml::from_str(src).unwrap(); + assert!(m.bundle_id.is_some()); + } + + #[test] + fn minimal_strict_err() { + let src = r#" + bundle-id = "test.workspace.main.crate" + foo = "bar" + "#; + assert!(toml::from_str::(src).is_err()); + + let src = r#"foo = "bar""#; + assert!(toml::from_str::(src).is_err()); + assert!(toml::from_str::(src).is_err()); + } + + #[test] + fn minimal_extra() { + let src = r#"bundle-id = "test.workspace.main.crate""#; + assert!(toml::from_str::(src).is_ok()); + + + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + foo = "bar" + "#; + + let m: ManifestWithAny = toml::from_str(src).unwrap(); + + assert!(m.inner().bundle_id.is_some()); + assert!(m.inner().description.is_some()); + assert!(m.extra().get("foo").is_some()); + } + + #[test] + fn meta_minimal() { + assert!(toml::from_str::("").is_ok()); + + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + "#; + + let m = toml::from_str::(src).unwrap(); + assert!(m.inner.manifest.inner().bundle_id.is_some()); + assert!(m.inner.manifest.inner().description.is_some()); + assert!(m.inner.manifest.extra.is_empty()); + } + + + #[test] + fn meta_extra() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + boo = 42 + [assets] + foo = "bar" + "#; + let expected_id = Some("test.workspace.main.crate"); + + let m: super::MetadataInner = toml::from_str(src).unwrap(); + assert_eq!(expected_id, m.manifest.inner().bundle_id.as_deref()); + assert!(m.manifest.inner().description.is_some()); + assert!(m.manifest.extra().get("boo").is_some()); + + let m: Metadata = toml::from_str(src).unwrap(); + assert_eq!(expected_id, m.inner.manifest.inner().bundle_id.as_deref()); + assert!(m.inner.manifest.inner().description.is_some()); + assert!(m.inner.manifest.extra().get("boo").is_some()); + + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + foo = "bar" + assets.target = "source" + "#; + let m: Metadata = toml::from_str(src).unwrap(); + assert_eq!(expected_id, m.inner.manifest.inner().bundle_id.as_deref()); + assert!(m.inner.manifest.inner().description.is_some()); + assert!(m.inner.manifest.extra().get("foo").is_some()); + assert!(!m.inner.assets.is_empty()); + } + + + #[test] + fn meta_strict_bins() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + [[bin]] + target = "cargo-target-name" + name = "Other Name" + [[bin]] + target = "cargo-another-target" + name = "Another Name" + "#; + + let m = toml::from_str::(src).unwrap(); + assert!(m.inner.manifest.inner().bundle_id.is_some()); + assert!(m.inner.manifest.inner().description.is_some()); + assert_eq!(2, m.inner.bins.len()); + } + + #[test] + fn meta_extra_bins() { + let src = r#" + bundle-id = "test.workspace.main.crate" + foo = "bar" + + [[bin]] + target = "cargo-target-name" + name = "Other Name" + boo = "bar" + "#; + + let m = toml::from_str::(src).unwrap(); + assert!(m.inner.manifest.inner().bundle_id.is_some()); + assert!(m.inner.manifest.extra().get("foo").is_some()); + assert_eq!(1, m.inner.bins.len()); + assert!( + m.inner + .bins + .first() + .unwrap() + .manifest + .extra() + .get("boo") + .is_some() + ); + } + + #[test] + fn meta_strict_examples() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + [[example]] + target = "cargo-target-name" + name = "Other Name" + [[example]] + target = "cargo-another-target" + name = "Another Name" + "#; + + let m = toml::from_str::(src).unwrap(); + assert!(m.inner.manifest.inner().bundle_id.is_some()); + assert!(m.inner.manifest.inner().description.is_some()); + assert_eq!(2, m.inner.examples.len()); + } + + #[test] + fn meta_strict_examples_map() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + [example.cargo-target-name] + name = "Other Name" + [example.cargo-another-target] + name = "Another Name" + "#; + + let m = toml::from_str::(src).unwrap(); + assert!(m.inner.manifest.inner().bundle_id.is_some()); + assert!(m.inner.manifest.inner().description.is_some()); + assert_eq!(2, m.inner.examples.len()); + } + + #[test] + fn meta_strict_examples_mix_err() { + let src = r#" + bundle-id = "test.workspace.main.crate" + description = "test" + [example.cargo-target-name] + name = "Other Name" + [[example]] + target = "cargo-another-target" + name = "Another Name" + "#; + + assert!(toml::from_str::(src).is_err()); + } + + #[test] + fn meta_extra_examples_mix_err() { + let src = r#" + bundle-id = "test.workspace.main.crate" + foo = "bar" + [example.cargo-target-name] + name = "Other Name" + [[example]] + target = "cargo-another-target" + name = "Another Name" + "#; + + assert!(toml::from_str::(src).is_err()); + } + + #[test] + fn options_empty() { + let m = toml::from_str::("").unwrap(); + assert!(m.assets.is_none()); + } + + #[test] + fn options_assets_deps() { + // default is false + assert_eq!(false, AssetsOptions::default_dependencies()); + let src = r#" [assets] "#; + let m = toml::from_str::(src).unwrap(); + assert_matches!( + m.assets, + Some(AssetsOptions { dependencies: false, + .. }) + ); + + // overrides default + let src = r#" + [assets] + dependencies = true + "#; + let m = toml::from_str::(src).unwrap(); + assert_matches!( + m.assets, + Some(AssetsOptions { dependencies: true, + .. }) + ); + } + + #[test] + fn assets_rules_empty() { + let m = toml::from_str::("").unwrap(); + assert!(m.is_empty()); + match m { + AssetsRules::List(rules) => assert!(rules.is_empty()), + AssetsRules::Map(rules) => assert!(rules.is_empty()), } } -} + #[test] + fn assets_rules_list_wrapped() { + #[derive(Debug, Clone, PartialEq, Deserialize)] + pub(super) struct Temp { + assets: AssetsRules, + } -/// Compatibility options. -/// e.g. Crank manifest path. -#[derive(Debug, Clone, Default)] -#[cfg_attr(feature = "serde", derive(Deserialize))] -pub struct Support { - // #[serde(alias = "crank-manifest")] - // pub crank_manifest: Option, // bool + let src = r#" + assets = ["one", "two"] + "#; + let m = toml::from_str::(src).unwrap(); + assert!(!m.assets.is_empty()); + assert_matches!(m.assets, AssetsRules::List(rules) if rules.len() == 2); + } + + #[test] + fn assets_rules_map() { + let src = r#" + included = true + excluded = false + "into/" = "files.*" + "#; + let m = toml::from_str::(src).unwrap(); + assert_matches!(m, AssetsRules::Map(rules) if rules.len() == 3); + } + + + #[test] + fn assets_rules_map_wrapped() { + #[derive(Debug, Clone, PartialEq, Deserialize)] + pub(super) struct Temp { + assets: AssetsRules, + } + let src = r#" + [assets] + included = true + excluded = false + "into/" = "files.*" + "#; + let m = toml::from_str::(src).unwrap(); + assert_matches!(m.assets, AssetsRules::Map(rules) if rules.len() == 3); + } + + + #[test] + fn meta_assets_options_value() { + let src = r#" + bundle-id = "test.workspace.main.crate" + [options.assets] + [assets] + "#; + assert!(toml::from_str::(src).is_ok()); + + let src = r#" + bundle-id = "test.workspace.main.crate" + [options.assets] + dependencies = true + "#; + let m = toml::from_str::(src).unwrap(); + assert!(m.assets.is_empty()); + assert_matches!( + m.options.assets, + Some(AssetsOptions { dependencies: true, + .. }) + ); + } + + #[test] + fn meta_assets_options() { + let src = r#" + bundle-id = "test.workspace.main.crate" + [assets] + options = {} + "#; + + assert!(toml::from_str::(src).is_err()); + } + + #[test] + fn meta_options_assets() { + let src = r#" + bundle-id = "test.workspace.main.crate" + [options] + assets = {} + "#; + + assert!(toml::from_str::(src).is_ok()); + } + + #[test] + fn meta_assets_options_mix() { + let src = r#" + bundle-id = "test.workspace.main.crate" + [options] + assets = {} + [assets] + options = {} + "#; + + assert!(toml::from_str::(src).is_err()); + } + + + #[test] + fn meta_assets_maps() { + let src = r#" + [assets] + included = true + excluded = false + other = "from/path" + [dev-assets] + a = true + b = false + c = "/c/path" + "#; + + let m = toml::from_str::(src).unwrap(); + + assert_matches!(m.assets(), AssetsRules::Map(_)); + match m.assets() { + AssetsRules::Map(rules) => { + assert_eq!(3, rules.len()); + assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("included")); + assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("excluded")); + assert_eq!(Some(&RuleValue::String("from/path".into())), rules.get("other")); + }, + _ => unreachable!(), + } + + assert_matches!(m.dev_assets(), AssetsRules::Map(_)); + match m.dev_assets() { + AssetsRules::Map(rules) => { + assert_eq!(3, rules.len()); + assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("a")); + assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("b")); + assert_eq!(Some(&RuleValue::String("/c/path".into())), rules.get("c")); + }, + _ => unreachable!(), + } + } + + #[test] + fn meta_assets_lists() { + let src = r#" + assets = ["a", "b", "c"] + dev-assets = ["d", "e", "f"] + "#; + + let m = toml::from_str::(src).unwrap(); + + assert_matches!(m.assets(), AssetsRules::List(_)); + assert_matches!(m.dev_assets(), AssetsRules::List(_)); + match m.assets() { + AssetsRules::List(rules) => assert_eq!(&["a", "b", "c"], &rules[..]), + _ => unreachable!(), + } + match m.dev_assets() { + AssetsRules::List(rules) => assert_eq!(&["d", "e", "f"], &rules[..]), + _ => unreachable!(), + } + } + + #[test] + fn meta_assets_mix() { + let src = r#" + assets = ["d", "e", "f"] + [dev-assets] + a = true + b = true + "#; + + let m = toml::from_str::(src).unwrap(); + + assert_matches!(m.assets(), AssetsRules::List(_)); + match m.assets() { + AssetsRules::List(rules) => { + assert_eq!(3, rules.len()); + assert_eq!(&["d", "e", "f"], &rules[..]); + }, + _ => unreachable!(), + } + + assert_matches!(m.dev_assets(), AssetsRules::Map(_)); + match m.dev_assets() { + AssetsRules::Map(rules) => { + assert_eq!(2, rules.len()); + assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("a")); + assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("b")); + }, + _ => unreachable!(), + } + } + + + #[test] + fn meta_full() { + let src = r#" + foo = "bar" # custom field + name = "Crate Name" + version = "0.1" + bundle-id = "test.workspace.main.crate" + description = "Crate description" + author = "Crate Author" + image-path = "image/path" + launch-sound-path = "launch-sound/path" + content-warning = "Attention!" + content-warning2 = "Alarm!" + build-number = 42 + options.assets.dependencies = true + [assets] + included = true + excluded = false + other = "from/path" + [dev-assets] + "dev-included" = true + [[bin]] + target = "cargo-target-bin-name" + name = "Bin Name" + bundle-id = "test.workspace.main.bin" + description = "This is a bin" + [[example]] + target = "cargo-target-example-name" + name = "Example Name" + bundle-id = "test.workspace.main.example" + description = "This is an example" + example-extra = 101 + "#; + + let m = toml::from_str::(src).unwrap(); + assert_eq!(Some("Crate Name"), m.manifest().name()); + assert_eq!(Some("0.1"), m.manifest().version()); + assert_eq!(Some("test.workspace.main.crate"), m.manifest().bundle_id()); + assert_eq!(Some("Crate description"), m.manifest().description()); + assert_eq!(Some("Crate Author"), m.manifest().author()); + assert_eq!(Some("image/path"), m.manifest().image_path()); + assert_eq!(Some("launch-sound/path"), m.manifest().launch_sound_path()); + assert_eq!(Some("Attention!"), m.manifest().content_warning()); + assert_eq!(Some("Alarm!"), m.manifest().content_warning2()); + + { + let s = m.manifest().to_manifest_string().unwrap(); + println!("meta manifest:\n{}", s.trim()) + } + + + let opts = m.assets_options(); + assert!(opts.dependencies); + assert!(!AssetsOptions::default_dependencies()); + + assert_matches!(m.assets(), AssetsRules::Map(_)); + match m.assets() { + AssetsRules::Map(rules) => { + assert_eq!(3, rules.len()); + assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("included")); + assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("excluded")); + assert_eq!(Some(&RuleValue::String("from/path".into())), rules.get("other")); + }, + _ => unreachable!(), + } + assert_matches!(m.dev_assets(), AssetsRules::Map(rules) if rules.get("dev-included").is_some()); + + assert_eq!(1, m.bins().len()); + assert_eq!(1, m.examples().len()); + + let bin_trg = m.bin_targets().into_iter().next().unwrap(); + assert_eq!("cargo-target-bin-name", bin_trg); + + let example_trg = m.example_targets().into_iter().next().unwrap(); + assert_eq!("cargo-target-example-name", example_trg); + + let (bin_trg_by_iter, bin) = m.bins_iter().and_then(|mut i| i.next()).unwrap().as_parts(); + assert_eq!(bin_trg, bin_trg_by_iter); + + let (example_trg_by_iter, example) = m.examples_iter().and_then(|mut i| i.next()).unwrap().as_parts(); + assert_eq!(example_trg, example_trg_by_iter); + + + assert_eq!(Some("Bin Name"), bin.name()); + assert_eq!(Some("test.workspace.main.bin"), bin.bundle_id()); + assert_eq!(Some("This is a bin"), bin.description()); + assert!(bin.version().is_none()); + assert!(bin.author().is_none()); + assert!(bin.image_path().is_none()); + assert!(bin.launch_sound_path().is_none()); + assert!(bin.content_warning().is_none()); + assert!(bin.content_warning2().is_none()); + assert!(!bin.has_extra()); + + { + let s = bin.to_manifest_string().unwrap(); + println!("bin over:\n{}", s.trim()) + } + + + assert_eq!(Some("Example Name"), example.name()); + assert_eq!(Some("test.workspace.main.example"), example.bundle_id()); + assert_eq!(Some("This is an example"), example.description()); + assert!(example.version().is_none()); + assert!(example.author().is_none()); + assert!(example.image_path().is_none()); + assert!(example.launch_sound_path().is_none()); + assert!(example.content_warning().is_none()); + assert!(example.content_warning2().is_none()); + assert!(example.has_extra()); + let example_extra: HashMap<_, _> = example.iter_extra() + .unwrap() + .into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())) + .collect(); + assert_eq!(1, example_extra.len()); + assert_eq!(Some(&ExtraValue::Int(101)), example_extra.get("example-extra")); + + + { + let s = example.to_manifest_string().unwrap(); + println!("example over:\n{}", s.trim()) + } + + + // test merged + + let bin = m.manifest_for_target(bin_trg, false).unwrap(); + assert_eq!(Some("Bin Name"), bin.name()); + assert_eq!(Some("0.1"), bin.version()); + assert_eq!(Some("test.workspace.main.bin"), bin.bundle_id()); + assert_eq!(Some("This is a bin"), bin.description()); + assert_eq!(Some("Crate Author"), bin.author()); + assert_eq!(Some("image/path"), bin.image_path()); + assert_eq!(Some("launch-sound/path"), bin.launch_sound_path()); + assert_eq!(Some("Attention!"), bin.content_warning()); + assert_eq!(Some("Alarm!"), bin.content_warning2()); + { + let s = bin.to_manifest_string().unwrap(); + println!("bin manifest:\n{}", s.trim()) + } + + let example = m.manifest_for_target(example_trg, true).unwrap(); + assert_eq!(Some("Example Name"), example.name()); + assert_eq!(Some("0.1"), example.version()); + assert_eq!(Some("test.workspace.main.example"), example.bundle_id()); + assert_eq!(Some("This is an example"), example.description()); + assert_eq!(Some("Crate Author"), example.author()); + assert_eq!(Some("image/path"), example.image_path()); + assert_eq!(Some("launch-sound/path"), example.launch_sound_path()); + assert_eq!(Some("Attention!"), example.content_warning()); + assert_eq!(Some("Alarm!"), example.content_warning2()); + { + let s = example.to_manifest_string().unwrap(); + println!("example manifest:\n{}", s.trim()) + } + + + // test merged any kind of target, just named + + let example = m.manifest_for_target_any(example_trg).unwrap(); + assert_eq!(Some("Example Name"), example.name()); + assert_eq!(Some("0.1"), example.version()); + assert_eq!(Some("test.workspace.main.example"), example.bundle_id()); + assert_eq!(Some("This is an example"), example.description()); + assert_eq!(Some("Crate Author"), example.author()); + assert_eq!(Some("image/path"), example.image_path()); + assert_eq!(Some("launch-sound/path"), example.launch_sound_path()); + assert_eq!(Some("Attention!"), example.content_warning()); + assert_eq!(Some("Alarm!"), example.content_warning2()); + { + let s = example.to_manifest_string().unwrap(); + println!("example manifest:\n{}", s.trim()) + } + + let missing = m.manifest_for_target_any("missing, wrong name").unwrap(); + assert_eq!(Some("Crate Name"), missing.name()); + assert_eq!(Some("0.1"), missing.version()); + assert_eq!(Some("test.workspace.main.crate"), missing.bundle_id()); + assert_eq!(Some("Crate description"), missing.description()); + assert_eq!(Some("Crate Author"), missing.author()); + assert_eq!(Some("image/path"), missing.image_path()); + assert_eq!(Some("launch-sound/path"), missing.launch_sound_path()); + assert_eq!(Some("Attention!"), missing.content_warning()); + assert_eq!(Some("Alarm!"), missing.content_warning2()); + { + let s = missing.to_manifest_string().unwrap(); + println!("missing (base meta) manifest:\n{}", s.trim()) + } + assert_eq!(m.manifest().into_owned(), missing.into_owned()); + } } diff --git a/support/build/src/metadata/mod.rs b/support/build/src/metadata/mod.rs index 5000b379..60e40f24 100644 --- a/support/build/src/metadata/mod.rs +++ b/support/build/src/metadata/mod.rs @@ -1,8 +1,6 @@ pub mod error; pub mod format; -pub mod cargo; - -use crate::value::Value; +pub mod source; pub const METADATA_FIELD: &str = "playdate"; diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs new file mode 100644 index 00000000..d488cfef --- /dev/null +++ b/support/build/src/metadata/source.rs @@ -0,0 +1,584 @@ +use std::borrow::Cow; + +use super::format::{AssetsOptions, AssetsRules, Ext, ExtraFields, ExtraValue, Manifest, Options, Support}; + + +pub trait TargetId { + fn target(&self) -> &str; +} + + +pub trait MetadataSource { + type Manifest: ManifestSourceOptExt; + type TargetManifest: ManifestSourceOptExt + TargetId; + + fn manifest(&self) -> impl ManifestSourceOptExt; + + fn bins<'t>(&'t self) -> &'t [Self::TargetManifest]; + fn examples<'t>(&'t self) -> &'t [Self::TargetManifest]; + + fn bin<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { + self.bins().into_iter().find(|b| b.target() == target) + } + fn example<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { + self.examples().into_iter().find(|b| b.target() == target) + } + + fn bin_targets(&self) -> impl IntoIterator; + fn example_targets(&self) -> impl IntoIterator; + fn all_targets(&self) -> impl IntoIterator { + self.bin_targets().into_iter().chain(self.example_targets()) + } + + fn bins_iter(&self) -> Option> { + (!self.bins().is_empty()).then_some(self.bins().into_iter()) + } + fn examples_iter(&self) -> Option> { + (!self.examples().is_empty()).then_some(self.examples().into_iter()) + } + + fn all_targets_iter(&self) -> impl Iterator { + self.bins_iter() + .into_iter() + .flatten() + .chain(self.examples_iter().into_iter().flatten()) + } + + fn assets(&self) -> &AssetsRules; + fn dev_assets(&self) -> &AssetsRules; + + fn options(&self) -> &Options; + fn assets_options(&self) -> Cow<'_, AssetsOptions>; + + fn support(&self) -> &Support; + + /// Make a manifest for a specific target, merged with base manifest for package. + /// Returns `None` if the target is not found. + fn manifest_for_target(&self, target: &str, dev: bool) -> Option { + use super::format::Manifest; + + // manifest() returns T without lifetime, so can't be associated with `&self`, + // that should be fixed + let base = self.manifest(); + + if dev { + if let Some(target) = self.example(target) { + let trg = base.override_with_extra(target); + Some(Ext::>::from(&trg)) + } else { + None + } + } else { + if let Some(target) = self.bin(target) { + let trg = base.override_with_extra(target); + Some(Ext::>::from(&trg)) + } else { + None + } + } + } + + fn manifest_for_target_any(&self, target: &str) -> Option { + self.manifest_for_target(target, false) + .or_else(|| self.manifest_for_target(target, true)) + .map(|m| m.into_manifest()) + .or_else(|| Some(self.manifest().into_manifest())) + } +} + + +impl MetadataSource for &T { + type Manifest = ::Manifest; + type TargetManifest = ::TargetManifest; + + + fn manifest(&self) -> impl ManifestSourceOptExt { (*self).manifest() } + + fn bins<'t>(&'t self) -> &'t [Self::TargetManifest] { ::bins(*self) } + fn examples<'t>(&'t self) -> &'t [Self::TargetManifest] { ::examples(*self) } + + fn bin_targets(&self) -> impl IntoIterator { (*self).bin_targets() } + fn example_targets(&self) -> impl IntoIterator { (*self).example_targets() } + + fn assets(&self) -> &AssetsRules { (*self).assets() } + fn dev_assets(&self) -> &AssetsRules { (*self).dev_assets() } + fn options(&self) -> &Options { (*self).options() } + fn assets_options(&self) -> Cow<'_, AssetsOptions> { (*self).assets_options() } + fn support(&self) -> &Support { (*self).support() } +} + + +pub trait ManifestSource { + fn name(&self) -> &str; + fn version(&self) -> &str; + fn author(&self) -> &str; + fn bundle_id(&self) -> &str; + fn description(&self) -> &str; + fn image_path(&self) -> &str; + fn launch_sound_path(&self) -> &str; + fn content_warning(&self) -> &str; + fn content_warning2(&self) -> &str; + fn build_number(&self) -> Option; +} + + +pub trait ManifestSourceOpt { + /// Possibly incomplete, that means that some of values could be `None`. + const MAY_BE_INCOMPLETE: bool; + fn name(&self) -> Option<&str>; + fn version(&self) -> Option<&str>; + fn author(&self) -> Option<&str>; + fn bundle_id(&self) -> Option<&str>; + fn description(&self) -> Option<&str>; + fn image_path(&self) -> Option<&str>; + fn launch_sound_path(&self) -> Option<&str>; + fn content_warning(&self) -> Option<&str>; + fn content_warning2(&self) -> Option<&str>; + fn build_number(&self) -> Option; + + fn override_with<'a, Over>(&'a self, overrider: &'a Over) -> impl ManifestSourceOpt + 'a + where Over: ManifestSourceOpt { + use super::format::Manifest; + + let trg = overrider; + Manifest::> { name: trg.name().or(self.name()).map(Into::into), + version: trg.version().or(self.version()).map(Into::into), + author: trg.author().or(self.author()).map(Into::into), + bundle_id: trg.bundle_id().or(self.bundle_id()).map(Into::into), + description: trg.description().or(self.description()).map(Into::into), + image_path: trg.image_path().or(self.image_path()).map(Into::into), + launch_sound_path: trg.launch_sound_path() + .or(self.launch_sound_path()) + .map(Into::into), + content_warning: trg.content_warning().or(self.content_warning()).map(Into::into), + content_warning2: trg.content_warning2() + .or(self.content_warning2()) + .map(Into::into), + build_number: trg.build_number().or(self.build_number()) } + } +} + + +pub trait ManifestSourceOptExt: ManifestSourceOpt { + const MAY_HAVE_EXTRA: bool; + + fn has_extra(&self) -> bool { Self::MAY_HAVE_EXTRA && self.iter_extra().is_some() } + fn iter_extra(&self) -> Option, impl AsRef)>>; + + fn override_with_extra_ref<'t, Over: ManifestSourceOptExt>(&'t self, + overrider: &'t Over) + -> impl ManifestSourceOptExt + 't { + let manifest = self.override_with(overrider); + if overrider.has_extra() || self.has_extra() { + let extra = match (self.iter_extra(), overrider.iter_extra()) { + (None, None) => None, + (None, Some(extra)) => { + let result = extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); + Some(result.collect()) + }, + (Some(extra), None) => { + let result = extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); + Some(result.collect()) + }, + (Some(base), Some(extra)) => { + let mut result: ExtraFields = + base.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect(); + result.extend( + extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())), + ); + Some(result) + }, + }.unwrap_or_else(|| ExtraFields::with_capacity(0)); + + Ext { main: manifest, + extra } + } else { + Ext { main: manifest, + extra: ExtraFields::with_capacity(0) } + } + } + + + fn override_with_extra(&self, overrider: &Over) -> impl ManifestSourceOptExt { + self.override_with_extra_ref(overrider).into_manifest() + } +} + + +impl ManifestSourceOpt for T { + const MAY_BE_INCOMPLETE: bool = false; + fn name(&self) -> Option<&str> { Some(ManifestSource::name(self)) } + fn version(&self) -> Option<&str> { Some(ManifestSource::version(self).as_ref()) } + fn author(&self) -> Option<&str> { Some(ManifestSource::author(self).as_ref()) } + fn bundle_id(&self) -> Option<&str> { Some(ManifestSource::bundle_id(self).as_ref()) } + fn description(&self) -> Option<&str> { Some(ManifestSource::description(self).as_ref()) } + fn image_path(&self) -> Option<&str> { Some(ManifestSource::image_path(self).as_ref()) } + fn launch_sound_path(&self) -> Option<&str> { Some(ManifestSource::launch_sound_path(self).as_ref()) } + fn content_warning(&self) -> Option<&str> { Some(ManifestSource::content_warning(self).as_ref()) } + fn content_warning2(&self) -> Option<&str> { Some(ManifestSource::content_warning2(self).as_ref()) } + fn build_number(&self) -> Option { ManifestSource::build_number(self) } +} + +impl ManifestSourceOpt for Cow<'_, T> { + const MAY_BE_INCOMPLETE: bool = T::MAY_BE_INCOMPLETE; + + fn name(&self) -> Option<&str> { self.as_ref().name() } + fn version(&self) -> Option<&str> { self.as_ref().version() } + fn author(&self) -> Option<&str> { self.as_ref().author() } + fn bundle_id(&self) -> Option<&str> { self.as_ref().bundle_id() } + fn description(&self) -> Option<&str> { self.as_ref().description() } + fn image_path(&self) -> Option<&str> { self.as_ref().image_path() } + fn launch_sound_path(&self) -> Option<&str> { self.as_ref().launch_sound_path() } + fn content_warning(&self) -> Option<&str> { self.as_ref().content_warning() } + fn content_warning2(&self) -> Option<&str> { self.as_ref().content_warning2() } + fn build_number(&self) -> Option { self.as_ref().build_number() } +} + +impl<'t, T: ManifestSourceOptExt> ManifestSourceOptExt for &'t T where &'t T: ManifestSourceOpt { + const MAY_HAVE_EXTRA: bool = true; + + fn iter_extra(&self) -> Option, impl AsRef)>> { + (*self).iter_extra() + } +} + + +pub trait IntoManifest: Sized + ManifestSourceOptExt { + fn into_manifest(self) -> Ext> { self.into_owned() } +} +impl IntoManifest for T {} + + +pub trait CrateInfoSource { + fn name(&self) -> Cow; + fn authors(&self) -> &[&str]; + fn version(&self) -> Cow; + fn description(&self) -> Option>; + fn metadata(&self) -> Option; + + // targets -> [name? or name+kind?] + + + fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { + use super::format::Manifest; + { + let author = { + let author = self.authors().join(", "); + if author.trim().is_empty() { + None + } else { + Some(author.into()) + } + }; + let version = Some(self.version()); + let package = Manifest { name: Some(self.name()), + description: self.description(), + author, + version, + bundle_id: None, + image_path: None, + launch_sound_path: None, + content_warning: None, + content_warning2: None, + build_number: None }; + + if let Some(meta) = self.metadata() { + let manifest = meta.manifest(); + let base = Ext { main: package, + extra: Default::default() }; + let result = base.override_with_extra(&manifest); + Ext { main: Manifest::from(&result), + extra: result.iter_extra() + .map(|m| { + m.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } + } else { + Ext { main: package.into_owned(), + extra: Default::default() } + } + } + } + + + /// Returns `None` if manifest for `target` not found, no fallback. + // fn manifest_for(&self, target: &str, dev: bool) -> Option { + fn manifest_for(&self, target: &str, dev: bool) -> Option>> { + let base = self.manifest_for_crate(); + + if let Some(root) = self.metadata() { + if dev { + if let Some(man) = root.example(target) { + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } + } else { + if let Some(man) = root.bin(target) { + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } + } + } else { + Some(base.into_owned()) + } + } + + /// Returns manifest for specified `target`. If not found, returns manifest for crate. + fn manifest_for_opt(&self, target: Option<&str>, dev: bool) -> Ext> { + target.and_then(|target| self.manifest_for(target, dev)) + .unwrap_or_else(|| self.manifest_for_crate().into_owned()) + } +} + + +pub(super) trait IntoOwned { + fn into_owned(self) -> T; +} + + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::metadata::format::Manifest; + use crate::metadata::format::Override; + use crate::metadata::format::Metadata; + use crate::metadata::format::MetadataInner; + use super::*; + + + // Default impl needed for tests only! + impl Default for Manifest { + fn default() -> Self { + Self { name: Default::default(), + version: Default::default(), + author: Default::default(), + bundle_id: Default::default(), + description: Default::default(), + image_path: Default::default(), + launch_sound_path: Default::default(), + content_warning: Default::default(), + content_warning2: Default::default(), + build_number: Default::default() } + } + } + + + #[test] + fn manifest_override() { + let base = Manifest { name: Some("Name"), + bundle_id: Some("dev.foo.bar"), + ..Default::default() }; + + let over = Manifest { name: Some("Over"), + bundle_id: None, + description: Some("description"), + ..Default::default() }; + + + { + let res = base.override_with(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + } + + { + let res = base.override_with(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + } + + { + let res = base.override_with_extra(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + assert!(res.iter_extra().is_none()); + } + } + + + #[test] + fn manifest_override_ext() { + let base = Manifest { name: Some("Name"), + bundle_id: Some("dev.foo.bar"), + ..Default::default() }; + + let mut extra = ExtraFields::with_capacity(1); + extra.insert("foo".into(), "bar".into()); + + + let base = Ext { main: base, extra }; + + let over = Manifest { name: Some("Over"), + bundle_id: None, + description: Some("description"), + ..Default::default() }; + + + { + let res = base.override_with(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + } + + { + let res = base.override_with(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + } + + { + let res = base.override_with_extra(&over); + assert_eq!(Some("Over"), res.name()); + assert_eq!(Some("dev.foo.bar"), res.bundle_id()); + assert_eq!(Some("description"), res.description()); + + assert!(res.iter_extra().is_some()); + let (k, v) = res.iter_extra().unwrap().into_iter().next().unwrap(); + assert_eq!("foo", k.as_ref()); + assert_eq!(&ExtraValue::String("bar".into()), v.as_ref()); + } + } + + + struct CrateInfoNoMeta; + impl CrateInfoSource for CrateInfoNoMeta { + fn name(&self) -> Cow { "Name".into() } + fn authors(&self) -> &[&str] { &["John"] } + fn version(&self) -> Cow { "0.0.0".into() } + fn description(&self) -> Option> { None } + fn metadata(&self) -> Option { None:: } + } + + #[test] + fn manifest_for_base() { + let base = CrateInfoNoMeta.manifest_for_crate(); + let spec = CrateInfoNoMeta.manifest_for_opt("target".into(), false); + let opt = CrateInfoNoMeta.manifest_for("target", false); + assert_eq!(opt, Some(spec.to_owned())); + assert_eq!(spec, base.into_owned()); + } + + + struct CrateInfo; + impl CrateInfoSource for CrateInfo { + fn name(&self) -> Cow { "Crate Name".into() } + fn authors(&self) -> &[&str] { &["John"] } + fn version(&self) -> Cow { "0.0.0".into() } + fn description(&self) -> Option> { None } + + fn metadata(&self) -> Option { + let base = Manifest { name: Some("Meta Name"), + bundle_id: Some("crate.id"), + ..Default::default() }; + + let mut extra = ExtraFields::with_capacity(1); + extra.insert("foo".into(), "bar".into()); + assert!(!extra.is_empty()); + + let manifest = Ext { main: base, extra }.into_owned(); + assert!(manifest.has_extra()); + + let bins = { + let base = Manifest { name: Some("Bin Name"), + author: Some("Alex"), + bundle_id: Some("bin.id"), + description: Some("description"), + ..Default::default() }; + + let mut extra = ExtraFields::with_capacity(1); + extra.insert("boo".into(), 42_usize.into()); + + + let manifest = Ext { main: base, extra }.into_owned(); + vec![Override { target: SOME_TARGET.to_owned(), + manifest }] + }; + + let meta = Metadata { inner: MetadataInner { manifest, + bins, + examples: vec![], + assets: Default::default(), + dev_assets: Default::default(), + options: Default::default(), + support: Default::default() } }; + + Some(meta) + } + } + + const SOME_TARGET: &str = "some-target"; + + #[test] + fn manifest_for_crate() { + let base = CrateInfo.manifest_for_crate(); + assert_eq!(Some("Meta Name"), base.name()); + assert_eq!(Some("John"), base.author()); + assert_eq!(Some("0.0.0"), base.version()); + assert_eq!(Some("crate.id"), base.bundle_id()); + assert!(base.description().is_none()); + assert!(base.has_extra()); + let extra = base.iter_extra() + .unwrap() + .into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())) + .collect::>(); + assert_eq!(1, extra.len()); + assert_eq!(Some(&"bar".into()), extra.get("foo")); + } + + #[test] + fn manifest_for_target_wrong_no_meta() { + let spec = CrateInfoNoMeta.manifest_for_opt(Some("WRONG"), false); + + assert_eq!(Some("Name"), spec.name()); + assert_eq!(Some("John"), spec.author()); + assert_eq!(Some("0.0.0"), spec.version()); + assert!(spec.bundle_id().is_none()); + } + + #[test] + fn manifest_for_target_wrong() { + let base = CrateInfo.manifest_for_crate(); + let spec = CrateInfo.manifest_for_opt(Some("WRONG"), false); + assert_eq!(Some("Meta Name"), spec.name()); + assert_eq!(Some("John"), spec.author()); + assert_eq!(Some("0.0.0"), spec.version()); + assert_eq!(Some("crate.id"), spec.bundle_id()); + assert_eq!(spec, base.into_owned()); + } + + #[test] + fn manifest_for_target_bin() { + let spec = CrateInfo.manifest_for_opt(SOME_TARGET.into(), false); + assert_eq!(Some("Bin Name"), spec.name()); + assert_eq!(Some("Alex"), spec.author()); + assert_eq!(Some("0.0.0"), spec.version()); + assert_eq!(Some("bin.id"), spec.bundle_id()); + assert_eq!(Some("description"), spec.description()); + let extra = spec.iter_extra() + .unwrap() + .into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())) + .collect::>(); + assert_eq!(2, extra.len()); + assert_eq!(Some(&"bar".into()), extra.get("foo")); + assert_eq!(Some(&42_usize.into()), extra.get("boo")); + } +} diff --git a/support/build/src/value.rs b/support/build/src/value.rs deleted file mode 100644 index 6ac608e3..00000000 --- a/support/build/src/value.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::Debug; -use std::fmt::Display; - -use crate::metadata::format::AssetsOptions; - -/// Value that can be __one of__ `bool` or `String`. -#[cfg(feature = "serde")] -pub trait Value: for<'de> serde::de::Deserialize<'de> + Clone + Debug + Display - where Self: TryInto { - fn as_bool(&self) -> Option; - fn as_str(&self) -> Option<&str>; -} - -/// Value that can be __one of__ `bool` or `String`, -/// without `serde::Deserialize` requirement. -#[cfg(not(feature = "serde"))] -pub trait Value: for<'de> Clone + Debug + Display - where Self: TryInto { - fn as_bool(&self) -> Option; - fn as_str(&self) -> Option<&str>; -} - - -#[cfg(feature = "serde_json")] -impl Value for serde_json::Value { - fn as_bool(&self) -> Option { serde_json::Value::as_bool(self) } - fn as_str(&self) -> Option<&str> { serde_json::Value::as_str(self) } -} - -#[cfg(feature = "toml")] -impl Value for toml::Value { - fn as_bool(&self) -> Option { toml::Value::as_bool(self) } - fn as_str(&self) -> Option<&str> { toml::Value::as_str(self) } -} - - -#[cfg(test)] -pub mod default { - use super::AssetsOptions; - - #[derive(Debug, Clone, PartialEq)] - pub enum Value { - Boolean(bool), - String(String), - } - - /// Fake `Value` for tests. - impl super::Value for Value { - fn as_bool(&self) -> Option { - match self { - Value::Boolean(v) => Some(*v), - Value::String(_) => None, - } - } - - fn as_str(&self) -> Option<&str> { - match self { - Value::Boolean(_) => None, - Value::String(s) => Some(s), - } - } - } - - impl std::fmt::Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Boolean(v) => v.fmt(f), - Value::String(v) => v.fmt(f), - } - } - } - - #[cfg(feature = "serde")] - impl<'t> serde::Deserialize<'t> for Value { - fn deserialize(_: D) -> Result - where D: serde::Deserializer<'t> { - unreachable!() - } - } - - impl TryInto for Value { - type Error = &'static str; - fn try_into(self) -> Result { unreachable!() } - } - - - impl From for Value { - fn from(value: bool) -> Self { Self::Boolean(value) } - } - - impl From<&str> for Value { - fn from(value: &str) -> Self { value.to_string().into() } - } - - impl From for Value { - fn from(value: String) -> Self { Self::String(value) } - } -} From c5048c0a9013472025356f28685b18dcc7b40fc2 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Tue, 28 May 2024 22:43:23 +0400 Subject: [PATCH 02/24] tests, improvements, minimal validation --- Cargo.lock | 1 + support/build/Cargo.toml | 1 + support/build/src/manifest/format.rs | 74 ++++- support/build/src/metadata/format.rs | 353 ++++++++++++----------- support/build/src/metadata/mod.rs | 1 + support/build/src/metadata/source.rs | 305 +++++++++++--------- support/build/src/metadata/validation.rs | 191 ++++++++++++ 7 files changed, 615 insertions(+), 311 deletions(-) create mode 100644 support/build/src/metadata/validation.rs diff --git a/Cargo.lock b/Cargo.lock index b706b0e4..0ac6aac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4109,6 +4109,7 @@ dependencies = [ "log", "playdate-build-utils", "regex", + "semver", "serde", "serde_json", "symlink", diff --git a/support/build/Cargo.toml b/support/build/Cargo.toml index f9167daa..82e2d952 100644 --- a/support/build/Cargo.toml +++ b/support/build/Cargo.toml @@ -17,6 +17,7 @@ log.workspace = true dirs.workspace = true fs_extra.workspace = true regex.workspace = true +semver.workspace = true wax = "0.6" symlink = "0.1" diff --git a/support/build/src/manifest/format.rs b/support/build/src/manifest/format.rs index ec7ea9df..2632febf 100644 --- a/support/build/src/manifest/format.rs +++ b/support/build/src/manifest/format.rs @@ -18,20 +18,20 @@ pub trait ManifestFmt { let is_not_empty = |s: &&str| !s.trim().is_empty(); { - let mut write_fmt = |k, v| to.write_fmt(format_args!("{}={}\n", k, v)); + let mut write_fmt = |k: &str, v: &str| to.write_fmt(format_args!("{}={}\n", k.trim(), v.trim())); if let Some(s) = data.name().filter(is_not_empty) { write_fmt("name", s)?; } + if let Some(s) = data.version().filter(is_not_empty) { + write_fmt("version", s)? + } if let Some(s) = data.author().filter(is_not_empty) { write_fmt("author", s)? } if let Some(s) = data.bundle_id().filter(is_not_empty) { write_fmt("bundleID", s)? } - if let Some(s) = data.version().filter(is_not_empty) { - write_fmt("version", s)? - } if let Some(s) = data.description().filter(is_not_empty) { write_fmt("description", s)? } @@ -57,7 +57,7 @@ pub trait ManifestFmt { for (key, value) in extra.into_iter() { let (key, value) = (key.as_ref(), value.as_ref()); if is_not_empty(&key) && !value.is_empty() { - to.write_fmt(format_args!("{}={}\n", key, value))? + to.write_fmt(format_args!("{}={}\n", key.trim(), value))? } } } @@ -76,3 +76,67 @@ pub trait ManifestFmt { impl ManifestFmt for T {} + + +#[cfg(test)] +mod tests { + use crate::metadata::format::Ext; + use crate::metadata::format::ExtraFields; + use super::ManifestFmt; + use super::Manifest; + + + #[test] + fn fmt_empty() { + let m = Manifest::<&str>::default(); + assert!(m.to_manifest_string().unwrap().is_empty()); + } + + #[test] + fn fmt_full() { + let m = Manifest::<&str> { name: "name".into(), + version: "version".into(), + author: "author".into(), + bundle_id: "bundle_id".into(), + description: "description".into(), + image_path: "image_path".into(), + launch_sound_path: "launch_sound_path".into(), + content_warning: "content_warning".into(), + content_warning2: "content_warning2".into(), + build_number: 42.into() }; + let s = m.to_manifest_string().unwrap(); + assert_eq!( + "name=name\nversion=version\nauthor=author\nbundleID=bundle_id\ndescription=description\nimagePath=image_path\nlaunchSoundPath=launch_sound_path\ncontentWarning=content_warning\ncontentWarning2=content_warning2\nbuildNumber=42\n", + s + ); + } + + #[test] + fn fmt_ext() { + let main = Manifest::<&str> { bundle_id: "bundle_id".into(), + ..Default::default() }; + let mut extra = ExtraFields::new(); + extra.insert("foo".to_owned(), "bar".to_owned().into()); + + let m = Ext::new(main, extra); + let s = m.to_manifest_string().unwrap(); + assert_eq!("bundleID=bundle_id\nfoo=bar\n", s); + } + + #[test] + fn fmt_trim() { + let main = Manifest::<&str> { name: " name".into(), + bundle_id: "bundle_id ".into(), + description: " description ".into(), + ..Default::default() }; + let mut extra = ExtraFields::new(); + extra.insert(" foo ".to_owned(), " bar ".to_owned().into()); + + let m = Ext::new(main, extra); + let s = m.to_manifest_string().unwrap(); + assert_eq!( + "name=name\nbundleID=bundle_id\ndescription=description\nfoo=bar\n", + s + ); + } +} diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 40ead947..b27eefd8 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -27,93 +27,6 @@ fn eq_metadata_field() { } -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "Main: Deserialize<'de>")))] -pub struct Ext
{ - #[cfg_attr(feature = "serde", serde(flatten))] - pub(super) main: Main, - #[cfg_attr(feature = "serde", serde(flatten))] - pub(super) extra: ExtraFields, -} - -impl Ext { - pub fn inner(&self) -> &T { &self.main } - pub fn extra(&self) -> &ExtraFields { &self.extra } -} - -impl Ext> where S: ToOwned { - pub fn clone_owned(self) -> Ext::Owned>> { - Ext { main: self.main.clone_owned(), - extra: self.extra.to_owned() } - } -} - - -pub type ExtraFields = HashMap; - - -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum ExtraValue { - Boolean(bool), - String(String), - Int(i64), -} - -impl ExtraValue { - pub fn is_empty(&self) -> bool { - match self { - Self::String(s) => s.trim().is_empty(), - _ => false, - } - } -} - -impl std::fmt::Display for ExtraValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Boolean(v) => v.fmt(f), - Self::String(v) => v.fmt(f), - Self::Int(v) => v.fmt(f), - } - } -} - -impl From for ExtraValue { - fn from(value: bool) -> Self { Self::Boolean(value.into()) } -} -impl From for ExtraValue { - fn from(value: i64) -> Self { Self::Int(value) } -} -impl From for ExtraValue { - fn from(value: isize) -> Self { Self::Int(value as _) } -} -impl From for ExtraValue { - fn from(value: u64) -> Self { Self::Int(value as _) } -} -impl From for ExtraValue { - fn from(value: usize) -> Self { Self::Int(value as _) } -} -impl From for ExtraValue { - fn from(value: String) -> Self { Self::String(value) } -} -impl From<&str> for ExtraValue { - fn from(value: &str) -> Self { Self::String(value.to_string()) } -} -impl<'t> From> for ExtraValue { - fn from(value: Cow<'t, str>) -> Self { Self::String(value.into_owned()) } -} - -impl AsRef for ExtraValue { - fn as_ref(&self) -> &ExtraValue { self } -} -impl AsMut for ExtraValue { - fn as_mut(&mut self) -> &mut ExtraValue { self } -} - - /// Package Playdate Metadata, contains: /// - Package Manifest fields /// - Assets tables - `assets` & `dev-assets` @@ -128,7 +41,6 @@ impl<'de> Deserialize<'de> for Metadata { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let meta = MetadataInner::deserialize(deserializer)?; - // TODO: validate fields, just critical retirements - bundle_id Ok(Self { inner: meta }) } } @@ -225,6 +137,130 @@ fn deserialize_targets_overrides<'de, D, S>(deserializer: D) -> Result { + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) main: Main, + #[cfg_attr(feature = "serde", serde(flatten))] + pub(super) extra: ExtraFields, +} + +impl Ext { + pub fn new(main: T, extra: ExtraFields) -> Self { Self { main, extra } } +} + +impl Ext { + pub fn inner(&self) -> &T { &self.main } + pub fn extra(&self) -> &ExtraFields { &self.extra } +} + +impl Ext> where S: ToOwned { + pub fn clone_owned(self) -> Ext::Owned>> { + Ext { main: self.main.clone_owned(), + extra: self.extra.to_owned() } + } +} + + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))] +pub struct Manifest { + pub name: Option, + pub version: Option, + pub author: Option, + #[cfg_attr(feature = "serde", serde(alias = "bundle-id"))] + pub bundle_id: Option, + pub description: Option, + #[cfg_attr(feature = "serde", serde(alias = "image-path"))] + pub image_path: Option, + #[cfg_attr(feature = "serde", serde(alias = "launch-sound-path"))] + pub launch_sound_path: Option, + #[cfg_attr(feature = "serde", serde(alias = "content-warning"))] + pub content_warning: Option, + #[cfg_attr(feature = "serde", serde(alias = "content-warning2"))] + pub content_warning2: Option, + #[cfg_attr(feature = "serde", serde(default, alias = "build-number"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_num_compat"))] + pub build_number: Option, +} + + +impl<'t, S> Cob<'t> for Manifest where S: Cob<'t> { + type Output = Manifest<>::Output>; + + fn as_borrow(&'t self) -> Self::Output { + Manifest { name: self.name.as_ref().map(Cob::as_borrow), + version: self.version.as_ref().map(Cob::as_borrow), + author: self.author.as_ref().map(Cob::as_borrow), + bundle_id: self.bundle_id.as_ref().map(Cob::as_borrow), + description: self.description.as_ref().map(Cob::as_borrow), + image_path: self.image_path.as_ref().map(Cob::as_borrow), + launch_sound_path: self.launch_sound_path.as_ref().map(Cob::as_borrow), + content_warning: self.content_warning.as_ref().map(Cob::as_borrow), + content_warning2: self.content_warning2.as_ref().map(Cob::as_borrow), + build_number: self.build_number.clone() } + } +} + +impl<'t, T> Cob<'t> for Ext where T: Cob<'t> { + type Output = Ext<>::Output>; + + fn as_borrow(&'t self) -> Self::Output { + let main = self.main.as_borrow(); + Ext { main, + extra: self.extra + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect() } + } +} + +impl<'t, T> Cob<'t> for Override where T: Cob<'t> { + type Output = Override<>::Output>; + + fn as_borrow(&'t self) -> Self::Output { + let Override { target, manifest } = self; + Override { target: target.as_borrow(), + manifest: manifest.as_borrow() } + } +} + + +impl IntoOwned::Owned>> for Manifest> { + fn into_owned(self) -> Manifest<::Owned> { + Manifest { name: self.name.map(|s| s.into_owned()), + version: self.version.map(|s| s.into_owned()), + author: self.author.map(|s| s.into_owned()), + bundle_id: self.bundle_id.map(|s| s.into_owned()), + description: self.description.map(|s| s.into_owned()), + image_path: self.image_path.map(|s| s.into_owned()), + launch_sound_path: self.launch_sound_path.map(|s| s.into_owned()), + content_warning: self.content_warning.map(|s| s.into_owned()), + content_warning2: self.content_warning2.map(|s| s.into_owned()), + build_number: self.build_number.clone() } + } +} + +impl Manifest where S: ToOwned { + pub fn clone_owned(self) -> Manifest<::Owned> { + Manifest { name: self.name.map(|s| s.to_owned()), + version: self.version.map(|s| s.to_owned()), + author: self.author.map(|s| s.to_owned()), + bundle_id: self.bundle_id.map(|s| s.to_owned()), + description: self.description.map(|s| s.to_owned()), + image_path: self.image_path.map(|s| s.to_owned()), + launch_sound_path: self.launch_sound_path.map(|s| s.to_owned()), + content_warning: self.content_warning.map(|s| s.to_owned()), + content_warning2: self.content_warning2.map(|s| s.to_owned()), + build_number: self.build_number.clone() } + } +} + + #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de> +Default")))] @@ -248,7 +284,6 @@ impl> Override { } } - impl> TargetId for Override { fn target(&self) -> &str { self.target.as_ref() } } @@ -306,91 +341,67 @@ impl Default for RuleValue { } +pub type ExtraFields = HashMap; + + #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))] -pub struct Manifest { - pub name: Option, - pub version: Option, - pub author: Option, - #[cfg_attr(feature = "serde", serde(alias = "bundle-id"))] - pub bundle_id: Option, - pub description: Option, - #[cfg_attr(feature = "serde", serde(alias = "image-path"))] - pub image_path: Option, - #[cfg_attr(feature = "serde", serde(alias = "launch-sound-path"))] - pub launch_sound_path: Option, - #[cfg_attr(feature = "serde", serde(alias = "content-warning"))] - pub content_warning: Option, - #[cfg_attr(feature = "serde", serde(alias = "content-warning2"))] - pub content_warning2: Option, - #[cfg_attr(feature = "serde", serde(default, alias = "build-number"))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_num_compat"))] - pub build_number: Option, +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum ExtraValue { + Boolean(bool), + String(String), + Int(i64), } -impl<'t, S: 't> Manifest where &'t S: Into> { - fn as_borrowed(&'t self) -> Manifest> { - Manifest { name: self.name.as_ref().map(Into::into), - version: self.version.as_ref().map(Into::into), - author: self.author.as_ref().map(Into::into), - bundle_id: self.bundle_id.as_ref().map(Into::into), - description: self.description.as_ref().map(Into::into), - image_path: self.image_path.as_ref().map(Into::into), - launch_sound_path: self.launch_sound_path.as_ref().map(Into::into), - content_warning: self.content_warning.as_ref().map(Into::into), - content_warning2: self.content_warning2.as_ref().map(Into::into), - build_number: self.build_number.clone() } +impl ExtraValue { + pub fn is_empty(&self) -> bool { + match self { + Self::String(s) => s.trim().is_empty(), + _ => false, + } } } -impl IntoOwned::Owned>> for Manifest> { - fn into_owned(self) -> Manifest<::Owned> { - Manifest { name: self.name.map(|s| s.into_owned()), - version: self.version.map(|s| s.into_owned()), - author: self.author.map(|s| s.into_owned()), - bundle_id: self.bundle_id.map(|s| s.into_owned()), - description: self.description.map(|s| s.into_owned()), - image_path: self.image_path.map(|s| s.into_owned()), - launch_sound_path: self.launch_sound_path.map(|s| s.into_owned()), - content_warning: self.content_warning.map(|s| s.into_owned()), - content_warning2: self.content_warning2.map(|s| s.into_owned()), - build_number: self.build_number.clone() } +impl std::fmt::Display for ExtraValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Boolean(v) => v.fmt(f), + Self::String(v) => v.trim().fmt(f), + Self::Int(v) => v.fmt(f), + } } } -impl Manifest where S: ToOwned { - pub fn clone_owned(self) -> Manifest<::Owned> { - Manifest { name: self.name.map(|s| s.to_owned()), - version: self.version.map(|s| s.to_owned()), - author: self.author.map(|s| s.to_owned()), - bundle_id: self.bundle_id.map(|s| s.to_owned()), - description: self.description.map(|s| s.to_owned()), - image_path: self.image_path.map(|s| s.to_owned()), - launch_sound_path: self.launch_sound_path.map(|s| s.to_owned()), - content_warning: self.content_warning.map(|s| s.to_owned()), - content_warning2: self.content_warning2.map(|s| s.to_owned()), - build_number: self.build_number.clone() } - } +impl From for ExtraValue { + fn from(value: bool) -> Self { Self::Boolean(value.into()) } +} +impl From for ExtraValue { + fn from(value: i64) -> Self { Self::Int(value) } +} +impl From for ExtraValue { + fn from(value: isize) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: u64) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: usize) -> Self { Self::Int(value as _) } +} +impl From for ExtraValue { + fn from(value: String) -> Self { Self::String(value) } +} +impl From<&str> for ExtraValue { + fn from(value: &str) -> Self { Self::String(value.to_string()) } +} +impl<'t> From> for ExtraValue { + fn from(value: Cow<'t, str>) -> Self { Self::String(value.into_owned()) } } - -#[cfg(feature = "serde")] -fn deserialize_num_compat<'de, D>(deserializer: D) -> Result, D::Error> - where D: Deserializer<'de> { - #[derive(Debug, Clone, PartialEq, Deserialize)] - #[serde(untagged)] - pub enum Value { - Num(usize), - Str(String), - } - let result = match Option::::deserialize(deserializer)? { - Some(Value::Num(value)) => Some(value), - Some(Value::Str(s)) => Some(s.parse().map_err(serde::de::Error::custom)?), - None => None, - }; - Ok(result) +impl AsRef for ExtraValue { + fn as_ref(&self) -> &ExtraValue { self } +} +impl AsMut for ExtraValue { + fn as_mut(&mut self) -> &mut ExtraValue { self } } @@ -610,6 +621,24 @@ pub struct Support { } +#[cfg(feature = "serde")] +fn deserialize_num_compat<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + #[derive(Debug, Clone, PartialEq, Deserialize)] + #[serde(untagged)] + pub enum Value { + Num(usize), + Str(String), + } + let result = match Option::::deserialize(deserializer)? { + Some(Value::Num(value)) => Some(value), + Some(Value::Str(s)) => Some(s.parse().map_err(serde::de::Error::custom)?), + None => None, + }; + Ok(result) +} + + #[cfg(test)] #[cfg(feature = "toml")] mod tests { diff --git a/support/build/src/metadata/mod.rs b/support/build/src/metadata/mod.rs index 60e40f24..bd519a3c 100644 --- a/support/build/src/metadata/mod.rs +++ b/support/build/src/metadata/mod.rs @@ -1,6 +1,7 @@ pub mod error; pub mod format; pub mod source; +pub mod validation; pub const METADATA_FIELD: &str = "playdate"; diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index d488cfef..12345fea 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -3,8 +3,92 @@ use std::borrow::Cow; use super::format::{AssetsOptions, AssetsRules, Ext, ExtraFields, ExtraValue, Manifest, Options, Support}; -pub trait TargetId { - fn target(&self) -> &str; +pub trait CrateInfoSource { + fn name(&self) -> Cow; + fn authors(&self) -> &[&str]; + fn version(&self) -> Cow; + fn description(&self) -> Option>; + fn metadata(&self) -> Option; + + // targets -> [name? or name+kind?] + + + fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { + use super::format::Manifest; + { + let author = { + let author = self.authors().join(", "); + if author.trim().is_empty() { + None + } else { + Some(author.into()) + } + }; + let version = Some(self.version()); + let package = Manifest { name: Some(self.name()), + description: self.description(), + author, + version, + bundle_id: None, + image_path: None, + launch_sound_path: None, + content_warning: None, + content_warning2: None, + build_number: None }; + + if let Some(meta) = self.metadata() { + let manifest = meta.manifest(); + let base = Ext { main: package, + extra: Default::default() }; + // TODO: Reduce coping, return associated type instead with all strings in the Cow<'self>. + // Also get merged manifest with refs, using `override_with_extra_ref` + let result = base.override_with_extra(&manifest); + Ext { main: Manifest::from(&result), + extra: result.iter_extra() + .map(|m| { + m.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } + } else { + Ext { main: package.into_owned(), + extra: Default::default() } + } + } + } + + + /// Returns `None` if manifest for `target` not found, no fallback. + fn manifest_for(&self, target: &str, dev: bool) -> Option>> { + let base = self.manifest_for_crate(); + + if let Some(root) = self.metadata() { + if dev { + if let Some(man) = root.example(target) { + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } + } else { + if let Some(man) = root.bin(target) { + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } + } + } else { + Some(base.into_owned()) + } + } + + /// Returns manifest for specified `target`. If not found, returns manifest for crate. + fn manifest_for_opt(&self, target: Option<&str>, dev: bool) -> Ext> { + target.and_then(|target| self.manifest_for(target, dev)) + .unwrap_or_else(|| self.manifest_for_crate().into_owned()) + } } @@ -55,23 +139,21 @@ pub trait MetadataSource { /// Make a manifest for a specific target, merged with base manifest for package. /// Returns `None` if the target is not found. fn manifest_for_target(&self, target: &str, dev: bool) -> Option { - use super::format::Manifest; - // manifest() returns T without lifetime, so can't be associated with `&self`, // that should be fixed let base = self.manifest(); if dev { if let Some(target) = self.example(target) { - let trg = base.override_with_extra(target); - Some(Ext::>::from(&trg)) + let trg = base.override_with_extra_ref(target); + Some(trg.into_owned()) } else { None } } else { if let Some(target) = self.bin(target) { - let trg = base.override_with_extra(target); - Some(Ext::>::from(&trg)) + let trg = base.override_with_extra_ref(target); + Some(trg.into_owned()) } else { None } @@ -125,6 +207,7 @@ pub trait ManifestSource { pub trait ManifestSourceOpt { /// Possibly incomplete, that means that some of values could be `None`. const MAY_BE_INCOMPLETE: bool; + fn name(&self) -> Option<&str>; fn version(&self) -> Option<&str>; fn author(&self) -> Option<&str>; @@ -136,25 +219,24 @@ pub trait ManifestSourceOpt { fn content_warning2(&self) -> Option<&str>; fn build_number(&self) -> Option; - fn override_with<'a, Over>(&'a self, overrider: &'a Over) -> impl ManifestSourceOpt + 'a + fn override_with<'a, Over>(&'a self, over: &'a Over) -> impl ManifestSourceOpt + 'a where Over: ManifestSourceOpt { use super::format::Manifest; - let trg = overrider; - Manifest::> { name: trg.name().or(self.name()).map(Into::into), - version: trg.version().or(self.version()).map(Into::into), - author: trg.author().or(self.author()).map(Into::into), - bundle_id: trg.bundle_id().or(self.bundle_id()).map(Into::into), - description: trg.description().or(self.description()).map(Into::into), - image_path: trg.image_path().or(self.image_path()).map(Into::into), - launch_sound_path: trg.launch_sound_path() - .or(self.launch_sound_path()) + Manifest::> { name: over.name().or(self.name()).map(Into::into), + version: over.version().or(self.version()).map(Into::into), + author: over.author().or(self.author()).map(Into::into), + bundle_id: over.bundle_id().or(self.bundle_id()).map(Into::into), + description: over.description().or(self.description()).map(Into::into), + image_path: over.image_path().or(self.image_path()).map(Into::into), + launch_sound_path: over.launch_sound_path() + .or(self.launch_sound_path()) + .map(Into::into), + content_warning: over.content_warning().or(self.content_warning()).map(Into::into), + content_warning2: over.content_warning2() + .or(self.content_warning2()) .map(Into::into), - content_warning: trg.content_warning().or(self.content_warning()).map(Into::into), - content_warning2: trg.content_warning2() - .or(self.content_warning2()) - .map(Into::into), - build_number: trg.build_number().or(self.build_number()) } + build_number: over.build_number().or(self.build_number()) } } } @@ -165,46 +247,45 @@ pub trait ManifestSourceOptExt: ManifestSourceOpt { fn has_extra(&self) -> bool { Self::MAY_HAVE_EXTRA && self.iter_extra().is_some() } fn iter_extra(&self) -> Option, impl AsRef)>>; - fn override_with_extra_ref<'t, Over: ManifestSourceOptExt>(&'t self, - overrider: &'t Over) - -> impl ManifestSourceOptExt + 't { - let manifest = self.override_with(overrider); - if overrider.has_extra() || self.has_extra() { - let extra = match (self.iter_extra(), overrider.iter_extra()) { - (None, None) => None, - (None, Some(extra)) => { - let result = extra.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); - Some(result.collect()) - }, - (Some(extra), None) => { - let result = extra.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); - Some(result.collect()) - }, - (Some(base), Some(extra)) => { - let mut result: ExtraFields = - base.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) - .collect(); - result.extend( - extra.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())), - ); - Some(result) - }, - }.unwrap_or_else(|| ExtraFields::with_capacity(0)); - - Ext { main: manifest, - extra } + fn override_with_extra_ref<'t, Over>(&'t self, over: &'t Over) -> impl ManifestSourceOptExt + 't + where Over: ManifestSourceOptExt { + let manifest = self.override_with(over); + let extra = if over.has_extra() || self.has_extra() { + match (self.iter_extra(), over.iter_extra()) { + (None, None) => None, + (None, Some(extra)) => { + let result = extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); + Some(result.collect()) + }, + (Some(extra), None) => { + let result = extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())); + Some(result.collect()) + }, + (Some(base), Some(extra)) => { + let mut result: ExtraFields = base.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect(); + result.extend( + extra.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())), + ); + Some(result) + }, + }.unwrap_or_else(|| ExtraFields::with_capacity(0)) } else { - Ext { main: manifest, - extra: ExtraFields::with_capacity(0) } - } + ExtraFields::with_capacity(0) + }; + + Ext { main: manifest, + extra } } - fn override_with_extra(&self, overrider: &Over) -> impl ManifestSourceOptExt { + fn override_with_extra(&self, + overrider: &Over) + -> impl ManifestSourceOptExt + Cob<'static> { self.override_with_extra_ref(overrider).into_manifest() } } @@ -248,102 +329,38 @@ impl<'t, T: ManifestSourceOptExt> ManifestSourceOptExt for &'t T where &'t T: Ma } +pub trait TargetId { + fn target(&self) -> &str; +} + + pub trait IntoManifest: Sized + ManifestSourceOptExt { fn into_manifest(self) -> Ext> { self.into_owned() } } impl IntoManifest for T {} -pub trait CrateInfoSource { - fn name(&self) -> Cow; - fn authors(&self) -> &[&str]; - fn version(&self) -> Cow; - fn description(&self) -> Option>; - fn metadata(&self) -> Option; - - // targets -> [name? or name+kind?] - - - fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { - use super::format::Manifest; - { - let author = { - let author = self.authors().join(", "); - if author.trim().is_empty() { - None - } else { - Some(author.into()) - } - }; - let version = Some(self.version()); - let package = Manifest { name: Some(self.name()), - description: self.description(), - author, - version, - bundle_id: None, - image_path: None, - launch_sound_path: None, - content_warning: None, - content_warning2: None, - build_number: None }; - - if let Some(meta) = self.metadata() { - let manifest = meta.manifest(); - let base = Ext { main: package, - extra: Default::default() }; - let result = base.override_with_extra(&manifest); - Ext { main: Manifest::from(&result), - extra: result.iter_extra() - .map(|m| { - m.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) - .collect() - }) - .unwrap_or_default() } - } else { - Ext { main: package.into_owned(), - extra: Default::default() } - } - } - } - - - /// Returns `None` if manifest for `target` not found, no fallback. - // fn manifest_for(&self, target: &str, dev: bool) -> Option { - fn manifest_for(&self, target: &str, dev: bool) -> Option>> { - let base = self.manifest_for_crate(); +pub(super) trait IntoOwned { + fn into_owned(self) -> T; +} - if let Some(root) = self.metadata() { - if dev { - if let Some(man) = root.example(target) { - Some(base.override_with_extra(man).into_owned()) - } else { - log::debug!("target not found: {}", target); - None - } - } else { - if let Some(man) = root.bin(target) { - Some(base.override_with_extra(man).into_owned()) - } else { - log::debug!("target not found: {}", target); - None - } - } - } else { - Some(base.into_owned()) - } - } - /// Returns manifest for specified `target`. If not found, returns manifest for crate. - fn manifest_for_opt(&self, target: Option<&str>, dev: bool) -> Ext> { - target.and_then(|target| self.manifest_for(target, dev)) - .unwrap_or_else(|| self.manifest_for_crate().into_owned()) - } +/// Cob as CopyBorrow - partially copy, partially borrow. +/// Used to produce instance of type with internally borrowed things from `self`. +pub trait Cob<'t> + where Self::Output: 't { + type Output; + fn as_borrow(&'t self) -> Self::Output; } +impl<'t> Cob<'t> for str where Self: 't { + type Output = Cow<'t, str>; + fn as_borrow(&'t self) -> Self::Output { self.into() } +} -pub(super) trait IntoOwned { - fn into_owned(self) -> T; +impl<'t, S: AsRef> Cob<'t> for S { + type Output = Cow<'t, str>; + fn as_borrow(&'t self) -> Self::Output { self.as_ref().into() } } diff --git a/support/build/src/metadata/validation.rs b/support/build/src/metadata/validation.rs new file mode 100644 index 00000000..f45cfdbf --- /dev/null +++ b/support/build/src/metadata/validation.rs @@ -0,0 +1,191 @@ +use super::source::CrateInfoSource; +use super::source::ManifestSourceOptExt; + + +#[derive(Debug, Clone)] +pub enum Problem { + UnknownTarget { name: String }, + MissingField { field: String }, + Warning(Warning), +} + +#[derive(Debug, Clone)] +pub enum Warning { + StrangeValue { + field: String, + value: String, + reason: Option<&'static str>, + }, + UnknownField { + field: String, + reason: Option<&'static str>, + }, + MissingField { + field: String, + reason: Option<&'static str>, + }, +} + +impl Problem { + pub fn is_err(&self) -> bool { + match self { + Problem::Warning(_) => false, + _ => true, + } + } +} + + +/// Check the implementor validity. +pub trait Validate { + /// Check critical requirements, returns it as errors. + /// Also returns warnings fo not so critical problems. + /// Use it before render the final result. + fn validate(&self) -> impl IntoIterator; +} + +impl Validate for T { + fn validate(&self) -> impl IntoIterator { + let is_not_empty = |s: &&str| !s.trim().is_empty(); + + fn check_some(name: &'static str, v: Option) -> Option { + v.is_none().then(|| Problem::MissingField { field: name.into() }) + } + + fn warn_none(name: &'static str, v: Option, warn_msg: Option<&'static str>) -> Option { + v.is_none().then(|| { + Problem::Warning(Warning::MissingField { field: name.into(), + reason: warn_msg }) + }) + } + + + let missed = [ + ( + "build-number", + self.build_number().is_some(), + Some("Required for sideloaded games."), + ), + ("description", self.description().is_some(), None), + ].into_iter() + .filter_map(|(k, v, msg)| warn_none(k, v.then_some(()), msg)); + + + let unknown = self.iter_extra().into_iter().flatten().map(|(k, _)| { + Problem::Warning(Warning::UnknownField { field: k.as_ref() + .to_owned(), + reason: None }) + }); + + + // required fields + let errors = [ + ("name", self.name().filter(is_not_empty)), + ("version", self.version().filter(is_not_empty)), + ("bundle-id", self.bundle_id().filter(is_not_empty)), + ].into_iter() + .filter_map(|(k, v)| check_some(k, v)); + + + errors.chain(missed) + .chain(self.version().into_iter().filter_map(validate_version)) + .chain(unknown) + } +} + + +fn validate_version(value: &str) -> Option { + let re = regex::Regex::new(r"^\d+(?:\.\d+){0,2}$").unwrap(); + if !re.is_match(value.trim()) { + if semver::Version::parse(value).is_err() { + Some(Problem::Warning(Warning::StrangeValue { field: "version".into(), + value: value.into(), + reason: Some("Can be confusing.") })) + } else { + None + } + } else { + None + } +} + + +/// Lint the crate-level source. +pub trait ValidateCrate: CrateInfoSource { + fn validate(&self) -> impl IntoIterator { + // - main manifest missing fields + // - main manifest fields in bad format + // - for each final target manifest: + // -> same as for the main manifest + + + if let Some(_meta) = self.metadata() { + // Check that all targets are exists + // - search the target in the crate for each in meta.all_targets() + } else { + // - warn: no metadata found + } + + // just temporary this, because not implemented yet: + self.manifest_for_crate() + .validate() + .into_iter() + .collect::>() + } + + + fn validate_for(&self, _target: &str) -> impl IntoIterator { [] } +} + +impl ValidateCrate for T where T: CrateInfoSource {} + + +#[cfg(test)] +mod tests { + use super::Validate; + use super::super::format::Manifest; + + + #[test] + fn validate_version() { + let cases = [ + ("0", true), + ("0.0", true), + ("0.0.0", true), + ("0.0.0-pre", true), + ("", false), + ("0.0.a", false), + ("beta", false), + ]; + + for (i, (ver, ok)) in cases.iter().enumerate() { + let result = super::validate_version(ver); + assert_eq!(*ok, result.is_none(), "{i}: {result:?}"); + } + } + + + #[test] + fn manifest_empty() { + let m = Manifest::<&str>::default(); + let errors = m.validate().into_iter().collect::>(); + assert_eq!(5, errors.len(), "{:#?}", errors); + assert_eq!(3, errors.iter().filter(|e| e.is_err()).count()); + } + + #[test] + fn manifest_valid() { + let m = Manifest::<&str> { name: "name".into(), + version: "0.0".into(), + author: "author".into(), + bundle_id: "bundle.id".into(), + description: "description".into(), + image_path: "image_path".into(), + launch_sound_path: "launch_sound_path".into(), + content_warning: "content_warning".into(), + content_warning2: "content_warning2".into(), + build_number: 42.into() }; + let errors = m.validate().into_iter().collect::>(); + assert!(errors.is_empty(), "{:#?}", errors); + } +} From 34606e230eab7131c84e617605505d15d2493c13 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Tue, 28 May 2024 23:03:03 +0400 Subject: [PATCH 03/24] Fix possible recursion in env resolver, remove panic on missed var --- support/build/src/assets/resolver.rs | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/support/build/src/assets/resolver.rs b/support/build/src/assets/resolver.rs index c4d5a450..c0b417e5 100644 --- a/support/build/src/assets/resolver.rs +++ b/support/build/src/assets/resolver.rs @@ -122,9 +122,13 @@ impl EnvResolver { pub fn str<'c, S: AsRef>(&self, s: S, env: &'c Env) -> Cow<'c, str> { let re = &self.0; - // XXX: possible recursion for case "${VAR}" where $VAR="${VAR}" + // Possible recursion for case "${VAR}" where $VAR="${VAR}" + let mut anti_recursion_counter: u8 = 42; + let mut replaced = String::from(s.as_ref()); - while re.is_match(replaced.as_str()) { + while re.is_match(replaced.as_str()) && anti_recursion_counter > 0 { + anti_recursion_counter -= 1; + if let Some(captures) = re.captures(replaced.as_str()) { let full = &captures[0]; let name = &captures[2]; @@ -133,12 +137,11 @@ impl EnvResolver { .get(name) .map(Cow::from) .or_else(|| std::env::var(name).map_err(log_err).ok().map(Cow::from)) - .unwrap_or_else(|| { - // XXX: should we panic here? - panic!("Env var \"{name}\" not found") - }); + .unwrap_or_else(|| name.into()); replaced = replaced.replace(full, &var); + } else { + break; } } replaced.into() @@ -534,11 +537,23 @@ mod tests { } #[test] - #[should_panic] fn resolver_missed() { let resolver = EnvResolver::new(); let env = Env::try_default().unwrap(); - let expr = Expr::from("${MISSED}/file.txt"); + let mut expr = Expr::from("${MISSED}/file.txt"); + resolver.expr(&mut expr, &env); + + assert_eq!("MISSED/file.txt", expr.actual()); + assert_eq!("MISSED/file.txt", expr.as_str()); + assert_eq!("${MISSED}/file.txt", expr.original()); + } + + #[test] + fn resolver_recursion() { + let resolver = EnvResolver::new(); + let mut env = Env::try_default().unwrap(); + env.vars.insert("VAR".into(), "${VAR}".into()); + let expr = Expr::from("${VAR}/file.txt"); resolver.expr(expr, &env); } } From e8c02cccd721cb758308acf0dc91d64967cc152d Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 29 May 2024 01:30:52 +0400 Subject: [PATCH 04/24] Add sources: unit-graph and full metadata by host cargo, MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use new manifest builder from `playdate-build` pre-0.4 * little `cargo-playdate` refactoring 🥵 --- Cargo.lock | 2 +- cargo/Cargo.toml | 4 +- cargo/src/assets/mod.rs | 23 +-- cargo/src/assets/plan.rs | 20 +- cargo/src/build/mod.rs | 2 +- cargo/src/build/plan/format.rs | 145 --------------- cargo/src/build/plan/mod.rs | 83 --------- cargo/src/config.rs | 26 ++- cargo/src/main.rs | 1 + cargo/src/package/mod.rs | 138 +++++++++----- cargo/src/proc/reader.rs | 92 +--------- cargo/src/proc/utils.rs | 66 ++++++- cargo/src/utils/cargo/build_plan.rs | 116 ++++++++++++ cargo/src/utils/cargo/format.rs | 202 +++++++++++++++++++++ cargo/src/utils/cargo/metadata.rs | 165 +++++++++++++++++ cargo/src/utils/{cargo.rs => cargo/mod.rs} | 7 + cargo/src/utils/cargo/unit_graph.rs | 73 ++++++++ cargo/src/utils/mod.rs | 3 +- cargo/tests/crates/metadata/Cargo.toml | 2 +- support/build/src/metadata/validation.rs | 50 ++++- 20 files changed, 812 insertions(+), 408 deletions(-) delete mode 100644 cargo/src/build/plan/format.rs delete mode 100644 cargo/src/build/plan/mod.rs create mode 100644 cargo/src/utils/cargo/build_plan.rs create mode 100644 cargo/src/utils/cargo/format.rs create mode 100644 cargo/src/utils/cargo/metadata.rs rename cargo/src/utils/{cargo.rs => cargo/mod.rs} (89%) create mode 100644 cargo/src/utils/cargo/unit_graph.rs diff --git a/Cargo.lock b/Cargo.lock index 0ac6aac9..b641e38b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -742,7 +742,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.4.14" +version = "0.5.0-pre1" dependencies = [ "anstyle", "anyhow", diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 04ac5cb7..949610f4 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.4.14" +version = "0.5.0-pre1" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] @@ -59,7 +59,7 @@ async-std = { version = "1.12", features = ["tokio1"] } [dependencies.build] workspace = true default-features = false -features = ["assets-report", "toml"] +features = ["assets-report", "toml", "json"] [dependencies.device] workspace = true diff --git a/cargo/src/assets/mod.rs b/cargo/src/assets/mod.rs index 712e08ed..876af7bc 100644 --- a/cargo/src/assets/mod.rs +++ b/cargo/src/assets/mod.rs @@ -5,7 +5,9 @@ use std::path::{PathBuf, Path}; use anstyle::AnsiColor as Color; use anyhow::bail; use cargo::CargoResult; -use cargo::core::{Package, Verbosity}; +use cargo::core::{Package, PackageId, Verbosity}; +use playdate::manifest::ManifestSourceOpt as _; +use playdate::metadata::source::MetadataSource as _; use playdate::metadata::METADATA_FIELD; use playdate::layout::Layout; @@ -15,7 +17,7 @@ use crate::layout::{PlaydateAssets, LayoutLockable, Layout as _, CrossTargetLayo use crate::logger::LogErr; use crate::utils::LazyBuildContext; use crate::utils::path::AsRelativeTo; -use self::plan::TomlMetadata; +use self::plan::Metadata; mod plan; @@ -23,15 +25,15 @@ mod pdc; #[derive(Debug)] -pub struct AssetsArtifact<'cfg> { - pub package: &'cfg Package, +pub struct AssetsArtifact { + pub package_id: PackageId, pub layout: PlaydateAssets, /// Cached metadata - pub metadata: Option, + pub metadata: Option, } /// One artifact per package. -pub type AssetsArtifacts<'cfg> = HashMap<&'cfg Package, AssetsArtifact<'cfg>>; +pub type AssetsArtifacts<'cfg> = HashMap<&'cfg Package, AssetsArtifact>; pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { @@ -348,7 +350,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { let metadata = options.remove(package); artifacts.insert( package, - AssetsArtifact { package, + AssetsArtifact { package_id: package.package_id(), layout, metadata, }, ); @@ -369,7 +371,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { fn deps_tree_metadata<'cfg: 'r, 't: 'r, 'r>(package: &'cfg Package, bcx: &'t LazyBuildContext<'t, 'cfg>, config: &Config<'_>) - -> CargoResult> { + -> CargoResult> { let mut packages = HashMap::new(); if let Some(metadata) = playdate_metadata(package) { // if explicitly allowed collect deps => scan deps-tree @@ -470,11 +472,10 @@ fn deps_tree_metadata<'cfg: 'r, 't: 'r, 'r>(package: &'cfg Package, } -pub fn playdate_metadata(package: &Package) -> Option { +pub fn playdate_metadata(package: &Package) -> Option { package.manifest() .custom_metadata() .and_then(|m| m.as_table().map(|t| t.get(METADATA_FIELD))) .flatten() - .and_then(|v| v.to_owned().try_into::().log_err().ok()) - .and_then(|mut m| m.merge_opts().map(|_| m).log_err().ok()) + .and_then(|v| v.to_owned().try_into::().log_err().ok()) } diff --git a/cargo/src/assets/plan.rs b/cargo/src/assets/plan.rs index d8be5198..b5a6debe 100644 --- a/cargo/src/assets/plan.rs +++ b/cargo/src/assets/plan.rs @@ -7,11 +7,12 @@ use playdate::assets::BuildReport; use playdate::assets::apply_build_plan; use playdate::config::Env; use playdate::metadata::format::AssetsOptions; +use playdate::metadata::source::MetadataSource as _; use crate::config::Config; use crate::utils::path::AsRelativeTo; use playdate::consts::SDK_ENV_VAR; use cargo::util::CargoResult; -use playdate::metadata::format::PlayDateMetadata; +pub use playdate::metadata::format::Metadata; use playdate::assets::plan::BuildPlan as AssetsPlan; use playdate::assets::plan::build_plan as assets_build_plan; use try_lazy_init::Lazy; @@ -19,9 +20,6 @@ use try_lazy_init::Lazy; use crate::layout::{PlaydateAssets, LayoutLock}; -pub type TomlMetadata = PlayDateMetadata; - - pub struct LazyEnvBuilder<'a, 'cfg> { config: &'a Config<'cfg>, package: &'cfg Package, @@ -68,15 +66,15 @@ pub type LockedLayout<'t> = LayoutLock<&'t mut PlaydateAssets>; /// Returns `None` if there is no `assets` metadata. pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, package: &'cfg Package, - metadata: &TomlMetadata, + metadata: &Metadata, env: &'cfg LazyEnvBuilder<'env, 'cfg>, layout: &'l LockedLayout<'l>, with_dev: bool) -> CargoResult> { let opts = metadata.assets_options(); - let has_dev_assets = with_dev && metadata.dev_assets.iter().any(|t| !t.is_empty()); - let is_empty = metadata.assets.is_empty() && !has_dev_assets; + let has_dev_assets = with_dev && !metadata.dev_assets().is_empty(); + let is_empty = !metadata.assets().is_empty() && !has_dev_assets; if is_empty { return Ok(PackageAssetsPlan { main: None, @@ -88,8 +86,8 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, .parent() .ok_or(anyhow!("No parent of manifest-path"))?; - let main = if !metadata.assets.is_empty() { - let plan = assets_build_plan(env, &metadata.assets, opts.as_ref(), Some(root))?; + let main = if !metadata.assets().is_empty() { + let plan = assets_build_plan(env, metadata.assets(), opts.as_ref(), Some(root))?; // main-assets plan: let path = layout.as_inner().assets_plan_for(config, package); @@ -105,8 +103,8 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, // dev-assets plan: - let dev = if has_dev_assets && metadata.dev_assets.is_some() { - let assets = metadata.dev_assets.as_ref().unwrap(); + let dev = if has_dev_assets && !metadata.dev_assets().is_empty() { + let assets = metadata.dev_assets(); let dev_plan = assets_build_plan(env, assets, opts.as_ref(), Some(root))?; let path = layout.as_inner().assets_plan_for_dev(config, package); diff --git a/cargo/src/build/mod.rs b/cargo/src/build/mod.rs index 2d084e22..b8ef6200 100644 --- a/cargo/src/build/mod.rs +++ b/cargo/src/build/mod.rs @@ -38,8 +38,8 @@ use crate::utils::cargo::CompileKindExt; use crate::utils::path::AsRelativeTo; use crate::utils::workspace::PossibleTargets; +use crate::utils::cargo::build_plan as plan; -pub mod plan; pub mod rustflags; diff --git a/cargo/src/build/plan/format.rs b/cargo/src/build/plan/format.rs deleted file mode 100644 index b484b017..00000000 --- a/cargo/src/build/plan/format.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::collections::BTreeMap; -use std::path::PathBuf; -use cargo::core::compiler::CompileTarget; -use cargo::core::compiler::CrateType; -use cargo::util::command_prelude::CompileMode; -use cargo::core::compiler::CompileKind; -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::Serializer; - - -#[derive(Debug, Serialize, Deserialize)] -pub struct BuildPlan { - /// Program invocations needed to build the target (along with dependency information). - pub invocations: Vec, - /// List of Cargo manifests involved in the build. - pub inputs: Vec, -} - -/// A tool invocation. -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub struct Invocation { - /// The package this invocation is building a part of. - pub package_name: String, - /// Version of the package that is being built. - pub package_version: semver::Version, - /// The kind of artifact this invocation creates. - pub target_kind: TargetKind, - /// Whether the files created by this invocation are for the host or target system. - #[serde(serialize_with = "CompileKind::serialize")] - #[serde(deserialize_with = "deserialize_compile_kind")] - pub kind: CompileKind, - #[serde(serialize_with = "CompileMode::serialize")] - #[serde(deserialize_with = "CompileModeProxy::deserialize")] - pub compile_mode: CompileMode, - /// List of invocations this invocation depends on. - /// - /// The vector contains indices into the [`BuildPlan::invocations`] list. - /// - /// [`BuildPlan::invocations`]: struct.BuildPlan.html#structfield.invocations - pub deps: Vec, - /// List of output artifacts (binaries/libraries) created by this invocation. - pub outputs: Vec, - /// Hardlinks of output files that should be placed. - pub links: BTreeMap, - /// The program to invoke. - pub program: String, - /// Arguments to pass to the program. - pub args: Vec, - /// Map of environment variables. - pub env: BTreeMap, - /// The working directory in which to execute the program. - pub cwd: Option, -} - -#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[serde(rename_all = "kebab-case")] -#[serde(remote = "CompileMode")] -pub enum CompileModeProxy { - /// A target being built for a test. - Test, - /// Building a target with `rustc` (lib or bin). - Build, - /// Building a target with `rustc` to emit `rmeta` metadata only. If - /// `test` is true, then it is also compiled with `--test` to check it like - /// a test. - Check { test: bool }, - /// Used to indicate benchmarks should be built. This is not used in - /// `Unit`, because it is essentially the same as `Test` (indicating - /// `--test` should be passed to rustc) and by using `Test` instead it - /// allows some de-duping of Units to occur. - Bench, - /// A target that will be documented with `rustdoc`. - /// If `deps` is true, then it will also document all dependencies. - Doc { deps: bool, json: bool }, - /// A target that will be tested with `rustdoc`. - Doctest, - /// An example or library that will be scraped for function calls by `rustdoc`. - Docscrape, - /// A marker for Units that represent the execution of a `build.rs` script. - RunCustomBuild, -} - - -fn deserialize_compile_kind<'de, D>(deserializer: D) -> Result - where D: Deserializer<'de> { - let res = if let Some(s) = Option::<&str>::deserialize(deserializer)? { - let target = CompileTarget::new(s).map_err(serde::de::Error::custom)?; - CompileKind::Target(target) - } else { - CompileKind::Host - }; - Ok(res) -} - - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -// #[serde(remote = "cargo::core::TargetKind")] -pub enum TargetKind { - Lib(Vec), - Bin, - Test, - Bench, - Example, - CustomBuild, -} - -impl<'de> Deserialize<'de> for TargetKind { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> { - use self::TargetKind::*; - - let raw = Vec::<&str>::deserialize(deserializer)?; - Ok(match *raw { - [] => return Err(serde::de::Error::invalid_length(0, &"at least one target kind")), - ["bin"] => Bin, - ["example"] => Example, - ["test"] => Test, - ["custom-build"] => CustomBuild, - ["bench"] => Bench, - ref lib_kinds => { - Lib(lib_kinds.iter() - .cloned() - .map(|s| CrateType::from(&s.to_owned())) - .collect()) - }, - }) - } -} - -impl Serialize for TargetKind { - fn serialize(&self, s: S) -> Result - where S: Serializer { - use self::TargetKind::*; - match self { - Lib(kinds) => s.collect_seq(kinds.iter().map(|t| t.to_string())), - Bin => ["bin"].serialize(s), - Example => ["example"].serialize(s), - Test => ["test"].serialize(s), - CustomBuild => ["custom-build"].serialize(s), - Bench => ["bench"].serialize(s), - } - } -} diff --git a/cargo/src/build/plan/mod.rs b/cargo/src/build/plan/mod.rs deleted file mode 100644 index 7df97738..00000000 --- a/cargo/src/build/plan/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use cargo::CargoResult; -use cargo::core::PackageId; -use cargo::util::command_prelude::CompileMode; -use crate::cli::cmd::Cmd; -use crate::config::Config; -use crate::proc::args_line_for_proc; -use crate::proc::cargo_proxy_cmd; -use self::format::TargetKind; - -pub mod format; - - -pub fn build_plan(cfg: &Config) -> CargoResult { - let config = cfg.workspace.config(); - let mut cargo = cargo_proxy_cmd(cfg, &Cmd::Build)?; - - if !cfg.compile_options.build_config.build_plan { - cargo.args(["--build-plan", "-Zunstable-options"]); - } - - cfg.log() - .verbose(|mut log| log.status("Cargo", args_line_for_proc(&cargo))); - - let output = cargo.output()?; - if !output.status.success() { - config.shell().err().write_all(&output.stderr)?; - output.status.exit_ok()?; - } - - let stdout = std::str::from_utf8(&output.stdout)?; - - // parse only last line of output: - let line = stdout.lines() - .find(|s| { - let s = s.trim(); - !s.is_empty() && s.starts_with('{') - }) - .unwrap_or("{}"); - - let value: format::BuildPlan = serde_json::de::from_str(line)?; - Ok(value) -} - - -impl format::BuildPlan { - pub fn build_package_invocations<'plan: 'i, 'p: 'i, 'i>( - &'plan self, - package: &'p PackageId) - -> impl Iterator + 'i { - self.invocations - .iter() - .filter(move |item| { - item.package_name == package.name().as_str() && package.version() == &item.package_version - }) - .filter(|item| item.compile_mode == CompileMode::Build) - } -} - - -#[allow(dead_code)] -pub enum TargetKindWild { - Lib, - Bin, - Test, - Bench, - ExampleLib, - ExampleBin, - CustomBuild, -} - -impl PartialEq for TargetKindWild { - fn eq(&self, other: &TargetKind) -> bool { - match self { - TargetKindWild::Lib => matches!(other, TargetKind::Lib(_)), - TargetKindWild::Bin => matches!(other, TargetKind::Bin), - TargetKindWild::Test => matches!(other, TargetKind::Test), - TargetKindWild::Bench => matches!(other, TargetKind::Bench), - TargetKindWild::ExampleLib => matches!(other, TargetKind::Example), - TargetKindWild::ExampleBin => matches!(other, TargetKind::Example), - TargetKindWild::CustomBuild => matches!(other, TargetKind::CustomBuild), - } - } -} diff --git a/cargo/src/config.rs b/cargo/src/config.rs index 1e6369cf..33874c6f 100644 --- a/cargo/src/config.rs +++ b/cargo/src/config.rs @@ -16,7 +16,6 @@ use crate::cli::cmd::Cmd; use crate::cli::deps::Dependency; use crate::cli::ide::Ide; use crate::cli::opts::Mount; -use crate::utils::LazyBuildContext; pub struct Config<'cfg> { @@ -62,7 +61,9 @@ pub struct Config<'cfg> { sdk: Lazy, gcc: Lazy, rustflags: Lazy, - build_plan: Lazy, + build_plan: Lazy, + unit_graph: Lazy, + ws_metadata: Lazy, target_infos: Lazy>>, pub rustc: Rustc, @@ -130,6 +131,8 @@ impl<'cfg> Config<'cfg> { gcc: Lazy::new(), rustflags: Lazy::new(), build_plan: Lazy::new(), + unit_graph: Lazy::new(), + ws_metadata: Lazy::new(), target_infos: Lazy::new(), rustup: Default::default() } } @@ -163,9 +166,19 @@ impl<'cfg> Config<'cfg> { }) } - pub fn build_plan(&self) -> CargoResult<&crate::build::plan::format::BuildPlan> { + pub fn build_plan(&self) -> CargoResult<&crate::utils::cargo::build_plan::format::BuildPlan> { self.build_plan - .try_get_or_create(|| crate::build::plan::build_plan(self)) + .try_get_or_create(|| crate::utils::cargo::build_plan::build_plan(self)) + } + + pub fn unit_graph(&self) -> CargoResult<&crate::utils::cargo::unit_graph::format::UnitGraph> { + self.unit_graph + .try_get_or_create(|| crate::utils::cargo::unit_graph::unit_graph(self)) + } + + pub fn metadata(&self) -> CargoResult<&crate::utils::cargo::metadata::CargoMetadataPd> { + self.ws_metadata + .try_get_or_create(|| crate::utils::cargo::metadata::metadata(self)) } pub fn target_info_for(&self, kind: CompileKind) -> CargoResult<&TargetInfo> { @@ -179,11 +192,6 @@ impl<'cfg> Config<'cfg> { .map(|v| v.try_get_or_create(|| self.target_info(kind))) .ok_or_else(|| anyhow::anyhow!("Target-info for unexpected {kind:?}, not prepared."))? } - - - pub fn create_bcx<'t: 'cfg>(&'t self) -> CargoResult> { - LazyBuildContext::new(self) - } } diff --git a/cargo/src/main.rs b/cargo/src/main.rs index 7ac28210..9ac39dd6 100644 --- a/cargo/src/main.rs +++ b/cargo/src/main.rs @@ -4,6 +4,7 @@ #![feature(btree_extract_if)] #![feature(byte_slice_trim_ascii)] #![feature(const_trait_impl)] +#![feature(let_chains)] extern crate build as playdate; diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index b409d3ea..0340dbbe 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; +use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; use std::process::Command; @@ -17,8 +18,10 @@ use clap_lex::OsStrExt; use playdate::fs::soft_link_checked; use playdate::layout::Layout; use playdate::layout::Name; -use playdate::manifest::ManifestDataSource; -use playdate::manifest::format::Manifest; +use playdate::manifest::format::ManifestFmt; +use playdate::manifest::CrateInfoSource; +use playdate::metadata::format::Metadata; +use playdate::metadata::validation::Validate; use crate::assets::AssetsArtifact; use crate::assets::AssetsArtifacts; @@ -98,7 +101,7 @@ fn package_single_target<'p>(config: &Config, ); if let Some(assets) = assets { - assert_eq!(assets.package, product.package); + assert_eq!(assets.package_id, product.package.package_id()); log::debug!("Preparing assets for packaging {}", product.presentable_name()); prepare_assets( @@ -112,9 +115,16 @@ fn package_single_target<'p>(config: &Config, } // manifest: - let ext_id = product.example.then(|| format!("dev.{}", product.name).into()); - let ext_name = product.example.then_some(product.name.as_str().into()); - build_manifest(config, &product.layout, product.package, assets, ext_id, ext_name)?; + let cargo_target = (matches!(product.src_ct, CrateType::Bin) || product.example).then_some(&product.name) + .map(Cow::from); + build_manifest( + config, + &product.layout, + product.package, + assets, + cargo_target, + product.example, + )?; // finally call pdc and pack: let mut artifact = execute_pdc(config, &product.layout)?; @@ -157,6 +167,23 @@ fn package_multi_target<'p>(config: &Config, .map(|p| format!("{}", p.dst_ct)) .collect::>() .join(", "); + + let cargo_targets = products.iter().fold(HashSet::new(), |mut set, product| { + set.insert(product.name.as_str()); + set + }); + if cargo_targets.len() > 1 { + // TODO: instead of this, group them by cargo-target - one or two for single cargo-target. + let list = cargo_targets.into_iter().collect::>().join(", "); + let msg = "Multiple cargo-targets not supported:"; + if !config.compile_options.build_config.keep_going { + bail!("{msg} [{list}]"); + } else { + config.log() + .error(format!("{msg} [{list}] (sources: {src_cts}, targets: {dst_cts})",)); + } + } + config.log().status( "Packaging", format!( @@ -234,8 +261,8 @@ fn package_multi_target<'p>(config: &Config, // Then the same as for single-product package: if let Some(assets) = assets { - log::debug!("Preparing assets for packaging {}", assets.package.name()); - assert_eq!(package, assets.package, "package must be same"); + log::debug!("Preparing assets for packaging {}", assets.package_id.name()); + assert_eq!(package.package_id(), assets.package_id, "package must be same"); prepare_assets( config, assets, @@ -247,9 +274,17 @@ fn package_multi_target<'p>(config: &Config, } // manifest: - let ext_id = dev.and_then(|p| p.example.then(|| format!("dev.{}", p.name).into())); - let ext_name = dev.and_then(|p| p.example.then_some(p.name.as_str().into())); - build_manifest(config, &layout, package, assets, ext_id, ext_name)?; + let cargo_target = + (matches!(products[0].src_ct, CrateType::Bin) || products[0].example).then_some(products[0].name.as_str()) + .map(Cow::from); + build_manifest( + config, + &layout, + package, + assets, + cargo_target, + products[0].example, + )?; // finally call pdc and pack: let mut artifact = execute_pdc(config, &layout)?; @@ -277,42 +312,35 @@ fn package_multi_target<'p>(config: &Config, fn build_manifest(config: &Config, layout: &Layout, package: &Package, - assets: Option<&AssetsArtifact<'_>>, - id_suffix: Option>, - name_override: Option>) + assets: Option<&AssetsArtifact>, + cargo_target: Option>, + dev: bool) -> CargoResult<()> { config.log().verbose(|mut log| { let msg = format!("building package manifest for {}", package.package_id()); log.status("Manifest", msg); }); - let mut manifest = if let Some(metadata) = assets.and_then(|a| a.metadata.as_ref()) { - let source = ManifestSource { package, - metadata: metadata.into() }; - Manifest::try_from_source(source) - } else { - let metadata = playdate_metadata(package); - let source = ManifestSource { package, - metadata: metadata.as_ref() }; - Manifest::try_from_source(source) - }.map_err(|err| anyhow!(err))?; - - // Override fields. This is a hacky not-so-braking hot-fix for issue #354. - // This is a temporary solution only until full metadata inheritance is implemented. - if id_suffix.is_some() || name_override.is_some() { - if let Some(id) = id_suffix { - log::trace!("Overriding bundle_id from {}", manifest.bundle_id); - manifest.bundle_id.push_str(".example."); - manifest.bundle_id.push_str(&id); - log::trace!(" to {}", manifest.bundle_id); - } - if let Some(name) = name_override { - log::trace!("Overriding program name {} -> {name}", manifest.name); - manifest.name = name.into_owned(); + let manifest = if let Some(metadata) = assets.and_then(|a| a.metadata.as_ref()) { + let source = ManifestSource::new(package, metadata.into()); + source.manifest_for_opt(cargo_target.as_deref(), dev) + } else { + let metadata = playdate_metadata(package); + let source = ManifestSource::new(package, metadata.as_ref()); + source.manifest_for_opt(cargo_target.as_deref(), dev) + }; + + // validation, lints + for problem in manifest.validate() { + let msg = format!("Manifest validation: {problem}"); + if problem.is_err() { + config.log().error(msg); + } else { + config.log().warn(msg); } } - std::fs::write(layout.manifest(), manifest.to_manifest_string())?; + std::fs::write(layout.manifest(), manifest.to_manifest_string()?)?; Ok(()) } @@ -491,15 +519,35 @@ impl<'cfg> TryFrom> for SuccessfulBuildProduct<'cfg> { struct ManifestSource<'cfg, 'm> { package: &'cfg Package, - metadata: Option<&'m playdate::metadata::format::PlayDateMetadata>, + authors: Vec<&'cfg str>, + metadata: Option<&'m Metadata>, } -impl ManifestDataSource for ManifestSource<'_, '_> { - type Value = toml::Value; +impl<'cfg, 'm> ManifestSource<'cfg, 'm> { + fn new(package: &'cfg Package, metadata: Option<&'m Metadata>) -> Self { + Self { authors: package.manifest() + .metadata() + .authors + .iter() + .map(|s| s.as_str()) + .collect(), + package, + metadata } + } +} - fn name(&self) -> &str { self.package.name().as_str() } - fn authors(&self) -> &[String] { &self.package.manifest().metadata().authors } +impl CrateInfoSource for ManifestSource<'_, '_> { + fn name(&self) -> Cow { self.package.name().as_str().into() } + // fn authors(&self) -> &[String] { &self.package.manifest().metadata().authors } + fn authors(&self) -> &[&str] { &self.authors } fn version(&self) -> Cow { self.package.version().to_string().into() } - fn description(&self) -> Option<&str> { self.package.manifest().metadata().description.as_deref() } - fn metadata(&self) -> Option<&playdate::metadata::format::PlayDateMetadata> { self.metadata } + fn description(&self) -> Option> { + self.package + .manifest() + .metadata() + .description + .as_deref() + .map(|s: &str| s.into()) + } + fn metadata(&self) -> Option { self.metadata } } diff --git a/cargo/src/proc/reader.rs b/cargo/src/proc/reader.rs index a01e0f4a..c25b9be6 100644 --- a/cargo/src/proc/reader.rs +++ b/cargo/src/proc/reader.rs @@ -119,17 +119,16 @@ impl SerializedTarget { pub mod format { #![allow(dead_code)] - use std::path::Path; use std::path::PathBuf; use cargo::core::compiler::CrateType; - use cargo::core::SourceId; use cargo::util::interning::InternedString; use cargo::util::machine_message::Message; use serde::Serialize; use serde::Deserialize; - use serde::Deserializer; use cargo::core::PackageId; - pub use crate::build::plan::format::TargetKind; + use crate::utils::cargo::build_plan::format::deserialize_crate_types; + use crate::utils::cargo::format::deserialize_package_id; + pub use crate::utils::cargo::format::TargetKind; #[derive(Serialize, Deserialize)] @@ -173,48 +172,6 @@ pub mod format { pub fresh: bool, } - /// Try deserialize using actual deserializer. - /// If fails, try to deserialize as old format. - /// Fixes breaking change between old and new format in cargo ~0.78.1. - fn deserialize_package_id<'de, D>(deserializer: D) -> Result - where D: Deserializer<'de> { - use serde::de::Error; - - let mut line = String::deserialize(deserializer)?; - // wrap into quotes for deserializer: - line.insert(0, '\"'); - line.push('\"'); - // preserve original value: - let value = &line[1..(line.len() - 1)]; - - // try actual format first: - let res = serde_json::from_str::(&line).map_err(Error::custom); - - // otherwise try old formats: - res.or_else(move |err| { - if let Some((uri, name_ver)) = value.split_once('#') { - let sid = SourceId::from_url(uri).map_err(Error::custom)?; - - if let Some((name, ver)) = name_ver.split_once('@') { - let ver = ver.parse().map_err(Error::custom)?; - let id = PackageId::new(name.into(), ver, sid); - return Ok(id); - } else { - let sid_temp = SourceId::from_url(value).map_err(Error::custom)?; - let url = sid_temp.url(); - if let Some(ver) = url.fragment() { - let ver = ver.parse().map_err(Error::custom)?; - let name = Path::new(url.path()).file_name() - .ok_or_else(|| Error::custom("Package name missed"))? - .to_string_lossy(); - let id = PackageId::new(name.as_ref().into(), ver, sid); - return Ok(id); - } - } - } - Err(err) - }) - } impl Message for Artifact { fn reason(&self) -> &str { "compiler-artifact" } @@ -228,7 +185,7 @@ pub mod format { pub kind: TargetKind, /// Corresponds to `--crate-type` compiler attribute. /// See - #[serde(deserialize_with = "deserialize_crate_type_vec")] + #[serde(deserialize_with = "deserialize_crate_types")] pub crate_types: Vec, pub name: InternedString, pub src_path: Option, @@ -242,15 +199,6 @@ pub mod format { pub test: bool, } - fn deserialize_crate_type_vec<'de, D>(deserializer: D) -> Result, D::Error> - where D: Deserializer<'de> { - let strings = Vec::<&str>::deserialize(deserializer)?; - let res = strings.into_iter() - .map(|s| CrateType::from(&s.to_owned())) - .collect(); - Ok(res) - } - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ArtifactProfile { @@ -291,40 +239,10 @@ pub mod format { use super::*; - #[derive(Debug, Serialize, Deserialize)] - pub struct PackageIdWrapped { - #[serde(deserialize_with = "super::deserialize_package_id")] - pub package_id: PackageId, - } - - - /// Before cargo 0.78 - #[test] - fn message_format_old_a() { - let msg = r#"{"package_id": "path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/api/sys#playdate-sys@0.3.3"}"#; - serde_json::from_str::(msg).unwrap(); - } - - /// Before cargo 0.78 - #[test] - fn message_format_old_b() { - let msg = r#"{"package_id": "path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/cargo/tests/crates/simple/with-cfg#0.1.0"}"#; - serde_json::from_str::(msg).unwrap(); - } - - - /// From cargo 0.78 - #[test] - fn message_format_new() { - let msg = r#"{"package_id": "playdate-sys 0.3.3 (path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/api/sys)"}"#; - serde_json::from_str::(msg).unwrap(); - } - - /// Before cargo 0.78 #[test] fn msg_message_format_old() { - let msg = r#"{"reason":"compiler-artifact","package_id":"path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/api/sys#playdate-sys@0.3.3","manifest_path":"/Users/U/Developer/Projects/Playdate/playdate-rs/api/sys/Cargo.toml","target":{"kind":["example"],"crate_types":["dylib","staticlib"],"name":"hello-world","src_path":"/Users/U/Developer/Projects/Playdate/playdate-rs/api/sys/examples/hello-world.rs","edition":"2021","required-features":["lang-items"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["allocator","arrayvec","bindgen","bindgen-runtime","bindings-derive-debug","default","eh-personality","lang-items","panic-handler"],"filenames":["/Users/U/Developer/Projects/Playdate/playdate-rs/target/aarch64-apple-darwin/debug/examples/libhello_world.dylib","/Users/U/Developer/Projects/Playdate/playdate-rs/target/aarch64-apple-darwin/debug/examples/libhello_world.a"],"executable":null,"fresh":false}"#; + let msg = r#"{"reason":"compiler-artifact","package_id":"path+file:///Users/U/Dev/playdate-rs/api/sys#playdate-sys@0.3.3","manifest_path":"/Users/U/Dev/playdate-rs/api/sys/Cargo.toml","target":{"kind":["example"],"crate_types":["dylib","staticlib"],"name":"hello-world","src_path":"/Users/U/Dev/playdate-rs/api/sys/examples/hello-world.rs","edition":"2021","required-features":["lang-items"],"doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["allocator","arrayvec","bindgen","bindgen-runtime","bindings-derive-debug","default","eh-personality","lang-items","panic-handler"],"filenames":["/Users/U/Dev/playdate-rs/target/aarch64-apple-darwin/debug/examples/libhello_world.dylib","/Users/U/Dev/playdate-rs/target/aarch64-apple-darwin/debug/examples/libhello_world.a"],"executable":null,"fresh":false}"#; serde_json::from_str::(msg).unwrap(); } } diff --git a/cargo/src/proc/utils.rs b/cargo/src/proc/utils.rs index e03cb256..1108c8a0 100644 --- a/cargo/src/proc/utils.rs +++ b/cargo/src/proc/utils.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::ffi::OsStr; use std::ffi::OsString; use std::path::*; use std::env; @@ -7,16 +8,28 @@ use std::process::Command; use build::consts::SDK_ENV_VAR; use cargo::CargoResult; use cargo::Config as CargoConfig; +use serde::de::DeserializeOwned; use crate::cli::cmd::Cmd; use crate::config::Config; +use crate::logger::LogErr; pub fn cargo_proxy_cmd(cfg: &Config, cmd: &Cmd) -> CargoResult { + cargo_proxy_with(cfg, cmd.as_str(), true) +} + + +pub fn cargo_proxy_with>(cfg: &Config, + cmd: S, + cfg_args: bool) + -> CargoResult { let rustflags = cfg.rustflags()?.rustflags_to_args_from(cfg); let mut proc = cargo(Some(cfg.workspace.config()))?; - proc.arg(cmd.as_ref()); - proc.args(&cfg.args); + proc.arg(cmd); + if cfg_args { + proc.args(&cfg.args); + } proc.args(&rustflags); if let Some(path) = cfg.sdk_path.as_deref() { @@ -28,12 +41,7 @@ pub fn cargo_proxy_cmd(cfg: &Config, cmd: &Cmd) -> CargoResult) -> CargoResult { - let cargo: Cow = - config.map_or_else( - || Some(PathBuf::from(env::var_os("CARGO").unwrap_or("cargo".into())).into()), - |cfg| cfg.cargo_exe().ok().map(Cow::from), - ) - .expect("Unable to get cargo bin from config"); + let cargo = cargo_bin_path(config); let mut proc = std::process::Command::new(cargo.as_ref()); if let Some(cfg) = &config { @@ -55,7 +63,6 @@ pub fn cargo(config: Option<&CargoConfig>) -> CargoResult "never" }; proc.env("CARGO_TERM_COLOR", color); - proc.arg(format!("--color={color}")); } // disable progress bar: @@ -64,6 +71,23 @@ pub fn cargo(config: Option<&CargoConfig>) -> CargoResult } +pub fn cargo_bin_path(config: Option<&CargoConfig>) -> Cow { + if let Some(cfg) = config { + let path = cfg.cargo_exe().log_err().ok().map(Cow::from); + if path.is_some() && path == std::env::current_exe().log_err().ok().map(Into::into) { + // Seems to we're in standalone mode. + cargo_bin_path(None) + } else if let Some(path) = path { + path + } else { + cargo_bin_path(None) + } + } else { + PathBuf::from(env::var_os("CARGO").unwrap_or("cargo".into())).into() + } +} + + pub fn args_line_for_proc(proc: &Command) -> String { proc.get_args() .collect::>() @@ -71,3 +95,27 @@ pub fn args_line_for_proc(proc: &Command) -> String { .to_string_lossy() .to_string() } + + +pub fn read_cargo_json(cfg: &Config, mut cmd: Command) -> CargoResult { + cfg.log() + .verbose(|mut log| log.status("Cargo", args_line_for_proc(&cmd))); + + let output = cmd.output()?; + if !output.status.success() { + cfg.workspace.config().shell().err().write_all(&output.stderr)?; + output.status.exit_ok()?; + } + + let stdout = std::str::from_utf8(&output.stdout)?; + + // parse only last line of output: + let line = stdout.lines() + .find(|s| { + let s = s.trim(); + !s.is_empty() && s.starts_with('{') + }) + .unwrap_or("{}"); + + Ok(serde_json::de::from_str::(line)?) +} diff --git a/cargo/src/utils/cargo/build_plan.rs b/cargo/src/utils/cargo/build_plan.rs new file mode 100644 index 00000000..9826dd2a --- /dev/null +++ b/cargo/src/utils/cargo/build_plan.rs @@ -0,0 +1,116 @@ +use cargo::CargoResult; +use cargo::core::PackageId; +use cargo::util::command_prelude::CompileMode; +use crate::cli::cmd::Cmd; +use crate::config::Config; +use crate::proc::cargo_proxy_cmd; +use crate::proc::read_cargo_json; +use self::format::TargetKind; + + +pub fn build_plan(cfg: &Config) -> CargoResult { + let mut cargo = cargo_proxy_cmd(cfg, &Cmd::Build)?; + + if !cfg.compile_options.build_config.build_plan { + cargo.args(["--build-plan", "-Zunstable-options"]); + } + + read_cargo_json(cfg, cargo) +} + + +impl format::BuildPlan { + pub fn build_package_invocations<'plan: 'i, 'p: 'i, 'i>( + &'plan self, + package: &'p PackageId) + -> impl Iterator + 'i { + self.invocations + .iter() + .filter(move |item| { + item.package_name == package.name().as_str() && package.version() == &item.package_version + }) + .filter(|item| item.compile_mode == CompileMode::Build) + } +} + + +#[allow(dead_code)] +pub enum TargetKindWild { + Lib, + Bin, + Test, + Bench, + ExampleLib, + ExampleBin, + CustomBuild, +} + +impl PartialEq for TargetKindWild { + fn eq(&self, other: &TargetKind) -> bool { + match self { + TargetKindWild::Lib => matches!(other, TargetKind::Lib(_)), + TargetKindWild::Bin => matches!(other, TargetKind::Bin), + TargetKindWild::Test => matches!(other, TargetKind::Test), + TargetKindWild::Bench => matches!(other, TargetKind::Bench), + TargetKindWild::ExampleLib => matches!(other, TargetKind::Example), + TargetKindWild::ExampleBin => matches!(other, TargetKind::Example), + TargetKindWild::CustomBuild => matches!(other, TargetKind::CustomBuild), + } + } +} + + +pub mod format { + use std::collections::BTreeMap; + use std::path::PathBuf; + use cargo::util::command_prelude::CompileMode; + use cargo::core::compiler::CompileKind; + use serde::{Serialize, Deserialize}; + + pub use super::super::format::*; + + + #[derive(Debug, Serialize, Deserialize)] + pub struct BuildPlan { + /// Program invocations needed to build the target (along with dependency information). + pub invocations: Vec, + /// List of Cargo manifests involved in the build. + pub inputs: Vec, + } + + /// A tool invocation. + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct Invocation { + /// The package this invocation is building a part of. + pub package_name: String, + /// Version of the package that is being built. + pub package_version: semver::Version, + /// The kind of artifact this invocation creates. + pub target_kind: TargetKind, + /// Whether the files created by this invocation are for the host or target system. + #[serde(serialize_with = "CompileKind::serialize")] + #[serde(deserialize_with = "deserialize_compile_kind")] + pub kind: CompileKind, + #[serde(serialize_with = "CompileMode::serialize")] + #[serde(deserialize_with = "CompileModeProxy::deserialize")] + pub compile_mode: CompileMode, + /// List of invocations this invocation depends on. + /// + /// The vector contains indices into the [`BuildPlan::invocations`] list. + /// + /// [`BuildPlan::invocations`]: struct.BuildPlan.html#structfield.invocations + pub deps: Vec, + /// List of output artifacts (binaries/libraries) created by this invocation. + pub outputs: Vec, + /// Hardlinks of output files that should be placed. + pub links: BTreeMap, + /// The program to invoke. + pub program: String, + /// Arguments to pass to the program. + pub args: Vec, + /// Map of environment variables. + pub env: BTreeMap, + /// The working directory in which to execute the program. + pub cwd: Option, + } +} diff --git a/cargo/src/utils/cargo/format.rs b/cargo/src/utils/cargo/format.rs new file mode 100644 index 00000000..57693449 --- /dev/null +++ b/cargo/src/utils/cargo/format.rs @@ -0,0 +1,202 @@ +use std::path::Path; + +use cargo::core::compiler::CompileKind; +use cargo::core::compiler::CompileMode; +use cargo::core::compiler::CompileTarget; +use cargo::core::compiler::CrateType; +use cargo::core::PackageId; +use cargo::core::SourceId; +use serde::{Serialize, Deserialize}; +use serde::{Serializer, Deserializer}; + + +#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[serde(rename_all = "kebab-case")] +#[serde(remote = "CompileMode")] +pub enum CompileModeProxy { + /// A target being built for a test. + Test, + /// Building a target with `rustc` (lib or bin). + Build, + /// Building a target with `rustc` to emit `rmeta` metadata only. If + /// `test` is true, then it is also compiled with `--test` to check it like + /// a test. + Check { test: bool }, + /// Used to indicate benchmarks should be built. This is not used in + /// `Unit`, because it is essentially the same as `Test` (indicating + /// `--test` should be passed to rustc) and by using `Test` instead it + /// allows some de-duping of Units to occur. + Bench, + /// A target that will be documented with `rustdoc`. + /// If `deps` is true, then it will also document all dependencies. + Doc { deps: bool, json: bool }, + /// A target that will be tested with `rustdoc`. + Doctest, + /// An example or library that will be scraped for function calls by `rustdoc`. + Docscrape, + /// A marker for Units that represent the execution of a `build.rs` script. + RunCustomBuild, +} + + +/// Remote-type for [`CompileMode`](cargo::core::TargetKind). +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum TargetKind { + Lib(Vec), + Bin, + Test, + Bench, + Example, + CustomBuild, +} + +impl<'de> Deserialize<'de> for TargetKind { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + use self::TargetKind::*; + + let raw = Vec::<&str>::deserialize(deserializer)?; + Ok(match *raw { + [] => return Err(serde::de::Error::invalid_length(0, &"at least one target kind")), + ["bin"] => Bin, + ["example"] => Example, + ["test"] => Test, + ["custom-build"] => CustomBuild, + ["bench"] => Bench, + ref lib_kinds => { + Lib(lib_kinds.iter() + .cloned() + .map(|s| CrateType::from(&s.to_owned())) + .collect()) + }, + }) + } +} + +impl Serialize for TargetKind { + fn serialize(&self, s: S) -> Result + where S: Serializer { + use self::TargetKind::*; + match self { + Lib(kinds) => s.collect_seq(kinds.iter().map(|t| t.to_string())), + Bin => ["bin"].serialize(s), + Example => ["example"].serialize(s), + Test => ["test"].serialize(s), + CustomBuild => ["custom-build"].serialize(s), + Bench => ["bench"].serialize(s), + } + } +} + + +pub fn deserialize_package_ids<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + let items = Vec::::deserialize(deserializer)?; + let mut ids = Vec::with_capacity(items.len()); + for item in items { + ids.push(string_to_package_id::(item)?); + } + Ok(ids) +} + +pub fn deserialize_package_id<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + string_to_package_id(String::deserialize(deserializer)?) +} + +/// Try deserialize using actual deserializer. +/// If fails, try to deserialize as old format. +/// Fixes breaking change between old and new format in cargo ~0.78.1. +pub fn string_to_package_id(mut line: String) -> Result { + // wrap into quotes for deserializer: + line.insert(0, '\"'); + line.push('\"'); + // preserve original value: + let value = &line[1..(line.len() - 1)]; + + // try actual format first: + let res = serde_json::from_str::(&line).map_err(Error::custom); + + // otherwise try old formats: + res.or_else(move |err| { + if let Some((uri, name_ver)) = value.split_once('#') { + let sid = SourceId::from_url(uri).map_err(Error::custom)?; + + if let Some((name, ver)) = name_ver.split_once('@') { + let ver = ver.parse().map_err(Error::custom)?; + let id = PackageId::new(name.into(), ver, sid); + return Ok(id); + } else { + let sid_temp = SourceId::from_url(value).map_err(Error::custom)?; + let url = sid_temp.url(); + if let Some(ver) = url.fragment() { + let ver = ver.parse().map_err(Error::custom)?; + let name = Path::new(url.path()).file_name() + .ok_or_else(|| Error::custom("Package name missed"))? + .to_string_lossy(); + let id = PackageId::new(name.as_ref().into(), ver, sid); + return Ok(id); + } + } + } + Err(err) + }) +} + + +pub fn deserialize_crate_types<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + let kinds = Vec::<&str>::deserialize(deserializer)?; + let kinds = kinds.into_iter() + .map(|s| CrateType::from(&s.to_owned())) + .collect(); + Ok(kinds) +} + + +pub fn deserialize_compile_kind<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + let res = if let Some(s) = Option::<&str>::deserialize(deserializer)? { + let target = CompileTarget::new(s).map_err(serde::de::Error::custom)?; + CompileKind::Target(target) + } else { + CompileKind::Host + }; + Ok(res) +} + + +#[cfg(test)] +mod tests { + use super::*; + + + #[derive(Debug, Serialize, Deserialize)] + pub struct PackageIdWrapped { + #[serde(deserialize_with = "super::deserialize_package_id")] + pub package_id: PackageId, + } + + + /// Before cargo 0.78 + #[test] + fn message_format_old_a() { + let msg = r#"{"package_id": "path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/api/sys#playdate-sys@0.3.3"}"#; + serde_json::from_str::(msg).unwrap(); + } + + /// Before cargo 0.78 + #[test] + fn message_format_old_b() { + let msg = r#"{"package_id": "path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/cargo/tests/crates/simple/with-cfg#0.1.0"}"#; + serde_json::from_str::(msg).unwrap(); + } + + + /// From cargo 0.78 + #[test] + fn message_format_new() { + let msg = r#"{"package_id": "playdate-sys 0.3.3 (path+file:///Users/U/Developer/Projects/Playdate/playdate-rs/api/sys)"}"#; + serde_json::from_str::(msg).unwrap(); + } +} diff --git a/cargo/src/utils/cargo/metadata.rs b/cargo/src/utils/cargo/metadata.rs new file mode 100644 index 00000000..c460d2dc --- /dev/null +++ b/cargo/src/utils/cargo/metadata.rs @@ -0,0 +1,165 @@ +use std::ffi::OsStr; + +use cargo::CargoResult; + +use crate::config::Config; +use crate::proc::cargo_proxy_with; +use crate::proc::read_cargo_json; + + +pub type CargoMetadataPd = format::Report; + + +pub fn metadata(cfg: &Config) -> CargoResult { + let mut cargo = cargo_proxy_with(cfg, "metadata", false)?; + + cargo.arg("--format-version=1"); + + let kinds = &cfg.compile_options.build_config.requested_kinds[..]; + if kinds.len() == 1 && + let Some(kind) = kinds.first() + { + match kind { + cargo::core::compiler::CompileKind::Target(target) if target != &cfg.host_target => { + cargo.args(["--filter-platform", &target.rustc_target()]); + }, + _ => (), + } + } + + // add manifest options: + { + const MANIFEST_PATH: &str = "--manifest-path"; + let expected = &[ + OsStr::new("--locked"), + OsStr::new("--offline"), + OsStr::new("--frozen"), + ]; + let args = cfg.args.iter().enumerate().filter(|(_, arg)| { + expected.contains(&arg.as_os_str()) || + arg.as_os_str() == MANIFEST_PATH || + arg.to_string_lossy().starts_with(MANIFEST_PATH) + }); + + args.for_each(|(i, arg)| { + cargo.arg(arg); + if arg.as_os_str() == MANIFEST_PATH { + cargo.arg(&cfg.args[i + 1]); + } + }); + + if !cfg.workspace.ignore_lock() && !cargo.get_args().any(|arg| arg == "--locked") { + cargo.arg("--locked"); + } + } + + read_cargo_json::(cfg, cargo) +} + + +pub mod format { + use std::path::PathBuf; + + use cargo::core::dependency::DepKind; + use cargo::core::PackageId; + use cargo::core::SourceId; + use serde::Deserialize; + use serde::Deserializer; + + use crate::utils::cargo::unit_graph::format::UnitTarget; + + pub use super::super::format::*; + + pub use playdate::metadata::format::CrateMetadata as Metadata; + + + /// `cargo metadata` output __v1__, + /// just necessary fields. + #[derive(Debug, Deserialize)] + #[serde(bound(deserialize = "Metadata: Deserialize<'de>"))] + pub struct Report { + pub version: usize, + pub packages: Vec>, + pub target_directory: PathBuf, + + pub workspace_members: Vec, + pub workspace_default_members: Vec, + pub workspace_root: PathBuf, + #[serde(alias = "metadata")] + pub workspace_metadata: Option, + + pub resolve: Resolve, + } + + #[derive(Deserialize, Debug)] + pub struct Resolve { + pub nodes: Vec, + pub root: Option, + } + + #[derive(Deserialize, Debug)] + pub struct ResolveNode { + #[serde(deserialize_with = "deserialize_package_id")] + pub id: PackageId, + #[serde(deserialize_with = "deserialize_package_ids")] + pub dependencies: Vec, + pub deps: Vec, + } + + + #[derive(Deserialize, Debug)] + #[serde(bound(deserialize = "Metadata: Deserialize<'de>"))] + pub struct Package { + #[serde(deserialize_with = "deserialize_package_id")] + pub id: PackageId, + pub source: Option, + pub dependencies: Vec, + + pub name: String, + pub authors: Vec, + pub version: String, + pub description: Option, + pub manifest_path: PathBuf, + pub targets: Vec, + pub metadata: Option, + } + + #[derive(Deserialize, Debug)] + pub struct PackageDep { + pub name: String, + pub rename: Option, + + pub source: Option, + pub req: semver::VersionReq, + #[serde(serialize_with = "DepKind::serialize")] + #[serde(deserialize_with = "deserialize_dep_kind")] + pub kind: DepKind, + + pub optional: bool, + // ... features, target, registry + // pub target: Option, + } + + #[derive(Deserialize, Debug)] + pub struct NodeDep { + pub name: String, + #[serde(deserialize_with = "deserialize_package_id")] + pub pkg: PackageId, + pub dep_kinds: serde_json::Value, + } + + pub fn deserialize_dep_kind<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + let kind = Option::<&str>::deserialize(deserializer)?; + let kind = match kind { + Some("dev") => DepKind::Development, + Some("build") => DepKind::Build, + None => DepKind::Normal, + kind => { + log::error!("Unknown dep kind: {kind:?}"); + DepKind::Normal + }, + }; + Ok(kind) + } +} diff --git a/cargo/src/utils/cargo.rs b/cargo/src/utils/cargo/mod.rs similarity index 89% rename from cargo/src/utils/cargo.rs rename to cargo/src/utils/cargo/mod.rs index e52e6db8..69606815 100644 --- a/cargo/src/utils/cargo.rs +++ b/cargo/src/utils/cargo/mod.rs @@ -3,6 +3,13 @@ use cargo::core::compiler::CompileTarget; use playdate::consts::DEVICE_TARGET; +/// Shared format +pub(crate) mod format; +pub mod build_plan; +pub mod unit_graph; +pub mod metadata; + + pub trait CompileKindExt { fn is_playdate(&self) -> bool; fn is_simulator(&self) -> bool; diff --git a/cargo/src/utils/cargo/unit_graph.rs b/cargo/src/utils/cargo/unit_graph.rs new file mode 100644 index 00000000..a36027df --- /dev/null +++ b/cargo/src/utils/cargo/unit_graph.rs @@ -0,0 +1,73 @@ +use cargo::CargoResult; + +use crate::cli::cmd::Cmd; +use crate::config::Config; +use crate::proc::cargo_proxy_cmd; +use crate::proc::read_cargo_json; +use self::format::UnitGraph; + + +pub fn unit_graph(cfg: &Config) -> CargoResult { + let mut cargo = cargo_proxy_cmd(cfg, &Cmd::Build)?; + + cargo.args(["--unit-graph", "-Zunstable-options"]); + + let value: UnitGraph = read_cargo_json(cfg, cargo)?; + Ok(value) +} + + +pub mod format { + #![allow(dead_code)] + use cargo::core::PackageId; + use cargo::util::command_prelude::CompileMode; + use cargo::core::compiler::CompileKind; + use cargo::core::compiler::CrateType; + use serde::Deserialize; + + pub use super::super::format::*; + + + #[derive(Debug, Deserialize)] + pub struct UnitGraph { + pub version: usize, + pub units: Vec, + pub roots: Vec, + } + + #[derive(Debug, Deserialize)] + pub struct Unit { + #[serde(deserialize_with = "deserialize_package_id", alias = "pkg_id")] + pub package_id: PackageId, + pub target: UnitTarget, + #[serde(serialize_with = "CompileKind::serialize")] + #[serde(deserialize_with = "deserialize_compile_kind")] + pub platform: CompileKind, + #[serde(serialize_with = "CompileMode::serialize")] + #[serde(deserialize_with = "CompileModeProxy::deserialize")] + pub mode: CompileMode, + pub features: Vec, + pub dependencies: Vec, + // ... + // pub profile: crate::proc::reader::format::ArtifactProfile, + } + + #[derive(Debug, Deserialize)] + pub struct UnitTarget { + pub kind: TargetKind, + #[serde(deserialize_with = "deserialize_crate_types")] + pub crate_types: Vec, + pub name: String, + pub src_path: String, + // ... + } + + #[derive(Debug, Deserialize)] + pub struct UnitDep { + pub index: usize, + pub extern_crate_name: String, + pub public: bool, + #[serde(alias = "noprelude")] + pub no_prelude: bool, + } +} diff --git a/cargo/src/utils/mod.rs b/cargo/src/utils/mod.rs index 2d6c04bb..fb89f99d 100644 --- a/cargo/src/utils/mod.rs +++ b/cargo/src/utils/mod.rs @@ -16,7 +16,8 @@ pub mod path; pub mod logging; -// TODO: It used several times, make it global. +#[deprecated(since = "0.5", + note = "TODO: use crate::utils::cargo:: unit_graph with metadata instead")] pub struct LazyBuildContext<'a, 'cfg> { workspace: &'cfg Workspace<'cfg>, bcx: Lazy>, diff --git a/cargo/tests/crates/metadata/Cargo.toml b/cargo/tests/crates/metadata/Cargo.toml index 74e8dd4e..b9f2a6d1 100644 --- a/cargo/tests/crates/metadata/Cargo.toml +++ b/cargo/tests/crates/metadata/Cargo.toml @@ -16,7 +16,7 @@ description = "test" [package.metadata.playdate.assets] "main/" = "Cargo.toml" -[package.metadata.playdate.assets.options] +[package.metadata.playdate.options.assets] dependencies = true overwrite = true diff --git a/support/build/src/metadata/validation.rs b/support/build/src/metadata/validation.rs index f45cfdbf..7fb1f4bf 100644 --- a/support/build/src/metadata/validation.rs +++ b/support/build/src/metadata/validation.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use super::source::CrateInfoSource; use super::source::ManifestSourceOptExt; @@ -26,6 +28,38 @@ pub enum Warning { }, } +impl Display for Warning { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::StrangeValue { field, value, reason } => { + write!(f, "Strange value {value:?} for field '{field}'")?; + if let Some(reason) = reason { + write!(f, ", {reason}") + } else { + Ok(()) + } + }, + Self::UnknownField { field, reason } => { + write!(f, "Unknown field '{field}'")?; + if let Some(reason) = reason { + write!(f, ", {reason}") + } else { + Ok(()) + } + }, + Self::MissingField { field, reason } => { + write!(f, "Missing field '{field}'")?; + if let Some(reason) = reason { + write!(f, ", {reason}") + } else { + Ok(()) + } + }, + } + } +} + + impl Problem { pub fn is_err(&self) -> bool { match self { @@ -33,6 +67,18 @@ impl Problem { _ => true, } } + + pub fn is_warn(&self) -> bool { !self.is_err() } +} + +impl Display for Problem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnknownTarget { name } => write!(f, "Unknown cargo-target: {name}"), + Self::MissingField { field } => write!(f, "Missing field: {field}"), + Self::Warning(warning) => warning.fmt(f), + } + } } @@ -64,7 +110,7 @@ impl Validate for T { ( "build-number", self.build_number().is_some(), - Some("Required for sideloaded games."), + Some("required for sideloaded games."), ), ("description", self.description().is_some(), None), ].into_iter() @@ -100,7 +146,7 @@ fn validate_version(value: &str) -> Option { if semver::Version::parse(value).is_err() { Some(Problem::Warning(Warning::StrangeValue { field: "version".into(), value: value.into(), - reason: Some("Can be confusing.") })) + reason: Some("can be confusing.") })) } else { None } From c9ac85f8afb5bd86bd9ad01ec01e096a466a632e Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 29 May 2024 01:53:00 +0400 Subject: [PATCH 05/24] env-resolver: remove panic on missed var (missed part) --- support/build/src/assets/resolver.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/support/build/src/assets/resolver.rs b/support/build/src/assets/resolver.rs index c0b417e5..f20f68c0 100644 --- a/support/build/src/assets/resolver.rs +++ b/support/build/src/assets/resolver.rs @@ -158,10 +158,7 @@ impl EnvResolver { let var = std::env::var(name).map_err(log_err) .map(Cow::from) - .unwrap_or_else(|_| { - // XXX: should we panic here? - panic!("Env var \"{name}\" not found") - }); + .unwrap_or_else(|_| name.into()); replaced = replaced.replace(full, &var); } } From 9c72320e33d4f2d449caca22eb3058af2c206f6a Mon Sep 17 00:00:00 2001 From: Alexander Koz <888526+boozook@users.noreply.github.com> Date: Wed, 29 May 2024 02:32:22 +0400 Subject: [PATCH 06/24] Apply clippy suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- cargo/src/build/mod.rs | 1 - support/build/src/metadata/format.rs | 18 ++++----- support/build/src/metadata/source.rs | 60 +++++++++++++--------------- 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/cargo/src/build/mod.rs b/cargo/src/build/mod.rs index b8ef6200..0ff11a95 100644 --- a/cargo/src/build/mod.rs +++ b/cargo/src/build/mod.rs @@ -38,7 +38,6 @@ use crate::utils::cargo::CompileKindExt; use crate::utils::path::AsRelativeTo; use crate::utils::workspace::PossibleTargets; -use crate::utils::cargo::build_plan as plan; pub mod rustflags; diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index b27eefd8..b1b3f662 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -53,8 +53,8 @@ impl MetadataSource for Metadata { fn manifest(&self) -> impl ManifestSourceOptExt { &self.inner.manifest } - fn bins<'t>(&'t self) -> &'t [Self::TargetManifest] { self.inner.bins.as_slice() } - fn examples<'t>(&'t self) -> &'t [Self::TargetManifest] { self.inner.examples.as_slice() } + fn bins(&self) -> &[Self::TargetManifest] { self.inner.bins.as_slice() } + fn examples(&self) -> &[Self::TargetManifest] { self.inner.examples.as_slice() } fn bin_targets(&self) -> impl IntoIterator { self.inner.bins.iter().map(|o| o.target.as_str()) } fn example_targets(&self) -> impl IntoIterator { @@ -202,7 +202,7 @@ impl<'t, S> Cob<'t> for Manifest where S: Cob<'t> { launch_sound_path: self.launch_sound_path.as_ref().map(Cob::as_borrow), content_warning: self.content_warning.as_ref().map(Cob::as_borrow), content_warning2: self.content_warning2.as_ref().map(Cob::as_borrow), - build_number: self.build_number.clone() } + build_number: self.build_number } } } @@ -241,7 +241,7 @@ impl IntoOwned::Owned>> for Manifest> { launch_sound_path: self.launch_sound_path.map(|s| s.into_owned()), content_warning: self.content_warning.map(|s| s.into_owned()), content_warning2: self.content_warning2.map(|s| s.into_owned()), - build_number: self.build_number.clone() } + build_number: self.build_number } } } @@ -256,7 +256,7 @@ impl Manifest where S: ToOwned { launch_sound_path: self.launch_sound_path.map(|s| s.to_owned()), content_warning: self.content_warning.map(|s| s.to_owned()), content_warning2: self.content_warning2.map(|s| s.to_owned()), - build_number: self.build_number.clone() } + build_number: self.build_number } } } @@ -373,7 +373,7 @@ impl std::fmt::Display for ExtraValue { } impl From for ExtraValue { - fn from(value: bool) -> Self { Self::Boolean(value.into()) } + fn from(value: bool) -> Self { Self::Boolean(value) } } impl From for ExtraValue { fn from(value: i64) -> Self { Self::Int(value) } @@ -417,7 +417,7 @@ impl ManifestSourceOpt for Manifest where S: Deref { fn launch_sound_path(&self) -> Option<&str> { self.launch_sound_path.as_deref() } fn content_warning(&self) -> Option<&str> { self.content_warning.as_deref() } fn content_warning2(&self) -> Option<&str> { self.content_warning2.as_deref() } - fn build_number(&self) -> Option { self.build_number.clone() } + fn build_number(&self) -> Option { self.build_number } } impl ManifestSourceOpt for Ext { @@ -483,7 +483,7 @@ impl<'s, T: ManifestSourceOpt, S: From<&'s str>> From<&'s T> for Manifest { launch_sound_path: source.launch_sound_path().map(Into::into), content_warning: source.content_warning().map(Into::into), content_warning2: source.content_warning2().map(Into::into), - build_number: source.build_number().clone() } + build_number: source.build_number() } } } @@ -870,7 +870,7 @@ mod tests { #[test] fn options_assets_deps() { // default is false - assert_eq!(false, AssetsOptions::default_dependencies()); + assert!(!AssetsOptions::default_dependencies()); let src = r#" [assets] "#; let m = toml::from_str::(src).unwrap(); assert_matches!( diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index 12345fea..58ced4a4 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -71,14 +71,12 @@ pub trait CrateInfoSource { log::debug!("target not found: {}", target); None } - } else { - if let Some(man) = root.bin(target) { - Some(base.override_with_extra(man).into_owned()) - } else { - log::debug!("target not found: {}", target); - None - } - } + } else if let Some(man) = root.bin(target) { + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } } else { Some(base.into_owned()) } @@ -98,14 +96,14 @@ pub trait MetadataSource { fn manifest(&self) -> impl ManifestSourceOptExt; - fn bins<'t>(&'t self) -> &'t [Self::TargetManifest]; - fn examples<'t>(&'t self) -> &'t [Self::TargetManifest]; + fn bins(&self) -> &[Self::TargetManifest]; + fn examples(&self) -> &[Self::TargetManifest]; fn bin<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { - self.bins().into_iter().find(|b| b.target() == target) + self.bins().iter().find(|b| b.target() == target) } fn example<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { - self.examples().into_iter().find(|b| b.target() == target) + self.examples().iter().find(|b| b.target() == target) } fn bin_targets(&self) -> impl IntoIterator; @@ -115,10 +113,10 @@ pub trait MetadataSource { } fn bins_iter(&self) -> Option> { - (!self.bins().is_empty()).then_some(self.bins().into_iter()) + (!self.bins().is_empty()).then_some(self.bins().iter()) } fn examples_iter(&self) -> Option> { - (!self.examples().is_empty()).then_some(self.examples().into_iter()) + (!self.examples().is_empty()).then_some(self.examples().iter()) } fn all_targets_iter(&self) -> impl Iterator { @@ -150,14 +148,12 @@ pub trait MetadataSource { } else { None } - } else { - if let Some(target) = self.bin(target) { - let trg = base.override_with_extra_ref(target); - Some(trg.into_owned()) - } else { - None - } - } + } else if let Some(target) = self.bin(target) { + let trg = base.override_with_extra_ref(target); + Some(trg.into_owned()) + } else { + None + } } fn manifest_for_target_any(&self, target: &str) -> Option { @@ -176,8 +172,8 @@ impl MetadataSource for &T { fn manifest(&self) -> impl ManifestSourceOptExt { (*self).manifest() } - fn bins<'t>(&'t self) -> &'t [Self::TargetManifest] { ::bins(*self) } - fn examples<'t>(&'t self) -> &'t [Self::TargetManifest] { ::examples(*self) } + fn bins(&self) -> &[Self::TargetManifest] { ::bins(*self) } + fn examples(&self) -> &[Self::TargetManifest] { ::examples(*self) } fn bin_targets(&self) -> impl IntoIterator { (*self).bin_targets() } fn example_targets(&self) -> impl IntoIterator { (*self).example_targets() } @@ -294,14 +290,14 @@ pub trait ManifestSourceOptExt: ManifestSourceOpt { impl ManifestSourceOpt for T { const MAY_BE_INCOMPLETE: bool = false; fn name(&self) -> Option<&str> { Some(ManifestSource::name(self)) } - fn version(&self) -> Option<&str> { Some(ManifestSource::version(self).as_ref()) } - fn author(&self) -> Option<&str> { Some(ManifestSource::author(self).as_ref()) } - fn bundle_id(&self) -> Option<&str> { Some(ManifestSource::bundle_id(self).as_ref()) } - fn description(&self) -> Option<&str> { Some(ManifestSource::description(self).as_ref()) } - fn image_path(&self) -> Option<&str> { Some(ManifestSource::image_path(self).as_ref()) } - fn launch_sound_path(&self) -> Option<&str> { Some(ManifestSource::launch_sound_path(self).as_ref()) } - fn content_warning(&self) -> Option<&str> { Some(ManifestSource::content_warning(self).as_ref()) } - fn content_warning2(&self) -> Option<&str> { Some(ManifestSource::content_warning2(self).as_ref()) } + fn version(&self) -> Option<&str> { Some(ManifestSource::version(self)) } + fn author(&self) -> Option<&str> { Some(ManifestSource::author(self)) } + fn bundle_id(&self) -> Option<&str> { Some(ManifestSource::bundle_id(self)) } + fn description(&self) -> Option<&str> { Some(ManifestSource::description(self)) } + fn image_path(&self) -> Option<&str> { Some(ManifestSource::image_path(self)) } + fn launch_sound_path(&self) -> Option<&str> { Some(ManifestSource::launch_sound_path(self)) } + fn content_warning(&self) -> Option<&str> { Some(ManifestSource::content_warning(self)) } + fn content_warning2(&self) -> Option<&str> { Some(ManifestSource::content_warning2(self)) } fn build_number(&self) -> Option { ManifestSource::build_number(self) } } From 3b415c21cf84cfed00fe14875201423e22079219 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 11:08:42 +0400 Subject: [PATCH 07/24] add targets, improve validation for crate-level source --- support/build/src/metadata/source.rs | 52 ++++++++++++++----- support/build/src/metadata/validation.rs | 63 ++++++++++++++++++------ 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index 58ced4a4..f3fc290e 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -1,16 +1,28 @@ use std::borrow::Cow; +use std::path::Path; use super::format::{AssetsOptions, AssetsRules, Ext, ExtraFields, ExtraValue, Manifest, Options, Support}; pub trait CrateInfoSource { + /// Crate name. fn name(&self) -> Cow; + /// Crate authors. fn authors(&self) -> &[&str]; + /// Crate version (semver). fn version(&self) -> Cow; + /// Crate description. fn description(&self) -> Option>; + /// Crate metadata - `playdate` table. fn metadata(&self) -> Option; - // targets -> [name? or name+kind?] + /// Names of `bin` cargo-targets. + fn bins(&self) -> &[&str]; + /// Names of `example` cargo-targets. + fn examples(&self) -> &[&str]; + + /// Crate manifest path (Cargo.toml). + fn manifest_path(&self) -> Cow; fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { @@ -72,11 +84,11 @@ pub trait CrateInfoSource { None } } else if let Some(man) = root.bin(target) { - Some(base.override_with_extra(man).into_owned()) - } else { - log::debug!("target not found: {}", target); - None - } + Some(base.override_with_extra(man).into_owned()) + } else { + log::debug!("target not found: {}", target); + None + } } else { Some(base.into_owned()) } @@ -94,14 +106,23 @@ pub trait MetadataSource { type Manifest: ManifestSourceOptExt; type TargetManifest: ManifestSourceOptExt + TargetId; + /// Main manifest, default and base for all cargo-targets. fn manifest(&self) -> impl ManifestSourceOptExt; + /// All manifests for "bin" cargo-targets. + /// Overrides main manifest field-by-field. fn bins(&self) -> &[Self::TargetManifest]; + /// All manifests for "example" cargo-targets. + /// Overrides main manifest field-by-field. fn examples(&self) -> &[Self::TargetManifest]; + /// Manifest for specified "bin" cargo-target. + /// Overrides main manifest field-by-field. fn bin<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { self.bins().iter().find(|b| b.target() == target) } + /// Manifest for specified "example" cargo-target. + /// Overrides main manifest field-by-field. fn example<'t>(&'t self, target: &'_ str) -> Option<&'t Self::TargetManifest> { self.examples().iter().find(|b| b.target() == target) } @@ -149,11 +170,11 @@ pub trait MetadataSource { None } } else if let Some(target) = self.bin(target) { - let trg = base.override_with_extra_ref(target); - Some(trg.into_owned()) - } else { - None - } + let trg = base.override_with_extra_ref(target); + Some(trg.into_owned()) + } else { + None + } } fn manifest_for_target_any(&self, target: &str) -> Option { @@ -476,7 +497,11 @@ mod tests { fn authors(&self) -> &[&str] { &["John"] } fn version(&self) -> Cow { "0.0.0".into() } fn description(&self) -> Option> { None } + fn bins(&self) -> &[&str] { &[SOME_TARGET] } + fn examples(&self) -> &[&str] { &[] } fn metadata(&self) -> Option { None:: } + + fn manifest_path(&self) -> Cow { Cow::Borrowed(Path::new("Cargo.toml")) } } #[test] @@ -496,6 +521,11 @@ mod tests { fn version(&self) -> Cow { "0.0.0".into() } fn description(&self) -> Option> { None } + fn bins(&self) -> &[&str] { &[SOME_TARGET] } + fn examples(&self) -> &[&str] { &[] } + + fn manifest_path(&self) -> Cow { Cow::Borrowed(Path::new("Cargo.toml")) } + fn metadata(&self) -> Option { let base = Manifest { name: Some("Meta Name"), bundle_id: Some("crate.id"), diff --git a/support/build/src/metadata/validation.rs b/support/build/src/metadata/validation.rs index 7fb1f4bf..4398aafe 100644 --- a/support/build/src/metadata/validation.rs +++ b/support/build/src/metadata/validation.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use super::source::CrateInfoSource; use super::source::ManifestSourceOptExt; +use super::source::MetadataSource; #[derive(Debug, Clone)] @@ -13,6 +14,7 @@ pub enum Problem { #[derive(Debug, Clone)] pub enum Warning { + MissingMetadata, StrangeValue { field: String, value: String, @@ -31,6 +33,7 @@ pub enum Warning { impl Display for Warning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::MissingMetadata => write!(f, "Metadata not found"), Self::StrangeValue { field, value, reason } => { write!(f, "Strange value {value:?} for field '{field}'")?; if let Some(reason) = reason { @@ -74,8 +77,8 @@ impl Problem { impl Display for Problem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::UnknownTarget { name } => write!(f, "Unknown cargo-target: {name}"), - Self::MissingField { field } => write!(f, "Missing field: {field}"), + Self::UnknownTarget { name } => write!(f, "Unknown cargo-target {name:?}"), + Self::MissingField { field } => write!(f, "Missing field: {field:?}"), Self::Warning(warning) => warning.fmt(f), } } @@ -158,29 +161,57 @@ fn validate_version(value: &str) -> Option { /// Lint the crate-level source. pub trait ValidateCrate: CrateInfoSource { - fn validate(&self) -> impl IntoIterator { + fn validate<'t>(&'t self) -> impl IntoIterator + 't { // - main manifest missing fields // - main manifest fields in bad format // - for each final target manifest: // -> same as for the main manifest - if let Some(_meta) = self.metadata() { - // Check that all targets are exists - // - search the target in the crate for each in meta.all_targets() - } else { - // - warn: no metadata found - } - - // just temporary this, because not implemented yet: - self.manifest_for_crate() - .validate() - .into_iter() - .collect::>() + // Check that all targets are exists + // - search the target in the crate for each in meta.all_targets + let missed = + self.metadata().into_iter().flat_map(|meta| { + let bins = meta.bin_targets() + .into_iter() + .filter(|name| !self.bins().contains(name)) + .map(|name| Problem::UnknownTarget { name: name.to_owned() }); + + let examples = meta.example_targets() + .into_iter() + .filter(|name| !self.examples().contains(name)) + .map(|name| Problem::UnknownTarget { name: name.to_owned() }); + + bins.chain(examples).collect::>() + }); + + let crate_name_eq = + self.metadata().into_iter().flat_map(|meta| { + let mut targets = meta.all_targets().into_iter(); + if let Some(name) = targets.find(|name| name == &self.name()) { + let msg = "target name is the same as the crate name"; + Some(Problem::Warning(Warning::StrangeValue { field: + "target".into(), + value: name.into(), + reason: Some(msg) })) + } else { + None + } + }); + + let no_meta = self.metadata() + .is_none() + .then(|| Problem::Warning(Warning::MissingMetadata)); + + + missed.into_iter().chain(crate_name_eq).chain(no_meta) } - fn validate_for(&self, _target: &str) -> impl IntoIterator { [] } + fn validate_for(&self, target: &str) -> impl IntoIterator { + println!("TODO: validate_for(target={target:?}) not implemented yet!"); + [] + } } impl ValidateCrate for T where T: CrateInfoSource {} From eb710e5d6104a61c109dac0b3d2ed3333eb99c37 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 11:09:20 +0400 Subject: [PATCH 08/24] update deps and rust toolchain --- Cargo.lock | 369 ++++++++++++++++++++++++-------------------- rust-toolchain.toml | 2 +- 2 files changed, 200 insertions(+), 171 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b641e38b..5f216919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "gimli", + "gimli 0.29.0", ] [[package]] @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arbitrary" @@ -155,12 +155,11 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.3.0", "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", @@ -168,9 +167,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" dependencies = [ "async-task", "concurrent-queue", @@ -185,7 +184,7 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.1", + "async-channel 2.3.1", "async-executor", "async-io 2.3.2", "async-lock 3.3.0", @@ -335,7 +334,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -352,7 +351,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -423,9 +422,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -488,7 +487,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.63", + "syn 2.0.66", "which 4.4.2", ] @@ -524,12 +523,11 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.2.1", - "async-lock 3.3.0", + "async-channel 2.3.1", "async-task", "futures-io", "futures-lite 2.3.0", @@ -771,10 +769,10 @@ dependencies = [ "serde_json", "target", "toml", - "toml_edit 0.22.12", + "toml_edit 0.22.13", "try-lazy-init", "walkdir", - "zip 1.2.3", + "zip 1.1.4", ] [[package]] @@ -818,9 +816,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", @@ -866,9 +864,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -909,7 +907,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -951,7 +949,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -1018,9 +1016,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" dependencies = [ "cfg-if", "cpufeatures", @@ -1132,18 +1130,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -1178,9 +1176,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-bigint" @@ -1286,7 +1284,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -1330,7 +1328,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -1382,9 +1380,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" dependencies = [ "serde", ] @@ -1480,11 +1478,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] @@ -1533,9 +1532,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -1558,7 +1557,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.3.0", + "event-listener 5.3.1", "pin-project-lite", ] @@ -1613,9 +1612,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" @@ -1733,7 +1732,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.2", + "parking_lot 0.12.3", ] [[package]] @@ -1778,7 +1777,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -1845,6 +1844,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "git2" version = "0.18.3" @@ -1920,7 +1925,7 @@ dependencies = [ "gix-validate", "gix-worktree", "once_cell", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "prodash", "smallvec", "thiserror", @@ -1978,9 +1983,9 @@ dependencies = [ [[package]] name = "gix-command" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90009020dc4b3de47beed28e1334706e0a330ddd17f5cfeb097df3b15a54b77" +checksum = "6c22e086314095c43ffe5cdc5c0922d5439da4fd726f3b0438c56147c34dc225" dependencies = [ "bstr", "gix-path", @@ -2055,9 +2060,9 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180b130a4a41870edfbd36ce4169c7090bca70e195da783dea088dd973daa59c" +checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99" dependencies = [ "bstr", "itoa", @@ -2106,7 +2111,7 @@ dependencies = [ "gix-trace", "libc", "once_cell", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "prodash", "sha1_smol", "thiserror", @@ -2173,7 +2178,7 @@ checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" dependencies = [ "gix-hash", "hashbrown 0.14.5", - "parking_lot 0.12.2", + "parking_lot 0.12.3", ] [[package]] @@ -2226,13 +2231,13 @@ dependencies = [ [[package]] name = "gix-macros" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dff438f14e67e7713ab9332f5fd18c8f20eb7eb249494f6c2bf170522224032" +checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -2284,7 +2289,7 @@ dependencies = [ "gix-pack", "gix-path", "gix-quote", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "tempfile", "thiserror", ] @@ -2304,7 +2309,7 @@ dependencies = [ "gix-path", "gix-tempfile", "memmap2", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "smallvec", "thiserror", ] @@ -2363,13 +2368,13 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5325eb17ce7b5e5d25dec5c2315d642a09d55b9888b3bf46b7d72e1621a55d8" +checksum = "fddabbc7c51c241600ab3c4623b19fa53bde7c1a2f637f61043ed5fcadf000cc" dependencies = [ "gix-command", "gix-config-value", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "rustix 0.38.34", "thiserror", ] @@ -2505,7 +2510,7 @@ dependencies = [ "gix-fs", "libc", "once_cell", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "tempfile", ] @@ -2576,9 +2581,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e39fc6e06044985eac19dd34d474909e517307582e462b2eb4c8fa51b6241545" +checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" dependencies = [ "bstr", "thiserror", @@ -2965,9 +2970,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -3122,7 +3127,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -3156,9 +3161,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" @@ -3267,9 +3272,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.16" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", "libc", @@ -3291,9 +3296,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -3305,6 +3310,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" @@ -3388,7 +3399,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -3458,7 +3469,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -3475,9 +3486,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -3638,6 +3649,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -3666,9 +3698,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -3797,9 +3829,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core 0.9.10", @@ -4008,7 +4040,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -4025,9 +4057,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -4089,7 +4121,7 @@ dependencies = [ "proc-macro2", "quote", "semver", - "syn 2.0.63", + "syn 2.0.66", "which 6.0.1", ] @@ -4405,7 +4437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -4417,11 +4449,20 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -4432,7 +4473,7 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" dependencies = [ - "parking_lot 0.12.2", + "parking_lot 0.12.3", ] [[package]] @@ -4453,9 +4494,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -4463,22 +4504,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] @@ -4713,15 +4754,15 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] name = "rustversion" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" @@ -4770,7 +4811,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -4821,21 +4862,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde-untagged" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a160535368dfc353348e7eaa299156bd508c60c45a9249725f5f6d370d82a66" +checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" dependencies = [ "erased-serde", "serde", + "typeid", ] [[package]] @@ -4850,13 +4892,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -4881,9 +4923,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -5225,7 +5267,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -5320,13 +5362,13 @@ dependencies = [ "elsa", "fallible-iterator 0.3.0", "flate2", - "gimli", + "gimli 0.28.1", "goblin", "lazy_static", "nom", "nom-supreme", "once_cell", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "pdb-addr2line", "regex", "scroll", @@ -5388,9 +5430,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -5415,9 +5457,9 @@ dependencies = [ [[package]] name = "target" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba852e71502340e2eaf2fa51f9b3ec6aa25750da1aa65771491c69d67789b05c" +checksum = "f4df6b0340c7cc29eb3b955cc588d145ed60651bf1ab939083295d19ec8cc282" [[package]] name = "tempfile" @@ -5465,22 +5507,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -5552,7 +5594,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.2", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", @@ -5579,7 +5621,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -5621,21 +5663,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.12", + "toml_edit 0.22.13", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -5655,15 +5697,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.8", + "winnow 0.6.9", ] [[package]] @@ -5745,7 +5787,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -5800,10 +5842,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typed-arena" -version = "2.0.2" +name = "typeid" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" [[package]] name = "typenum" @@ -5978,9 +6020,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" @@ -6028,7 +6070,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -6062,7 +6104,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6216,7 +6258,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -6227,7 +6269,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -6429,9 +6471,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] @@ -6470,28 +6512,14 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.63", -] +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zip" @@ -6507,9 +6535,9 @@ dependencies = [ [[package]] name = "zip" -version = "1.2.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700ea425e148de30c29c580c1f9508b93ca57ad31c9f4e96b83c194c37a7a8f" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" dependencies = [ "aes", "arbitrary", @@ -6523,26 +6551,27 @@ dependencies = [ "hmac", "indexmap 2.2.6", "lzma-rs", + "num_enum", "pbkdf2", - "rand", "sha1", "thiserror", "time", - "zeroize", "zopfli", "zstd", ] [[package]] name = "zopfli" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" dependencies = [ + "bumpalo", "crc32fast", + "lockfree-object-pool", "log", + "once_cell", "simd-adler32", - "typed-arena", ] [[package]] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 64c81e34..01652420 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-04-12" +channel = "nightly-2024-05-28" profile = "minimal" targets = ["thumbv7em-none-eabihf"] components = [ From a2227e3c5c7d5bb8ee75aa212506ed064840b12d Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 12:38:07 +0400 Subject: [PATCH 09/24] simplification, get rid of `Package` dep in layout --- cargo/src/assets/mod.rs | 2 +- cargo/src/assets/plan.rs | 5 +- cargo/src/init/mod.rs | 4 +- cargo/src/layout/playdate.rs | 40 ++++++++-------- cargo/src/package/mod.rs | 79 +++++++++++++++++++++++++++---- cargo/src/proc/utils.rs | 46 +++++++++++------- cargo/src/utils/cargo/metadata.rs | 6 +-- 7 files changed, 127 insertions(+), 55 deletions(-) diff --git a/cargo/src/assets/mod.rs b/cargo/src/assets/mod.rs index 876af7bc..b5dc5a47 100644 --- a/cargo/src/assets/mod.rs +++ b/cargo/src/assets/mod.rs @@ -43,7 +43,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { for (package, targets, ..) in config.possible_targets()? { let env = plan::LazyEnvBuilder::new(config, package); let mut plans: HashMap<&Package, _> = Default::default(); - let global_layout = CrossTargetLayout::new(config, package, None)?; + let global_layout = CrossTargetLayout::new(config, package.package_id(), None)?; let mut layout = global_layout.assets_layout(config); let mut options = HashMap::new(); diff --git a/cargo/src/assets/plan.rs b/cargo/src/assets/plan.rs index b5a6debe..4fac6f75 100644 --- a/cargo/src/assets/plan.rs +++ b/cargo/src/assets/plan.rs @@ -90,7 +90,7 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, let plan = assets_build_plan(env, metadata.assets(), opts.as_ref(), Some(root))?; // main-assets plan: - let path = layout.as_inner().assets_plan_for(config, package); + let path = layout.as_inner().assets_plan_for(config, &package.package_id()); let mut cached = CachedPlan::new(path, plan)?; if config.compile_options.build_config.force_rebuild { cached.difference = Difference::Missing; @@ -107,7 +107,8 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, let assets = metadata.dev_assets(); let dev_plan = assets_build_plan(env, assets, opts.as_ref(), Some(root))?; - let path = layout.as_inner().assets_plan_for_dev(config, package); + let path = layout.as_inner() + .assets_plan_for_dev(config, &package.package_id()); let mut dev_cached = CachedPlan::new(path, dev_plan)?; // Inheritance, if main is stale or missing - this one is too: diff --git a/cargo/src/init/mod.rs b/cargo/src/init/mod.rs index aa7e7dd1..485247ae 100644 --- a/cargo/src/init/mod.rs +++ b/cargo/src/init/mod.rs @@ -89,7 +89,7 @@ pub fn new_or_init<'cfg>(config: &'cfg Config<'cfg>) -> CargoResult<()> { for dep in deps_to_add { // TODO call cargo add WITH PWD=path - let mut cargo = proc::cargo(config.workspace.config().into())?; + let mut cargo = proc::cargo(config.into())?; cargo.current_dir(path); cargo.arg("add"); cargo.arg(dep.as_ref()); @@ -242,7 +242,7 @@ fn cargo_add<'s>(config: &Config<'_>, rename: Option<&str>, features: Option>) -> CargoResult<()> { - let mut cargo = proc::cargo(config.workspace.config().into())?; + let mut cargo = proc::cargo(config.into())?; cargo.current_dir(pwd); cargo.arg("add"); diff --git a/cargo/src/layout/playdate.rs b/cargo/src/layout/playdate.rs index 73585e40..583bc64c 100644 --- a/cargo/src/layout/playdate.rs +++ b/cargo/src/layout/playdate.rs @@ -4,10 +4,10 @@ use std::path::Path; use std::path::PathBuf; use std::hash::{Hash, Hasher}; +use cargo::core::PackageId; use cargo::util::StableHasher; use cargo_util::paths; use cargo::CargoResult; -use cargo::core::Package; use cargo::core::profiles::Profiles; pub use playdate::layout::Name as TargetName; use crate::config::Config; @@ -59,10 +59,10 @@ impl ForTargetLayout { #[derive(Debug, Clone)] -pub struct CrossTargetLayout<'cfg> { +pub struct CrossTargetLayout { /// Target name (e.g. `game-example-name`) name: TargetName, - package: &'cfg Package, + package: PackageId, /// The root directory: `/$target-dir/playdate`. root: PathBuf, @@ -76,20 +76,20 @@ pub struct CrossTargetLayout<'cfg> { assets: PathBuf, } -impl<'cfg> CrossTargetLayout<'cfg> { - pub fn new(config: &'cfg Config, package: &'cfg Package, name: Option) -> CargoResult { +impl<'cfg> CrossTargetLayout { + pub fn new(config: &'cfg Config, package_id: PackageId, name: Option) -> CargoResult { let profiles = Profiles::new( &config.workspace, config.compile_options.build_config.requested_profile, )?; let profile = profiles.get_dir_name().as_str(); - let name = name.unwrap_or_else(|| TargetName::with_package(package.name().as_str())); + let name = name.unwrap_or_else(|| TargetName::with_package(package_id.name().as_str())); let root = config.workspace.target_dir().as_path_unlocked().join("playdate"); let dest = root.join(profile); let target = dest.join(name.as_path()); let assets = root.join("assets"); Ok(Self { name, - package, + package: package_id, root, dest, target, @@ -101,11 +101,11 @@ impl<'cfg> CrossTargetLayout<'cfg> { /// Global assets layout for cross-target & cross-profile assets build. pub fn assets_layout(&self, config: &Config) -> PlaydateAssets { - PlaydateAssets::global(self, config, self.package) + PlaydateAssets::global(self, config, &self.package) } } -impl Layout for CrossTargetLayout<'_> { +impl Layout for CrossTargetLayout { fn root(&self) -> &Path { self.root.as_ref() } fn dest(&self) -> Cow { self.dest.as_path().into() } @@ -130,7 +130,7 @@ impl Layout for CrossTargetLayout<'_> { } -impl playdate::layout::Layout for CrossTargetLayout<'_> { +impl playdate::layout::Layout for CrossTargetLayout { fn name(&self) -> &TargetName { &self.name } fn root(&self) -> &Path { self.dest.as_path() } fn dest(&self) -> Cow { self.target.as_path().into() } @@ -154,7 +154,7 @@ impl playdate::layout::Layout for CrossTargetLayout<'_> { } } -impl crate::layout::LayoutLockable for CrossTargetLayout<'_> { +impl crate::layout::LayoutLockable for CrossTargetLayout { /// The lockfile filename for a build fn lockfilename(&self) -> Cow<'static, str> { ".playdate-lock".into() } } @@ -170,20 +170,20 @@ pub struct PlaydateAssets { } impl PlaydateAssets { - fn global(root: &CrossTargetLayout<'_>, config: &Config, package: &Package) -> Self { - let name = Self::name_for_package(config, package); + fn global(root: &CrossTargetLayout, config: &Config, package_id: &PackageId) -> Self { + let name = Self::name_for_package(config, package_id); Self { name, root: root.assets.to_owned() } } - pub fn assets_plan_for(&self, config: &Config, package: &Package) -> PathBuf { + pub fn assets_plan_for(&self, config: &Config, package_id: &PackageId) -> PathBuf { use playdate::layout::Layout; - let name = Self::name_for_package(config, package); + let name = Self::name_for_package(config, package_id); self.assets_plan().with_file_name(name).with_extension("json") } - pub fn assets_plan_for_dev(&self, config: &Config, package: &Package) -> PathBuf { - let mut path = self.assets_plan_for(config, package); + pub fn assets_plan_for_dev(&self, config: &Config, package_id: &PackageId) -> PathBuf { + let mut path = self.assets_plan_for(config, package_id); let mut name = path.file_stem() .map(std::ffi::OsStr::to_string_lossy) .unwrap_or_default() @@ -197,12 +197,12 @@ impl PlaydateAssets { path } - fn name_for_package(config: &Config, package: &Package) -> TargetName { + fn name_for_package(config: &Config, package_id: &PackageId) -> TargetName { let mut hasher = StableHasher::new(); - let stable = package.package_id().stable_hash(config.workspace.root()); + let stable = package_id.stable_hash(config.workspace.root()); stable.hash(&mut hasher); let hash = hasher.finish(); - TargetName::with_package(format!("{}-{hash:016x}", package.name())) + TargetName::with_package(format!("{}-{hash:016x}", package_id.name())) } } diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index 0340dbbe..8af6bbc6 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -22,6 +22,7 @@ use playdate::manifest::format::ManifestFmt; use playdate::manifest::CrateInfoSource; use playdate::metadata::format::Metadata; use playdate::metadata::validation::Validate; +use playdate::metadata::validation::ValidateCrate; use crate::assets::AssetsArtifact; use crate::assets::AssetsArtifacts; @@ -236,7 +237,8 @@ fn package_multi_target<'p>(config: &Config, // cross-target layout: let layout_target_name = Name::with_names(package.name().as_str(), products.first().map(|p| &p.name)); let mut layout = - CrossTargetLayout::new(config, package, Some(layout_target_name))?.lock(config.workspace.config())?; + CrossTargetLayout::new(config, package.package_id(), Some(layout_target_name))?.lock(config.workspace + .config())?; if let Some(assets) = assets { debug_assert_eq!( layout.as_ref().assets_layout(config).root(), @@ -321,24 +323,53 @@ fn build_manifest(config: &Config, log.status("Manifest", msg); }); + use ::playdate::metadata::validation::Problem; + let log_problem = |pre: &str, problem: Problem| { + let msg = format!("{pre}: {problem}"); + if problem.is_err() { + config.log().error(msg); + } else { + config.log().warn(msg); + } + }; + let log_src_problem = |problem: Problem| { + let msg = format!("Manifest validation"); + log_problem(&msg, problem) + }; + let log_meta_problem = |problem: Problem| { + let msg = format!("Metadata validation"); + log_problem(&msg, problem) + }; + + let validate = |src: &ManifestSource| { + if let Some(target) = &cargo_target { + src.validate_for(target) + .into_iter() + // .filter(Problem::is_err) + .for_each(log_meta_problem); + } else { + src.validate() + .into_iter() + // .filter(Problem::is_err) + .for_each(log_meta_problem); + } + }; + let manifest = if let Some(metadata) = assets.and_then(|a| a.metadata.as_ref()) { let source = ManifestSource::new(package, metadata.into()); + // This validation not needed at this step. May be earlier: + validate(&source); source.manifest_for_opt(cargo_target.as_deref(), dev) } else { let metadata = playdate_metadata(package); let source = ManifestSource::new(package, metadata.as_ref()); + // This validation not needed at this step. May be earlier: + validate(&source); source.manifest_for_opt(cargo_target.as_deref(), dev) }; // validation, lints - for problem in manifest.validate() { - let msg = format!("Manifest validation: {problem}"); - if problem.is_err() { - config.log().error(msg); - } else { - config.log().warn(msg); - } - } + manifest.validate().into_iter().for_each(log_src_problem); std::fs::write(layout.manifest(), manifest.to_manifest_string()?)?; Ok(()) @@ -520,17 +551,40 @@ impl<'cfg> TryFrom> for SuccessfulBuildProduct<'cfg> { struct ManifestSource<'cfg, 'm> { package: &'cfg Package, authors: Vec<&'cfg str>, + bins: Vec<&'cfg str>, + examples: Vec<&'cfg str>, metadata: Option<&'m Metadata>, } impl<'cfg, 'm> ManifestSource<'cfg, 'm> { fn new(package: &'cfg Package, metadata: Option<&'m Metadata>) -> Self { + log::debug!("new manifest-source for {}", package.package_id()); + + let mut bins = Vec::new(); + let mut examples = Vec::new(); + + package.targets() + .iter() + .inspect(|t| log::trace!("target: {} ({:?})", t.description_named(), t.crate_name())) + .filter(|t| !t.is_custom_build()) + .for_each(|t| { + if t.is_bin() { + bins.push(t.name()); + log::debug!("+bin: {}", t.description_named()); + } else if t.is_example() { + examples.push(t.name()); + log::debug!("+example: {}", t.description_named()); + } + }); + Self { authors: package.manifest() .metadata() .authors .iter() .map(|s| s.as_str()) .collect(), + bins, + examples, package, metadata } } @@ -538,7 +592,6 @@ impl<'cfg, 'm> ManifestSource<'cfg, 'm> { impl CrateInfoSource for ManifestSource<'_, '_> { fn name(&self) -> Cow { self.package.name().as_str().into() } - // fn authors(&self) -> &[String] { &self.package.manifest().metadata().authors } fn authors(&self) -> &[&str] { &self.authors } fn version(&self) -> Cow { self.package.version().to_string().into() } fn description(&self) -> Option> { @@ -549,5 +602,11 @@ impl CrateInfoSource for ManifestSource<'_, '_> { .as_deref() .map(|s: &str| s.into()) } + + fn bins(&self) -> &[&str] { &self.bins } + fn examples(&self) -> &[&str] { &self.examples } + fn metadata(&self) -> Option { self.metadata } + + fn manifest_path(&self) -> Cow { Cow::Borrowed(self.package.manifest_path()) } } diff --git a/cargo/src/proc/utils.rs b/cargo/src/proc/utils.rs index 1108c8a0..9ef7e76d 100644 --- a/cargo/src/proc/utils.rs +++ b/cargo/src/proc/utils.rs @@ -7,7 +7,6 @@ use std::process::Command; use build::consts::SDK_ENV_VAR; use cargo::CargoResult; -use cargo::Config as CargoConfig; use serde::de::DeserializeOwned; use crate::cli::cmd::Cmd; @@ -25,7 +24,7 @@ pub fn cargo_proxy_with>(cfg: &Config, cfg_args: bool) -> CargoResult { let rustflags = cfg.rustflags()?.rustflags_to_args_from(cfg); - let mut proc = cargo(Some(cfg.workspace.config()))?; + let mut proc = cargo(Some(cfg))?; proc.arg(cmd); if cfg_args { proc.args(&cfg.args); @@ -40,11 +39,10 @@ pub fn cargo_proxy_with>(cfg: &Config, } -pub fn cargo(config: Option<&CargoConfig>) -> CargoResult { - let cargo = cargo_bin_path(config); - let mut proc = std::process::Command::new(cargo.as_ref()); +pub fn cargo(cfg: Option<&Config>) -> CargoResult { + let mut proc = cargo_cmd(cfg); - if let Some(cfg) = &config { + if let Some(cfg) = cfg.map(|cfg| cfg.workspace.config()) { // transparent env: cfg.env_config()?.iter().for_each(|(k, v)| { let value = v.resolve(cfg); @@ -71,20 +69,34 @@ pub fn cargo(config: Option<&CargoConfig>) -> CargoResult } -pub fn cargo_bin_path(config: Option<&CargoConfig>) -> Cow { - if let Some(cfg) = config { - let path = cfg.cargo_exe().log_err().ok().map(Cow::from); - if path.is_some() && path == std::env::current_exe().log_err().ok().map(Into::into) { - // Seems to we're in standalone mode. - cargo_bin_path(None) - } else if let Some(path) = path { - path +pub fn cargo_cmd(cfg: Option<&Config>) -> std::process::Command { + fn cargo_path<'t>(cfg: Option<&'t Config<'t>>) -> (Cow<'t, Path>, Option<&str>) { + if let Some(cfg) = cfg { + let path = cfg.workspace.config().cargo_exe().log_err().ok().map(Cow::from); + if path.is_some() && path == std::env::current_exe().log_err().ok().map(Into::into) { + // Seems to we're in standalone mode. + cargo_path(None) + } else if let Some(path) = path { + (path, None) + } else { + cargo_path(None) + } + } else if let Some(path) = env::var_os("CARGO") { + (PathBuf::from(path).into(), None) } else { - cargo_bin_path(None) + let arg = cfg.and_then(|cfg| cfg.rustup.as_os_str()).map(|_| "+nightly"); + (Path::new("cargo").into(), arg) } - } else { - PathBuf::from(env::var_os("CARGO").unwrap_or("cargo".into())).into() } + + let (bin, arg) = cargo_path(cfg); + + let mut proc = std::process::Command::new(bin.as_ref()); + if let Some(arg) = arg { + proc.arg(arg); + } + + proc } diff --git a/cargo/src/utils/cargo/metadata.rs b/cargo/src/utils/cargo/metadata.rs index c460d2dc..69aeeae0 100644 --- a/cargo/src/utils/cargo/metadata.rs +++ b/cargo/src/utils/cargo/metadata.rs @@ -48,9 +48,9 @@ pub fn metadata(cfg: &Config) -> CargoResult { } }); - if !cfg.workspace.ignore_lock() && !cargo.get_args().any(|arg| arg == "--locked") { - cargo.arg("--locked"); - } + // if !cfg.workspace.ignore_lock() && !cargo.get_args().any(|arg| arg == "--locked") { + // cargo.arg("--locked"); + // } } read_cargo_json::(cfg, cargo) From 5da646a97c59aa96d34e9f854c75a0f6349f5863 Mon Sep 17 00:00:00 2001 From: Alexander Koz <888526+boozook@users.noreply.github.com> Date: Thu, 30 May 2024 12:56:10 +0400 Subject: [PATCH 10/24] Apply clippy suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- cargo/src/package/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index 8af6bbc6..6889d841 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -333,11 +333,11 @@ fn build_manifest(config: &Config, } }; let log_src_problem = |problem: Problem| { - let msg = format!("Manifest validation"); + let msg = "Manifest validation".to_string(); log_problem(&msg, problem) }; let log_meta_problem = |problem: Problem| { - let msg = format!("Metadata validation"); + let msg = "Metadata validation".to_string(); log_problem(&msg, problem) }; From 65bab4fb72063af6d85e1f14629187a4eda9b046 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 16:53:08 +0400 Subject: [PATCH 11/24] fix totally ignored assets --- cargo/src/assets/plan.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cargo/src/assets/plan.rs b/cargo/src/assets/plan.rs index 4fac6f75..e25edc53 100644 --- a/cargo/src/assets/plan.rs +++ b/cargo/src/assets/plan.rs @@ -74,7 +74,7 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, let opts = metadata.assets_options(); let has_dev_assets = with_dev && !metadata.dev_assets().is_empty(); - let is_empty = !metadata.assets().is_empty() && !has_dev_assets; + let is_empty = metadata.assets().is_empty() && !has_dev_assets; if is_empty { return Ok(PackageAssetsPlan { main: None, From 149506a00cd9ca481344797ef914d567a220a634 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 21:50:36 +0400 Subject: [PATCH 12/24] improve deserialisation of legacy and error for `AssetsRules` --- support/build/src/metadata/format.rs | 92 +++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index b1b3f662..25f73155 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -36,11 +36,13 @@ pub struct Metadata { pub(super) inner: MetadataInner, } + #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Metadata { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let meta = MetadataInner::deserialize(deserializer)?; + // here is should be some validation Ok(Self { inner: meta }) } } @@ -91,8 +93,10 @@ pub(super) struct MetadataInner { pub(super) manifest: Ext>, #[cfg_attr(feature = "serde", serde(default))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "AssetsRules::deserialize_ext"))] pub(super) assets: AssetsRules, #[cfg_attr(feature = "serde", serde(default, alias = "dev-assets"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "AssetsRules::deserialize_ext"))] pub(super) dev_assets: AssetsRules, #[cfg_attr(feature = "serde", serde(default))] @@ -328,6 +332,40 @@ impl AssetsRules { } +/// Actually anti-compat, just validation and proper error message. +mod compat { + use super::{AssetsOptions, Deserialize, Deserializer, HashMap, RuleValue}; + + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "serde", derive(Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] + enum AssetsRules { + Normal(super::AssetsRules), + LegacyMap { + options: AssetsOptions, + #[cfg_attr(feature = "serde", serde(flatten))] + rules: HashMap, + }, + } + + impl super::AssetsRules { + /// Deserialize through a wrapper that supports legacy, + /// then report it in error. + pub fn deserialize_ext<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + match AssetsRules::deserialize(deserializer)? { + AssetsRules::Normal(rules) => Ok(rules), + AssetsRules::LegacyMap { .. } => { + let err = "unsupported field `assets.options` (that was before), use `options.assets` instead"; + let err = serde::de::Error::custom(err); + Err(err) + }, + } + } + } +} + + #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] @@ -861,6 +899,20 @@ mod tests { assert!(toml::from_str::(src).is_err()); } + + #[test] + fn assets_num_err() { + let src = r#" + [playdate] + bundle-id = "test.workspace.main.crate" + [playdate.assets] + foo = "bar" # ok + num = 42 # err + "#; + assert!(toml::from_str::(src).is_err()); + } + + #[test] fn options_empty() { let m = toml::from_str::("").unwrap(); @@ -947,7 +999,19 @@ mod tests { #[test] - fn meta_assets_options_value() { + fn options_assets_wrong() { + let src = r#" + [playdate] + bundle-id = "test.workspace.main.crate" + [playdate.options.assets] + foo = "bar" # err + [playdate.assets] + "#; + assert!(toml::from_str::(src).is_err()); + } + + #[test] + fn meta_assets_options() { let src = r#" bundle-id = "test.workspace.main.crate" [options.assets] @@ -970,14 +1034,38 @@ mod tests { } #[test] - fn meta_assets_options() { + fn meta_assets_options_legacy() { let src = r#" bundle-id = "test.workspace.main.crate" [assets] options = {} "#; + assert!(toml::from_str::(src).is_err()); + + let src = r#" + bundle-id = "test.workspace.main.crate" + [assets] + options = { dependencies = true } + "#; + assert!(toml::from_str::(src).is_err()); + let src = r#" + bundle-id = "test.workspace.main.crate" + [assets] + foo = "bar" + boo = true + options = { } + "#; assert!(toml::from_str::(src).is_err()); + + + let src = r#" + [playdate] + bundle-id = "test.workspace.main.crate" + [playdate.assets] + [playdate.assets.options] # err + "#; + assert!(toml::from_str::(src).is_err()); } #[test] From f3f1cc211a1e03ebc9110e0b253d957fdb9a6366 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 21:55:41 +0400 Subject: [PATCH 13/24] fix missed feature-gate --- support/build/src/metadata/format.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 25f73155..31767f78 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -334,16 +334,16 @@ impl AssetsRules { /// Actually anti-compat, just validation and proper error message. mod compat { + #![cfg(feature = "serde")] use super::{AssetsOptions, Deserialize, Deserializer, HashMap, RuleValue}; - #[derive(Debug, Clone, PartialEq)] - #[cfg_attr(feature = "serde", derive(Deserialize))] - #[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] + #[derive(Debug, Clone, PartialEq, Deserialize)] + #[serde(untagged, deny_unknown_fields)] enum AssetsRules { Normal(super::AssetsRules), LegacyMap { options: AssetsOptions, - #[cfg_attr(feature = "serde", serde(flatten))] + #[serde(flatten)] rules: HashMap, }, } From 9ffb277d12227845cc832410c5a37a49a5518bb3 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Thu, 30 May 2024 23:01:21 +0400 Subject: [PATCH 14/24] fix `AssetsRules` again --- support/build/src/metadata/format.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 31767f78..9ff3ec8d 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -338,7 +338,7 @@ mod compat { use super::{AssetsOptions, Deserialize, Deserializer, HashMap, RuleValue}; #[derive(Debug, Clone, PartialEq, Deserialize)] - #[serde(untagged, deny_unknown_fields)] + #[serde(untagged)] enum AssetsRules { Normal(super::AssetsRules), LegacyMap { @@ -353,11 +353,20 @@ mod compat { /// then report it in error. pub fn deserialize_ext<'de, D>(deserializer: D) -> Result where D: Deserializer<'de> { - match AssetsRules::deserialize(deserializer)? { - AssetsRules::Normal(rules) => Ok(rules), - AssetsRules::LegacyMap { .. } => { - let err = "unsupported field `assets.options` (that was before), use `options.assets` instead"; - let err = serde::de::Error::custom(err); + match AssetsRules::deserialize(deserializer) { + Ok(result) => { + match result { + AssetsRules::Normal(rules) => Ok(rules), + AssetsRules::LegacyMap { .. } => { + const ERR: &str = + "unsupported field `assets.options` (that was before), use `options.assets` instead"; + Err(serde::de::Error::custom(ERR)) + }, + } + }, + Err(err) => { + const PRE: &str = "invalid `assets`, expected a list of paths or map of rules"; + let err = serde::de::Error::custom(format_args!("{}: {}", PRE, err)); Err(err) }, } From 14c9e4ecf26da4e8096aa3f6a0c67ea257dbf41a Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Fri, 31 May 2024 15:35:35 +0400 Subject: [PATCH 15/24] improve deserialisation and errors --- support/build/src/metadata/format.rs | 263 +++++++++++++++++---------- 1 file changed, 162 insertions(+), 101 deletions(-) diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 9ff3ec8d..360e7047 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -88,16 +88,16 @@ impl MetadataSource for Metadata { #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] -pub(super) struct MetadataInner { +pub(super) struct MetadataInner { #[cfg_attr(feature = "serde", serde(flatten))] pub(super) manifest: Ext>, #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "AssetsRules::deserialize_ext"))] - pub(super) assets: AssetsRules, + #[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::assets_rules"))] + pub(super) assets: AssetsRules, #[cfg_attr(feature = "serde", serde(default, alias = "dev-assets"))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "AssetsRules::deserialize_ext"))] - pub(super) dev_assets: AssetsRules, + #[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::assets_rules"))] + pub(super) dev_assets: AssetsRules, #[cfg_attr(feature = "serde", serde(default))] pub(super) options: Options, @@ -105,42 +105,14 @@ pub(super) struct MetadataInner { pub(super) support: Support, #[cfg_attr(feature = "serde", serde(default, alias = "bin", rename = "bin"))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_targets_overrides"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::targets_overrides"))] pub(super) bins: Vec>, #[cfg_attr(feature = "serde", serde(default, alias = "example", rename = "example"))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_targets_overrides"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::targets_overrides"))] pub(super) examples: Vec>, } -#[cfg(feature = "serde")] -fn deserialize_targets_overrides<'de, D, S>(deserializer: D) -> Result>, D::Error> - where D: Deserializer<'de>, - S: Deserialize<'de> + Default + Eq + Hash { - #[derive(Debug, Clone, PartialEq)] - #[cfg_attr(feature = "serde", derive(Deserialize))] - #[cfg_attr(feature = "serde", serde(untagged))] - pub enum Targets { - List(Vec>), - Map(HashMap>>), - } - - let result = match Targets::::deserialize(deserializer)? { - Targets::List(vec) => vec, - Targets::Map(map) => { - map.into_iter() - .map(|(k, v)| { - Override:: { target: k, - manifest: v } - }) - .collect() - }, - }; - - Ok(result) -} - - #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(bound(deserialize = "Main: Deserialize<'de>")))] @@ -188,7 +160,7 @@ pub struct Manifest { #[cfg_attr(feature = "serde", serde(alias = "content-warning2"))] pub content_warning2: Option, #[cfg_attr(feature = "serde", serde(default, alias = "build-number"))] - #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_num_compat"))] + #[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::usize_or_from_str"))] pub build_number: Option, } @@ -311,14 +283,16 @@ impl Override where S: ToOwned { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] -pub enum AssetsRules { +#[cfg_attr(feature = "serde", + serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] +pub enum AssetsRules { /// List of paths to include. - List(Vec), + List(Vec), /// Rules & queries used to resolve paths to include. - Map(HashMap), + Map(HashMap), } -impl Default for AssetsRules { +impl Default for AssetsRules { fn default() -> Self { Self::List(Vec::with_capacity(0)) } } @@ -332,49 +306,6 @@ impl AssetsRules { } -/// Actually anti-compat, just validation and proper error message. -mod compat { - #![cfg(feature = "serde")] - use super::{AssetsOptions, Deserialize, Deserializer, HashMap, RuleValue}; - - #[derive(Debug, Clone, PartialEq, Deserialize)] - #[serde(untagged)] - enum AssetsRules { - Normal(super::AssetsRules), - LegacyMap { - options: AssetsOptions, - #[serde(flatten)] - rules: HashMap, - }, - } - - impl super::AssetsRules { - /// Deserialize through a wrapper that supports legacy, - /// then report it in error. - pub fn deserialize_ext<'de, D>(deserializer: D) -> Result - where D: Deserializer<'de> { - match AssetsRules::deserialize(deserializer) { - Ok(result) => { - match result { - AssetsRules::Normal(rules) => Ok(rules), - AssetsRules::LegacyMap { .. } => { - const ERR: &str = - "unsupported field `assets.options` (that was before), use `options.assets` instead"; - Err(serde::de::Error::custom(ERR)) - }, - } - }, - Err(err) => { - const PRE: &str = "invalid `assets`, expected a list of paths or map of rules"; - let err = serde::de::Error::custom(format_args!("{}: {}", PRE, err)); - Err(err) - }, - } - } - } -} - - #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] @@ -668,21 +599,125 @@ pub struct Support { } +/// Because serde's error for untagged enum with various inner types is not pretty helpful, +/// like "data did not match any variant of untagged enum AssetsRules", +/// we need some custom implementations with more detailed error messages. #[cfg(feature = "serde")] -fn deserialize_num_compat<'de, D>(deserializer: D) -> Result, D::Error> - where D: Deserializer<'de> { - #[derive(Debug, Clone, PartialEq, Deserialize)] - #[serde(untagged)] - pub enum Value { - Num(usize), - Str(String), +mod one_of { + use std::marker::PhantomData; + + use std::fmt; + use serde::de; + use serde::de::MapAccess; + use serde::de::SeqAccess; + use serde::de::Visitor; + + use super::*; + + + pub fn usize_or_from_str<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + struct OneOf; + + impl<'de> Visitor<'de> for OneOf { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("unsigned integer or string with it") + } + + fn visit_u8(self, v: u8) -> Result { Ok(Some(v as _)) } + fn visit_u16(self, v: u16) -> Result { Ok(Some(v as _)) } + fn visit_u32(self, v: u32) -> Result { Ok(Some(v as _)) } + + fn visit_u64(self, v: u64) -> Result { + Ok(Some(v.try_into().map_err(de::Error::custom)?)) + } + + fn visit_u128(self, v: u128) -> Result { + Ok(Some(v.try_into().map_err(de::Error::custom)?)) + } + + fn visit_i64(self, v: i64) -> Result { + if v.is_negative() { + Err(de::Error::invalid_type(de::Unexpected::Signed(v), &self)) + } else { + Ok(Some(v.try_into().map_err(de::Error::custom)?)) + } + } + + fn visit_str(self, s: &str) -> Result { + let v = s.parse().map_err(serde::de::Error::custom)?; + Ok(Some(v)) + } + } + + deserializer.deserialize_any(OneOf) + } + + + pub fn assets_rules<'de, S, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de>, + S: Deserialize<'de> + Eq + Hash + Default { + struct OneOf(PhantomData); + + impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash + Default { + type Value = super::AssetsRules; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("list of includes or map of rules") + } + + fn visit_seq>(self, seq: A) -> Result { + let deserializer = de::value::SeqAccessDeserializer::new(seq); + let res: Vec = Deserialize::deserialize(deserializer)?; + Ok(super::AssetsRules::List(res)) + } + + fn visit_map>(self, map: M) -> Result { + let deserializer = de::value::MapAccessDeserializer::new(map); + let res: HashMap = Deserialize::deserialize(deserializer)?; + Ok(super::AssetsRules::Map(res)) + } + } + + deserializer.deserialize_any(OneOf::(PhantomData)) + } + + + pub fn targets_overrides<'de, S, D>(deserializer: D) -> Result>, D::Error> + where D: Deserializer<'de>, + S: Deserialize<'de> + Eq + Hash + Default { + struct OneOf(PhantomData); + + impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash + Default { + type Value = Vec>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("list of includes or map of rules") + } + + fn visit_seq>(self, seq: A) -> Result { + let deserializer = de::value::SeqAccessDeserializer::new(seq); + Deserialize::deserialize(deserializer) + } + + fn visit_map>(self, map: M) -> Result { + use super::{Ext, Manifest}; + + let deserializer = de::value::MapAccessDeserializer::new(map); + let res: HashMap>> = Deserialize::deserialize(deserializer)?; + Ok(res.into_iter() + .map(|(k, v)| { + Override:: { target: k, + manifest: v } + }) + .collect()) + } + } + + deserializer.deserialize_any(OneOf::(PhantomData)) } - let result = match Option::::deserialize(deserializer)? { - Some(Value::Num(value)) => Some(value), - Some(Value::Str(s)) => Some(s.parse().map_err(serde::de::Error::custom)?), - None => None, - }; - Ok(result) } @@ -1008,15 +1043,41 @@ mod tests { #[test] - fn options_assets_wrong() { + fn options_assets_err() { let src = r#" [playdate] bundle-id = "test.workspace.main.crate" - [playdate.options.assets] - foo = "bar" # err - [playdate.assets] + [playdate.options.assets] + foo = "bar" # err "#; - assert!(toml::from_str::(src).is_err()); + let result = toml::from_str::(src); + assert!(result.is_err(), "must be err, but {result:?}"); + assert!(result.as_ref() + .unwrap_err() + .to_string() + .contains("unknown field `foo`")); + } + + #[test] + fn assets_options_err() { + let src = r#" + [playdate] + bundle-id = "test.workspace.main.crate" + [playdate.assets] + foo = "bar" + options = { dependencies = true } + "#; + let result = toml::from_str::(src); + assert!(result.is_err(), "must be err, but {result:?}"); + + let src = r#" + [playdate] + bundle-id = "test.workspace.main.crate" + [playdate.assets.options] + dependencies = true + "#; + let result = toml::from_str::(src); + assert!(result.is_err(), "must be err, but {result:?}"); } #[test] From 650c62e0a51eed3f8013497d3fed0f702facf4ea Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Fri, 31 May 2024 15:36:16 +0400 Subject: [PATCH 16/24] upd deps --- Cargo.lock | 22 +++++++++++----------- cargo/Cargo.toml | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index def8935a..12afa804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,7 +757,7 @@ dependencies = [ "fs_extra", "futures-lite 2.3.0", "log", - "nix 0.28.0", + "nix 0.29.0", "once_cell", "playdate-build", "playdate-device", @@ -848,9 +848,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "cipher" @@ -864,9 +864,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "f803f94ecf597339c7a34eed2036ef83f86aaba937f001f7c5b5e251f043f1f9" dependencies = [ "glob", "libc", @@ -3571,9 +3571,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -5585,9 +5585,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -5615,9 +5615,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 949610f4..91e33ae9 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -89,11 +89,11 @@ features = [ [dev-dependencies] -target = "2.0.0" +target = "2.0.1" rand = "0.8" [target.'cfg(unix)'.dev-dependencies] -nix = { version = "0.28", features = ["signal"] } +nix = { version = "0.29", features = ["signal"] } [features] From 5f9405b497477d32c35235c5acf4d39ec86e10d4 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Fri, 31 May 2024 16:51:35 +0400 Subject: [PATCH 17/24] parametrize strings in metadata format --- support/build/src/assets/plan.rs | 17 ++++++++++------- support/build/src/metadata/format.rs | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/support/build/src/assets/plan.rs b/support/build/src/assets/plan.rs index 0945c1b0..48ae5b43 100644 --- a/support/build/src/assets/plan.rs +++ b/support/build/src/assets/plan.rs @@ -12,11 +12,13 @@ use super::resolver::*; /// Create build plan for assets. -pub fn build_plan<'l, 'r, 'c: 'l /* V */>(env: &'c Env, - assets: &AssetsRules, - options: &AssetsOptions, - crate_root: Option<&'c Path>) - -> Result, super::Error> { +pub fn build_plan<'l, 'r, 'c: 'l, 'v, S>(env: &'c Env, + assets: &AssetsRules, + options: &AssetsOptions, + crate_root: Option<&'c Path>) + -> Result, super::Error> + where S: Eq + Hash + ToString +{ // copy_unresolved => get all files with glob // include_unresolved => same // exclude_unresolved => @@ -34,8 +36,9 @@ pub fn build_plan<'l, 'r, 'c: 'l /* V */>(env: &'c Env, let crate_root = crate_root.unwrap_or_else(|| env.cargo_manifest_dir()); let link_behavior = options.link_behavior(); - let to_relative = |s: &String| -> String { - let p = Path::new(s); + let to_relative = |s: &S| -> String { + let s = s.to_string(); + let p = Path::new(&s); if p.is_absolute() || p.has_root() { let trailing_sep = p.components().count() > 1 && s.ends_with(PATH_SEPARATOR); let mut s = p.components().skip(1).collect::().display().to_string(); diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 360e7047..6a8e8ee3 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -1,11 +1,9 @@ use std::ops::Deref; use std::cmp::Eq; +use std::hash::Hash; use std::borrow::Cow; use std::collections::HashMap; -#[cfg(feature = "serde")] -use std::hash::Hash; - #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer}; @@ -14,9 +12,11 @@ use super::source::*; #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub struct CrateMetadata { +#[cfg_attr(feature = "serde", + serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] +pub struct CrateMetadata { #[cfg_attr(feature = "serde", serde(rename = "playdate"))] - pub inner: Option, + pub inner: Option>, } /// Just ensure that `METADATA_FIELD` is not changed and something missed. @@ -32,16 +32,17 @@ fn eq_metadata_field() { /// - Assets tables - `assets` & `dev-assets` /// - Configuration table - `options` #[derive(Debug, Clone, PartialEq)] -pub struct Metadata { - pub(super) inner: MetadataInner, + +pub struct Metadata { + pub(super) inner: MetadataInner, } #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Metadata { +impl<'de, S: Deserialize<'de> + Default + Eq + Hash> Deserialize<'de> for Metadata { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { - let meta = MetadataInner::deserialize(deserializer)?; + let meta = MetadataInner::::deserialize(deserializer)?; // here is should be some validation Ok(Self { inner: meta }) } From 7f496b5041ebb9fd1fe89fc454e60efb8da2d4b1 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Fri, 31 May 2024 17:52:16 +0400 Subject: [PATCH 18/24] reduce usage of `Package` where only `PackageId` needed --- cargo/src/assets/mod.rs | 16 +++++++++------- cargo/src/assets/plan.rs | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cargo/src/assets/mod.rs b/cargo/src/assets/mod.rs index b5dc5a47..a8910de5 100644 --- a/cargo/src/assets/mod.rs +++ b/cargo/src/assets/mod.rs @@ -113,7 +113,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { .map(|plan| (plan, AssetKind::Package)) .chain(plan.dev.as_ref().into_iter().map(|plan| (plan, AssetKind::Dev))) { - let message = plan.printable_serializable(package, kind); + let message = plan.printable_serializable(package.package_id(), kind); config.workspace.config().shell().print_json(&message)?; } } @@ -149,7 +149,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { let mut has_errors = false; let mut targets = HashMap::new(); - let mut check_duplicates = |package: &Package, target_kind: AssetKind, plan| { + let mut check_duplicates = |package_id: PackageId, target_kind: AssetKind, plan| { for target in plan { if let Some((pid, kind)) = targets.get::>(&target) { has_errors = true; @@ -160,7 +160,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { } }; let a = err_msg(pid, *kind); - let b = err_msg(&package.package_id(), target_kind); + let b = err_msg(&package_id, target_kind); let message = format!( "Duplicate dev-asset destination: '{}':\n\t{a}\n\t{b}", target.as_relative_to_root(config).display(), @@ -168,19 +168,20 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { config.log().error(message); } else { - targets.insert(target, (package.package_id(), target_kind)); + targets.insert(target, (package_id, target_kind)); } } }; for (package, plan) in plans.iter() { + let package_id = package.package_id(); if let Some(plan) = plan.main.as_ref() { - check_duplicates(package, AssetKind::Package, plan.as_inner().targets()); + check_duplicates(package_id, AssetKind::Package, plan.as_inner().targets()); } - if package.package_id() == target_pid { + if package_id == target_pid { if let Some(plan) = plan.dev.as_ref() { - check_duplicates(package, AssetKind::Dev, plan.as_inner().targets()); + check_duplicates(package_id, AssetKind::Dev, plan.as_inner().targets()); } } } @@ -234,6 +235,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { }); + // FIXME: use primary (top-level) assets-options, but not options of dependency! let metadata = options.get(dependency).expect("Metadata is gone, impossible!"); let report = plan.apply(&dest, &metadata.assets_options(), config)?; diff --git a/cargo/src/assets/plan.rs b/cargo/src/assets/plan.rs index e25edc53..9f01e849 100644 --- a/cargo/src/assets/plan.rs +++ b/cargo/src/assets/plan.rs @@ -223,8 +223,8 @@ impl<'t, 'cfg> CachedPlan<'t, 'cfg> { } - pub fn printable_serializable(&self, source: &Package, kind: AssetKind) -> SerializablePlan<'_, 't, 'cfg> { - SerializablePlan { package: source.package_id(), + pub fn printable_serializable(&self, source: PackageId, kind: AssetKind) -> SerializablePlan<'_, 't, 'cfg> { + SerializablePlan { package: source, plan: &self.plan, difference: &self.difference, path: &self.path, From 7d16e3c0f737270a3d244c4de99ff12505a181c9 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Fri, 31 May 2024 17:55:09 +0400 Subject: [PATCH 19/24] set correct version --- Cargo.lock | 2 +- cargo/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12afa804..6bf1bee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,7 +740,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.5.0-pre1" +version = "0.5.0-alpha.2" dependencies = [ "anstyle", "anyhow", diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 91e33ae9..6fccff83 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.5.0-pre1" +version = "0.5.0-alpha.2" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] From 570684cd75e7f91e08b58aa841166148a96b9394 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Sat, 1 Jun 2024 16:17:04 +0400 Subject: [PATCH 20/24] allow use other strings --- cargo/src/assets/mod.rs | 1 + cargo/src/utils/cargo/metadata.rs | 3 +- support/build/src/metadata/format.rs | 68 ++++++++++---------- support/build/src/metadata/source.rs | 92 ++++++++++++++-------------- 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/cargo/src/assets/mod.rs b/cargo/src/assets/mod.rs index a8910de5..fc931e5f 100644 --- a/cargo/src/assets/mod.rs +++ b/cargo/src/assets/mod.rs @@ -55,6 +55,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { layout.clean()?; } + // primary top-level package let target_pid = package.package_id(); let has_dev = targets.iter() .any(|t| t.is_example() || t.is_test() || t.is_bench()); diff --git a/cargo/src/utils/cargo/metadata.rs b/cargo/src/utils/cargo/metadata.rs index 69aeeae0..61b5d83b 100644 --- a/cargo/src/utils/cargo/metadata.rs +++ b/cargo/src/utils/cargo/metadata.rs @@ -1,5 +1,6 @@ use std::ffi::OsStr; +use cargo::util::interning::InternedString; use cargo::CargoResult; use crate::config::Config; @@ -7,7 +8,7 @@ use crate::proc::cargo_proxy_with; use crate::proc::read_cargo_json; -pub type CargoMetadataPd = format::Report; +pub type CargoMetadataPd = format::Report>; pub fn metadata(cfg: &Config) -> CargoResult { diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 6a8e8ee3..aba77561 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -13,8 +13,8 @@ use super::source::*; #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", - serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] -pub struct CrateMetadata { + serde(bound(deserialize = "S: Deserialize<'de> + Eq + Hash")))] +pub struct CrateMetadata { #[cfg_attr(feature = "serde", serde(rename = "playdate"))] pub inner: Option>, } @@ -33,13 +33,13 @@ fn eq_metadata_field() { /// - Configuration table - `options` #[derive(Debug, Clone, PartialEq)] -pub struct Metadata { +pub struct Metadata { pub(super) inner: MetadataInner, } #[cfg(feature = "serde")] -impl<'de, S: Deserialize<'de> + Default + Eq + Hash> Deserialize<'de> for Metadata { +impl<'de, S: Deserialize<'de> + Eq + Hash> Deserialize<'de> for Metadata { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let meta = MetadataInner::::deserialize(deserializer)?; @@ -49,23 +49,29 @@ impl<'de, S: Deserialize<'de> + Default + Eq + Hash> Deserialize<'de> for Metada } -impl MetadataSource for Metadata { - type Manifest = Ext>; - type TargetManifest = Override; +impl MetadataSource for Metadata + where S: Eq + Hash + AsRef, + Override: ManifestSourceOptExt, + Ext>: ManifestSourceOptExt, + for<'t> &'t Ext>: ManifestSourceOptExt +{ + type S = S; + type Manifest = Ext>; + type TargetManifest = Override; - fn manifest(&self) -> impl ManifestSourceOptExt { &self.inner.manifest } + fn manifest(&self) -> &Self::Manifest { &self.inner.manifest } fn bins(&self) -> &[Self::TargetManifest] { self.inner.bins.as_slice() } fn examples(&self) -> &[Self::TargetManifest] { self.inner.examples.as_slice() } - fn bin_targets(&self) -> impl IntoIterator { self.inner.bins.iter().map(|o| o.target.as_str()) } + fn bin_targets(&self) -> impl IntoIterator { self.inner.bins.iter().map(|o| o.target.as_ref()) } fn example_targets(&self) -> impl IntoIterator { - self.inner.examples.iter().map(|o| o.target.as_str()) + self.inner.examples.iter().map(|o| o.target.as_ref()) } - fn assets(&self) -> &AssetsRules { &self.inner.assets } - fn dev_assets(&self) -> &AssetsRules { &self.inner.dev_assets } + fn assets(&self) -> &AssetsRules { &self.inner.assets } + fn dev_assets(&self) -> &AssetsRules { &self.inner.dev_assets } fn options(&self) -> &Options { &self.inner.options } @@ -88,8 +94,8 @@ impl MetadataSource for Metadata { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", - serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))] -pub(super) struct MetadataInner { + serde(bound(deserialize = "S: Deserialize<'de> + Eq + Hash")))] +pub(super) struct MetadataInner { #[cfg_attr(feature = "serde", serde(flatten))] pub(super) manifest: Ext>, @@ -134,7 +140,7 @@ impl Ext { } impl Ext> where S: ToOwned { - pub fn clone_owned(self) -> Ext::Owned>> { + pub fn clone_owned(&self) -> Ext::Owned>> { Ext { main: self.main.clone_owned(), extra: self.extra.to_owned() } } @@ -223,16 +229,16 @@ impl IntoOwned::Owned>> for Manifest> { } impl Manifest where S: ToOwned { - pub fn clone_owned(self) -> Manifest<::Owned> { - Manifest { name: self.name.map(|s| s.to_owned()), - version: self.version.map(|s| s.to_owned()), - author: self.author.map(|s| s.to_owned()), - bundle_id: self.bundle_id.map(|s| s.to_owned()), - description: self.description.map(|s| s.to_owned()), - image_path: self.image_path.map(|s| s.to_owned()), - launch_sound_path: self.launch_sound_path.map(|s| s.to_owned()), - content_warning: self.content_warning.map(|s| s.to_owned()), - content_warning2: self.content_warning2.map(|s| s.to_owned()), + pub fn clone_owned(&self) -> Manifest<::Owned> { + Manifest { name: self.name.as_ref().map(|s| s.to_owned()), + version: self.version.as_ref().map(|s| s.to_owned()), + author: self.author.as_ref().map(|s| s.to_owned()), + bundle_id: self.bundle_id.as_ref().map(|s| s.to_owned()), + description: self.description.as_ref().map(|s| s.to_owned()), + image_path: self.image_path.as_ref().map(|s| s.to_owned()), + launch_sound_path: self.launch_sound_path.as_ref().map(|s| s.to_owned()), + content_warning: self.content_warning.as_ref().map(|s| s.to_owned()), + content_warning2: self.content_warning2.as_ref().map(|s| s.to_owned()), build_number: self.build_number } } } @@ -240,7 +246,7 @@ impl Manifest where S: ToOwned { #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de> +Default")))] +#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))] pub struct Override { /// Associated cargo-target name #[cfg_attr(feature = "serde", serde(rename = "id", alias = "target"))] @@ -274,7 +280,7 @@ impl<'t> IntoOwned> for Override> { } impl Override where S: ToOwned { - pub fn clone_owned(self) -> Override<::Owned> { + pub fn clone_owned(&self) -> Override<::Owned> { Override { target: self.target.to_owned(), manifest: self.manifest.clone_owned() } } @@ -659,10 +665,10 @@ mod one_of { pub fn assets_rules<'de, S, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, - S: Deserialize<'de> + Eq + Hash + Default { + S: Deserialize<'de> + Eq + Hash { struct OneOf(PhantomData); - impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash + Default { + impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash { type Value = super::AssetsRules; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -688,10 +694,10 @@ mod one_of { pub fn targets_overrides<'de, S, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, - S: Deserialize<'de> + Eq + Hash + Default { + S: Deserialize<'de> + Eq + Hash { struct OneOf(PhantomData); - impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash + Default { + impl<'de, S> Visitor<'de> for OneOf where S: Deserialize<'de> + Eq + Hash { type Value = Vec>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index f3fc290e..f983510d 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -1,3 +1,4 @@ +use std::hash::Hash; use std::borrow::Cow; use std::path::Path; @@ -27,46 +28,45 @@ pub trait CrateInfoSource { fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { use super::format::Manifest; - { - let author = { - let author = self.authors().join(", "); - if author.trim().is_empty() { - None - } else { - Some(author.into()) - } - }; - let version = Some(self.version()); - let package = Manifest { name: Some(self.name()), - description: self.description(), - author, - version, - bundle_id: None, - image_path: None, - launch_sound_path: None, - content_warning: None, - content_warning2: None, - build_number: None }; - - if let Some(meta) = self.metadata() { - let manifest = meta.manifest(); - let base = Ext { main: package, - extra: Default::default() }; - // TODO: Reduce coping, return associated type instead with all strings in the Cow<'self>. - // Also get merged manifest with refs, using `override_with_extra_ref` - let result = base.override_with_extra(&manifest); - Ext { main: Manifest::from(&result), - extra: result.iter_extra() - .map(|m| { - m.into_iter() - .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) - .collect() - }) - .unwrap_or_default() } + + let author = { + let author = self.authors().join(", "); + if author.trim().is_empty() { + None } else { - Ext { main: package.into_owned(), - extra: Default::default() } + Some(author.into()) } + }; + let version = Some(self.version()); + let package = Manifest { name: Some(self.name()), + description: self.description(), + author, + version, + bundle_id: None, + image_path: None, + launch_sound_path: None, + content_warning: None, + content_warning2: None, + build_number: None }; + + if let Some(meta) = self.metadata() { + let manifest = meta.manifest(); + let base = Ext { main: package, + extra: Default::default() }; + // TODO: Reduce coping, return associated type instead with all strings in the Cow<'self>. + // Also get merged manifest with refs, using `override_with_extra_ref` + let result = base.override_with_extra(manifest); + Ext { main: Manifest::from(&result), + extra: result.iter_extra() + .map(|m| { + m.into_iter() + .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone())) + .collect() + }) + .unwrap_or_default() } + } else { + Ext { main: package.into_owned(), + extra: Default::default() } } } @@ -103,11 +103,12 @@ pub trait CrateInfoSource { pub trait MetadataSource { + type S: Eq + Hash; type Manifest: ManifestSourceOptExt; type TargetManifest: ManifestSourceOptExt + TargetId; /// Main manifest, default and base for all cargo-targets. - fn manifest(&self) -> impl ManifestSourceOptExt; + fn manifest(&self) -> &Self::Manifest; /// All manifests for "bin" cargo-targets. /// Overrides main manifest field-by-field. @@ -147,8 +148,8 @@ pub trait MetadataSource { .chain(self.examples_iter().into_iter().flatten()) } - fn assets(&self) -> &AssetsRules; - fn dev_assets(&self) -> &AssetsRules; + fn assets(&self) -> &AssetsRules; + fn dev_assets(&self) -> &AssetsRules; fn options(&self) -> &Options; fn assets_options(&self) -> Cow<'_, AssetsOptions>; @@ -181,17 +182,18 @@ pub trait MetadataSource { self.manifest_for_target(target, false) .or_else(|| self.manifest_for_target(target, true)) .map(|m| m.into_manifest()) - .or_else(|| Some(self.manifest().into_manifest())) + .or_else(|| Some(Ext::>::from(self.manifest()))) } } impl MetadataSource for &T { + type S = ::S; type Manifest = ::Manifest; type TargetManifest = ::TargetManifest; - fn manifest(&self) -> impl ManifestSourceOptExt { (*self).manifest() } + fn manifest(&self) -> &Self::Manifest { (*self).manifest() } fn bins(&self) -> &[Self::TargetManifest] { ::bins(*self) } fn examples(&self) -> &[Self::TargetManifest] { ::examples(*self) } @@ -199,8 +201,8 @@ impl MetadataSource for &T { fn bin_targets(&self) -> impl IntoIterator { (*self).bin_targets() } fn example_targets(&self) -> impl IntoIterator { (*self).example_targets() } - fn assets(&self) -> &AssetsRules { (*self).assets() } - fn dev_assets(&self) -> &AssetsRules { (*self).dev_assets() } + fn assets(&self) -> &AssetsRules { (*self).assets() } + fn dev_assets(&self) -> &AssetsRules { (*self).dev_assets() } fn options(&self) -> &Options { (*self).options() } fn assets_options(&self) -> Cow<'_, AssetsOptions> { (*self).assets_options() } fn support(&self) -> &Support { (*self).support() } From 6008903320b792e8b19eceaacaa1478fd4b01298 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Sun, 2 Jun 2024 15:13:50 +0400 Subject: [PATCH 21/24] deps: downgraded clang-sys v1.8.2 -> v1.8.1 --- Cargo.lock | 71 +++++++++++++++++++----------------------------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bf1bee3..6efd6f41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener-strategy 0.5.2", + "event-listener-strategy", "futures-core", "pin-project-lite", ] @@ -186,8 +186,8 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", "once_cell", @@ -216,17 +216,17 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ - "async-lock 3.3.0", + "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.0", + "polling 3.7.1", "rustix 0.38.34", "slab", "tracing", @@ -244,12 +244,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.3.1", + "event-listener-strategy", "pin-project-lite", ] @@ -272,12 +272,12 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +checksum = "329972aa325176e89114919f2a80fdae4f4c040f66a370b1a1159c6c0f94e7aa" dependencies = [ - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", @@ -864,9 +864,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f803f94ecf597339c7a34eed2036ef83f86aaba937f001f7c5b5e251f043f1f9" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1519,17 +1519,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -1541,16 +1530,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - [[package]] name = "event-listener-strategy" version = "0.5.2" @@ -4057,9 +4036,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -4390,9 +4369,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" dependencies = [ "cfg-if", "concurrent-queue", @@ -4460,9 +4439,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -5457,9 +5436,9 @@ dependencies = [ [[package]] name = "target" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4df6b0340c7cc29eb3b955cc588d145ed60651bf1ab939083295d19ec8cc282" +checksum = "1e8f05f774b2db35bdad5a8237a90be1102669f8ea013fea9777b366d34ab145" [[package]] name = "tempfile" From e96cbd4e8e0786e8e84d5e7ad373f3fd58e706bd Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Tue, 4 Jun 2024 15:08:52 +0400 Subject: [PATCH 22/24] improve `CrateInfoSource` --- cargo/src/package/mod.rs | 6 ++++-- cargo/src/utils/cargo/unit_graph.rs | 8 ++++---- support/build/src/lib.rs | 1 + support/build/src/metadata/format.rs | 2 +- support/build/src/metadata/source.rs | 17 +++++++++++++---- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index 6889d841..858857ee 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -590,9 +590,11 @@ impl<'cfg, 'm> ManifestSource<'cfg, 'm> { } } -impl CrateInfoSource for ManifestSource<'_, '_> { +impl<'cfg> CrateInfoSource for ManifestSource<'cfg, '_> { + type Authors = [&'cfg str]; + fn name(&self) -> Cow { self.package.name().as_str().into() } - fn authors(&self) -> &[&str] { &self.authors } + fn authors(&self) -> &[&'cfg str] { &self.authors } fn version(&self) -> Cow { self.package.version().to_string().into() } fn description(&self) -> Option> { self.package diff --git a/cargo/src/utils/cargo/unit_graph.rs b/cargo/src/utils/cargo/unit_graph.rs index a36027df..709105a8 100644 --- a/cargo/src/utils/cargo/unit_graph.rs +++ b/cargo/src/utils/cargo/unit_graph.rs @@ -35,7 +35,7 @@ pub mod format { pub roots: Vec, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] pub struct Unit { #[serde(deserialize_with = "deserialize_package_id", alias = "pkg_id")] pub package_id: PackageId, @@ -46,13 +46,13 @@ pub mod format { #[serde(serialize_with = "CompileMode::serialize")] #[serde(deserialize_with = "CompileModeProxy::deserialize")] pub mode: CompileMode, - pub features: Vec, pub dependencies: Vec, // ... + // pub features: Vec, // pub profile: crate::proc::reader::format::ArtifactProfile, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] pub struct UnitTarget { pub kind: TargetKind, #[serde(deserialize_with = "deserialize_crate_types")] @@ -62,7 +62,7 @@ pub mod format { // ... } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] pub struct UnitDep { pub index: usize, pub extern_crate_name: String, diff --git a/support/build/src/lib.rs b/support/build/src/lib.rs index 619e5229..a1d3a7d1 100644 --- a/support/build/src/lib.rs +++ b/support/build/src/lib.rs @@ -1,5 +1,6 @@ #![feature(extract_if)] #![feature(io_error_more)] +#![feature(slice_concat_trait)] #![cfg_attr(test, feature(assert_matches))] #[macro_use] diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index aba77561..3dab6396 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -303,7 +303,7 @@ impl Default for AssetsRules { fn default() -> Self { Self::List(Vec::with_capacity(0)) } } -impl AssetsRules { +impl AssetsRules { pub fn is_empty(&self) -> bool { match self { Self::List(list) => list.is_empty(), diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index f983510d..963c97bd 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -6,10 +6,13 @@ use super::format::{AssetsOptions, AssetsRules, Ext, ExtraFields, ExtraValue, Ma pub trait CrateInfoSource { + type Authors: ?Sized + std::slice::Join<&'static str, Output = String>; + /// Crate name. fn name(&self) -> Cow; /// Crate authors. - fn authors(&self) -> &[&str]; + fn authors(&self) -> &Self::Authors; + // fn authors(&self) -> &[&str]; /// Crate version (semver). fn version(&self) -> Cow; /// Crate description. @@ -28,9 +31,11 @@ pub trait CrateInfoSource { fn manifest_for_crate(&self) -> impl ManifestSourceOptExt { use super::format::Manifest; + use std::slice::Join; let author = { - let author = self.authors().join(", "); + // let author = self.authors().join(", "); + let author = Join::join(self.authors(), ", "); if author.trim().is_empty() { None } else { @@ -495,8 +500,10 @@ mod tests { struct CrateInfoNoMeta; impl CrateInfoSource for CrateInfoNoMeta { + type Authors = [&'static str]; + fn name(&self) -> Cow { "Name".into() } - fn authors(&self) -> &[&str] { &["John"] } + fn authors(&self) -> &Self::Authors { &["John"] } fn version(&self) -> Cow { "0.0.0".into() } fn description(&self) -> Option> { None } fn bins(&self) -> &[&str] { &[SOME_TARGET] } @@ -518,8 +525,10 @@ mod tests { struct CrateInfo; impl CrateInfoSource for CrateInfo { + type Authors = [&'static str]; + fn name(&self) -> Cow { "Crate Name".into() } - fn authors(&self) -> &[&str] { &["John"] } + fn authors(&self) -> &[&'static str] { &["John"] } fn version(&self) -> Cow { "0.0.0".into() } fn description(&self) -> Option> { None } From 575ee8c4450c56d3d9af99f67c7026df4efdb398 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Tue, 4 Jun 2024 19:32:11 +0400 Subject: [PATCH 23/24] add draft for meta-tree (unit-graph + metadata) --- Cargo.lock | 2 +- cargo/Cargo.toml | 2 +- cargo/src/utils/cargo/meta_deps.rs | 392 +++++++++++++++++++++++++++ cargo/src/utils/cargo/metadata.rs | 1 + cargo/src/utils/cargo/mod.rs | 1 + support/build/src/metadata/source.rs | 1 - 6 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 cargo/src/utils/cargo/meta_deps.rs diff --git a/Cargo.lock b/Cargo.lock index 6efd6f41..0c44e843 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,7 +740,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.5.0-alpha.2" +version = "0.5.0-beta.1" dependencies = [ "anstyle", "anyhow", diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 6fccff83..b3e9d693 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.5.0-alpha.2" +version = "0.5.0-beta.1" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] diff --git a/cargo/src/utils/cargo/meta_deps.rs b/cargo/src/utils/cargo/meta_deps.rs new file mode 100644 index 00000000..b13d9192 --- /dev/null +++ b/cargo/src/utils/cargo/meta_deps.rs @@ -0,0 +1,392 @@ +use std::collections::HashMap; + +use cargo::core::compiler::CompileMode; +use cargo::core::PackageId; +use cargo::util::interning::InternedString; +use cargo::CargoResult; +use playdate::manifest::CrateInfoSource; +use playdate::metadata::source::MetadataSource; +use serde::Deserialize; +use serde::de::IntoDeserializer; + +use crate::config::Config; +use crate::logger::LogErr; + +use super::build_plan::format::TargetKind; +use super::build_plan::TargetKindWild; +use super::metadata::format::{Package, Metadata}; +use super::metadata::CargoMetadataPd; +use super::unit_graph::format::Unit; +use super::unit_graph::format::UnitGraph; + + +pub fn meta_deps<'cfg>(cfg: &'cfg Config<'cfg>) -> CargoResult> { + let units = cfg.unit_graph()?; + let meta = cfg.metadata()?; + Ok(MetaDeps::new(units, meta)) +} + + +pub struct MetaDeps<'cfg> { + units: &'cfg UnitGraph, + meta: &'cfg CargoMetadataPd, + roots: Vec>, +} + +#[derive(Debug, Clone)] +pub struct RootNode<'cfg> { + node: Node<'cfg>, + deps: Vec>, +} + + +#[derive(Debug, Clone, Copy)] +pub struct Node<'cfg> { + meta: Option<&'cfg Package>>, + unit: &'cfg Unit, +} + +impl<'t> RootNode<'t> { + pub fn package_id(&self) -> &'t PackageId { &self.node.package_id() } + + pub fn node(&self) -> &Node<'t> { &self.node } + + /// Dependencies _with assets_ of the root node, + /// in topological order, + /// including the root node if it has assets. + pub fn deps(&self) -> &[Node<'t>] { &self.deps } +} + +impl<'t> Node<'t> { + pub fn package_id(&self) -> &'t PackageId { &self.unit.package_id } +} + + +impl<'t> MetaDeps<'t> { + pub fn new(units: &'t UnitGraph, meta: &'t CargoMetadataPd) -> Self { + let mode_is_build = |u: &&Unit| matches!(u.mode, CompileMode::Build); + let is_prime_tk = |u: &&Unit| { + matches!( + u.target.kind, + TargetKind::Lib(_) | TargetKind::Bin | TargetKind::Example + ) + }; + let is_sub_tk = |u: &&Unit| matches!(u.target.kind, TargetKind::Lib(_)); + + + let mut roots = units.roots + .iter() + .map(|i| &units.units[*i]) + .filter(mode_is_build) + .filter(is_prime_tk) + .map(|u| { + let m = meta.packages.iter().find(|p| p.id == u.package_id); + Node::<'t> { meta: m, unit: u } + }) + .map(|node| { + RootNode::<'t> { node, + deps: Vec::with_capacity(0) } + }) + .collect::>(); + + + let deps_of = |u: &'t Unit| { + log::trace!("deps of {}::{}:", u.package_id.name(), u.target.name); + u.dependencies + .iter() + .map(|d| &units.units[d.index]) + .filter(mode_is_build) + .filter(is_sub_tk) + .map(|u| { + let m = meta.packages.iter().find(|p| p.id == u.package_id); + Node::<'t> { meta: m, unit: u } + }) + .inspect(|n| { + log::trace!(" {} (meta: {})", n.package_id().name(), n.meta.is_some(),); + }) + }; + + + // flat meta-deps-tree: + for root in roots.iter_mut() { + let deps_allowed = root.deps_allowed(); + let root_is_dev = matches!(root.node.unit.target.kind, TargetKind::Example); + log::trace!("root (dev: {root_is_dev}) {}", root.package_id().name()); + log::trace!("deps allowed: {deps_allowed} for {}", root.package_id().name()); + + let mut deps = Vec::with_capacity(units.units.len()); + + if deps_allowed { + // level 0: + deps.extend(deps_of(root.node.unit)); + + let mut from = 0; + let mut end = deps.len(); + + // level 1..-: + let mut level = 1; + while from != end { + log::trace!("depth level: {level}"); + + let next: Vec<_> = deps[from..].iter().map(|n| n.unit).flat_map(deps_of).collect(); + deps.extend(next.into_iter()); + + from = end; + end = deps.len(); + level += 1; + } + + log::debug!( + "Total deps for {}::{} ({:?}) : {}", + root.package_id().name(), + root.node.unit.target.name, + root.node.unit.target.kind, + deps.len() + ); + + + // Pre-filter. Remove following: + // - units without pd-meta + // - units without assets in the pd-meta + // - remove units with id eq root's + let removed = deps.extract_if(|n| { + n.package_id() == root.package_id() || + n.meta + .and_then(|m| m.metadata.as_ref()) + .and_then(|m| m.inner.as_ref()) + .filter(|m| !m.assets().is_empty()) + .is_none() + }) + .count(); + log::debug!("removed {removed} without metadata or assets"); + + + // dedup: remove first ones, leave a last one: + let mut dups = deps.iter().fold(HashMap::new(), |mut acc, n| { + acc.entry(n.package_id()).and_modify(|v| *v += 1).or_insert(0); + acc + }); + let removed = deps.extract_if(move |n| { + let v = dups[n.package_id()]; + if v > 0 { + dups.insert(n.package_id(), v - 1); + true + } else { + false + } + }) + .count(); + log::debug!("removed {removed} duplicates"); + + log::debug!( + "Total reduced deps for {}::{} ({:?}) : {}", + root.package_id().name(), + root.node.unit.target.name, + root.node.unit.target.kind, + deps.len() + ); + } else { + log::debug!( + "No deps for {}::{} 🤷🏻‍♂️", + root.package_id().name(), + root.node.unit.target.name + ); + } + + + // add the root: + if root.node + .meta + .and_then(|m| m.metadata.as_ref()) + .and_then(|m| m.inner.as_ref()) + .filter(|m| !m.assets().is_empty() || (root_is_dev && !m.dev_assets().is_empty())) + .is_some() + { + log::trace!( + "add root too because it has assets for {}", + root.node.unit.target.name + ); + + deps.insert(0, root.node.clone()); + } + + + deps.iter().enumerate().for_each(|(i, n)| { + log::trace!( + "{i}: {}::{} ({:?}), meta: {}", + root.package_id().name(), + root.node.unit.target.name, + root.node.unit.target.kind, + n.meta.is_some() + ); + }); + + log::debug!( + "Total finally deps for {}::{} ({:?}) : {}", + root.package_id().name(), + root.node.unit.target.name, + root.node.unit.target.kind, + deps.len() + ); + + root.deps = deps; + } + + Self { units, meta, roots } + } + + + pub fn roots(&self) -> &[RootNode<'t>] { self.roots.as_slice() } + + pub fn root_for(&self, id: &PackageId, tk: &TargetKindWild, tname: &str) -> CargoResult<&RootNode<'t>> { + self.roots + .iter() + .find(|n| n.package_id() == id && *tk == n.node.unit.target.kind && n.node.unit.target.name == tname) + .ok_or_else(|| anyhow::anyhow!("Root not found for {id}::{tname}")) + } + + pub fn units(&self) -> &'t UnitGraph { &*self.units } + pub fn meta(&self) -> &'t CargoMetadataPd { &*self.meta } + + + pub fn deps_allowed_for(&self, root: PackageId) -> bool { + self.roots + .iter() + .find(|u| u.package_id() == &root) + .and_then(|u| { + u.node + .meta + .as_ref() + .and_then(|m| m.metadata.as_ref()) + .and_then(|m| m.inner.as_ref()) + .map(|m| m.assets_options()) + }) + .unwrap_or_default() + .dependencies + } +} + + +pub trait DependenciesAllowed { + fn deps_allowed(&self) -> bool; +} + + +impl DependenciesAllowed for RootNode<'_> { + fn deps_allowed(&self) -> bool { self.node.deps_allowed() } +} + +impl DependenciesAllowed for Node<'_> { + fn deps_allowed(&self) -> bool { + self.meta + .as_ref() + .and_then(|m| m.metadata.as_ref()) + .and_then(|m| m.inner.as_ref()) + .map(|m| m.assets_options()) + .unwrap_or_default() + .dependencies + } +} + + +impl DependenciesAllowed for cargo::core::Package { + fn deps_allowed(&self) -> bool { + self.manifest() + .custom_metadata() + .and_then(|v| { + Metadata::::deserialize(v.to_owned().into_deserializer()).log_err() + .ok() + }) + .and_then(|m| m.inner) + .map(|m| m.assets_options().dependencies) + .unwrap_or_default() + } +} + + +impl<'t> Node<'t> { + pub fn into_source(self) -> impl CrateInfoSource + 't { CrateNode::from(self) } + pub fn as_source(&self) -> impl CrateInfoSource + 't { self.to_owned().into_source() } +} + +impl<'t> RootNode<'t> { + pub fn into_source(self) -> impl CrateInfoSource + 't { CrateNode::from(self.node) } + pub fn as_source(&self) -> impl CrateInfoSource + 't { self.to_owned().into_source() } +} + + +struct CrateNode<'t> { + node: Node<'t>, + bins: Vec<&'t str>, + examples: Vec<&'t str>, +} + +impl<'t> From> for CrateNode<'t> { + fn from(node: Node<'t>) -> Self { + Self { node, + bins: node.meta + .as_ref() + .into_iter() + .flat_map(|m| m.targets.iter()) + .filter(|t| t.kind == TargetKind::Bin) + .map(|t| t.name.as_str()) + .collect(), + examples: node.meta + .as_ref() + .into_iter() + .flat_map(|m| m.targets.iter()) + .filter(|t| t.kind == TargetKind::Example) + .map(|t| t.name.as_str()) + .collect() } + } +} + +impl CrateInfoSource for CrateNode<'_> { + type Authors = [String]; + + fn name(&self) -> std::borrow::Cow { self.node.package_id().name().as_str().into() } + + fn authors(&self) -> &Self::Authors { + self.node + .meta + .as_ref() + .map(|m| m.authors.as_slice()) + .unwrap_or_default() + } + + fn version(&self) -> std::borrow::Cow { + self.node + .meta + .as_ref() + .map(|m| m.version.as_str().into()) + .unwrap_or_default() + } + + fn description(&self) -> Option> { + self.node + .meta + .as_ref() + .and_then(|m| m.description.as_deref()) + .map(Into::into) + } + + fn metadata(&self) -> Option { + self.node + .meta + .as_ref() + .and_then(|m| m.metadata.as_ref()) + .and_then(|m| m.inner.as_ref()) + } + + fn bins(&self) -> &[&str] { &self.bins } + + fn examples(&self) -> &[&str] { &self.examples } + + fn manifest_path(&self) -> std::borrow::Cow { + self.node + .meta + .as_ref() + .map(|m| m.manifest_path.as_path().into()) + .unwrap_or_default() + } +} diff --git a/cargo/src/utils/cargo/metadata.rs b/cargo/src/utils/cargo/metadata.rs index 61b5d83b..157fb3e1 100644 --- a/cargo/src/utils/cargo/metadata.rs +++ b/cargo/src/utils/cargo/metadata.rs @@ -59,6 +59,7 @@ pub fn metadata(cfg: &Config) -> CargoResult { pub mod format { + #![allow(dead_code)] use std::path::PathBuf; use cargo::core::dependency::DepKind; diff --git a/cargo/src/utils/cargo/mod.rs b/cargo/src/utils/cargo/mod.rs index 69606815..120de93a 100644 --- a/cargo/src/utils/cargo/mod.rs +++ b/cargo/src/utils/cargo/mod.rs @@ -7,6 +7,7 @@ use playdate::consts::DEVICE_TARGET; pub(crate) mod format; pub mod build_plan; pub mod unit_graph; +pub mod meta_deps; pub mod metadata; diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index 963c97bd..63c93d09 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -34,7 +34,6 @@ pub trait CrateInfoSource { use std::slice::Join; let author = { - // let author = self.authors().join(", "); let author = Join::join(self.authors(), ", "); if author.trim().is_empty() { None From 9f1ce14476635dfca16cb0a2baa3e9fcea4b98e7 Mon Sep 17 00:00:00 2001 From: Alexander Koz <888526+boozook@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:37:24 +0400 Subject: [PATCH 24/24] apply clippy suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- cargo/src/utils/cargo/meta_deps.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cargo/src/utils/cargo/meta_deps.rs b/cargo/src/utils/cargo/meta_deps.rs index b13d9192..86f1ac27 100644 --- a/cargo/src/utils/cargo/meta_deps.rs +++ b/cargo/src/utils/cargo/meta_deps.rs @@ -47,7 +47,7 @@ pub struct Node<'cfg> { } impl<'t> RootNode<'t> { - pub fn package_id(&self) -> &'t PackageId { &self.node.package_id() } + pub fn package_id(&self) -> &'t PackageId { self.node.package_id() } pub fn node(&self) -> &Node<'t> { &self.node } @@ -207,7 +207,7 @@ impl<'t> MetaDeps<'t> { root.node.unit.target.name ); - deps.insert(0, root.node.clone()); + deps.insert(0, root.node); } @@ -245,8 +245,8 @@ impl<'t> MetaDeps<'t> { .ok_or_else(|| anyhow::anyhow!("Root not found for {id}::{tname}")) } - pub fn units(&self) -> &'t UnitGraph { &*self.units } - pub fn meta(&self) -> &'t CargoMetadataPd { &*self.meta } + pub fn units(&self) -> &'t UnitGraph { self.units } + pub fn meta(&self) -> &'t CargoMetadataPd { self.meta } pub fn deps_allowed_for(&self, root: PackageId) -> bool {