From 5a6be83218c05a67930585d6d3273bdddff77e95 Mon Sep 17 00:00:00 2001 From: Alexander Koz <888526+boozook@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:40:11 +0400 Subject: [PATCH] Assets-builder refactoring (#376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add `options.workspace` draft for future implementation * add workspace metadata, fix: use `PackageIdSpec` instead of `PackageId` in some places * improvements, assets-plan display fmt, less lifetimes * small fixes, reduce `Package` using where just id needed * partially rewritten assets-builder: now lazy, supports dependencies, supports cargo's auto-targets (#355); prototype of usage new source of unit-tree * apply clippy suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * ~fixes * fmt * CI: reviewdog can fail on windows 🤷🏻‍♂️ * fix missed assets for dev target * update deps, improve method names, impl manifest validation for specified target * impl structural inheritance for `package.metadata.playdate.options` by `workspace.metadata.playdate.options` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 1 + Cargo.lock | 208 ++++++----- Cargo.toml | 7 +- cargo/Cargo.toml | 2 +- cargo/src/assets/mod.rs | 414 ++++++++++++++++++++- cargo/src/assets/pdc.rs | 10 +- cargo/src/assets/plan.rs | 436 ++++++++++++++++++++++- cargo/src/main.rs | 2 +- cargo/src/package/mod.rs | 11 +- cargo/src/proc/logging.rs | 14 +- cargo/src/utils/cargo/format.rs | 25 +- cargo/src/utils/cargo/meta_deps.rs | 112 ++++-- cargo/src/utils/cargo/metadata.rs | 29 +- cargo/src/utils/cargo/unit_graph.rs | 61 +++- support/build/Cargo.toml | 2 +- support/build/README.md | 41 ++- support/build/src/assets/mod.rs | 12 +- support/build/src/assets/plan.rs | 156 +++++--- support/build/src/assets/resolver.rs | 6 +- support/build/src/manifest/mod.rs | 2 +- support/build/src/metadata/format.rs | 122 +++++-- support/build/src/metadata/source.rs | 95 +++-- support/build/src/metadata/validation.rs | 23 +- 23 files changed, 1455 insertions(+), 336 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c789647e..a17dd11b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -570,6 +570,7 @@ jobs: - name: Suggestions uses: reviewdog/action-suggester@v1 + continue-on-error: ${{ runner.os == 'Windows' }} # sometimes reviewdog fails on windows with: filter_mode: diff_context fail_on_error: false diff --git a/Cargo.lock b/Cargo.lock index 6cbfb8c9..41e5bec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329972aa325176e89114919f2a80fdae4f4c040f66a370b1a1159c6c0f94e7aa" +checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" dependencies = [ "async-io 2.3.3", "async-lock 3.4.0", @@ -550,7 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata 0.4.6", + "regex-automata 0.4.7", "serde", ] @@ -740,7 +740,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.5.0-beta.1" +version = "0.5.0-beta.2" dependencies = [ "anstyle", "anyhow", @@ -769,7 +769,7 @@ dependencies = [ "serde_json", "target", "toml", - "toml_edit 0.22.13", + "toml_edit 0.22.14", "try-lazy-init", "walkdir", "zip 1.1.4", @@ -816,9 +816,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", @@ -875,9 +875,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" dependencies = [ "clap_builder", "clap_derive", @@ -885,9 +885,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" dependencies = [ "anstream", "anstyle", @@ -900,9 +900,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -912,9 +912,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clru" @@ -1815,19 +1815,19 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -dependencies = [ - "fallible-iterator 0.3.0", - "stable_deref_trait", -] +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "gimli" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "e2e1d97fbe9722ba9bbd0c97051c2956e726562b61f86a25a4360398a40edfc9" +dependencies = [ + "fallible-iterator 0.3.0", + "stable_deref_trait", +] [[package]] name = "git2" @@ -2601,8 +2601,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -2619,13 +2619,13 @@ dependencies = [ [[package]] name = "goblin" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" dependencies = [ "log", "plain", - "scroll", + "scroll 0.12.0", ] [[package]] @@ -2672,6 +2672,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", + "serde", ] [[package]] @@ -2829,9 +2830,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -2883,7 +2884,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.6", + "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", @@ -2927,6 +2928,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.5", + "serde", ] [[package]] @@ -3508,11 +3510,11 @@ dependencies = [ [[package]] name = "msvc-demangler" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb67c6dd0fa9b00619c41c5700b6f92d5f418be49b45ddb9970fbd4569df3c8" +checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -3893,7 +3895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863" dependencies = [ "fallible-iterator 0.2.0", - "scroll", + "scroll 0.11.0", "uuid", ] @@ -4113,7 +4115,7 @@ dependencies = [ [[package]] name = "playdate-build" -version = "0.4.0-pre1" +version = "0.4.0-pre2" dependencies = [ "dirs", "fs_extra", @@ -4467,7 +4469,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", "unarray", ] @@ -4620,14 +4622,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -4641,13 +4643,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -4658,9 +4660,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rfc6979" @@ -4778,15 +4780,21 @@ name = "scroll" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", @@ -5308,9 +5316,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "symbolic" -version = "12.8.0" +version = "12.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ec4f53c56d7ee8809c2322925d362e193bcc7bbe7e777a3304b34ea7e85a36" +checksum = "85aabcf85c883278298596217d678c8d3ca256445d732eac59303ce04863c46f" dependencies = [ "symbolic-common", "symbolic-debuginfo", @@ -5319,9 +5327,9 @@ dependencies = [ [[package]] name = "symbolic-common" -version = "12.8.0" +version = "12.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cccfffbc6bb3bb2d3a26cd2077f4d055f6808d266f9d4d158797a4c60510dfe" +checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" dependencies = [ "debugid", "memmap2", @@ -5331,9 +5339,9 @@ dependencies = [ [[package]] name = "symbolic-debuginfo" -version = "12.8.0" +version = "12.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb52777be67777947c5a159f1b6e8bfe4473d91fad7e5d4aff85ee4d3963cc04" +checksum = "abdc791ca87a69a5d09913d87f1e5ac95229be414ec0ff6c0fe2ddff6199f3b6" dependencies = [ "debugid", "dmsort", @@ -5341,7 +5349,7 @@ dependencies = [ "elsa", "fallible-iterator 0.3.0", "flate2", - "gimli 0.28.1", + "gimli 0.30.0", "goblin", "lazy_static", "nom", @@ -5350,7 +5358,7 @@ dependencies = [ "parking_lot 0.12.3", "pdb-addr2line", "regex", - "scroll", + "scroll 0.12.0", "serde", "serde_json", "smallvec", @@ -5358,14 +5366,15 @@ dependencies = [ "symbolic-ppdb", "thiserror", "wasmparser", - "zip 0.6.6", + "zip 2.1.1", + "zstd", ] [[package]] name = "symbolic-demangle" -version = "12.8.0" +version = "12.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a99812da4020a67e76c4eb41f08c87364c14170495ff780f30dd519c221a68" +checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" dependencies = [ "cc", "cpp_demangle", @@ -5376,9 +5385,9 @@ dependencies = [ [[package]] name = "symbolic-ppdb" -version = "12.8.0" +version = "12.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dace84623ccc926886fc880c36e2a81af4b17f8276abc4d77dc947ca3c6c8f8c" +checksum = "92ccffa1e6b313c007dddcc3a91166a64055a0a956e1429ee179a808fa3b2c62" dependencies = [ "flate2", "indexmap 2.2.6", @@ -5426,9 +5435,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tar" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" dependencies = [ "filetime", "libc", @@ -5642,14 +5651,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.13", + "toml_edit 0.22.14", ] [[package]] @@ -5676,15 +5685,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.9", + "winnow 0.6.13", ] [[package]] @@ -5909,9 +5918,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -5963,9 +5972,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -6096,12 +6105,16 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasmparser" -version = "0.118.2" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f1154f1ab868e2a01d9834a805faca7bf8b50d041b4ca714d005d0dab1c50c" +checksum = "07035cc9a9b41e62d3bb3a3815a66ab87c993c06fe1cf6b2a3f2a18499d937db" dependencies = [ + "ahash", + "bitflags 2.5.0", + "hashbrown 0.14.5", "indexmap 2.2.6", "semver", + "serde", ] [[package]] @@ -6253,9 +6266,9 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f721bc2e55efb506a1a395a545cb76c2481fb023d33b51f0050e7888716281cf" +checksum = "acc134c90a0318d873ec962b13149e9c862ff0d2669082a709a4810167a3c6ee" dependencies = [ "windows-result", "windows-targets 0.52.5", @@ -6263,9 +6276,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets 0.52.5", ] @@ -6450,9 +6463,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.9" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -6500,18 +6513,6 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "byteorder", - "crc32fast", - "crossbeam-utils", - "flate2", -] - [[package]] name = "zip" version = "1.1.4" @@ -6539,6 +6540,23 @@ dependencies = [ "zstd", ] +[[package]] +name = "zip" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.2.6", + "memchr", + "thiserror", + "zopfli", +] + [[package]] name = "zopfli" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 1fc08570..a0eae196 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.4.0-pre1", path = "support/build", package = "playdate-build", default-features = false } +build = { version = "=0.4.0-pre2", 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 } @@ -51,3 +51,8 @@ futures-lite = "2.3" thiserror = "1.0" tokio = { version = "1.37", default-features = false } async-std = { version = "1.12", default-features = false } + + +[workspace.metadata.playdate.options.assets] +dependencies = true +overwrite = false diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index b3e9d693..187b4d39 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.5.0-beta.1" +version = "0.5.0-beta.2" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] diff --git a/cargo/src/assets/mod.rs b/cargo/src/assets/mod.rs index fc931e5f..1ed07b82 100644 --- a/cargo/src/assets/mod.rs +++ b/cargo/src/assets/mod.rs @@ -1,13 +1,12 @@ use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::{PathBuf, Path}; use anstyle::AnsiColor as Color; use anyhow::bail; use cargo::CargoResult; use cargo::core::{Package, PackageId, Verbosity}; -use playdate::manifest::ManifestSourceOpt as _; -use playdate::metadata::source::MetadataSource as _; +use playdate::metadata::source::MetadataSource; use playdate::metadata::METADATA_FIELD; use playdate::layout::Layout; @@ -36,12 +35,390 @@ pub struct AssetsArtifact { pub type AssetsArtifacts<'cfg> = HashMap<&'cfg Package, AssetsArtifact>; +pub mod proto { + use super::*; + + use plan::proto::MultiKey; + use plan::Difference; + use playdate::assets::plan::BuildPlan; + use playdate::assets::BuildReport; + use playdate::layout::Layout as _; + use playdate::metadata::format::AssetsOptions; + + use crate::utils::cargo::meta_deps::{MetaDeps, RootNode}; + use crate::layout::{PlaydateAssets, Layout}; + + + #[derive(Debug)] + pub struct AssetsArtifact { + pub package_id: PackageId, + pub layout: PlaydateAssets, + pub kind: AssetKind, + } + + + pub struct AssetsArtifactsNew<'t, 'cfg> { + artifacts: Vec, + index: BTreeMap>, + tree: &'t MetaDeps<'cfg>, + } + + + impl AssetsArtifactsNew<'_, '_> { + pub fn iter(&self) -> impl Iterator)> { + self.index + .iter() + .flat_map(|(key, index)| { + self.tree + .roots() + .iter() + .filter(|r| key.is_for(r)) + .map(|root| (root, index.as_slice())) + }) + .map(|(root, index)| { + let arts = index.iter().map(|i| &self.artifacts[*i]); + (root, arts) + }) + } + } + + impl core::fmt::Debug for AssetsArtifactsNew<'_, '_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AssetsArtifacts") + .field_with("artifacts", |f| { + self.artifacts + .iter() + .enumerate() + .collect::>() + .fmt(f) + }) + .field("index", &self.index) + .finish_non_exhaustive() + } + } + + + pub fn build_all<'t, 'cfg>(cfg: &Config<'cfg>, + tree: &'t MetaDeps<'cfg>) + -> CargoResult> { + // planning: + let plans = plan::proto::plan_all(cfg, tree)?; + + // validation: + if let Err(err) = plan::proto::merge_all_virtually(cfg, tree, &plans) && + !cfg.compile_options.build_config.keep_going + { + return Err(err.context("Assets validation failed")); + } + + // results: + let mut artifacts = AssetsArtifactsNew { artifacts: Vec::with_capacity(plans.plans.len()), + index: Default::default(), + tree }; + + + // checking cache, apply each plan: + for (index, plan) in plans.plans.into_iter().enumerate() { + let key = plans.index.iter().find(|(_, v)| **v == index).expect("key").0; + + log::debug!("#{index} build (dev:{}) {}", key.dev, key.id); + + + let global_layout = CrossTargetLayout::new(cfg, key.id, None)?; + let mut layout = global_layout.assets_layout(cfg); + + + // clean layout if needed: + if !cfg.dry_run && cfg.compile_options.build_config.force_rebuild { + if !matches!(cfg.workspace.config().shell().verbosity(), Verbosity::Quiet) { + cfg.log().status("Clean", format!("assets for {}", key.id)); + } + layout.clean()?; + } + + + let mut locked = layout.lock_mut(cfg.workspace.config())?; + locked.prepare()?; + + // path of build-plan file: + let path = if key.dev { + locked.as_inner().assets_plan_for_dev(cfg, &key.id) + } else { + locked.as_inner().assets_plan_for(cfg, &key.id) + }; + + let mut cache = plan_cache(path, &plan)?; + if cfg.compile_options.build_config.force_rebuild { + cache.difference = Difference::Missing; + } + + + let dest = if key.dev { + locked.as_inner().assets_dev() + } else { + locked.as_inner().assets() + }; + + + // kind of assets just for log: + let kind_prefix = key.dev.then_some("dev-").unwrap_or_default(); + // this one for assets: + let kind = if key.dev { + AssetKind::Dev + } else { + AssetKind::Package + }; + + + // build if needed: + if cache.difference.is_same() { + cfg.log().status( + "Skip", + format!( + "{} {kind_prefix}assets cache state is {:?}", + key.id, &cache.difference + ), + ); + } else { + cfg.log() + .status("Build", format!("{kind_prefix}assets for {}", key.id)); + cfg.log().verbose(|mut log| { + let dep_root = plan.crate_root(); + let dest = format!("destination: {:?}", dest.as_relative_to_root(cfg)); + log.status("", dest); + let src = format!("root: {:?}", dep_root.as_relative_to_root(cfg)); + log.status("", src); + }); + + + // Since we build each plan separately independently, the default options are sufficient. + // The options are needed further when merging assets into a package. + let dep_opts = Default::default(); + let report = apply(cache, plan, &dest, &dep_opts, cfg)?; + + + // print report: + for (x, (m, results)) in report.results.iter().enumerate() { + let results = results.iter().enumerate(); + let expr = m.exprs(); + let incs = m.sources(); + + for (y, res) in results { + let path = incs[y].target(); + let path = path.as_relative_to_root(cfg); + match res { + Ok(op) => { + cfg.log().verbose(|mut log| { + let msg = format!("asset [{x}:{y}] {}", path.display()); + log.status(format!("{op:?}"), msg) + }) + }, + Err(err) => { + use fs_extra::error::ErrorKind as FsExtraErrorKind; + + let error = match &err.kind { + FsExtraErrorKind::Io(err) => format!("IO: {err}"), + FsExtraErrorKind::StripPrefix(err) => format!("StripPrefix: {err}"), + FsExtraErrorKind::OsString(err) => format!("OsString: {err:?}"), + _ => err.to_string(), + }; + let message = format!( + "Asset [{x}:{y}], rule: '{} <- {} | {}', {error}", + expr.0.original(), + expr.1.original(), + path.display() + ); + + cfg.log().status_with_color("Error", message, Color::Red) + }, + }; + } + } + + // TODO: log report.exclusions + + if report.has_errors() && !cfg.compile_options.build_config.keep_going { + use anyhow::Error; + + #[derive(Debug)] + pub struct Mapping(String); + impl std::error::Error for Mapping {} + impl std::fmt::Display for Mapping { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } + } + + let err = report.results + .into_iter() + .filter_map(|(map, res)| { + if res.iter().any(|res| res.is_err()) { + let err = Mapping(map.pretty_print_compact()); + let err = res.into_iter() + .filter_map(|res| res.err()) + .fold(Error::new(err), Error::context); + Some(err) + } else { + None + } + }) + .fold(Error::msg("Assets build failed"), Error::context); + return Err(err); + } + + + // finally build with pdc: + // if not disallowed explicitly + if cfg.skip_prebuild { + const REASON: &str = "as requested"; + let msg = format!("{kind_prefix}assets pre-build for {}, {REASON}.", key.id); + cfg.log().status("Skip", msg); + } else { + match pdc::build(cfg, &key.id, locked.as_inner(), kind) { + Ok(_) => { + let msg = format!("{kind_prefix}assets for {}", key.id); + cfg.log().status("Finished", msg); + }, + Err(err) => { + let msg = format!("build {kind_prefix}assets with pdc failed: {err}"); + cfg.log().status_with_color("Error", msg, Color::Red); + if !cfg.compile_options.build_config.keep_going { + bail!("Assets build failed."); + } + }, + } + } + } + + + // Finale: + + locked.unlock(); + + + let art_index = artifacts.artifacts.len(); + artifacts.artifacts.push(AssetsArtifact { kind, + package_id: key.id, + layout: layout.clone() }); + + log::debug!( + "Assets artifact for {} at {:?}", + key.id, + crate::layout::Layout::dest(&layout).as_relative_to_root(cfg) + ); + + for (r_key, index) in plans.targets.iter().filter(|(_, i)| i.contains(&index)) { + artifacts.index + .entry(r_key.to_owned()) + .or_insert(Vec::with_capacity(index.len())) + .push(art_index); + } + } + + + cfg.log_extra_verbose(|mut logger| { + artifacts.iter().for_each(|(root, arts)| { + let root = format!( + "({}) {}::{}", + root.node().target().kind().description(), + root.node().package_id().name(), + root.node().target().name, + ); + logger.status("Assets", format!("artifacts for {root}:")); + arts.for_each(|art| { + let dest = match art.kind { + AssetKind::Package => art.layout.assets(), + AssetKind::Dev => art.layout.assets_dev(), + }; + let msg = format!( + "[{:?}] {} - {:?}", + art.kind, + art.package_id.name(), + dest.as_relative_to_root(cfg) + ); + logger.status("", msg); + }); + }); + }); + + Ok(artifacts) + } + + + struct PlanCache { + pub difference: Difference, + pub serialized: Option, + pub path: PathBuf, + } + + + #[must_use = "Cached plan must be used"] + fn plan_cache(path: PathBuf, plan: &BuildPlan<'_, '_>) -> CargoResult { + let mut serializable = plan.iter_flatten_meta().collect::>(); + serializable.sort(); + let json = serde_json::to_string(&serializable)?; + + let difference = if path.try_exists()? { + if std::fs::read_to_string(&path)? == json { + log::debug!("Cached plan is the same"); + Difference::Same + } else { + log::debug!("Cache mismatch, need diff & rebuild"); + Difference::Different + } + } else { + log::debug!("Cache mismatch, full rebuilding"); + Difference::Missing + }; + + let serialized = (!difference.is_same()).then_some(json); + + Ok(PlanCache { path, + difference, + serialized }) + } + + + fn apply<'l, 'r>(cache: PlanCache, + plan: BuildPlan<'l, 'r>, + dest: &Path, + options: &AssetsOptions, + config: &Config) + -> CargoResult> { + use crate::playdate::assets::apply_build_plan; + + let report = apply_build_plan(plan, dest, options)?; + // and finally save cache of just successfully applied plan: + // only if there is no errors + if !report.has_errors() { + if let Some(data) = cache.serialized.as_deref() { + log::trace!("writing cache to {:?}", cache.path); + std::fs::write(&cache.path, data)?; + config.log().verbose(|mut log| { + let path = cache.path.as_relative_to_root(config); + log.status("Cache", format_args!("saved to {}", path.display())); + }); + } else { + config.log().verbose(|mut log| { + log.status("Cache", "nothing to save"); + }); + } + } else { + config.log().verbose(|mut log| { + let message = "build has errors, so cache was not saved"; + log.status("Cache", message); + }); + } + + Ok(report) + } +} + + pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { let bcx = LazyBuildContext::new(config)?; let mut artifacts = AssetsArtifacts::new(); for (package, targets, ..) in config.possible_targets()? { - let env = plan::LazyEnvBuilder::new(config, package); + let env = plan::LazyEnvBuilder::new_for(config, package); let mut plans: HashMap<&Package, _> = Default::default(); let global_layout = CrossTargetLayout::new(config, package.package_id(), None)?; let mut layout = global_layout.assets_layout(config); @@ -65,6 +442,31 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { let packages = deps_tree_metadata(package, &bcx, config)?; + // XXX: compare with beta-proto + #[cfg(debug_assertions)] + { + let tree = crate::utils::cargo::meta_deps::meta_deps(config)?; + + // planning: + let plans = plan::proto::plan_all(config, &tree)?; + + + for (p, _) in packages.iter() + .filter(|(_, m)| !m.assets().is_empty() || !m.dev_assets().is_empty()) + { + let id = p.package_id(); + assert!(plans.index.keys().any(|k| k.id == id), "not found: {id}"); + } + + // validation: + if let Err(err) = plan::proto::merge_all_virtually(config, &tree, &plans) && + !config.compile_options.build_config.keep_going + { + return Err(err.context("Assets validation failed")); + } + } + + // TODO: list deps in the plan for (package, metadata) in packages { @@ -291,7 +693,7 @@ pub fn build<'cfg>(config: &'cfg Config) -> CargoResult> { let msg = format!("{kind_prefix}assets pre-build for {}, {REASON}.", dep_pkg_id); config.log().status("Skip", msg); } else { - match pdc::build(config, dependency, locked.as_inner(), kind) { + match pdc::build(config, &dependency.package_id(), locked.as_inner(), kind) { Ok(_) => { let msg = format!("{kind_prefix}assets for {}", dep_pkg_id); config.log().status("Finished", msg); @@ -378,7 +780,7 @@ fn deps_tree_metadata<'cfg: 'r, 't: 'r, 'r>(package: &'cfg Package, let mut packages = HashMap::new(); if let Some(metadata) = playdate_metadata(package) { // if explicitly allowed collect deps => scan deps-tree - if metadata.assets_options().dependencies { + if metadata.assets_options().dependencies() { log::debug!("inspecting deps-tree of {}", package.package_id()); packages.insert(package, metadata); diff --git a/cargo/src/assets/pdc.rs b/cargo/src/assets/pdc.rs index 54d50c1b..646f10cb 100644 --- a/cargo/src/assets/pdc.rs +++ b/cargo/src/assets/pdc.rs @@ -3,8 +3,8 @@ use std::path::PathBuf; use std::process::Command; use anyhow::bail; +use cargo::core::PackageId; use cargo::CargoResult; -use cargo::core::Package; use playdate::fs::soft_link_checked; use playdate::layout::Layout; @@ -16,7 +16,7 @@ use super::plan::AssetKind; pub fn build(config: &Config, - package: &Package, + package_id: &PackageId, layout: &PlaydateAssets, kind: AssetKind) -> CargoResult<()> { @@ -34,12 +34,12 @@ pub fn build(config: &Config, }, }; - build_in(config, package, &src, &build, &layout.dest()) + build_in(config, package_id, &src, &build, &layout.dest()) } -fn build_in(config: &Config, package: &Package, src: &Path, build: &Path, root: &Path) -> CargoResult<()> { +fn build_in(config: &Config, package_id: &PackageId, src: &Path, build: &Path, root: &Path) -> CargoResult<()> { config.log() - .status("Compiling", format!("assets for {}", package.package_id())); + .status("Compiling", format!("assets for {}", package_id)); if config.no_sdk { bail!("Build without Playdate SDK is not supported yet."); diff --git a/cargo/src/assets/plan.rs b/cargo/src/assets/plan.rs index 9f01e849..18c2bbe0 100644 --- a/cargo/src/assets/plan.rs +++ b/cargo/src/assets/plan.rs @@ -22,22 +22,28 @@ use crate::layout::{PlaydateAssets, LayoutLock}; pub struct LazyEnvBuilder<'a, 'cfg> { config: &'a Config<'cfg>, - package: &'cfg Package, + package_id: PackageId, + root: &'cfg Path, env: Lazy, } impl<'a, 'cfg> LazyEnvBuilder<'a, 'cfg> { - pub fn new(config: &'cfg Config, package: &'cfg Package) -> Self { + pub fn new(config: &'cfg Config, pkg_id: PackageId, root: &'cfg Path) -> Self { Self { env: Lazy::new(), - package, + package_id: pkg_id, + root, config } } + pub fn new_for(config: &'cfg Config, package: &'cfg Package) -> Self { + Self::new(config, package.package_id(), package.root()) + } + pub fn get(&'a self) -> CargoResult<&'a Env> { self.env.try_get_or_create(move || { - let root = self.package.root().display().to_string(); + let root = self.root.display().to_string(); let vars = vec![ - ("CARGO_PKG_NAME", self.package.name().to_string()), + ("CARGO_PKG_NAME", self.package_id.name().to_string()), ("CARGO_MANIFEST_DIR", root.to_string()), ]; @@ -63,6 +69,420 @@ impl<'a, 'cfg> LazyEnvBuilder<'a, 'cfg> { pub type LockedLayout<'t> = LayoutLock<&'t mut PlaydateAssets>; +pub mod proto { + use std::borrow::Cow; + use std::collections::{BTreeMap, HashMap, HashSet}; + use std::path::Path; + + use super::{PackageId, Config, CargoResult}; + use super::assets_build_plan; + + use playdate::assets::plan::BuildPlan; + use playdate::consts::SDK_ENV_VAR; + use playdate::manifest::PackageSource as _; + use playdate::metadata::format::AssetsOptions; + use playdate::metadata::source::MetadataSource as _; + + use crate::utils::cargo::meta_deps::MetaDeps; + use crate::utils::cargo::meta_deps::{Node, RootNode}; + use crate::utils::path::AsRelativeTo; + + + pub struct AssetsPlans<'cfg> { + pub plans: Vec>, + /// per-dep ?dev plan + pub index: BTreeMap, + /// per-root plans to merge + pub targets: BTreeMap>, + } + + #[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)] + pub struct Key { + pub id: PackageId, + pub dev: bool, + } + + impl Key { + fn with_dev(&self, dev: bool) -> Self { + Self { id: self.id.to_owned(), + dev } + } + } + impl From<&'_ Node<'_>> for Key { + fn from(node: &'_ Node<'_>) -> Self { + Key { id: node.package_id().to_owned(), + dev: node.target().is_dev() } + } + } + + + #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] + pub struct MultiKey { + /// Dependencies + id: Vec, + /// Primary target is dev + dev: bool, + } + impl From<&'_ RootNode<'_>> for MultiKey { + fn from(root: &'_ RootNode<'_>) -> Self { + Self { dev: root.node().target().is_dev(), + id: root.deps().iter().map(|d| d.package_id().to_owned()).collect() } + } + } + impl MultiKey { + pub fn dev(&self) -> bool { self.dev } + + pub fn is_for(&self, root: &'_ RootNode<'_>) -> bool { + root.node().target().is_dev() == self.dev && + root.deps() + .iter() + .enumerate() + .all(|(i, d)| self.id.get(i).filter(|k| *k == d.package_id()).is_some()) + // root.deps().into_iter().enumerate().all(|(i, d)| d.package_id() == &self.id[i]) + } + } + + + pub fn plan_all<'cfg>(cfg: &Config<'cfg>, tree: &MetaDeps<'cfg>) -> CargoResult> { + // results: + let mut plans = AssetsPlans { plans: Vec::with_capacity(tree.roots().len() * 2), + index: BTreeMap::new(), + targets: BTreeMap::new() }; + + // prepare env: + let global_env: BTreeMap<_, _> = + std::env::vars().chain({ + cfg.sdk() + .map(|sdk| sdk.path()) + .ok() + .or(cfg.sdk_path.as_deref()) + .map(|p| (SDK_ENV_VAR.to_string(), p.display().to_string())) + .into_iter() + }) + .collect(); + let env = |id: &PackageId, root: &Path| { + use playdate::config::Env; + + let vars = [ + (Cow::from("CARGO_PKG_NAME"), Cow::from(id.name().as_str())), + (Cow::from("CARGO_MANIFEST_DIR"), root.display().to_string().into()), + ]; + + let iter = global_env.iter() + .map(|(k, v)| (Cow::Borrowed(k.as_str()), Cow::Borrowed(v.as_str()))) + .chain(vars); + + Env::try_from_iter(iter).map_err(|err| anyhow::anyhow!("{err}")) + }; + + + for root in tree.roots() { + let meta_source = root.as_source(); + + let options = meta_source.assets_options(); + + let root_is_dev = root.node().target().is_dev(); + + log::debug!( + "planning for {} ({}) +dev:{root_is_dev}", + root.package_id(), + root.node().target().kind().description() + ); + log::debug!(" dependencies are allowed: {}", options.dependencies()); + + + let plan_key = MultiKey::from(root); + if plans.targets.contains_key(&plan_key) { + log::debug!(" skip: already done"); + continue; + } + + + let mut indices = Vec::::with_capacity(root.deps().len()); + + for dep in root.deps().iter().rev() { + log::debug!(" planning dep: {}", dep.package_id()); + + let crate_root = dep.manifest_path() + .and_then(|p| p.parent()) + .ok_or_else(|| anyhow::anyhow!("Unable to get crate root"))?; + + let env = env(dep.package_id(), crate_root)?; + + // dep_key is dev only if this it a primary target (root unit) and dev is requested: + let with_dev = root_is_dev && dep.package_id() == root.package_id(); + let dep_key = Key::from(dep).with_dev(with_dev); + + + let plan_for = + |plans: &mut AssetsPlans, indices: &mut Vec, key: Key, dev: bool| -> anyhow::Result<()> { + let source = dep.as_source(); + let name_log = dev.then_some("dev-").unwrap_or_default(); + if let Some(assets) = source.metadata() + .map(|m| if dev { m.dev_assets() } else { m.assets() }) && + !assets.is_empty() + { + // let plan = + match assets_build_plan(&env, assets, &options, Some(crate_root.into())) { + Ok(plan) => { + let pid = key.id; + let is_dev = key.dev; + let dev_index = plans.plans.len(); + plans.index.insert(key, dev_index); + plans.plans.push(plan); + indices.push(dev_index); + + log::debug!(" done: +#{dev_index} (dev:{is_dev})"); + cfg.log().verbose(|mut log| { + log.status("Plan", format_args!("{name_log}assets for {pid} planned",)) + }) + }, + Err(err) => { + cfg.log() + .error(format_args!("{err}, caused when planning {name_log}assets for {}", key.id)); + return cfg.compile_options + .build_config + .keep_going + .then_some(()) + .ok_or(err.into()); + }, + } + } else { + cfg.log().verbose(|mut log| { + log.status( + "Skip", + format_args!( + "{name_log}assets for {} without plan, reason: empty", + key.id + ), + ) + }) + } + Ok(()) + }; + + if let Some(i) = plans.index.get(&dep_key) { + // we already have plan for this dep + log::debug!(" done (~#{i}) (dev:{})", dep_key.dev); + indices.push(*i); + } else if with_dev && let Some(base_index) = plans.index.get(&dep_key.with_dev(false)).copied() { + // we already have plan for this dep, but not for dev part + indices.push(base_index); + log::debug!(" done (~#{base_index}) (dev:{})", dep_key.dev); + + plan_for(&mut plans, &mut indices, dep_key, true)?; + } else { + // else just build a plan + plan_for(&mut plans, &mut indices, dep_key, false)?; + + // also for dev targets if needed + if with_dev { + plan_for(&mut plans, &mut indices, dep_key, true)?; + } + } + } + + + plans.targets + .entry(plan_key) + .and_modify(|vec| vec.append(&mut indices)) + .or_insert(indices); + } + + + // report: + cfg.log() + .status("Assets", "planning complete for all requested targets"); + cfg.log_extra_verbose(|mut logger| { + for (k, v) in &plans.targets { + let dev = k.dev.then_some(" +dev").unwrap_or_default(); + let key = k.id.iter().map(|p| p.name()).collect::>().join(", "); + logger.status("Plans", format_args!("for{dev} {key}")); + for i in v { + let plan = &plans.plans[*i]; + logger.status("Plan", format_args!("#{i}:\n{plan:>10}")); + } + } + }); + + // check: + for root in tree.roots() { + let key = MultiKey::from(root); + debug_assert!(plans.targets.contains_key(&key)); + } + + Ok(plans) + } + + + /// Try to merge virtually and validate. + /// Emits warnings and errors, returns errors-chain. + pub fn merge_all_virtually<'cfg>(cfg: &Config<'cfg>, + tree: &MetaDeps<'cfg>, + plans: &AssetsPlans<'cfg>) + -> CargoResult<()> { + // prepare context: + let mut root_package: HashMap<&MultiKey, HashSet<&PackageId>> = HashMap::with_capacity(tree.roots().len()); + let mut root_options: HashMap<&PackageId, AssetsOptions> = HashMap::with_capacity(tree.roots().len()); + + plans.targets + .iter() + .flat_map(|(key, _)| { + tree.roots() + .iter() + .filter(|r| key.is_for(r)) + .map(move |r| (key, r)) + }) + .for_each(|(key, root)| { + root_package.entry(key) + .or_insert_with(|| HashSet::with_capacity(tree.roots().len())) + .insert(root.package_id()); + + if !root_options.contains_key(root.package_id()) { + let options = root.as_source().assets_options(); + root_options.insert(root.package_id(), options); + } + }); + + + // Buffered errors: + let mut overrides = Vec::new(); + + + // merge, analyse: + for (key, index) in plans.targets.iter() { + // Note, correct ordering in `index` guaranteed by the planner. + + // Need merge many into one: + let _many = index.len() > 1; + + for root_id in root_package.get(key).into_iter().flat_map(|set| set.iter()) { + use playdate::assets::plan::*; + + log::trace!("v-merging for {} (dev:{})", root_id.name(), key.dev); + + let options = &root_options[root_id]; + + let mut _plan: Vec = Default::default(); + + + let mut targets = BTreeMap::new(); + let mut sources = BTreeMap::new(); + + + for i in index { + let next = &plans.plans[*i]; + + for (kind, dst, src) in next.iter_flatten() { + let target: Cow<_> = match kind { + MappingKind::AsIs | MappingKind::ManyInto => dst, + MappingKind::Into => dst.join(src.file_name().expect("filename")), + }.into(); + + // Save for future check if we already have this source: + sources.entry(Cow::from(src)) + .or_insert_with(|| Vec::with_capacity(2)) + .push(*i); + + // Check if we already have this target: + targets.entry(target.clone()) + .or_insert_with(|| Vec::with_capacity(2)) + .push(*i); + + if let Some(past) = targets.get(&target) && + past.len() > 1 + { + let id = past.iter() + .flat_map(|x| plans.index.iter().find_map(|(key, i)| (i == x).then_some(key))) + .collect::>(); + debug_assert!(!id.is_empty()); + + let this = id.last().unwrap(); + let other = &id[..id.len() - 1]; + + let dev = this.dev.then_some("dev-").unwrap_or_default(); + let others = other.is_empty() + .then_some("by itself") + .map(Cow::from) + .unwrap_or_else(|| { + other.iter() + .map(|k| { + let dev = k.dev.then_some("dev-").unwrap_or_default(); + format!("{}'s '{dev}assets'", k.id.name()) + }) + .collect::>() + .join(", ") + .into() + }); + + let name = this.id.name(); + let root_name = root_id.name(); + let why = format!("but that's not allowed by the top-level crate {root_name}"); + let msg = format!("{name}'s `{dev}assets.{target:?}` overrides {others}, {why}"); + + if options.overwrite() { + cfg.log().warn(msg) + } else { + cfg.log().error(&msg); + overrides.push(msg); + } + } + } + } + + // Check if we already have this source: + for (src, index) in sources { + if index.len() < 2 { + continue; + } + + let id = index.into_iter() + .flat_map(|x| plans.index.iter().find_map(|(key, i)| (*i == x).then_some(key))) + .collect::>(); + debug_assert!(!id.is_empty()); + + let src_rel = src.as_relative_to_root(cfg); + let others = id.is_empty() + .then_some("itself") + .map(Cow::from) + .unwrap_or_else(|| { + id.into_iter() + .map(|k| { + let dev = k.dev.then_some("dev-").unwrap_or_default(); + format!("{}'s '{dev}assets'", k.id.name()) + }) + .collect::>() + .join(", ") + .into() + }); + let msg = format!("asset {src_rel:?} used multiple times in {others}"); + cfg.log().warn(msg); + } + } + } + + + { + use err::Override; + use anyhow::Error; + overrides.is_empty() + .then_some(()) + .ok_or_else(|| overrides.into_iter().fold(Error::new(Override), Error::context)) + } + } + + + mod err { + #[derive(Debug)] + pub struct Override; + impl std::error::Error for Override {} + impl std::fmt::Display for Override { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { "Not allowed override".fmt(f) } + } + } +} + + /// Returns `None` if there is no `assets` metadata. pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, package: &'cfg Package, @@ -87,7 +507,7 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, .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 plan = assets_build_plan(env, metadata.assets(), opts.as_ref(), Some(root.into()))?; // main-assets plan: let path = layout.as_inner().assets_plan_for(config, &package.package_id()); @@ -105,7 +525,7 @@ pub fn plan_for<'cfg, 'env, 'l>(config: &'cfg Config, // dev-assets plan: 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 dev_plan = assets_build_plan(env, assets, opts.as_ref(), Some(root.into()))?; let path = layout.as_inner() .assets_plan_for_dev(config, &package.package_id()); @@ -165,7 +585,7 @@ pub struct CachedPlan<'t, 'cfg> { impl<'t, 'cfg> CachedPlan<'t, 'cfg> { #[must_use = "Cached plan must be used"] fn new(path: PathBuf, plan: AssetsPlan<'t, 'cfg>) -> CargoResult { - let mut serializable = plan.iter_flatten().collect::>(); + let mut serializable = plan.iter_flatten_meta().collect::>(); serializable.sort_by_key(|(_, _, (p, _))| p.to_string_lossy().to_string()); let json = serde_json::to_string(&serializable)?; diff --git a/cargo/src/main.rs b/cargo/src/main.rs index 9ac39dd6..d5587713 100644 --- a/cargo/src/main.rs +++ b/cargo/src/main.rs @@ -2,9 +2,9 @@ #![feature(never_type)] #![feature(exit_status_error)] #![feature(btree_extract_if)] -#![feature(byte_slice_trim_ascii)] #![feature(const_trait_impl)] #![feature(let_chains)] +#![feature(debug_closure_helpers)] extern crate build as playdate; diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index 858857ee..608cc122 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -19,7 +19,7 @@ use playdate::fs::soft_link_checked; use playdate::layout::Layout; use playdate::layout::Name; use playdate::manifest::format::ManifestFmt; -use playdate::manifest::CrateInfoSource; +use playdate::manifest::PackageSource; use playdate::metadata::format::Metadata; use playdate::metadata::validation::Validate; use playdate::metadata::validation::ValidateCrate; @@ -359,13 +359,13 @@ fn build_manifest(config: &Config, 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) + source.manifest_override_or_crate(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) + source.manifest_override_or_crate(cargo_target.as_deref(), dev) }; // validation, lints @@ -590,8 +590,9 @@ impl<'cfg, 'm> ManifestSource<'cfg, 'm> { } } -impl<'cfg> CrateInfoSource for ManifestSource<'cfg, '_> { +impl<'cfg> PackageSource for ManifestSource<'cfg, '_> { type Authors = [&'cfg str]; + type Metadata = Metadata; fn name(&self) -> Cow { self.package.name().as_str().into() } fn authors(&self) -> &[&'cfg str] { &self.authors } @@ -608,7 +609,7 @@ impl<'cfg> CrateInfoSource for ManifestSource<'cfg, '_> { fn bins(&self) -> &[&str] { &self.bins } fn examples(&self) -> &[&str] { &self.examples } - fn metadata(&self) -> Option { self.metadata } + fn metadata(&self) -> Option<&Self::Metadata> { self.metadata } fn manifest_path(&self) -> Cow { Cow::Borrowed(self.package.manifest_path()) } } diff --git a/cargo/src/proc/logging.rs b/cargo/src/proc/logging.rs index 453822a6..ef58ba6d 100644 --- a/cargo/src/proc/logging.rs +++ b/cargo/src/proc/logging.rs @@ -52,12 +52,14 @@ pub fn cmd_logged(config: &Config, mut cmd: Command) -> CargoResult { }); } else { config.log_extra_verbose(|mut log| { - log.status(&tool, "output:"); - output.stdout.lines().for_each(|line| { - if let Ok(line) = line { - log.status("", line); - } - }); + if !output.stdout.trim_ascii().is_empty() { + log.status(&tool, "output:"); + output.stdout.lines().for_each(|line| { + if let Ok(line) = line { + log.status("", line); + } + }); + } }); } diff --git a/cargo/src/utils/cargo/format.rs b/cargo/src/utils/cargo/format.rs index 57693449..1d8d820a 100644 --- a/cargo/src/utils/cargo/format.rs +++ b/cargo/src/utils/cargo/format.rs @@ -117,8 +117,23 @@ pub fn string_to_package_id(mut line: String) -> Result // try actual format first: let res = serde_json::from_str::(&line).map_err(Error::custom); - // otherwise try old formats: - res.or_else(move |err| { + + res.or_else(|err| { + // it also can be PackageIdSpec + use cargo::core::PackageIdSpec; + + let spec = PackageIdSpec::parse(&line).map_err(Error::custom)?; + spec.url() + .map(|url| url.as_str()) + .map(SourceId::from_url) + .and_then(|res| res.ok()) + .map(|src| (spec, src)) + .and_then(|(spec, src)| spec.version().map(|ver| (spec, src, ver))) + .map(|(spec, src, ver)| PackageId::new(spec.name().into(), ver, src)) + .ok_or(err) + }) + .or_else(move |err| { + // otherwise try old formats: if let Some((uri, name_ver)) = value.split_once('#') { let sid = SourceId::from_url(uri).map_err(Error::custom)?; @@ -180,14 +195,14 @@ mod tests { /// Before cargo 0.78 #[test] - fn message_format_old_a() { + fn package_spec_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() { + fn package_spec_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(); } @@ -195,7 +210,7 @@ mod tests { /// From cargo 0.78 #[test] - fn message_format_new() { + fn package_id() { 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/meta_deps.rs b/cargo/src/utils/cargo/meta_deps.rs index 86f1ac27..ad7d7d86 100644 --- a/cargo/src/utils/cargo/meta_deps.rs +++ b/cargo/src/utils/cargo/meta_deps.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; +use std::path::Path; use cargo::core::compiler::CompileMode; -use cargo::core::PackageId; +use cargo::core::{PackageId, PackageIdSpecQuery}; use cargo::util::interning::InternedString; use cargo::CargoResult; -use playdate::manifest::CrateInfoSource; +use playdate::manifest::PackageSource; +use playdate::metadata::format::ws::WorkspaceMetadata; use playdate::metadata::source::MetadataSource; +use playdate::metadata::format::Metadata as MainMetadata; use serde::Deserialize; use serde::de::IntoDeserializer; @@ -14,9 +17,9 @@ use crate::logger::LogErr; use super::build_plan::format::TargetKind; use super::build_plan::TargetKindWild; -use super::metadata::format::{Package, Metadata}; +use super::metadata::format::{Package, CrateMetadata}; use super::metadata::CargoMetadataPd; -use super::unit_graph::format::Unit; +use super::unit_graph::format::{Unit, UnitTarget}; use super::unit_graph::format::UnitGraph; @@ -37,13 +40,8 @@ pub struct MetaDeps<'cfg> { pub struct RootNode<'cfg> { node: Node<'cfg>, deps: Vec>, -} - -#[derive(Debug, Clone, Copy)] -pub struct Node<'cfg> { - meta: Option<&'cfg Package>>, - unit: &'cfg Unit, + ws: Option<&'cfg WorkspaceMetadata>, } impl<'t> RootNode<'t> { @@ -57,15 +55,28 @@ impl<'t> RootNode<'t> { pub fn deps(&self) -> &[Node<'t>] { &self.deps } } + +#[derive(Debug, Clone, Copy)] +pub struct Node<'cfg> { + meta: Option<&'cfg Package>>, + unit: &'cfg Unit, +} + impl<'t> Node<'t> { pub fn package_id(&self) -> &'t PackageId { &self.unit.package_id } + + pub fn unit(&self) -> &'t Unit { self.unit } + pub fn meta(&self) -> Option<&'t Package>> { self.meta } + pub fn target(&self) -> &'t UnitTarget { &self.unit.target } + + pub fn manifest_path(&self) -> Option<&'t Path> { self.meta.as_ref().map(|m| m.manifest_path.as_path()) } } 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| { + let is_norm_tk = |u: &&Unit| { matches!( u.target.kind, TargetKind::Lib(_) | TargetKind::Bin | TargetKind::Example @@ -73,18 +84,20 @@ impl<'t> MetaDeps<'t> { }; let is_sub_tk = |u: &&Unit| matches!(u.target.kind, TargetKind::Lib(_)); + let ws = meta.workspace_metadata.as_ref(); let mut roots = units.roots .iter() .map(|i| &units.units[*i]) .filter(mode_is_build) - .filter(is_prime_tk) + .filter(is_norm_tk) .map(|u| { - let m = meta.packages.iter().find(|p| p.id == u.package_id); + let m = meta.packages.iter().find(|p| p.id.matches(u.package_id)); Node::<'t> { meta: m, unit: u } }) .map(|node| { - RootNode::<'t> { node, + RootNode::<'t> { ws, + node, deps: Vec::with_capacity(0) } }) .collect::>(); @@ -98,7 +111,7 @@ impl<'t> MetaDeps<'t> { .filter(mode_is_build) .filter(is_sub_tk) .map(|u| { - let m = meta.packages.iter().find(|p| p.id == u.package_id); + let m = meta.packages.iter().find(|p| p.id.matches(u.package_id)); Node::<'t> { meta: m, unit: u } }) .inspect(|n| { @@ -262,7 +275,7 @@ impl<'t> MetaDeps<'t> { .map(|m| m.assets_options()) }) .unwrap_or_default() - .dependencies + .dependencies() } } @@ -273,7 +286,7 @@ pub trait DependenciesAllowed { impl DependenciesAllowed for RootNode<'_> { - fn deps_allowed(&self) -> bool { self.node.deps_allowed() } + fn deps_allowed(&self) -> bool { self.node.deps_allowed() || self.as_source().assets_options().dependencies() } } impl DependenciesAllowed for Node<'_> { @@ -284,7 +297,7 @@ impl DependenciesAllowed for Node<'_> { .and_then(|m| m.inner.as_ref()) .map(|m| m.assets_options()) .unwrap_or_default() - .dependencies + .dependencies() } } @@ -294,24 +307,32 @@ impl DependenciesAllowed for cargo::core::Package { self.manifest() .custom_metadata() .and_then(|v| { - Metadata::::deserialize(v.to_owned().into_deserializer()).log_err() - .ok() + CrateMetadata::::deserialize(v.to_owned().into_deserializer()).log_err() + .ok() }) .and_then(|m| m.inner) - .map(|m| m.assets_options().dependencies) + .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() } + pub fn into_source(self) -> impl PackageSource> + 't { + CrateNode::from(self) + } + pub fn as_source(&self) -> impl PackageSource> + '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() } + pub fn into_source(self) -> impl PackageSource> + 't { + CrateNode::from(&self) + } + pub fn as_source(&self) -> impl PackageSource> + 't { + CrateNode::from(self) + } } @@ -319,6 +340,8 @@ struct CrateNode<'t> { node: Node<'t>, bins: Vec<&'t str>, examples: Vec<&'t str>, + + ws: Option<&'t WorkspaceMetadata>, } impl<'t> From> for CrateNode<'t> { @@ -337,12 +360,36 @@ impl<'t> From> for CrateNode<'t> { .flat_map(|m| m.targets.iter()) .filter(|t| t.kind == TargetKind::Example) .map(|t| t.name.as_str()) - .collect() } + .collect(), + ws: None } + } +} + +impl<'t> From<&RootNode<'t>> for CrateNode<'t> { + fn from(root: &RootNode<'t>) -> Self { + let node = root.node; + 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(), + ws: root.ws } } } -impl CrateInfoSource for CrateNode<'_> { +impl PackageSource for CrateNode<'_> { type Authors = [String]; + type Metadata = MainMetadata; fn name(&self) -> std::borrow::Cow { self.node.package_id().name().as_str().into() } @@ -370,7 +417,7 @@ impl CrateInfoSource for CrateNode<'_> { .map(Into::into) } - fn metadata(&self) -> Option { + fn metadata(&self) -> Option<&Self::Metadata> { self.node .meta .as_ref() @@ -389,4 +436,13 @@ impl CrateInfoSource for CrateNode<'_> { .map(|m| m.manifest_path.as_path().into()) .unwrap_or_default() } + + + // from ws metadata: + fn default_options(&self) -> Option<&playdate::metadata::format::ws::OptionsDefault> { + self.ws + .and_then(|m| m.inner.as_ref()) + .as_ref() + .and_then(|m| m.options.as_ref()) + } } diff --git a/cargo/src/utils/cargo/metadata.rs b/cargo/src/utils/cargo/metadata.rs index 157fb3e1..45fdf432 100644 --- a/cargo/src/utils/cargo/metadata.rs +++ b/cargo/src/utils/cargo/metadata.rs @@ -8,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, format::WorkspaceMetadata>; pub fn metadata(cfg: &Config) -> CargoResult { @@ -63,32 +63,31 @@ pub mod format { use std::path::PathBuf; use cargo::core::dependency::DepKind; - use cargo::core::PackageId; + use cargo::core::PackageIdSpec; 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; + pub use playdate::metadata::format::CrateMetadata; + pub use playdate::metadata::format::ws::WorkspaceMetadata; /// `cargo metadata` output __v1__, /// just necessary fields. #[derive(Debug, Deserialize)] - #[serde(bound(deserialize = "Metadata: Deserialize<'de>"))] - pub struct Report { + #[serde(bound(deserialize = "Meta: Deserialize<'de>, WsMeta: Deserialize<'de>"))] + pub struct Report { pub version: usize, - pub packages: Vec>, + 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 workspace_metadata: Option, pub resolve: Resolve, } @@ -101,10 +100,8 @@ pub mod format { #[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 id: PackageIdSpec, + pub dependencies: Vec, pub deps: Vec, } @@ -112,8 +109,7 @@ pub mod format { #[derive(Deserialize, Debug)] #[serde(bound(deserialize = "Metadata: Deserialize<'de>"))] pub struct Package { - #[serde(deserialize_with = "deserialize_package_id")] - pub id: PackageId, + pub id: PackageIdSpec, pub source: Option, pub dependencies: Vec, @@ -145,8 +141,7 @@ pub mod format { #[derive(Deserialize, Debug)] pub struct NodeDep { pub name: String, - #[serde(deserialize_with = "deserialize_package_id")] - pub pkg: PackageId, + pub pkg: PackageIdSpec, pub dep_kinds: serde_json::Value, } diff --git a/cargo/src/utils/cargo/unit_graph.rs b/cargo/src/utils/cargo/unit_graph.rs index 709105a8..d5dcb351 100644 --- a/cargo/src/utils/cargo/unit_graph.rs +++ b/cargo/src/utils/cargo/unit_graph.rs @@ -6,6 +6,8 @@ use crate::proc::cargo_proxy_cmd; use crate::proc::read_cargo_json; use self::format::UnitGraph; +use super::build_plan::TargetKindWild; + pub fn unit_graph(cfg: &Config) -> CargoResult { let mut cargo = cargo_proxy_cmd(cfg, &Cmd::Build)?; @@ -17,6 +19,57 @@ pub fn unit_graph(cfg: &Config) -> CargoResult { } +impl format::UnitTarget { + pub fn is_dev(&self) -> bool { + // This is not so correct because `format::TargetKind::CustomBuild` isn't dev-target, + // bu we're working with libs, bins and examples only. + // Also in meta-tree roots are already filtered out, so we have no custom-build here anyway. + !matches!(self.kind, format::TargetKind::Lib(_) | format::TargetKind::Bin) + } + + + pub fn kind(&self) -> cargo::core::TargetKind { + use cargo::core::TargetKind as TK; + use cargo::core::compiler::CrateType as CT; + + match self.kind { + format::TargetKind::Lib(ref ct) => TK::Lib(ct.clone()), + format::TargetKind::Bin => TK::Bin, + format::TargetKind::Test => TK::Test, + format::TargetKind::Bench => TK::Bench, + format::TargetKind::Example => { + if &self.crate_types == &[CT::Bin] { + TK::ExampleBin + } else { + TK::ExampleLib(self.crate_types.clone()) + } + }, + format::TargetKind::CustomBuild => TK::CustomBuild, + } + } + + + pub fn kind_wild(&self) -> TargetKindWild { + use cargo::core::compiler::CrateType as CT; + + match self.kind { + format::TargetKind::Lib(_) => TargetKindWild::Lib, + format::TargetKind::Bin => TargetKindWild::Bin, + format::TargetKind::Test => TargetKindWild::Test, + format::TargetKind::Bench => TargetKindWild::Bench, + format::TargetKind::Example => { + if &self.crate_types == &[CT::Bin] { + TargetKindWild::ExampleBin + } else { + TargetKindWild::ExampleLib + } + }, + format::TargetKind::CustomBuild => TargetKindWild::CustomBuild, + } + } +} + + pub mod format { #![allow(dead_code)] use cargo::core::PackageId; @@ -35,7 +88,7 @@ pub mod format { pub roots: Vec, } - #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Unit { #[serde(deserialize_with = "deserialize_package_id", alias = "pkg_id")] pub package_id: PackageId, @@ -52,9 +105,9 @@ pub mod format { // pub profile: crate::proc::reader::format::ArtifactProfile, } - #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct UnitTarget { - pub kind: TargetKind, + pub(crate) kind: TargetKind, #[serde(deserialize_with = "deserialize_crate_types")] pub crate_types: Vec, pub name: String, @@ -62,7 +115,7 @@ pub mod format { // ... } - #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] + #[derive(Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct UnitDep { pub index: usize, pub extern_crate_name: String, diff --git a/support/build/Cargo.toml b/support/build/Cargo.toml index 82e2d952..8c5e96bc 100644 --- a/support/build/Cargo.toml +++ b/support/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-build" -version = "0.4.0-pre1" +version = "0.4.0-pre2" readme = "README.md" description = "Utils that help to build package for Playdate" keywords = ["playdate", "package", "encoding", "manifest", "assets"] diff --git a/support/build/README.md b/support/build/README.md index 7f4417d9..54c590eb 100644 --- a/support/build/README.md +++ b/support/build/README.md @@ -138,32 +138,45 @@ Also this way supports simple include and exclude instructions: ``` -#### Assets Options - -This is how assets will be collected for your package. - -```toml -[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`) -``` - - ### Options Package build options, instruction for Playdate Package Build System such as cargo-playdate. ```toml [package.metadata.playdate.options] +workspace = true # use `workspace.metadata.playdate.options` as defaults (default is `false`) +assets.dependencies = true # just set or override corresponding value from `workspace.metadata` ``` +Field `workspace` works like the cargo's feature [inheriting a dependency from a workspace][cargo-inheriting-dep-ws], turning on structural inheritance of `package.metadata.playdate.options` by `workspace.metadata.playdate.options`. + + Available options is `assets`, see [Assets Options](#assets-options). -Currently there is no more options, it's just reserved for future use. +_Currently there is no more options, it's just reserved for future use._ + +This configuration is used for primary packages only. Primary packages are the ones the user selected on the command-line, either with `-p` flags or the defaults based on the current directory and the default workspace members. +So, `options` from top-level package are applying to entire dependency tree ignoring `options` of dependencies. Thus, only the end user controls how the assets will be collected & built. +_Note: this is depends on implementation, above is how it works in the reference impl `cargo-playdate`._ + + +[cargo-inheriting-dep-ws]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace + + +#### Assets Options + +This is how assets will be collected for your package. + +```toml +[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`, alias: `override`) +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`) +``` +Field `overwrite` also allows higher dependencies to overwrite assets of deeper dependency. - - - diff --git a/support/build/src/assets/mod.rs b/support/build/src/assets/mod.rs index 37ce2287..bde694af 100644 --- a/support/build/src/assets/mod.rs +++ b/support/build/src/assets/mod.rs @@ -23,8 +23,8 @@ pub fn apply_build_plan<'l, 'r, P: AsRef>(plan: BuildPlan<'l, 'r>, use crate::fs::ensure_dir_exists; let target_root = target_root.as_ref(); - let build_method = assets_options.method; - let overwrite = assets_options.overwrite; + let build_method = assets_options.method(); + let overwrite = assets_options.overwrite(); info!("collecting assets:"); debug!("assets build method: {build_method:?}, overwrite: {overwrite}"); @@ -123,16 +123,16 @@ pub fn apply_build_plan<'l, 'r, P: AsRef>(plan: BuildPlan<'l, 'r>, for entry in plan.drain(..) { let current: Vec<_> = match &entry { Mapping::AsIs(inc, ..) => { - let source = abs_if_existing_any(inc.source(), crate_root); + let source = abs_if_existing_any(inc.source(), &crate_root); vec![method(&source, &inc.target(), false)] }, Mapping::Into(inc, ..) => { - let source = abs_if_existing_any(inc.source(), crate_root); + let source = abs_if_existing_any(inc.source(), &crate_root); vec![method(&source, &inc.target(), true)] }, Mapping::ManyInto { sources, target, .. } => { sources.iter() - .map(|inc| (abs_if_existing_any(inc.source(), crate_root), target.join(inc.target()))) + .map(|inc| (abs_if_existing_any(inc.source(), &crate_root), target.join(inc.target()))) .map(|(ref source, ref target)| method(source, target, false)) .collect() }, @@ -171,7 +171,7 @@ pub enum OpRes { impl AssetsOptions { fn link_behavior(&self) -> LinkBehavior { - if self.follow_symlinks { + if self.follow_symlinks() { LinkBehavior::ReadTarget } else { LinkBehavior::ReadFile diff --git a/support/build/src/assets/plan.rs b/support/build/src/assets/plan.rs index 48ae5b43..4fb32159 100644 --- a/support/build/src/assets/plan.rs +++ b/support/build/src/assets/plan.rs @@ -12,11 +12,11 @@ use super::resolver::*; /// Create build plan for assets. -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> +pub fn build_plan<'l, 'r, S>(env: &Env, + assets: &AssetsRules, + options: &AssetsOptions, + crate_root: Option>) + -> Result, super::Error> where S: Eq + Hash + ToString { // copy_unresolved => get all files with glob @@ -33,7 +33,7 @@ pub fn build_plan<'l, 'r, 'c: 'l, 'v, S>(env: &'c Env, const PATH_SEPARATOR: [char; 2] = [MAIN_SEPARATOR, '/']; let enver = EnvResolver::new(); - let crate_root = crate_root.unwrap_or_else(|| env.cargo_manifest_dir()); + let crate_root = crate_root.unwrap_or_else(|| env.cargo_manifest_dir().into()); let link_behavior = options.link_behavior(); let to_relative = |s: &S| -> String { @@ -97,13 +97,13 @@ pub fn build_plan<'l, 'r, 'c: 'l, 'v, S>(env: &'c Env, let key = PathBuf::from(k.as_str()); let value = Cow::Borrowed(v.as_str()); let into_dir = k.as_str().ends_with(PATH_SEPARATOR); - let source_exists = abs_if_existing(Path::new(value.as_ref()), crate_root)?.is_some(); + let source_exists = abs_if_existing(Path::new(value.as_ref()), &crate_root)?.is_some(); let mapping = match (source_exists, into_dir) { (true, true) => Mapping::Into(Match::new(value.as_ref(), key), (k, v)), (true, false) => Mapping::AsIs(Match::new(value.as_ref(), key), (k, v)), (false, _) => { - let mut resolved = resolve_includes(value, crate_root, &exclude_exprs, link_behavior)?; + let mut resolved = resolve_includes(value, &crate_root, &exclude_exprs, link_behavior)?; debug!("Possible ManyInto, resolved: {}", resolved.len()); @@ -184,7 +184,7 @@ pub fn build_plan<'l, 'r, 'c: 'l, 'v, S>(env: &'c Env, for k in include_unresolved { - let resolved = resolve_includes(&k, crate_root, &exclude_exprs, link_behavior)?; + let resolved = resolve_includes(&k, &crate_root, &exclude_exprs, link_behavior)?; mappings.extend(resolved.into_iter() .map(|inc| Mapping::AsIs(inc, (k.clone(), "true".into())))); } @@ -196,7 +196,7 @@ pub fn build_plan<'l, 'r, 'c: 'l, 'v, S>(env: &'c Env, // TODO: find source duplicates and warn! Ok(BuildPlan { plan: mappings, - crate_root }) + crate_root: crate_root.to_path_buf() }) } @@ -272,18 +272,17 @@ pub struct BuildPlan<'left, 'right> { /// Instructions - what file where to put plan: Vec>, /// Root directory of associated crate - crate_root: &'left Path, + crate_root: PathBuf, } impl<'left, 'right> BuildPlan<'left, 'right> { pub fn into_inner(self) -> Vec> { self.plan } pub fn as_inner(&self) -> &[Mapping<'left, 'right>] { &self.plan[..] } - pub fn into_parts(self) -> (Vec>, &'left Path) { (self.plan, self.crate_root) } + pub fn into_parts(self) -> (Vec>, PathBuf) { (self.plan, self.crate_root) } - pub fn crate_root(&self) -> &Path { self.crate_root } - pub fn set_crate_root(&mut self, path: &'left Path) -> &Path { - let old = self.crate_root; - self.crate_root = path; + pub fn crate_root(&self) -> &Path { &self.crate_root } + pub fn set_crate_root>(&mut self, path: T) -> PathBuf { + let old = std::mem::replace(&mut self.crate_root, path.into()); old } } @@ -295,24 +294,53 @@ impl<'left, 'right> AsRef<[Mapping<'left, 'right>]> for BuildPlan<'left, 'right> impl std::fmt::Display for BuildPlan<'_, '_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut print = |inc: &Match, (left, right): &(Expr, Expr)| -> std::fmt::Result { + let align = |f: &mut std::fmt::Formatter<'_>| -> std::fmt::Result { + if !matches!(f.align(), Some(std::fmt::Alignment::Right)) { + return Ok(()); + } + + let c = f.fill(); + if let Some(width) = f.width() { + for _ in 0..width { + write!(f, "{c}")?; + } + Ok(()) + } else { + write!(f, "{c}") + } + }; + + + let print = |f: &mut std::fmt::Formatter<'_>, + inc: &Match, + (left, right): &(Expr, Expr), + br: bool| + -> std::fmt::Result { let target = inc.target(); let source = inc.source(); let left = left.original(); let right = right.original(); - write!(f, "{target:#?} <- {source:#?} ({left} = {right})") + align(f)?; + write!(f, "{target:#?} <- {source:#?} ({left} = {right})")?; + if br { writeln!(f) } else { Ok(()) } }; - for item in self.as_inner() { + let items = self.as_inner(); + let len = items.len(); + for (i, item) in items.into_iter().enumerate() { + let last = i == len - 1; match item { - Mapping::AsIs(inc, exprs) => print(inc, exprs)?, - Mapping::Into(inc, exprs) => print(inc, exprs)?, + Mapping::AsIs(inc, exprs) => print(f, inc, exprs, !last)?, + Mapping::Into(inc, exprs) => print(f, inc, exprs, !last)?, Mapping::ManyInto { sources, target, exprs, .. } => { - for inc in sources { - print(&Match::new(inc.source(), target.join(inc.target())), exprs)? + let len = sources.len(); + for (i_in, inc) in sources.iter().enumerate() { + let last = last && i_in == len - 1; + let m = Match::new(inc.source(), target.join(inc.target())); + print(f, &m, exprs, !last)?; } }, } @@ -338,31 +366,33 @@ impl BuildPlan<'_, '_> { }) } - pub fn iter_flatten( - &self) - -> impl Iterator))> + '_ { + pub fn iter_flatten(&self) -> impl Iterator + '_ { let pair = |inc: &Match| { - (inc.target().to_path_buf(), abs_if_existing_any(inc.source(), self.crate_root).to_path_buf()) + (inc.target().to_path_buf(), abs_if_existing_any(inc.source(), &self.crate_root).to_path_buf()) }; - self.as_inner() - .iter() - .flat_map(move |mapping| { - let mut rows = Vec::new(); - let kind = mapping.kind(); - match mapping { - Mapping::AsIs(inc, _) | Mapping::Into(inc, _) => rows.push(pair(inc)), - Mapping::ManyInto { sources, target, .. } => { - rows.extend(sources.iter() - .map(|inc| pair(&Match::new(inc.source(), target.join(inc.target()))))); - }, - }; - rows.into_iter().map(move |(l, r)| (kind, l, r)) - }) - .map(|(k, t, p)| { - let time = p.metadata().ok().and_then(|m| m.modified().ok()); - (k, t, (p, time)) - }) + self.as_inner().iter().flat_map(move |mapping| { + let mut rows = Vec::new(); + let kind = mapping.kind(); + match mapping { + Mapping::AsIs(inc, _) | Mapping::Into(inc, _) => rows.push(pair(inc)), + Mapping::ManyInto { sources, target, .. } => { + rows.extend(sources.iter().map(|inc| { + pair(&Match::new(inc.source(), target.join(inc.target()))) + })); + }, + }; + rows.into_iter().map(move |(l, r)| (kind, l, r)) + }) + } + + pub fn iter_flatten_meta( + &self) + -> impl Iterator))> + '_ { + self.iter_flatten().map(|(k, t, p)| { + let time = p.metadata().ok().and_then(|m| m.modified().ok()); + (k, t, (p, time)) + }) } } @@ -435,10 +465,20 @@ impl Mapping<'_, '_> { Mapping::ManyInto { .. } => MappingKind::ManyInto, } } + + + pub fn pretty_print_compact(&self) -> String { + let (k, l, r) = match self { + Mapping::AsIs(_, (l, r)) => ('=', l, r), + Mapping::Into(_, (l, r)) => ('I', l, r), + Mapping::ManyInto { exprs: (l, r), .. } => ('M', l, r), + }; + format!("{{{k}:{:?}={:?}}}", l.original(), r.original()) + } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum MappingKind { /// Copy source __to__ target. @@ -602,7 +642,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(Cow::Borrowed(root.as_path())); let tests: HashSet<_> = vec!["Cargo.toml", "src/lib.rs"].into_iter().collect(); @@ -635,7 +675,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); let tests: HashMap<_, _> = { let man_abs = PathBuf::from("Cargo.toml").canonicalize() @@ -681,7 +721,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); let tests: HashMap<_, _> = { vec![("${SRC}/lib.rs", "src/lib.rs"),].into_iter().collect() }; @@ -716,7 +756,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); // tests: @@ -767,7 +807,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); let exprs = ["${TMP}/*.txt", "${SUB}/*.txt"]; @@ -820,7 +860,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); let tests: HashSet<_> = vec!["Cargo.toml", "src/lib.rs"].into_iter().collect(); @@ -853,7 +893,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); // left hand of rule: let targets = ["trg", "/trg", "//trg"]; @@ -870,7 +910,7 @@ mod tests { let assets = AssetsRules::Map(exprs); - let plan = build_plan(&env, &assets, &opts, root).unwrap(); + let plan = build_plan(&env, &assets, &opts, root.clone()).unwrap(); for pair in plan.as_inner() { if let Mapping::AsIs( @@ -901,7 +941,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); // left hand of rule: let targets = ["trg/", "trg//", "/trg/", "//trg/"]; @@ -916,7 +956,7 @@ mod tests { let assets = AssetsRules::Map(exprs); - let plan = build_plan(&env, &assets, &opts, root).unwrap(); + let plan = build_plan(&env, &assets, &opts, root.clone()).unwrap(); for pair in plan.as_inner() { if let Mapping::Into( @@ -947,7 +987,7 @@ mod tests { let opts = AssetsOptions::default(); let root = crate_root(); - let root = Some(root.as_path()); + let root = Some(root.as_path().into()); // left hand of rule: let targets = ["/trg/", "//trg/", "/trg", "trg"]; @@ -963,7 +1003,7 @@ mod tests { let assets = AssetsRules::Map(exprs); - let plan = build_plan(&env, &assets, &opts, root).unwrap(); + let plan = build_plan(&env, &assets, &opts, root.clone()).unwrap(); for pair in plan.as_inner() { if let Mapping::ManyInto { sources, diff --git a/support/build/src/assets/resolver.rs b/support/build/src/assets/resolver.rs index f20f68c0..1299d7b3 100644 --- a/support/build/src/assets/resolver.rs +++ b/support/build/src/assets/resolver.rs @@ -119,7 +119,7 @@ impl Default for EnvResolver { } impl EnvResolver { - pub fn str<'c, S: AsRef>(&self, s: S, env: &'c Env) -> Cow<'c, str> { + pub fn str>(&self, s: S, env: &Env) -> String { let re = &self.0; // Possible recursion for case "${VAR}" where $VAR="${VAR}" @@ -144,7 +144,7 @@ impl EnvResolver { break; } } - replaced.into() + replaced } pub fn str_only<'c, S: AsRef>(&self, s: S) -> Cow<'c, str> { @@ -165,7 +165,7 @@ impl EnvResolver { replaced.into() } - pub fn expr<'a, 'e, 'c: 'e, Ex: AsMut>>(&self, mut expr: Ex, env: &'c Env) -> Ex { + pub fn expr<'e, Ex: AsMut>>(&self, mut expr: Ex, env: &Env) -> Ex { let editable = expr.as_mut(); let replaced = self.str(editable.actual(), env); if replaced != editable.actual() { diff --git a/support/build/src/manifest/mod.rs b/support/build/src/manifest/mod.rs index adc434aa..b4d7753b 100644 --- a/support/build/src/manifest/mod.rs +++ b/support/build/src/manifest/mod.rs @@ -1,5 +1,5 @@ pub use crate::compile::PDX_PKG_MANIFEST_FILENAME; -pub use crate::metadata::source::CrateInfoSource; +pub use crate::metadata::source::PackageSource; pub use crate::metadata::source::{ManifestSourceOpt, ManifestSourceOptExt}; pub mod format; diff --git a/support/build/src/metadata/format.rs b/support/build/src/metadata/format.rs index 3dab6396..505a8e2b 100644 --- a/support/build/src/metadata/format.rs +++ b/support/build/src/metadata/format.rs @@ -27,6 +27,32 @@ fn eq_metadata_field() { } +pub mod ws { + #[derive(Debug)] + #[cfg_attr(feature = "serde", derive(super::Deserialize))] + pub struct WorkspaceMetadata { + #[cfg_attr(feature = "serde", serde(rename = "playdate"))] + pub inner: Option, + } + + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "serde", derive(super::Deserialize))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] + pub struct Metadata { + pub options: Option, + pub support: Option, + } + + #[derive(Debug, Clone, Default, PartialEq)] + #[cfg_attr(feature = "serde", derive(super::Deserialize))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] + pub struct OptionsDefault { + #[cfg_attr(feature = "serde", serde(default))] + pub assets: super::AssetsOptions, + } +} + + /// Package Playdate Metadata, contains: /// - Package Manifest fields /// - Assets tables - `assets` & `dev-assets` @@ -75,13 +101,7 @@ impl MetadataSource for Metadata 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 assets_options(&self) -> Cow<'_, AssetsOptions> { Cow::Borrowed(&self.options().assets) } fn support(&self) -> &Support { &self.inner.support } } @@ -542,46 +562,80 @@ impl ManifestSourceOptExt for Override where Manifest: ManifestSourceOp #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Options { - pub assets: Option, + /// Use [`PackageSource::default_options`] as defaults for this. + #[cfg_attr(feature = "serde", serde(default))] + pub workspace: bool, + #[cfg_attr(feature = "serde", serde(default))] + pub assets: AssetsOptions, // Output layout ctrl, temporary removed. } +impl Options { + pub fn with_workspace(&self, def: Option<&ws::OptionsDefault>) -> Cow<'_, Options> { + let merge_assets = |assets: &AssetsOptions| { + if def.is_some() { + log::debug!("merge options.assets with ws.defaults") + } -#[derive(Debug, Clone, PartialEq)] + let overwrite = assets.overwrite + .or_else(|| def.and_then(|d| d.assets.overwrite)) + .unwrap_or(AssetsOptions::default_overwrite()); + let follow_symlinks = assets.follow_symlinks + .or_else(|| def.and_then(|d| d.assets.follow_symlinks)) + .unwrap_or(AssetsOptions::default_follow_symlinks()); + let method = assets.method + .or_else(|| def.and_then(|d| d.assets.method)) + .unwrap_or_default(); + let dependencies = assets.dependencies + .or_else(|| def.and_then(|d| d.assets.dependencies)) + .unwrap_or(AssetsOptions::default_dependencies()); + + AssetsOptions { overwrite: Some(overwrite), + follow_symlinks: Some(follow_symlinks), + method: Some(method), + dependencies: Some(dependencies) } + }; + + + if self.workspace { + let res = Self { workspace: self.workspace, + assets: merge_assets(&self.assets) }; + Cow::Owned(res) + } else { + Cow::Borrowed(self) + } + } +} + + +#[derive(Default, 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"))] - #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_overwrite"))] - pub overwrite: bool, + overwrite: Option, #[cfg_attr(feature = "serde", serde(alias = "follow-symlinks"))] - #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_follow_symlinks"))] - pub follow_symlinks: bool, + follow_symlinks: Option, - #[cfg_attr(feature = "serde", serde(alias = "build-method", default))] - pub method: AssetsBuildMethod, + #[cfg_attr(feature = "serde", serde(alias = "build-method"))] + method: Option, /// Allow building assets for dependencies - #[cfg_attr(feature = "serde", serde(default = "AssetsOptions::default_dependencies"))] - pub dependencies: bool, + dependencies: Option, } impl AssetsOptions { + pub fn overwrite(&self) -> bool { self.overwrite.unwrap_or(Self::default_overwrite()) } + pub fn dependencies(&self) -> bool { self.dependencies.unwrap_or(Self::default_dependencies()) } + pub fn follow_symlinks(&self) -> bool { self.follow_symlinks.unwrap_or(Self::default_follow_symlinks()) } + pub fn method(&self) -> AssetsBuildMethod { self.method.unwrap_or_default() } + 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, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Deserialize))] @@ -967,7 +1021,7 @@ mod tests { #[test] fn options_empty() { let m = toml::from_str::("").unwrap(); - assert!(m.assets.is_none()); + assert_eq!(Options::default(), m); } #[test] @@ -978,8 +1032,8 @@ mod tests { let m = toml::from_str::(src).unwrap(); assert_matches!( m.assets, - Some(AssetsOptions { dependencies: false, - .. }) + AssetsOptions { dependencies: None, + .. } ); // overrides default @@ -990,8 +1044,8 @@ mod tests { let m = toml::from_str::(src).unwrap(); assert_matches!( m.assets, - Some(AssetsOptions { dependencies: true, - .. }) + AssetsOptions { dependencies: Some(true), + .. } ); } @@ -1105,8 +1159,8 @@ mod tests { assert!(m.assets.is_empty()); assert_matches!( m.options.assets, - Some(AssetsOptions { dependencies: true, - .. }) + AssetsOptions { dependencies: Some(true), + .. } ); } @@ -1313,7 +1367,7 @@ mod tests { let opts = m.assets_options(); - assert!(opts.dependencies); + assert!(opts.dependencies()); assert!(!AssetsOptions::default_dependencies()); assert_matches!(m.assets(), AssetsRules::Map(_)); diff --git a/support/build/src/metadata/source.rs b/support/build/src/metadata/source.rs index 63c93d09..aead97a6 100644 --- a/support/build/src/metadata/source.rs +++ b/support/build/src/metadata/source.rs @@ -3,10 +3,13 @@ use std::borrow::Cow; use std::path::Path; use super::format::{AssetsOptions, AssetsRules, Ext, ExtraFields, ExtraValue, Manifest, Options, Support}; +use super::format::ws::OptionsDefault; -pub trait CrateInfoSource { +pub trait PackageSource { type Authors: ?Sized + std::slice::Join<&'static str, Output = String>; + type Metadata: MetadataSource; + /// Crate name. fn name(&self) -> Cow; @@ -18,7 +21,26 @@ pub trait CrateInfoSource { /// Crate description. fn description(&self) -> Option>; /// Crate metadata - `playdate` table. - fn metadata(&self) -> Option; + fn metadata(&self) -> Option<&Self::Metadata>; + + /// [`Options`] used as default. + /// + /// Usually it may be from `workspace.metadata.playdate.options` or some external config, + /// depends on implementation. + /// + /// If this __and__ `metadata.options` is `None` - [`Options::default()`] is used. + fn default_options(&self) -> Option<&OptionsDefault> { None } + + /// Cloned or default [`AssetsOptions`] from `metadata.options`, + /// merged with `default_options`, + /// if `metadata.options.workspace` is `true`. + fn assets_options(&self) -> AssetsOptions { + self.metadata() + .map(|m| m.options().with_workspace(self.default_options())) + .unwrap_or_default() + .assets + .to_owned() + } /// Names of `bin` cargo-targets. fn bins(&self) -> &[&str]; @@ -76,7 +98,7 @@ pub trait CrateInfoSource { /// Returns `None` if manifest for `target` not found, no fallback. - fn manifest_for(&self, target: &str, dev: bool) -> Option>> { + fn manifest_override_for(&self, target: &str, dev: bool) -> Option>> { let base = self.manifest_for_crate(); if let Some(root) = self.metadata() { @@ -84,13 +106,13 @@ pub trait CrateInfoSource { if let Some(man) = root.example(target) { Some(base.override_with_extra(man).into_owned()) } else { - log::debug!("target not found: {}", target); + log::debug!("dev-target override not found for {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); + log::debug!("target override not found for {target:?}"); None } } else { @@ -99,8 +121,8 @@ pub trait CrateInfoSource { } /// 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)) + fn manifest_override_or_crate(&self, target: Option<&str>, dev: bool) -> Ext> { + target.and_then(|target| self.manifest_override_for(target, dev)) .unwrap_or_else(|| self.manifest_for_crate().into_owned()) } } @@ -498,8 +520,9 @@ mod tests { struct CrateInfoNoMeta; - impl CrateInfoSource for CrateInfoNoMeta { + impl PackageSource for CrateInfoNoMeta { type Authors = [&'static str]; + type Metadata = Metadata; fn name(&self) -> Cow { "Name".into() } fn authors(&self) -> &Self::Authors { &["John"] } @@ -507,7 +530,7 @@ mod tests { fn description(&self) -> Option> { None } fn bins(&self) -> &[&str] { &[SOME_TARGET] } fn examples(&self) -> &[&str] { &[] } - fn metadata(&self) -> Option { None:: } + fn metadata(&self) -> Option<&Self::Metadata> { None } fn manifest_path(&self) -> Cow { Cow::Borrowed(Path::new("Cargo.toml")) } } @@ -515,28 +538,16 @@ mod tests { #[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); + let spec = CrateInfoNoMeta.manifest_override_or_crate("target".into(), false); + let opt = CrateInfoNoMeta.manifest_override_for("target", false); assert_eq!(opt, Some(spec.to_owned())); assert_eq!(spec, base.into_owned()); } - struct CrateInfo; - impl CrateInfoSource for CrateInfo { - type Authors = [&'static str]; - - fn name(&self) -> Cow { "Crate Name".into() } - fn authors(&self) -> &[&'static 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 manifest_path(&self) -> Cow { Cow::Borrowed(Path::new("Cargo.toml")) } - - fn metadata(&self) -> Option { + struct CrateInfo(Metadata); + impl CrateInfo { + fn new() -> Self { let base = Manifest { name: Some("Meta Name"), bundle_id: Some("crate.id"), ..Default::default() }; @@ -572,15 +583,33 @@ mod tests { options: Default::default(), support: Default::default() } }; - Some(meta) + Self(meta) } } + impl PackageSource for CrateInfo { + type Authors = [&'static str]; + type Metadata = Metadata; + + fn name(&self) -> Cow { "Crate Name".into() } + fn authors(&self) -> &[&'static 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 manifest_path(&self) -> Cow { Cow::Borrowed(Path::new("Cargo.toml")) } + + fn metadata(&self) -> Option<&Self::Metadata> { Some(&self.0) } + } + const SOME_TARGET: &str = "some-target"; #[test] fn manifest_for_crate() { - let base = CrateInfo.manifest_for_crate(); + let base_src = CrateInfo::new(); + let base = base_src.manifest_for_crate(); assert_eq!(Some("Meta Name"), base.name()); assert_eq!(Some("John"), base.author()); assert_eq!(Some("0.0.0"), base.version()); @@ -598,7 +627,7 @@ mod tests { #[test] fn manifest_for_target_wrong_no_meta() { - let spec = CrateInfoNoMeta.manifest_for_opt(Some("WRONG"), false); + let spec = CrateInfoNoMeta.manifest_override_or_crate(Some("WRONG"), false); assert_eq!(Some("Name"), spec.name()); assert_eq!(Some("John"), spec.author()); @@ -608,8 +637,9 @@ mod tests { #[test] fn manifest_for_target_wrong() { - let base = CrateInfo.manifest_for_crate(); - let spec = CrateInfo.manifest_for_opt(Some("WRONG"), false); + let base_src = CrateInfo::new(); + let base = base_src.manifest_for_crate(); + let spec = base_src.manifest_override_or_crate(Some("WRONG"), false); assert_eq!(Some("Meta Name"), spec.name()); assert_eq!(Some("John"), spec.author()); assert_eq!(Some("0.0.0"), spec.version()); @@ -619,7 +649,8 @@ mod tests { #[test] fn manifest_for_target_bin() { - let spec = CrateInfo.manifest_for_opt(SOME_TARGET.into(), false); + let base_src = CrateInfo::new(); + let spec = base_src.manifest_override_or_crate(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()); diff --git a/support/build/src/metadata/validation.rs b/support/build/src/metadata/validation.rs index 4398aafe..446956a8 100644 --- a/support/build/src/metadata/validation.rs +++ b/support/build/src/metadata/validation.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use super::source::CrateInfoSource; +use super::source::PackageSource; use super::source::ManifestSourceOptExt; use super::source::MetadataSource; @@ -160,7 +160,7 @@ fn validate_version(value: &str) -> Option { /// Lint the crate-level source. -pub trait ValidateCrate: CrateInfoSource { +pub trait ValidateCrate: PackageSource { fn validate<'t>(&'t self) -> impl IntoIterator + 't { // - main manifest missing fields // - main manifest fields in bad format @@ -209,12 +209,25 @@ pub trait ValidateCrate: CrateInfoSource { fn validate_for(&self, target: &str) -> impl IntoIterator { - println!("TODO: validate_for(target={target:?}) not implemented yet!"); - [] + let no_meta = self.metadata() + .is_none() + .then(|| Problem::Warning(Warning::MissingMetadata)); + + let man = self.bins() + .contains(&target) + .then_some(false) + .or_else(|| self.examples().contains(&target).then_some(true)) + .map(|dev| self.manifest_override_or_crate(target.into(), dev)) + .map_or_else( + || vec![Problem::UnknownTarget { name: target.to_owned(), }], + |m| m.validate().into_iter().collect::>(), + ); + + no_meta.into_iter().chain(man) } } -impl ValidateCrate for T where T: CrateInfoSource {} +impl ValidateCrate for T where T: PackageSource {} #[cfg(test)]