From e8c02cccd721cb758308acf0dc91d64967cc152d Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 29 May 2024 01:30:52 +0400 Subject: [PATCH] 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 }