cargo_toml-0.22.3/.cargo_vcs_info.json0000644000000001360000000000100132460ustar { "git": { "sha1": "787729202ba6338ec4145d5efac650169b15d2d7" }, "path_in_vcs": "" }cargo_toml-0.22.3/Cargo.lock0000644000000067730000000000100112360ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "cargo_toml" version = "0.22.3" dependencies = [ "serde", "toml", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_parser", "toml_writer", "winnow", ] [[package]] name = "toml_datetime" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] name = "toml_parser" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" dependencies = [ "winnow", ] [[package]] name = "toml_writer" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" cargo_toml-0.22.3/Cargo.toml0000644000000031450000000000100112470ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.71" name = "cargo_toml" version = "0.22.3" authors = ["Kornel "] build = false include = [ "src/*.rs", "Cargo.toml", "README.md", "LICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "`Cargo.toml` struct definitions for parsing with Serde" homepage = "https://lib.rs/cargo_toml" documentation = "https://docs.rs/cargo_toml" readme = "README.md" keywords = [ "cargo", "metadata", "schema", "struct", "serde", ] categories = [ "rust-patterns", "parser-implementations", ] license = "Apache-2.0 OR MIT" repository = "https://gitlab.com/lib.rs/cargo_toml" [package.metadata.release] tag-prefix = "" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] all-features = true rustdoc-args = [ "--cfg=docsrs", "--generate-link-to-definition", ] [badges.maintenance] status = "actively-developed" [features] features = [] [lib] name = "cargo_toml" path = "src/cargo_toml.rs" [dependencies.serde] version = "1.0.219" features = ["derive"] [dependencies.toml] version = "0.9.2" cargo_toml-0.22.3/Cargo.toml.orig000064400000000000000000000020041046102023000147210ustar 00000000000000[package] version = "0.22.3" edition = "2021" name = "cargo_toml" authors = ["Kornel "] description = "`Cargo.toml` struct definitions for parsing with Serde" keywords = ["cargo", "metadata", "schema", "struct", "serde"] categories = ["rust-patterns", "parser-implementations"] homepage = "https://lib.rs/cargo_toml" repository = "https://gitlab.com/lib.rs/cargo_toml" documentation = "https://docs.rs/cargo_toml" license = "Apache-2.0 OR MIT" include = ["src/*.rs", "Cargo.toml", "README.md", "LICENSE"] rust-version = "1.71" [lib] name = "cargo_toml" path = "src/cargo_toml.rs" [features] # Helper for processing the `[features]` section features = [] [dependencies] serde = { version = "1.0.219", features = ["derive"] } toml = "0.9.2" [badges] maintenance = { status = "actively-developed" } [package.metadata.release] tag-prefix="" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] all-features = true rustdoc-args = ["--cfg=docsrs", "--generate-link-to-definition"] [workspace] cargo_toml-0.22.3/LICENSE000064400000000000000000000256711046102023000130560ustar 00000000000000© Kornel Lesiński Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ---- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cargo_toml-0.22.3/README.md000064400000000000000000000045421046102023000133220ustar 00000000000000# Deserialize `Cargo.toml` tom replacement This is a definition of fields in `Cargo.toml` files for [serde](https://serde.rs). It allows reading of `Cargo.toml` data, and serializing it using TOML or other formats. It's used by [the lib.rs site](https://lib.rs) to extract information about crates. This crate is more than just schema definition. It supports post-processing of the data to emulate Cargo's workspace inheritance and `autobins` features. It supports files on disk as well as other non-disk data sources. To get started, see [`Manifest::from_slice`][docs]. If you need to get information about Cargo projects local to devs' machines, consider using [cargo_metadata](https://lib.rs/crates/cargo_metadata) instead. Running `cargo metadata` gives more complete information, and comes from the authoritative source. Editing of TOML through Serde is lossy (e.g. comments aren't preseved). If you want to modify `Cargo.toml` files, use [`toml_edit`](https://lib.rs/crates/toml_edit). [docs]: https://docs.rs/cargo_toml/latest/cargo_toml/struct.Manifest.html#method.from_slice ## Features * Allows parsing `Cargo.toml` independently of Cargo. It can read manifests that use nightly features, without requiring a nightly Cargo version. Unlike `cargo metadata`, this is a standalone self-contained implementation, and it doesn't run any external commands. * It is safe to use with untrusted code. It is just a parser. It won't run any build commands nor apply any `.cargo/config.toml` files. * It supports Cargo workspaces and inheritance of fields. * It supports abstracting the file system, so parsing of `Cargo.toml` can auto-detect files [parsed from `.crate` tarballs](https://lib.rs/crates/crate_untar), bare git repositories, and other data sources, without having to extract the files to disk first. * It has optional helper functions for interpreting the `[features]` section. ## There will be updates Cargo regularly adds new features to `Cargo.toml`. Keep this crate up-to-date to correctly parse them all — **use [dependabot][db] or [renovate][ren]**. [db]: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates [ren]: https://docs.renovatebot.com/rust/ cargo_toml-0.22.3/src/afs.rs000064400000000000000000000141101046102023000137410ustar 00000000000000use crate::{Error, Manifest, Value}; use std::collections::HashSet; use std::fs::read_dir; use std::io; use std::path::{Path, PathBuf}; /// This crate supports reading `Cargo.toml` not only from a real directory, but also directly from other sources, like tarballs or bare git repos (BYO directory reader). /// /// The implementation must have a concept of the current directory, which is set to the crate's manifest dir. pub trait AbstractFilesystem { /// List all files and directories at the given relative path (no leading `/`). fn file_names_in(&self, rel_path: &str) -> io::Result>>; /// `parse_root_workspace` is preferred. /// /// The `rel_path_hint` may be specified explicitly by `package.workspace` (it may be relative like `"../"`, without `Cargo.toml`) or `None`, /// which means you have to search for workspace's `Cargo.toml` in parent directories. /// /// Read bytes of the root workspace manifest TOML file and return the path it's been read from. /// The path needs to be an absolute path, because it will be used as the base path for inherited readmes, and would be ambiguous otherwise. #[deprecated(note = "implement parse_root_workspace instead")] #[doc(hidden)] fn read_root_workspace(&self, _rel_path_hint: Option<&Path>) -> io::Result<(Vec, PathBuf)> { Err(io::Error::new(io::ErrorKind::Unsupported, "AbstractFilesystem::read_root_workspace unimplemented")) } /// The `rel_path_hint` may be specified explicitly by `package.workspace` (it may be relative like `"../"`, without `Cargo.toml`) or `None`, /// which means you have to search for workspace's `Cargo.toml` in parent directories. /// /// Read and parse the root workspace manifest TOML file and return the path it's been read from. /// The path needs to be an absolute path, because it will be used as the base path for inherited readmes, and would be ambiguous otherwise. #[allow(deprecated)] fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest, PathBuf), Error> { let (data, path) = self.read_root_workspace(rel_path_hint).map_err(|e| Error::Workspace(Box::new((e.into(), rel_path_hint.map(PathBuf::from)))))?; let manifest = match Manifest::from_slice(&data) { Ok(m) => m, Err(e) => return Err(Error::Workspace(Box::new((e, Some(path))))), }; if manifest.workspace.is_none() { return Err(Error::Workspace(Box::new( (Error::WorkspaceIntegrity("Not a Workspace.\nUse package.workspace to select a differnt path, or implement cargo_toml::AbstractFilesystem::parse_root_workspace".into()), Some(path)) ))); } Ok((manifest, path)) } } impl AbstractFilesystem for &T where T: AbstractFilesystem + ?Sized, { fn file_names_in(&self, rel_path: &str) -> io::Result>> { ::file_names_in(*self, rel_path) } #[allow(deprecated)] fn read_root_workspace(&self, rel_path_hint: Option<&Path>) -> io::Result<(Vec, PathBuf)> { ::read_root_workspace(*self, rel_path_hint) } fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest, PathBuf), Error> { ::parse_root_workspace(*self, rel_path_hint) } } /// [`AbstractFilesystem`] implementation for real files. pub struct Filesystem<'a> { path: &'a Path, } impl<'a> Filesystem<'a> { #[must_use] pub fn new(path: &'a Path) -> Self { Self { path } } } impl AbstractFilesystem for Filesystem<'_> { fn file_names_in(&self, rel_path: &str) -> io::Result>> { Ok(read_dir(self.path.join(rel_path))? .filter_map(|entry| entry.ok().map(|e| e.file_name().to_string_lossy().into_owned().into())) .collect()) } fn parse_root_workspace(&self, path: Option<&Path>) -> Result<(Manifest, PathBuf), Error> { match path { Some(path) => { let ws = self.path.join(path); let toml_path = ws.join("Cargo.toml"); let data = match std::fs::read(&toml_path) { Ok(d) => d, Err(e) => return Err(Error::Workspace(Box::new((Error::Io(e), Some(toml_path))))), }; Ok((parse_workspace(&data, &toml_path)?, ws)) }, None => { // Try relative path first match find_workspace(self.path) { Ok(found) => Ok(found), Err(err) if self.path.is_absolute() => Err(err), Err(_) => find_workspace(&self.path.ancestors().last().unwrap().canonicalize()?), } }, } } } #[inline(never)] fn find_workspace(path: &Path) -> Result<(Manifest, PathBuf), Error> { if path.parent().is_none() { return Err(io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}', because it has no parent directories", path.display())).into()) } let mut last_error = None; path.ancestors().skip(1) .map(|parent| parent.join("Cargo.toml")) .find_map(|p| { let data = std::fs::read(&p).ok()?; match parse_workspace(&data, &p) { Ok(manifest) => Some((manifest, p)), Err(e) => { last_error = Some(e); None }, } }) .ok_or(last_error.unwrap_or_else(|| { let has_slash = path.to_str().is_some_and(|s| s.ends_with('/')); io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}{}..'", path.display(), if has_slash { "" } else { "/" })).into() })) } #[inline(never)] fn parse_workspace(data: &[u8], path: &Path) -> Result, Error> { let manifest = Manifest::from_slice(data)?; if manifest.workspace.is_none() { return Err(Error::WorkspaceIntegrity(format!("Manifest at {} was expected to be a workspace.", path.display()))); } Ok(manifest) } cargo_toml-0.22.3/src/cargo_toml.rs000064400000000000000000002312741046102023000153320ustar 00000000000000#![forbid(unsafe_code)] #![allow(clippy::inline_always)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::needless_for_each)] #![allow(clippy::new_without_default)] #![allow(clippy::redundant_closure_for_method_calls)] #![allow(clippy::trivially_copy_pass_by_ref)] #![cfg_attr(docsrs, feature(doc_cfg))] //! This crate defines `struct`s that can be deserialized with Serde //! to load and inspect `Cargo.toml` metadata. //! //! See [`Manifest::from_path`]. Note that `Cargo.toml` files are not self-contained. Correct interpretation of the manifest requires other files on disk: //! //! * List of files in order to auto-discover binaries, examples, benchmarks, and tests. //! * Potentially a `Manifest` from one of parent directories, that acts as a workspace root for inheritance of shared workspace information. //! //! Because of this filesystem-dependence, loading `Cargo.toml` [from a string](`Manifest::from_str`) is [an advanced operation](`Manifest::complete_from_abstract_filesystem`). //! The crate has methods for processing this information, but if you don't already have a full crate on disk, you will need to write some glue code to obtain it. See [`Manifest::complete_from_path_and_workspace`]. use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Display; use std::mem::take; use std::path::{Path, PathBuf}; use std::{fs, io}; pub use toml::Value; /// Dependencies. The keys in this map are not always crate names, this can be overriden by the `package` field, and there may be multiple copies of the same crate. /// /// Optional dependencies may create implicit features, see the [`features`] module for dealing with this. pub type DepsSet = BTreeMap; /// Config target (see [`parse_cfg`](https://lib.rs/parse_cfg) crate) + deps for the target. pub type TargetDepsSet = BTreeMap; /// The `[features]` section. This set may be incomplete! /// /// The `default` is special, and there may be more features /// implied by optional dependencies. /// See the [`features`] module for more info. pub type FeatureSet = BTreeMap>; /// Locally replace dependencies pub type PatchSet = BTreeMap; /// A set of lints. pub type LintSet = BTreeMap; /// Lint groups such as [lints.rust]. pub type LintGroups = BTreeMap; mod afs; mod error; mod inheritable; pub use crate::afs::*; pub use crate::error::Error; pub use crate::inheritable::Inheritable; #[cfg(feature = "features")] #[cfg_attr(docsrs, doc(cfg(feature = "features")))] pub mod features; /// The top-level `Cargo.toml` structure. **This is the main type in this library.** /// /// The `Metadata` is a generic type for `[package.metadata]` table. You can replace it with /// your own struct type if you use the metadata and don't want to use the catch-all `Value` type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Manifest { /// Package definition (a cargo crate) pub package: Option>, /// Workspace-wide settings pub workspace: Option>, /// Normal dependencies #[serde(default, skip_serializing_if = "DepsSet::is_empty")] pub dependencies: DepsSet, /// Dev/test-only deps #[serde(default, skip_serializing_if = "DepsSet::is_empty")] pub dev_dependencies: DepsSet, /// Build-time deps #[serde(default, skip_serializing_if = "DepsSet::is_empty")] pub build_dependencies: DepsSet, /// `[target.cfg.dependencies]` #[serde(default, skip_serializing_if = "TargetDepsSet::is_empty")] pub target: TargetDepsSet, /// The `[features]` section. This set may be incomplete! /// /// Optional dependencies may create implied Cargo features. /// This features section also supports microsyntax with `dep:`, `/`, and `?` /// for managing dependencies and their features.io /// /// This crate has an optional [`features`] module for dealing with this /// complexity and getting the real list of features. #[serde(default, skip_serializing_if = "FeatureSet::is_empty", deserialize_with = "feature_set")] pub features: FeatureSet, /// Obsolete #[serde(default, skip_serializing_if = "DepsSet::is_empty")] #[deprecated(note = "Cargo recommends patch instead")] pub replace: DepsSet, /// `[patch.crates-io]` section #[serde(default, skip_serializing_if = "PatchSet::is_empty")] pub patch: PatchSet, /// Note that due to autolibs feature this is not the complete list /// unless you run [`Manifest::complete_from_path`] pub lib: Option, /// Compilation/optimization settings #[serde(default, skip_serializing_if = "Profiles::should_skip_serializing")] pub profile: Profiles, /// `[badges]` section #[serde(default, skip_serializing_if = "Badges::should_skip_serializing")] pub badges: Badges, /// Note that due to autobins feature this is not the complete list /// unless you run [`Manifest::complete_from_path`] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub bin: Vec, /// Benchmarks #[serde(default, skip_serializing_if = "Vec::is_empty")] pub bench: Vec, /// Integration tests #[serde(default, skip_serializing_if = "Vec::is_empty")] pub test: Vec, /// Examples #[serde(default, skip_serializing_if = "Vec::is_empty")] pub example: Vec, /// Lints #[serde(default, skip_serializing_if = "Inheritable::::is_empty")] pub lints: Inheritable, } /// A manifest can contain both a package and workspace-wide properties #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "kebab-case")] pub struct Workspace { /// Relative paths of crates in here #[serde(default)] pub members: Vec, /// Members to operate on when in the workspace root. /// /// When specified, `default-members` must expand to a subset of `members`. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub default_members: Vec, /// Template for inheritance #[serde(skip_serializing_if = "Option::is_none")] pub package: Option, /// Ignore these dirs #[serde(default, skip_serializing_if = "Vec::is_empty")] pub exclude: Vec, /// Shared info #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, /// Compatibility setting #[serde(skip_serializing_if = "Option::is_none")] pub resolver: Option, /// Template for `needs_workspace_inheritance` #[serde(default, skip_serializing_if = "DepsSet::is_empty")] pub dependencies: DepsSet, /// Workspace-level lint groups #[serde(default, skip_serializing_if = "LintGroups::is_empty")] pub lints: LintGroups, } /// Workspace can predefine properties that can be inherited via `{ workspace = true }` in its member packages. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct PackageTemplate { /// Deprecated #[serde(default, skip_serializing_if = "Option::is_none")] pub authors: Option>, /// See #[serde(default, skip_serializing_if = "Option::is_none")] pub categories: Option>, /// Multi-line text, some people use Markdown here #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, /// URL #[serde(default, skip_serializing_if = "Option::is_none")] pub documentation: Option, /// Opt-in to new Rust behaviors #[serde(default, skip_serializing_if = "Option::is_none")] pub edition: Option, /// Don't publish these files, relative to workspace #[serde(default, skip_serializing_if = "Option::is_none")] pub exclude: Option>, /// Homepage URL #[serde(default, skip_serializing_if = "Option::is_none")] pub homepage: Option, /// Publish these files, relative to workspace #[serde(default, skip_serializing_if = "Option::is_none")] pub include: Option>, /// For search #[serde(default, skip_serializing_if = "Option::is_none")] pub keywords: Option>, /// SPDX #[serde(default, skip_serializing_if = "Option::is_none")] pub license: Option, /// If not SPDX #[serde(default, skip_serializing_if = "Option::is_none")] pub license_file: Option, /// Block publishing or choose custom registries #[serde(default, skip_serializing_if = "Publish::is_default")] pub publish: Publish, /// Opt-out or custom path, relative to workspace #[serde(default, skip_serializing_if = "OptionalFile::is_default")] pub readme: OptionalFile, /// (HTTPS) repository URL #[serde(default, skip_serializing_if = "Option::is_none")] pub repository: Option, /// Minimum required rustc version in format `1.99` #[serde(default, skip_serializing_if = "Option::is_none")] pub rust_version: Option, /// Package version semver #[serde(default, skip_serializing_if = "Option::is_none")] pub version: Option, } fn default_true() -> bool { true } fn is_true(val: &bool) -> bool { *val } fn is_false(val: &bool) -> bool { !*val } impl Manifest { /// Parse contents from a `Cargo.toml` file on disk. /// /// Calls [`Manifest::complete_from_path`] to discover implicit binaries, etc. /// If needed, it will search the file system for a workspace, and fill in data inherited from the workspace. #[inline] pub fn from_path(cargo_toml_path: impl AsRef) -> Result { Self::from_path_with_metadata(cargo_toml_path) } /// Warning: this will make an incomplete manifest, which will be missing data and panic when using workspace inheritance. /// /// Parse contents of a `Cargo.toml` file already loaded as a byte slice. It's **not** a file name, but file's TOML-syntax content. /// /// If you don't call [`Manifest::complete_from_path`], it may be missing implicit data, and panic if workspace inheritance is used. #[inline(always)] pub fn from_slice(cargo_toml_content: &[u8]) -> Result { Self::from_slice_with_metadata(cargo_toml_content) } /// Warning: this will make an incomplete manifest, which will be missing data and panic when using workspace inheritance. /// /// It parses contents of a `Cargo.toml` file loaded as a string. It's **not** a file name, but file's TOML-syntax content. /// /// For a more reliable method, see `from_path`. /// /// If you don't call [`Manifest::complete_from_path`], it may be missing implicit data, and panic if workspace inheritance is used. #[inline(always)] #[allow(clippy::should_implement_trait)] pub fn from_str(cargo_toml_content: &str) -> Result { Self::from_slice_with_metadata_str(cargo_toml_content) } } impl Deserialize<'a>> Manifest { /// Warning: this will make an incomplete manifest, which will be missing data and panic when using workspace inheritance. /// /// Parse `Cargo.toml`, and parse its `[package.metadata]` into a custom Serde-compatible type. /// /// It does not call [`Manifest::complete_from_path`], so may be missing implicit data. #[inline] pub fn from_slice_with_metadata(cargo_toml_content: &[u8]) -> Result { let cargo_toml_content = std::str::from_utf8(cargo_toml_content).map_err(|_| Error::Other("utf8"))?; Self::from_slice_with_metadata_str(cargo_toml_content) } #[inline(never)] fn from_slice_with_metadata_str(cargo_toml_content: &str) -> Result { let mut manifest: Self = toml::from_str(cargo_toml_content)?; if let Some(package) = &mut manifest.package { // This is a clumsy implementation of Cargo's rule that missing version defaults publish to false. // Serde just doesn't support such relationship for default field values, so this will be incorrect // for explicit `version = "0.0.0"` and `publish = true`. if package.version.get().is_ok_and(|v| v == "0.0.0") && package.publish.get().is_ok_and(|p| p.is_default()) { package.publish = Inheritable::Set(Publish::Flag(false)); } } Ok(manifest) } /// Parse contents from `Cargo.toml` file on disk, with custom Serde-compatible metadata type. /// /// Calls [`Manifest::complete_from_path`], so it will load a workspace if necessary. pub fn from_path_with_metadata>(cargo_toml_path: P) -> Result { let cargo_toml_path = cargo_toml_path.as_ref(); let cargo_toml_content = fs::read_to_string(cargo_toml_path)?; let mut manifest = Self::from_slice_with_metadata_str(&cargo_toml_content)?; manifest.complete_from_path(cargo_toml_path)?; Ok(manifest) } } impl Manifest { /// `Cargo.toml` doesn't contain explicit information about `[lib]` and `[[bin]]`, /// which are inferred based on files on disk. /// /// This scans the disk to make the data in the manifest as complete as possible. /// /// It supports workspace inheritance and will search for a root workspace. /// Use [`Manifest::complete_from_path_and_workspace`] to provide the workspace explicitly. pub fn complete_from_path(&mut self, path: &Path) -> Result<(), Error> { let manifest_dir = path.parent().ok_or(Error::Other("bad path"))?; self.complete_from_abstract_filesystem::(Filesystem::new(manifest_dir), None) } /// [`Manifest::complete_from_path`], but allows passing workspace manifest explicitly. /// /// `workspace_manifest_and_path` is the root workspace manifest already parsed, /// and the path is the path to the root workspace's directory. /// If it's `None`, the root workspace will be discovered automatically. pub fn complete_from_path_and_workspace(&mut self, package_manifest_path: &Path, workspace_manifest_and_path: Option<(&Manifest, &Path)>) -> Result<(), Error> { let manifest_dir = package_manifest_path.parent().ok_or(Error::Other("bad path"))?; self.complete_from_abstract_filesystem(Filesystem::new(manifest_dir), workspace_manifest_and_path) } /// `Cargo.toml` doesn't contain explicit information about `[lib]` and `[[bin]]`, /// which are inferred based on files on disk. /// /// You can provide any implementation of directory scan, which doesn't have to /// be reading straight from disk (might scan a tarball or a git repo, for example). /// /// If `workspace_manifest_and_path` is set, it will inherit from this workspace. /// If it's `None`, it will try to find a workspace if needed. /// /// Call it like `complete_from_abstract_filesystem::(…)` if the arguments are ambiguous. pub fn complete_from_abstract_filesystem( &mut self, fs: Fs, workspace_manifest_and_path: Option<(&Manifest, &Path)>, ) -> Result<(), Error> { let tmp; let err_path; let res = if let Some((ws, ws_path)) = workspace_manifest_and_path { err_path = Some(ws_path); self._inherit_workspace(ws.workspace.as_ref(), ws_path) } else if let Some(ws) = self.workspace.take() { err_path = Some(Path::new("")); // Manifest may be both a workspace and a package let res = self._inherit_workspace(Some(&ws), Path::new("")); self.workspace = Some(ws); res } else if self.needs_workspace_inheritance() { let ws_path_hint = self.package.as_ref().and_then(|p| p.workspace.as_deref()); match fs.parse_root_workspace(ws_path_hint) { Ok((ws_manifest, base_path)) => { tmp = base_path; err_path = Some(&tmp); self._inherit_workspace(ws_manifest.workspace.as_ref(), &tmp) }, Err(e) => { err_path = if let Some(p) = ws_path_hint { tmp = p.to_owned(); Some(&tmp) } else { None }; Err(e) }, } } else { err_path = None; Ok(()) }; match res.and_then(|()| self.complete_from_abstract_filesystem_inner(&fs)) { Ok(()) => Ok(()), Err(e @ Error::Workspace(_)) => Err(e), Err(e) => Err(Error::Workspace(Box::new((e, err_path.map(PathBuf::from))))), } } /// If `true`, some fields are unavailable. If `false`, it's fully usable as-is. /// /// It is `false` in manifests that use workspace inheritance, but had their data completed from the root manifest already. pub fn needs_workspace_inheritance(&self) -> bool { self.package.as_ref().is_some_and(Package::needs_workspace_inheritance) || !self.lints.is_set() || self.dependencies.values() .chain(self.build_dependencies.values()) .chain(self.dev_dependencies.values()) .any(|dep| { matches!(dep, Dependency::Inherited(_)) }) } fn _inherit_workspace(&mut self, workspace: Option<&Workspace>, workspace_base_path: &Path) -> Result<(), Error> { let workspace_base_path = if workspace_base_path.file_name() == Some("Cargo.toml".as_ref()) { workspace_base_path.parent().ok_or(Error::Other("bad path"))? } else { workspace_base_path }; inherit_dependencies(&mut self.dependencies, workspace, workspace_base_path)?; inherit_dependencies(&mut self.build_dependencies, workspace, workspace_base_path)?; inherit_dependencies(&mut self.dev_dependencies, workspace, workspace_base_path)?; for target in self.target.values_mut() { inherit_dependencies(&mut target.dependencies, workspace, workspace_base_path)?; inherit_dependencies(&mut target.build_dependencies, workspace, workspace_base_path)?; inherit_dependencies(&mut target.dev_dependencies, workspace, workspace_base_path)?; } if let Some(ws) = workspace { self.lints.inherit(&ws.lints); } let package = match &mut self.package { Some(p) => p, None => return Ok(()), }; if let Some(ws) = workspace.and_then(|w| w.package.as_ref()) { Self::inherit_package_properties(package, ws, workspace_base_path)?; } if package.needs_workspace_inheritance() { return Err(Error::WorkspaceIntegrity(format!("not all fields of `{}` have been present in workspace.package", package.name()))); } Ok(()) } fn inherit_package_properties(package: &mut Package, ws: &PackageTemplate, workspace_base_path: &Path) -> Result<(), Error> { fn maybe_inherit(to: Option<&mut Inheritable>, from: Option<&T>) { if let Some(from) = from { if let Some(to) = to { to.inherit(from); } } } fn inherit(to: &mut Inheritable, from: Option<&T>) { if let Some(from) = from { to.inherit(from); } } inherit(&mut package.authors, ws.authors.as_ref()); inherit(&mut package.categories, ws.categories.as_ref()); inherit(&mut package.edition, ws.edition.as_ref()); inherit(&mut package.exclude, ws.exclude.as_ref()); inherit(&mut package.include, ws.include.as_ref()); inherit(&mut package.keywords, ws.keywords.as_ref()); inherit(&mut package.version, ws.version.as_ref()); maybe_inherit(package.description.as_mut(), ws.description.as_ref()); maybe_inherit(package.documentation.as_mut(), ws.documentation.as_ref()); maybe_inherit(package.homepage.as_mut(), ws.homepage.as_ref()); maybe_inherit(package.license.as_mut(), ws.license.as_ref()); maybe_inherit(package.repository.as_mut(), ws.repository.as_ref()); maybe_inherit(package.rust_version.as_mut(), ws.rust_version.as_ref()); package.publish.inherit(&ws.publish); match (&mut package.readme, &ws.readme) { (r @ Inheritable::Inherited, flag @ OptionalFile::Flag(_)) => { r.set(flag.clone()); }, (r @ Inheritable::Inherited, OptionalFile::Path(path)) => { r.set(OptionalFile::Path(workspace_base_path.join(path))); }, _ => {}, } if let Some((f, ws)) = package.license_file.as_mut().zip(ws.license_file.as_ref()) { f.set(workspace_base_path.join(ws)); } Ok(()) } fn complete_from_abstract_filesystem_inner(&mut self, fs: &dyn AbstractFilesystem) -> Result<(), Error> { let Some(package) = &self.package else { return Ok(()) }; let src = match fs.file_names_in("src") { Ok(src) => src, Err(err) if err.kind() == io::ErrorKind::NotFound => Default::default(), Err(err) => return Err(err.into()), }; let has_path = self.lib.as_ref().is_some_and(|l| l.path.is_some()); if !has_path && (package.autolib || self.lib.is_some()) && src.contains("lib.rs") { self.lib .get_or_insert_with(Product::default) .path = Some("src/lib.rs".to_string()); } if let Some(lib) = &mut self.lib { lib.name.get_or_insert_with(|| package.name.replace('-', "_")); if lib.edition.is_none() { lib.edition = Some(*package.edition.get()?); } if lib.crate_type.is_empty() { lib.crate_type.push("lib".to_string()); } lib.required_features.clear(); // not applicable } if package.autobins { let mut bin = take(&mut self.bin); let (fully_overrided, mut partial_overrided) = self.autoset(&mut bin, "src/bin", fs)?; self.bin = bin; if src.contains("main.rs") && !fully_overrided.contains("src/main.rs") { let rel_path = "src/main.rs".to_string(); let name = &package.name; let product = if let Some(mut product) = partial_overrided.remove(name) { product.path = Some(rel_path); product } else { Product { name: Some(name.clone()), path: Some(rel_path), edition: Some(*package.edition.get()?), ..Product::default() } }; self.bin.push(product); } } Self::sort_products(&mut self.bin); if package.autoexamples { let mut example = take(&mut self.example); self.autoset(&mut example, "examples", fs)?; self.example = example; } Self::sort_products(&mut self.example); if package.autotests { let mut test = take(&mut self.test); self.autoset(&mut test, "tests", fs)?; self.test = test; } Self::sort_products(&mut self.test); if package.autobenches { let mut bench = take(&mut self.bench); self.autoset(&mut bench, "benches", fs)?; self.bench = bench; } Self::sort_products(&mut self.bench); let Some(package) = &mut self.package else { return Ok(()) }; let root_files = fs.file_names_in("")?; if matches!(package.build, None | Some(OptionalFile::Flag(true))) && root_files.contains("build.rs") { package.build = Some(OptionalFile::Path("build.rs".into())); } if matches!(package.readme.get()?, OptionalFile::Flag(true)) { if let Some(name) = root_files.get("README.md").or_else(|| root_files.get("README.txt")).or_else(|| root_files.get("README")) { package.readme = Inheritable::Set(OptionalFile::Path(PathBuf::from(&**name))); } } Ok(()) } /// Return the set of path overrided in `Cargo.toml`. fn autoset( &self, out: &mut Vec, dir: &str, fs: &dyn AbstractFilesystem, ) -> Result<(BTreeSet, BTreeMap), Error> { let fully_overrided: BTreeSet<_> = out.iter() .filter_map(|product| product.path.clone()) .collect(); let mut partial_overrided: BTreeMap = out.iter() .filter_map(|product| { match (&product.path, &product.name) { (None, Some(name)) => { Some((name.clone(), product.clone())) }, _ => None } }) .collect(); // Remove partially overrided items out.retain(|product| product.path.is_some()); if let Some(package) = &self.package { if let Ok(bins) = fs.file_names_in(dir) { for name in bins { let rel_path = format!("{dir}/{name}"); if name.ends_with(".rs") { if !fully_overrided.contains(&rel_path) { let name = name.trim_end_matches(".rs"); let product = if let Some(mut product) = partial_overrided.remove(name) { product.path = Some(rel_path); product } else { Product { name: Some(name.to_string()), path: Some(rel_path), edition: Some(*package.edition.get()?), ..Product::default() } }; out.push(product); } } else if let Ok(sub) = fs.file_names_in(&rel_path) { let rel_path = format!("{rel_path}/main.rs"); if sub.contains("main.rs") && !fully_overrided.contains(&rel_path) { let product = if let Some(mut product) = partial_overrided.remove(&*name) { product.path = Some(rel_path); product } else { Product { name: Some(name.into()), path: Some(rel_path), edition: Some(*package.edition.get()?), ..Product::default() } }; out.push(product); } } } } } Ok((fully_overrided, partial_overrided)) } /// ensure bins are deterministic fn sort_products(products: &mut [Product]) { products.sort_unstable_by(|a, b| a.name.cmp(&b.name).then(a.path.cmp(&b.path))); } /// Panics if it's not a package (only a workspace). /// /// You can access the `.package` field directly to handle the `Option`: /// /// ```rust,ignore /// manifest.package.as_ref().ok_or(SomeError::NotAPackage)?; /// ``` #[track_caller] #[inline] pub fn package(&self) -> &Package { self.package.as_ref().expect("not a package") } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) pub fn lints(&self) -> &LintGroups { self.lints.as_ref().unwrap() } } fn inherit_dependencies(deps_to_inherit: &mut BTreeMap, workspace: Option<&Workspace>, workspace_base_path: &Path) -> Result<(), Error> { for (key, dep) in deps_to_inherit { if let Dependency::Inherited(overrides) = dep { let template = workspace.and_then(|ws| ws.dependencies.get(key)) .ok_or_else(|| Error::WorkspaceIntegrity(format!("workspace dependencies are missing `{key}`")))?; let mut overrides = overrides.clone(); *dep = template.clone(); if overrides.optional { dep.try_detail_mut()?.optional = true; } if !overrides.features.is_empty() { dep.try_detail_mut()?.features.append(&mut overrides.features); } if let Dependency::Detailed(dep) = dep { dep.inherited = true; if let Some(path) = &mut dep.path { *path = workspace_base_path.join(&path).display().to_string(); } } } } Ok(()) } impl Default for Manifest { #[allow(deprecated)] fn default() -> Self { Self { package: Default::default(), workspace: Default::default(), dependencies: Default::default(), dev_dependencies: Default::default(), build_dependencies: Default::default(), target: Default::default(), features: Default::default(), replace: Default::default(), patch: Default::default(), lib: Default::default(), profile: Default::default(), badges: Default::default(), bin: Default::default(), bench: Default::default(), test: Default::default(), example: Default::default(), lints: Default::default(), } } } /// Build-in an custom build/optimization settings #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct Profiles { /// Used for `--release` #[serde(skip_serializing_if = "Option::is_none")] pub release: Option, /// Used by default, weirdly called `debug` profile. #[serde(skip_serializing_if = "Option::is_none")] pub dev: Option, /// Used for `cargo test` #[serde(skip_serializing_if = "Option::is_none")] pub test: Option, /// Used for `cargo bench` (nightly) #[serde(skip_serializing_if = "Option::is_none")] pub bench: Option, /// Unused #[serde(skip_serializing_if = "Option::is_none")] #[deprecated(note = "the `doc` profile is obsolete. `cargo doc` uses the `dev` profile")] pub doc: Option, /// User-suppiled for `cargo --profile=name` #[serde(flatten)] pub custom: BTreeMap, } impl Profiles { /// Determine whether or not a Profiles struct should be serialized fn should_skip_serializing(&self) -> bool { self.release.is_none() && self.dev.is_none() && self.test.is_none() && self.bench.is_none() && self.custom.is_empty() } } /// Verbosity of debug info in a [`Profile`] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] #[serde(try_from = "toml::Value")] pub enum DebugSetting { /// 0 or false None = 0, /// 1 = line tables only Lines = 1, /// 2 or true Full = 2, } impl TryFrom for DebugSetting { type Error = Error; fn try_from(v: Value) -> Result { Ok(match v { Value::Boolean(b) => if b { Self::Full } else { Self::None }, Value::Integer(n) => match n { 0 => Self::None, 1 => Self::Lines, 2 => Self::Full, _ => return Err(Error::Other("wrong number for debug setting")), }, Value::String(s) => match s.as_str() { "none" => Self::None, "limited" | "line-directives-only" | "line-tables-only" => Self::Lines, "full" => Self::Full, _ => return Err(Error::Other("wrong name for debug setting")), }, _ => return Err(Error::Other("wrong data type for debug setting")), }) } } impl Serialize for DebugSetting { fn serialize(&self, serializer: S) -> Result { match self { Self::None => serializer.serialize_bool(false), Self::Lines => serializer.serialize_i8(1), Self::Full => serializer.serialize_bool(true), } } } /// Handling of debug symbols in a build profile #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] #[serde(try_from = "toml::Value")] pub enum StripSetting { /// Same as `strip = false` None, /// Detailed debug is stripped, but coarse debug is preserved Debuginfo, /// Stronger than the `Debuginfo` setting, same as `strip = true` Symbols, } impl Serialize for StripSetting { fn serialize(&self, serializer: S) -> Result { match self { Self::None => serializer.serialize_bool(false), Self::Debuginfo => serializer.serialize_str("debuginfo"), Self::Symbols => serializer.serialize_bool(true), } } } impl TryFrom for StripSetting { type Error = Error; fn try_from(v: Value) -> Result { Ok(match v { Value::Boolean(b) => if b { Self::Symbols } else { Self::None }, Value::String(s) => match s.as_str() { "none" => Self::None, "debuginfo" => Self::Debuginfo, "symbols" => Self::Symbols, _ => return Err(Error::Other("strip setting has unknown string value")), }, _ => return Err(Error::Other("wrong data type for strip setting")), }) } } /// Handling of LTO in a build profile #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] #[serde(try_from = "toml::Value")] pub enum LtoSetting { /// off None, /// false ThinLocal, Thin, /// True Fat, } impl Serialize for LtoSetting { fn serialize(&self, serializer: S) -> Result { match self { Self::None => serializer.serialize_str("off"), Self::ThinLocal => serializer.serialize_bool(false), Self::Thin => serializer.serialize_str("thin"), Self::Fat => serializer.serialize_bool(true), } } } impl TryFrom for LtoSetting { type Error = Error; fn try_from(v: Value) -> Result { Ok(match v { Value::Boolean(b) => if b { Self::Fat } else { Self::ThinLocal }, Value::String(s) => match s.as_str() { "off" | "n" | "no" => Self::None, "thin" => Self::Thin, "fat" | "on" | "y" | "yes" | "true" => Self::Fat, "false" => Self::ThinLocal, _ => return Err(Error::Other("lto setting has unknown string value")), }, _ => return Err(Error::Other("wrong data type for lto setting")), }) } } /// Compilation/optimization settings for a workspace #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Profile { /// num or z, s #[serde(default, skip_serializing_if = "Option::is_none")] pub opt_level: Option, /// 0,1,2 or bool #[serde(default, skip_serializing_if = "Option::is_none")] pub debug: Option, /// Move debug info to separate files #[serde(default, skip_serializing_if = "Option::is_none")] pub split_debuginfo: Option, /// For dynamic libraries #[serde(default, skip_serializing_if = "Option::is_none")] pub rpath: Option, /// Link-time-optimization #[serde(default, skip_serializing_if = "Option::is_none")] pub lto: Option, /// Extra assertions #[serde(default, skip_serializing_if = "Option::is_none")] pub debug_assertions: Option, /// Parallel compilation #[serde(default, skip_serializing_if = "Option::is_none")] pub codegen_units: Option, /// Handling of panics/unwinding #[serde(default, skip_serializing_if = "Option::is_none")] pub panic: Option, /// Support for incremental rebuilds #[serde(default, skip_serializing_if = "Option::is_none")] pub incremental: Option, /// Check integer arithmetic #[serde(default, skip_serializing_if = "Option::is_none")] pub overflow_checks: Option, /// Remove debug info #[serde(default, skip_serializing_if = "Option::is_none")] pub strip: Option, /// Profile overrides for dependencies, `*` is special. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub package: BTreeMap, /// Profile overrides for build dependencies, `*` is special. #[serde(default, skip_serializing_if = "Option::is_none")] pub build_override: Option, /// Only relevant for non-standard profiles #[serde(default, skip_serializing_if = "Option::is_none")] pub inherits: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] /// Cargo uses the term "target" for both "target platform" and "build target" (the thing to build), /// which makes it ambigous. /// Here Cargo's bin/lib **target** is renamed to **product**. pub struct Product { /// This field points at where the crate is located, relative to the `Cargo.toml`. pub path: Option, /// The name of a product is the name of the library or binary that will be generated. /// This is defaulted to the name of the package, with any dashes replaced /// with underscores. (Rust `extern crate` declarations reference this name; /// therefore the value must be a valid Rust identifier to be usable.) pub name: Option, /// A flag for enabling unit tests for this product. This is used by `cargo test`. #[serde(default = "default_true", skip_serializing_if = "is_true")] pub test: bool, /// A flag for enabling documentation tests for this product. This is only relevant /// for libraries, it has no effect on other sections. This is used by /// `cargo test`. #[serde(default = "default_true", skip_serializing_if = "is_true")] pub doctest: bool, /// A flag for enabling benchmarks for this product. This is used by `cargo bench`. #[serde(default = "default_true", skip_serializing_if = "is_true")] pub bench: bool, /// A flag for enabling documentation of this product. This is used by `cargo doc`. #[serde(default = "default_true", skip_serializing_if = "is_true")] pub doc: bool, /// If the product is meant to be a compiler plugin, this field must be set to true /// for Cargo to correctly compile it and make it available for all dependencies. #[serde(default, skip_serializing_if = "is_false")] pub plugin: bool, /// If the product is meant to be a "macros 1.1" procedural macro, this field must /// be set to true. #[serde(default, alias = "proc_macro", alias = "proc-macro", skip_serializing_if = "is_false")] pub proc_macro: bool, /// If set to false, `cargo test` will omit the `--test` flag to rustc, which /// stops it from generating a test harness. This is useful when the binary being /// built manages the test runner itself. #[serde(default = "default_true", skip_serializing_if = "is_true")] pub harness: bool, /// Deprecated. Edition should be set only per package. /// /// If set then a product can be configured to use a different edition than the /// `[package]` is configured to use, perhaps only compiling a library with the /// 2018 edition or only compiling one unit test with the 2015 edition. By default /// all products are compiled with the edition specified in `[package]`. #[serde(default, skip_serializing_if = "Option::is_none")] pub edition: Option, /// The available options are "dylib", "rlib", "staticlib", "cdylib", and "proc-macro". #[serde(default, skip_serializing_if = "Vec::is_empty")] pub crate_type: Vec, /// The `required-features` field specifies which features the product needs in order to be built. /// If any of the required features are not selected, the product will be skipped. /// This is only relevant for the `[[bin]]`, `[[bench]]`, `[[test]]`, and `[[example]]` sections, /// it has no effect on `[lib]`. #[serde(default)] pub required_features: Vec, } impl Default for Product { fn default() -> Self { Self { path: None, name: None, test: true, doctest: true, bench: true, doc: true, harness: true, plugin: false, proc_macro: false, required_features: Vec::new(), crate_type: Vec::new(), edition: None, } } } /// Dependencies that are platform-specific or enabled through custom `cfg()`. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Target { /// platform-specific normal deps #[serde(default)] pub dependencies: DepsSet, /// platform-specific dev-only/test-only deps #[serde(default)] pub dev_dependencies: DepsSet, /// platform-specific build-time deps #[serde(default)] pub build_dependencies: DepsSet, } /// Dependency definition. Note that this struct doesn't carry it's key/name, which you need to read from its section. /// /// It can be simple version number, or detailed settings, or inherited. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum Dependency { /// Version requirement (e.g. `^1.5`) Simple(String), /// Incomplete data Inherited(InheritedDependencyDetail), // order is important for serde /// `{ version = "^1.5", features = ["a", "b"] }` etc. Detailed(Box), } impl Dependency { /// Get object with special dependency settings if it's not just a version number. /// /// Returns `None` if it's inherited and the value is not available #[inline] #[must_use] pub fn detail(&self) -> Option<&DependencyDetail> { match self { Self::Detailed(d) => Some(d), Self::Simple(_) | Self::Inherited(_) => None, } } /// Panics if inherited value is not available #[inline] #[track_caller] pub fn detail_mut(&mut self) -> &mut DependencyDetail { self.try_detail_mut().expect("dependency not available due to workspace inheritance") } /// Returns error if inherited value is not available /// /// Makes it detailed otherwise pub fn try_detail_mut(&mut self) -> Result<&mut DependencyDetail, Error> { match self { Self::Detailed(d) => Ok(d), Self::Simple(ver) => { *self = Self::Detailed(Box::new(DependencyDetail { version: Some(ver.clone()), ..Default::default() })); match self { Self::Detailed(d) => Ok(d), _ => unreachable!(), } }, Self::Inherited(_) => Err(Error::InheritedUnknownValue), } } /// Panics if inherited value is not available /// /// Version requirement #[inline] #[track_caller] #[must_use] pub fn req(&self) -> &str { self.try_req().unwrap() } /// Version requirement /// /// Returns Error if inherited value is not available #[inline] #[track_caller] pub fn try_req(&self) -> Result<&str, Error> { match self { Self::Simple(v) => Ok(v), Self::Detailed(d) => Ok(d.version.as_deref().unwrap_or("*")), Self::Inherited(_) => Err(Error::InheritedUnknownValue), } } /// Enable extra features for this dep, in addition to the `default` features controlled via `default_features`. #[inline] #[must_use] pub fn req_features(&self) -> &[String] { match self { Self::Simple(_) => &[], Self::Detailed(d) => &d.features, Self::Inherited(d) => &d.features, } } /// Is it optional. Note that optional deps can be used as features, unless features use `dep:`/`?` syntax for them. /// See the [`features`] module for more info. #[inline] #[must_use] pub fn optional(&self) -> bool { match self { Self::Simple(_) => false, Self::Detailed(d) => d.optional, Self::Inherited(d) => d.optional, } } /// `Some` if it overrides the package name. /// If `None`, use the dependency name as the package name. #[inline] #[must_use] pub fn package(&self) -> Option<&str> { match self { Self::Detailed(d) => d.package.as_deref(), Self::Simple(_) | Self::Inherited(_) => None, } } /// Git URL of this dependency, if any #[inline] #[must_use] pub fn git(&self) -> Option<&str> { self.detail()?.git.as_deref() } /// Git commit of this dependency, if any #[inline] #[must_use] pub fn git_rev(&self) -> Option<&str> { self.detail()?.rev.as_deref() } /// `true` if it's an usual crates.io dependency, /// `false` if git/path/alternative registry #[track_caller] #[must_use] pub fn is_crates_io(&self) -> bool { match self { Self::Simple(_) => true, Self::Detailed(d) => { // TODO: allow registry to be set to crates.io explicitly? d.path.is_none() && d.registry.is_none() && d.registry_index.is_none() && d.git.is_none() && d.tag.is_none() && d.branch.is_none() && d.rev.is_none() }, Self::Inherited(_) => panic!("data not available with workspace inheritance"), } } } /// When definition of a dependency is more than just a version string. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct DependencyDetail { /// Semver requirement. Note that a plain version number implies this version *or newer* compatible one. #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, /// If `Some`, use this as the crate name instead of `[dependencies]`'s table key. /// /// By using this, a crate can have multiple versions of the same dependency. #[serde(skip_serializing_if = "Option::is_none")] pub package: Option, /// Fetch this dependency from a custom 3rd party registry (alias defined in Cargo config), not crates-io. /// /// This depends on local cargo configuration. It becomes `registry_index` after the crate is uploaded to a registry. #[serde(skip_serializing_if = "Option::is_none")] pub registry: Option, /// Directly define custom 3rd party registry URL (may be `sparse+https:`) instead of a config nickname. #[serde(skip_serializing_if = "Option::is_none")] pub registry_index: Option, /// This path is usually relative to the crate's manifest, but when using workspace inheritance, it may be relative to the workspace! /// /// When calling [`Manifest::complete_from_path_and_workspace`] use absolute path for the workspace manifest, and then this will be corrected to be an absolute /// path when inherited from the workspace. #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, /// If true, the dependency has been defined at the workspace level, so the `path` is joined with workspace's base path. /// /// This is a field added by this crate, does not exist in TOML. /// Note that `Dependency::Simple` won't have this flag, even if it was inherited. #[serde(skip)] pub inherited: bool, /// Read dependency from git repo URL, not allowed on crates-io. #[serde(skip_serializing_if = "Option::is_none")] pub git: Option, /// Read dependency from git branch, not allowed on crates-io. #[serde(skip_serializing_if = "Option::is_none")] pub branch: Option, /// Read dependency from git tag, not allowed on crates-io. #[serde(skip_serializing_if = "Option::is_none")] pub tag: Option, /// Read dependency from git commit, not allowed on crates-io. #[serde(skip_serializing_if = "Option::is_none")] pub rev: Option, /// Enable these features of the dependency. /// /// Note that Cargo interprets `default` in a special way. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub features: Vec, /// NB: Not allowed at workspace level /// /// If not used with `dep:` or `?/` syntax in `[features]`, this also creates an implicit feature. /// See the [`features`] module for more info. #[serde(default, skip_serializing_if = "is_false")] pub optional: bool, /// Enable the `default` set of features of the dependency (enabled by default). #[serde(default = "default_true", skip_serializing_if = "is_true")] pub default_features: bool, /// Contains the remaining unstable keys and values for the dependency. #[serde(flatten)] pub unstable: BTreeMap, } impl Default for DependencyDetail { fn default() -> Self { Self { version: None, registry: None, registry_index: None, path: None, inherited: false, git: None, branch: None, tag: None, rev: None, features: Vec::new(), optional: false, default_features: true, // != bool::default() package: None, unstable: BTreeMap::new(), } } } /// When a dependency is defined as `{ workspace = true }`, /// and workspace data hasn't been applied yet. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct InheritedDependencyDetail { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub features: Vec, #[serde(default, skip_serializing_if = "is_false")] pub optional: bool, #[serde(skip_serializing_if = "is_false")] pub workspace: bool, } /// The `[package]` section of the [`Manifest`]. This is where crate properties are. /// /// Note that most of these properties can be inherited from a workspace, and therefore not available just from reading a single `Cargo.toml`. See [`Manifest::inherit_workspace`]. /// /// You can replace `Metadata` generic type with your own /// to parse into something more useful than a generic toml `Value` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct Package { /// Careful: some names are uppercase, case-sensitive. `-` changes to `_` when used as a Rust identifier. pub name: String, /// See [the `version()` getter for more info](`Package::version()`). /// /// Must parse as semver, e.g. "1.9.0" /// /// This field may have unknown value when using workspace inheritance, /// and when the `Manifest` has been loaded without its workspace. #[serde(default = "default_version")] pub version: Inheritable, /// Package's edition opt-in. Use [`Package::edition()`] to read it. #[serde(default)] pub edition: Inheritable, /// MSRV 1.x (beware: does not require semver formatting) #[serde(default, skip_serializing_if = "Option::is_none")] pub rust_version: Option>, /// Build script definition #[serde(default, skip_serializing_if = "Option::is_none")] pub build: Option, /// Workspace this package is a member of (`None` if it's implicit) #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace: Option, /// e.g. `["Author ", "etc"]` /// /// Deprecated. #[serde(default, skip_serializing_if = "Inheritable::>::is_empty")] pub authors: Inheritable>, /// It doesn't link to anything #[serde(default, skip_serializing_if = "Option::is_none")] pub links: Option, /// A short blurb about the package. This is not rendered in any format when /// uploaded to crates.io (aka this is not markdown). #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option>, /// Project's homepage #[serde(default, skip_serializing_if = "Option::is_none")] pub homepage: Option>, /// Path to your custom docs. Unnecssary if you rely on docs.rs. #[serde(default, skip_serializing_if = "Option::is_none")] pub documentation: Option>, /// This points to a file under the package root (relative to this `Cargo.toml`). /// implied if README.md, README.txt or README exists. #[serde(default, skip_serializing_if = "Inheritable::is_default")] pub readme: Inheritable, /// Up to 5, for search #[serde(default, skip_serializing_if = "Inheritable::>::is_empty")] pub keywords: Inheritable>, /// This is a list of up to five categories where this crate would fit. /// e.g. `["command-line-utilities", "development-tools::cargo-plugins"]` #[serde(default, skip_serializing_if = "Inheritable::>::is_empty")] pub categories: Inheritable>, /// Don't publish these files #[serde(default, skip_serializing_if = "Inheritable::>::is_empty")] pub exclude: Inheritable>, /// Publish these files #[serde(default, skip_serializing_if = "Inheritable::>::is_empty")] pub include: Inheritable>, /// e.g. "MIT" #[serde(default, skip_serializing_if = "Option::is_none")] pub license: Option>, /// If `license` is not standard #[serde(default, skip_serializing_if = "Option::is_none")] pub license_file: Option>, /// (HTTPS) URL to crate's repository #[serde(default, skip_serializing_if = "Option::is_none")] pub repository: Option>, /// The default binary to run by cargo run. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_run: Option, /// Discover binaries from the file system /// /// This may be incorrectly set to `true` if the crate uses 2015 edition and has explicit `[[bin]]` sections #[serde(default = "default_true", skip_serializing_if = "is_true")] pub autobins: bool, /// Discover libraries from the file system #[serde(default = "default_true", skip_serializing_if = "is_true")] pub autolib: bool, /// Discover examples from the file system /// /// This may be incorrectly set to `true` if the crate uses 2015 edition and has explicit `[[example]]` sections #[serde(default = "default_true", skip_serializing_if = "is_true")] pub autoexamples: bool, /// Discover tests from the file system /// /// This may be incorrectly set to `true` if the crate uses 2015 edition and has explicit `[[test]]` sections #[serde(default = "default_true", skip_serializing_if = "is_true")] pub autotests: bool, /// Discover benchmarks from the file system /// /// This may be incorrectly set to `true` if the crate uses 2015 edition and has explicit `[[bench]]` sections #[serde(default = "default_true", skip_serializing_if = "is_true")] pub autobenches: bool, /// Disable publishing or select custom registries. #[serde(default, skip_serializing_if = "Inheritable::is_default")] pub publish: Inheritable, /// The feature resolver version. #[serde(default, skip_serializing_if = "Option::is_none")] pub resolver: Option, /// Arbitrary metadata of any type, an extension point for 3rd party tools. #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, } #[allow(deprecated)] impl Package { /// Prefer creating it by parsing a [`Manifest`] instead. pub fn new(name: impl Into, version: impl Into) -> Self { Self { name: name.into(), version: Inheritable::Set(version.into()), edition: Inheritable::Set(Edition::E2021), rust_version: None, build: None, workspace: None, authors: Default::default(), links: None, description: None, homepage: None, documentation: None, readme: Inheritable::Set(OptionalFile::Flag(true)), keywords: Default::default(), categories: Default::default(), exclude: Default::default(), include: Default::default(), license: None, license_file: None, repository: None, default_run: None, autolib: true, autobins: true, autoexamples: true, autotests: true, autobenches: true, publish: Inheritable::Set(Publish::Flag(true)), resolver: None, metadata: None, } } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// [`Manifest::from_str`] does not know where the TOML data came from, so it has no way of /// searching the file system (or tarball or git) for a matching /// [Cargo Workspace Manifest](https://doc.rust-lang.org/cargo/reference/workspaces.html). /// /// Without a workspace, properties that use /// [inheritance](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-package-table) /// are missing data, and therefore can't be returned, and will panic. /// /// You can access these properties directly, they are an [`Inheritable`] enum. /// /// The version will default to `0.0.0` if the `version` field was absent in the manifest. #[track_caller] #[inline] pub fn version(&self) -> &str { self.version.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn authors(&self) -> &[String] { self.authors.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn categories(&self) -> &[String] { self.categories.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn categories_mut(&mut self) -> &mut Vec { self.categories.as_mut().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn description(&self) -> Option<&str> { Some(self.description.as_ref()?.as_ref().unwrap()) } #[inline] pub fn set_description(&mut self, description: Option) { self.description = description.map(Inheritable::Set); } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn documentation(&self) -> Option<&str> { Some(self.documentation.as_ref()?.as_ref().unwrap()) } #[inline] pub fn set_documentation(&mut self, documentation: Option) { self.documentation = documentation.map(Inheritable::Set); } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn edition(&self) -> Edition { self.edition.unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn exclude(&self) -> &[String] { self.exclude.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn include(&self) -> &[String] { self.include.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn homepage(&self) -> Option<&str> { Some(self.homepage.as_ref()?.as_ref().unwrap()) } #[inline] pub fn set_homepage(&mut self, homepage: Option) { self.homepage = homepage.map(Inheritable::Set); } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn keywords(&self) -> &[String] { self.keywords.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn license(&self) -> Option<&str> { Some(self.license.as_ref()?.as_ref().unwrap()) } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn license_file(&self) -> Option<&Path> { Some(self.license_file.as_ref()?.as_ref().unwrap()) } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn publish(&self) -> &Publish { self.publish.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn readme(&self) -> &OptionalFile { self.readme.as_ref().unwrap() } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn repository(&self) -> Option<&str> { Some(self.repository.as_ref()?.as_ref().unwrap()) } pub fn set_repository(&mut self, repository: Option) { self.repository = repository.map(Inheritable::Set); } /// Panics if the field is not available (inherited from a workspace that hasn't been loaded) /// /// See [`version`](`Package::version()`) for more information. #[track_caller] #[inline] pub fn rust_version(&self) -> Option<&str> { Some(self.rust_version.as_ref()?.as_ref().unwrap()) } pub fn set_rust_version(&mut self, rust_version: Option) { self.rust_version = rust_version.map(Inheritable::Set); } /// The property that doesn't actually link with anything. /// /// Can't be inherited. #[inline] pub fn links(&self) -> Option<&str> { self.links.as_deref() } /// Name of the package/crate. Libraries and binaries can override it. /// /// Can't be inherited. #[inline] pub fn name(&self) -> &str { &self.name } /// If `true`, some fields are unavailable. /// /// It is `false` in manifests that use inheritance, but had their data completed from the root manifest already. fn needs_workspace_inheritance(&self) -> bool { !(self.authors.is_set() && self.categories.is_set() && self.edition.is_set() && self.exclude.is_set() && self.include.is_set() && self.keywords.is_set() && self.version.is_set() && self.description.as_ref().map_or(true, Inheritable::is_set) && self.documentation.as_ref().map_or(true, Inheritable::is_set) && self.homepage.as_ref().map_or(true, Inheritable::is_set) && self.license.as_ref().map_or(true, Inheritable::is_set) && self.license_file.as_ref().map_or(true, Inheritable::is_set) && self.repository.as_ref().map_or(true, Inheritable::is_set) && self.rust_version.as_ref().map_or(true, Inheritable::is_set) && self.publish.is_set() && self.readme.is_set()) } } impl Default for Package { fn default() -> Self { Self::new("", "") } } /// A way specify or disable README or `build.rs`. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged, expecting = "the value should be either a boolean or a file path")] pub enum OptionalFile { /// Opt-in to default, or explicit opt-out Flag(bool), /// Explicit path Path(PathBuf), } impl Default for OptionalFile { #[inline] fn default() -> Self { Self::Flag(true) } } impl OptionalFile { #[must_use] pub fn display(&self) -> &str { match self { Self::Path(p) => p.to_str().unwrap_or(""), Self::Flag(true) => "", Self::Flag(false) => "", } } #[inline] fn is_default(&self) -> bool { matches!(self, Self::Flag(flag) if *flag) } /// This returns `none` even if `Flag(true)` is set. #[inline] #[must_use] pub fn as_path(&self) -> Option<&Path> { match self { Self::Path(p) => Some(p), Self::Flag(_) => None, } } #[inline] #[must_use] pub fn is_some(&self) -> bool { matches!(self, Self::Flag(true) | Self::Path(_)) } } /// Forbids or selects custom registry #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged, expecting = "the value should be either a boolean, or an array of registry names")] pub enum Publish { Flag(bool), Registry(Vec), } impl Publish { fn is_default(&self) -> bool { matches!(self, Self::Flag(flag) if *flag) } } impl Default for Publish { #[inline] fn default() -> Self { Self::Flag(true) } } impl PartialEq for bool { #[inline] fn eq(&self, p: &Publish) -> bool { match p { Publish::Flag(flag) => *flag == *self, Publish::Registry(reg) => reg.is_empty() != *self, } } } impl PartialEq for Publish { #[inline] fn eq(&self, b: &bool) -> bool { b.eq(self) } } impl PartialEq for &Publish { #[inline] fn eq(&self, b: &bool) -> bool { b.eq(*self) } } impl PartialEq<&Publish> for bool { #[inline] fn eq(&self, b: &&Publish) -> bool { (*self).eq(*b) } } /// In badges section of Cargo.toml /// /// Mostly obsolete. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Badge { pub repository: String, #[serde(default = "default_master")] pub branch: String, pub service: Option, pub id: Option, pub project_name: Option, } fn default_master() -> String { "master".to_string() } fn ok_or_default<'de, T, D>(deserializer: D) -> Result where T: Deserialize<'de> + Default, D: Deserializer<'de>, { Ok(Deserialize::deserialize(deserializer).unwrap_or_default()) } fn default_version() -> Inheritable { Inheritable::Set("0.0.0".into()) } /// `[badges]` section of `Cargo.toml`, deprecated by crates-io except `maintenance`. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Badges { /// Appveyor: `repository` is required. `branch` is optional; default is `master` /// `service` is optional; valid values are `github` (default), `bitbucket`, and /// `gitlab`; `id` is optional; you can specify the appveyor project id if you /// want to use that instead. `project_name` is optional; use when the repository /// name differs from the appveyor project name. #[serde(default, deserialize_with = "ok_or_default")] pub appveyor: Option, /// Circle CI: `repository` is required. `branch` is optional; default is `master` #[serde(default, deserialize_with = "ok_or_default")] pub circle_ci: Option, /// GitLab: `repository` is required. `branch` is optional; default is `master` #[serde(default, deserialize_with = "ok_or_default")] pub gitlab: Option, /// Travis CI: `repository` in format `"/"` is required. /// `branch` is optional; default is `master` #[serde(default, deserialize_with = "ok_or_default")] #[deprecated(note = "badges are deprecated, and travis is dead")] pub travis_ci: Option, /// Codecov: `repository` is required. `branch` is optional; default is `master` /// `service` is optional; valid values are `github` (default), `bitbucket`, and /// `gitlab`. #[serde(default, deserialize_with = "ok_or_default")] pub codecov: Option, /// Coveralls: `repository` is required. `branch` is optional; default is `master` /// `service` is optional; valid values are `github` (default) and `bitbucket`. #[serde(default, deserialize_with = "ok_or_default")] pub coveralls: Option, /// Is it maintained resolution time: `repository` is required. #[serde(default, deserialize_with = "ok_or_default")] pub is_it_maintained_issue_resolution: Option, /// Is it maintained percentage of open issues: `repository` is required. #[serde(default, deserialize_with = "ok_or_default")] pub is_it_maintained_open_issues: Option, /// Maintenance: `status` is required. Available options are `actively-developed`, /// `passively-maintained`, `as-is`, `experimental`, `looking-for-maintainer`, /// `deprecated`, and the default `none`, which displays no badge on crates.io. /// /// ```toml /// [badges] /// maintenance.status = "as-is" /// ``` #[serde(default, deserialize_with = "ok_or_default")] pub maintenance: Maintenance, } impl Badges { #[allow(deprecated)] /// Determine whether or not a Profiles struct should be serialized fn should_skip_serializing(&self) -> bool { self.appveyor.is_none() && self.circle_ci.is_none() && self.gitlab.is_none() && self.travis_ci.is_none() && self.codecov.is_none() && self.coveralls.is_none() && self.is_it_maintained_issue_resolution.is_none() && self.is_it_maintained_open_issues.is_none() && matches!(self.maintenance.status, MaintenanceStatus::None) } } /// A [`Badges`] field with [`MaintenanceStatus`]. /// /// ```toml /// [badges] /// maintenance.status = "experimental" /// ``` #[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Serialize, Deserialize)] pub struct Maintenance { pub status: MaintenanceStatus, } /// Mainly used to deprecate crates. /// /// ```toml /// [badges] /// maintenance.status = "deprecated" /// ``` #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[derive(Default)] pub enum MaintenanceStatus { #[default] None, ActivelyDeveloped, PassivelyMaintained, AsIs, Experimental, LookingForMaintainer, Deprecated, } /// Edition setting, which opts in to new Rust/Cargo behaviors. #[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)] #[non_exhaustive] #[serde(expecting = "if there's a newer edition, then this parser (cargo_toml crate) has to be updated")] pub enum Edition { /// 2015 #[serde(rename = "2015")] #[default] E2015 = 2015, /// 2018 #[serde(rename = "2018")] E2018 = 2018, /// 2021 #[serde(rename = "2021")] E2021 = 2021, /// 2024 #[serde(rename = "2024")] E2024 = 2024, } impl std::fmt::Display for Edition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::E2015 => "2015", Self::E2018 => "2018", Self::E2021 => "2021", Self::E2024 => "2024", }) } } impl Edition { /// Returns minor version (1.x) of the oldest rustc that supports this edition #[must_use] pub fn min_rust_version_minor(self) -> u16 { match self { Self::E2015 => 1, Self::E2018 => 31, Self::E2021 => 56, Self::E2024 => 85, } } } /// The feature resolver version. /// /// Needed in [`Workspace`], but implied by [`Edition`] in packages. #[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)] #[serde(expecting = "if there's a newer resolver, then this parser (cargo_toml crate) has to be updated")] pub enum Resolver { #[serde(rename = "1")] #[default] /// The default for editions prior to 2021. V1 = 1, /// The default for the 2021 edition. #[serde(rename = "2")] V2 = 2, /// The default for the 2024 edition. #[serde(rename = "3")] V3 = 3, } impl Display for Resolver { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::V1 => "1", Self::V2 => "2", Self::V3 => "3", }) } } /// Lint definition. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(from = "LintSerdeParser", into = "LintSerdeParser")] pub struct Lint { /// allow/warn/deny pub level: LintLevel, /// Controls which lints or lint groups override other lint groups. pub priority: i8, /// Unstable pub config: BTreeMap, } /// Internal #[derive(Serialize, Deserialize)] #[serde(untagged, expecting = "lints' values should be a string or { level = \"…\", priority = 1 }")] enum LintSerdeParser { Simple(LintLevel), Detailed { level: LintLevel, /// Controls which lints or lint groups override other lint groups. #[serde(default)] priority: i8, /// Unstable #[serde(default, flatten)] config: BTreeMap, }, } impl From for Lint { fn from(parsed: LintSerdeParser) -> Self { match parsed { LintSerdeParser::Simple(level) => Self { level, priority: 0, config: Default::default() }, LintSerdeParser::Detailed { level, priority, config } => Self { level, priority, config }, } } } impl From for LintSerdeParser { fn from(orig: Lint) -> Self { if orig.priority == 0 && orig.config.is_empty() { Self::Simple(orig.level) } else { Self::Detailed { level: orig.level, priority: orig.priority, config: orig.config, } } } } /// Lint level. #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LintLevel { Allow, Warn, ForceWarn, Deny, Forbid, } #[derive(Deserialize)] #[non_exhaustive] struct Rfc3416FeatureDetail { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub enables: Vec, #[allow(unused)] /// `public` indicates whether or not the feature should be visible in documentation, and defaults to true #[serde(default = "default_true", skip_serializing_if = "is_true")] pub public: bool, #[allow(unused)] /// Add a description to the feature #[serde(default, skip_serializing_if = "Option::is_none")] pub doc: Option, } #[derive(Deserialize)] #[serde(untagged)] enum Rfc3416Feature { Simple(Vec), Detailed(Rfc3416FeatureDetail), } fn feature_set<'de, D>(deserializer: D) -> Result where D: Deserializer<'de> { let detailed = BTreeMap::::deserialize(deserializer)?; Ok(detailed.into_iter().map(|(k, v)| (k, match v { Rfc3416Feature::Simple(f) => f, Rfc3416Feature::Detailed(d) => d.enables, })).collect()) } cargo_toml-0.22.3/src/error.rs000064400000000000000000000046531046102023000143340ustar 00000000000000use std::error::Error as StdErr; use std::path::PathBuf; use std::{fmt, io}; /// In this crate's `Result`s. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// TOML parsing errors Parse(Box), /// Filesystem access errors Io(io::Error), /// Manifest uses workspace inheritance, and the workspace failed to load Workspace(Box<(Error, Option)>), /// Manifest uses workspace inheritance, and the data hasn't been inherited yet InheritedUnknownValue, /// Manifest uses workspace inheritance, but the root workspace is missing data WorkspaceIntegrity(String), /// ??? Other(&'static str), } impl StdErr for Error { fn source(&self) -> Option<&(dyn StdErr + 'static)> { match self { Self::Parse(err) => Some(err), Self::Io(err) => Some(err), Self::Workspace(err) => Some(&err.0), Self::Other(_) | Self::InheritedUnknownValue | Self::WorkspaceIntegrity(_) => None, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Parse(err) => err.fmt(f), Self::Io(err) => err.fmt(f), Self::Other(msg) => f.write_str(msg), Self::WorkspaceIntegrity(s) => f.write_str(s), Self::Workspace(err_path) => { f.write_str("can't load root workspace")?; if let Some(path) = &err_path.1 { write!(f, " at {}", path.display())?; } f.write_str(": ")?; err_path.0.fmt(f) }, Self::InheritedUnknownValue => f.write_str("value from workspace hasn't been set"), } } } impl Clone for Error { fn clone(&self) -> Self { match self { Self::Parse(err) => Self::Parse(err.clone()), Self::Io(err) => Self::Io(io::Error::new(err.kind(), err.to_string())), Self::Other(msg) => Self::Other(msg), Self::WorkspaceIntegrity(msg) => Self::WorkspaceIntegrity(msg.clone()), Self::Workspace(e) => Self::Workspace(e.clone()), Self::InheritedUnknownValue => Self::InheritedUnknownValue, } } } impl From for Error { fn from(o: toml::de::Error) -> Self { Self::Parse(Box::new(o)) } } impl From for Error { fn from(o: io::Error) -> Self { Self::Io(o) } } cargo_toml-0.22.3/src/features.rs000064400000000000000000000655071046102023000150260ustar 00000000000000//! Helper for parsing the microsyntax of the `[features]` section and computing implied features from optional dependencies. use crate::{Dependency, DepsSet, Manifest, Product, TargetDepsSet}; use std::borrow::Cow; use std::collections::hash_map::{Entry, RandomState}; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::hash::BuildHasher; use std::marker::PhantomData; /// Maximum number of features and dependencies, to protect against DoS /// crates.io limit is 300. const MAX_ITEMS: usize = 2048; /// Call [`features::Resolver::new()`](Resolver::new) to get started. /// /// The extra `Hasher` arg is for optionally using [`ahash`](https://lib.rs/ahash). pub struct Resolver<'config, Hasher = RandomState> { always_keep: Option<&'config dyn Fn(&str) -> bool>, _hasher: PhantomData Hasher>, } /// Parse result /// /// It's a temporary struct that borrows from the manifest. Copy things out of this if lifetimes get in the way. /// /// The extra `Hasher` arg is for optionally using [`ahash`](https://lib.rs/ahash). #[derive(Debug)] #[non_exhaustive] pub struct Features<'manifest, 'deps, Hasher = RandomState> { /// All features, resolved and normalized /// /// Default features are literally under the "default" key. pub features: HashMap<&'manifest str, Feature<'manifest>, Hasher>, /// FYI, dependencies referenced by the features, keyed by dependencies' name/identifier in TOML (that's not always the crate name) /// /// This doesn't include *all* dependencies. Dependencies unaffected by any features selection are skipped. pub dependencies: HashMap<&'deps str, FeatureDependency<'deps>, Hasher>, /// True if there were features with names staring with `_` and were inlined and merged into other features /// /// See arg of [`new_with_hasher_and_filter`](Resolver::new_with_hasher_and_filter) to disable removal. pub removed_hidden_features: bool, /// A redirect from removed feature to its replacements pub hidden_features: HashMap<&'manifest str, BTreeSet<&'manifest str>, Hasher>, } /// How an enabled feature affects the dependency #[derive(Debug, PartialEq, Clone)] #[non_exhaustive] pub struct DepAction<'a> { /// Uses `?` syntax, so it doesn't enable the depenency pub is_conditional: bool, /// Uses `dep:` or `?` syntax, so it doesn't imply a feature name pub is_dep_only: bool, /// Features of the dependency to enable (the text after slash, possibly aggregated from multiple items) pub dep_features: BTreeSet>, } /// A feature from `[features]` with all the details #[derive(Debug, Clone)] #[non_exhaustive] pub struct Feature<'a> { /// Name of the feature pub key: &'a str, /// Deps this enables or modifies, by their manifest key (the key isn't always the same as the crate name) /// /// This set is shallow (this feature may also be enabling other features that enable more deps), see [`Feature::enables_recursive`]. pub enables_deps: BTreeMap<&'a str, DepAction<'a>>, /// Enables these explicitly named features /// /// This set is shallow, and the features listed here may be enabling more features, see [`Feature::enables_recursive`]. /// Note that Cargo permits infinite loops (A enables B, B enables A). pub enables_features: BTreeSet<&'a str>, /// Keys of other features that directly enable this feature (this is shallow, not recursive) pub enabled_by: BTreeSet<&'a str>, /// Names of binaries that mention this feature in their `required-features = [this feature]` /// /// Name of the default unnamed binary is set to the package name, and not normalized. pub required_by_bins: Vec<&'a str>, /// If true, it's from `[features]`. If false, it's from `[dependencies]`. /// /// If it's not explicit, and `is_referenced() == true`, it's probably a mistake and wasn't supposed to be a feature. /// See `is_user_facing`. pub explicit: bool, } /// Outer key is the dependency key/name, the `Vec` contains feature names pub type DependenciesEnabledByFeatures<'a, S> = HashMap<&'a str, Vec<(&'a str, &'a DepAction<'a>)>, S>; impl<'a> Feature<'a> { /// Heuristic whether this feature should be shown to users /// /// Skips underscore-prefixed features, and possibly unintended features implied by optional dependencies #[inline] #[must_use] pub fn is_user_facing(&self) -> bool { (self.explicit || !self.is_referenced()) && !self.key.starts_with('_') } /// Just `enabled_by` except the "default" feature #[inline] pub fn non_default_enabled_by(&self) -> impl Iterator { self.enabled_by.iter().copied().filter(|&e| e != "default") } /// Is any other feature using this one? #[inline] #[must_use] pub fn is_referenced(&self) -> bool { !self.enabled_by.is_empty() } /// Finds all features and dependencies that this feature enables, recursively and exhaustively /// /// The first `HashMap` is features by their key, the second is dependencies by their key. It includes only dependencies changed by the features, not all crate dependencies. #[must_use] pub fn enables_recursive(&'a self, features: &'a HashMap<&'a str, Feature<'a>, S>) -> (HashMap<&'a str, &'a Feature<'a>, S>, DependenciesEnabledByFeatures<'a, S>) { let mut features_set = HashMap::with_capacity_and_hasher(self.enabled_by.len() + self.enabled_by.len()/2, S::default()); let mut deps_set = HashMap::with_capacity_and_hasher(self.enables_deps.len() + self.enables_deps.len()/2, S::default()); self.add_to_set(features, &mut features_set, &mut deps_set); (features_set, deps_set) } #[inline(never)] fn add_to_set(&'a self, features: &'a HashMap<&'a str, Feature<'a>, S>, features_set: &mut HashMap<&'a str, &'a Feature<'a>, S>, deps_set: &mut DependenciesEnabledByFeatures<'a, S>) { if features_set.insert(self.key, self).is_none() { for (&dep_key, action) in &self.enables_deps { if !action.is_conditional { deps_set.entry(dep_key).or_default().push((self.key, action)); } } for &key in &self.enables_features { if let Some(feature) = features.get(key) { feature.add_to_set(features, features_set, deps_set); } } } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TargetKey<'a> { pub kind: Kind, /// cfg. None for all targets. pub target: Option<&'a str>, } /// A dependency referenced by a feature #[derive(Debug)] #[non_exhaustive] pub struct FeatureDependency<'dep> { /// Actual crate of this dependency. Note that multiple dependencies can be the same crate, in different versions. pub crate_name: &'dep str, /// By kind and target pub targets: BTreeMap, &'dep Dependency>, } impl<'dep> FeatureDependency<'dep> { #[inline] #[must_use] pub fn dep(&self) -> &'dep Dependency { self.detail().0 } /// Extra metadata for the most common usage (normal > build > dev) of this dependency #[inline] #[must_use] pub fn detail(&self) -> (&'dep Dependency, Kind) { let (k, dep) = self.targets.iter().next().unwrap(); (dep, k.kind) } } impl Resolver<'static, RandomState> { /// Next step: [`.parse(manifest)`](Resolver::parse). #[inline] #[must_use] pub fn new() -> Self { Self { always_keep: None, _hasher: PhantomData, } } } impl<'manifest, 'config, RandomState: BuildHasher + Default> Resolver<'config, RandomState> { /// Use turbofish to configure `RandomState` of a hasher you want this to use. /// /// `should_keep_hidden_feature` is a reference to a closure that will receive feature names starting with `_`, and return whether to keep or hide them. #[must_use] pub fn new_with_hasher_and_filter(should_keep_hidden_feature: &'config dyn Fn(&str) -> bool) -> Self { Self { always_keep: Some(should_keep_hidden_feature), _hasher: PhantomData, } } /// Parse features from a Cargo.toml manifest pub fn parse(&self, manifest: &'manifest Manifest) -> Features<'manifest, 'manifest, RandomState> { let mut features = Self::parse_features( manifest.features.iter().take(MAX_ITEMS), manifest.features.contains_key("default"), ); let dependencies = Self::add_dependencies( &mut features, &manifest.dependencies, &manifest.build_dependencies, &manifest.dev_dependencies, &manifest.target, ); Self::set_required_by_bins(&mut features, &manifest.bin, manifest.package().name()); Self::remove_redundant_dep_action_features(&mut features, &dependencies); Self::set_enabled_by(&mut features); let hidden_features = self.remove_hidden_features(&mut features); Features { features, dependencies, removed_hidden_features: !hidden_features.is_empty(), hidden_features, } } /// Instead of processing a `Cargo.toml` manifest, take bits of information directly instead /// /// It won't fill in `required_by_bins` fields. pub fn parse_custom<'deps, S: BuildHasher>(&self, manifest_features: &'manifest HashMap, S>, deps: impl Iterator>) -> Features<'manifest, 'deps, RandomState> where 'manifest: 'deps { let mut features: HashMap<&'manifest str, Feature<'manifest>, _> = Self::parse_features( manifest_features.iter().take(MAX_ITEMS), manifest_features.contains_key("default"), ); let named_using_dep_syntax = Self::named_using_dep_syntax(&features); // First one wins, so order is important let mut dependencies = HashMap::<&'deps str, FeatureDependency<'deps>, RandomState>::default(); for dep in deps { Self::add_dependency(&mut features, &mut dependencies, named_using_dep_syntax.get(dep.key).copied(), dep.kind, dep.target, dep.key, dep.dep); } Self::remove_redundant_dep_action_features(&mut features, &dependencies); Self::set_enabled_by(&mut features); let hidden_features = self.remove_hidden_features(&mut features); Features { features, dependencies, removed_hidden_features: !hidden_features.is_empty(), hidden_features, } } } /// For parsing in `parse_custom`. Can be constructed from the crates.io index, instead of `Cargo.toml`. /// /// Note about lifetimes: it's not possible to make `&Dependency` on the fly. /// You will have to collect *owned* `Dependency` objects to a `Vec` or `HashMap` first. #[derive(Debug, Clone)] pub struct ParseDependency<'a, 'tmp> { /// Name/id of the dependency, not always the crate name pub key: &'a str, pub kind: Kind, /// Name `[target."from here".dependencies]` pub target: Option<&'a str>, /// Possibly more detail pub dep: &'tmp Dependency, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub enum Kind { #[default] Normal, Build, Dev, } impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> { fn parse_features(features: impl Iterator)>, has_explicit_default: bool) -> HashMap<&'a str, Feature<'a>, S> { features .map(|(key, f)| (key.as_str(), f.as_slice())) .chain(if has_explicit_default { None } else { Some(("default", &[][..])) }) // there must always be the default key .map(|(feature_key, actions)| Self::parse_feature(feature_key, actions)) .collect() } #[inline(never)] fn parse_feature(feature_key: &'a str, actions: &'a [String]) -> (&'a str, Feature<'a>) { // coalesce dep_feature let mut enables_deps = BTreeMap::new(); let mut enables_features = BTreeSet::new(); actions.iter().take(MAX_ITEMS).for_each(|action| { let mut parts = action.splitn(2, '/'); let mut atarget = parts.next().unwrap_or_default(); let dep_feature = parts.next(); let is_dep_prefix = if let Some(k) = atarget.strip_prefix("dep:") { atarget = k; true } else { false }; let is_conditional = if let Some(k) = atarget.strip_suffix('?') { atarget = k; true } else { false }; // dep/feature is old, doesn't actually mean it's dep:only! let is_dep_only = is_dep_prefix; if is_dep_only || dep_feature.is_some() { let action = enables_deps.entry(atarget).or_insert(DepAction { is_conditional, is_dep_only, dep_features: BTreeSet::default(), }); if !is_conditional { action.is_conditional = false; } if is_dep_only { action.is_dep_only = true; } if let Some(df) = dep_feature { action.dep_features.insert(Cow::Borrowed(df)); } } else { // dep/foo can be both, and missing enables_deps is added later after checking all for dep: enables_features.insert(atarget); } }); (feature_key, Feature { key: feature_key, enables_features, required_by_bins: vec![], enables_deps, explicit: true, enabled_by: BTreeSet::new(), // fixed later }) } fn add_implied_optional_deps(features: &mut HashMap<&'a str, Feature<'a>, S>, deps_for_features: &mut HashMap<&'a str, FeatureDependency<'a>, S>, crate_deps: &'a BTreeMap, named_using_dep_syntax: &HashMap<&str, bool, S>, dep_kind: Kind, only_for_target: Option<&'a str>) { for (key, dep) in crate_deps.iter().take(MAX_ITEMS) { let key = key.as_str(); Self::add_dependency(features, deps_for_features, named_using_dep_syntax.get(key).copied(), dep_kind, only_for_target, key, dep); } } #[inline(never)] fn add_dependency<'d>(features: &mut HashMap<&'a str, Feature<'a>, S>, deps_for_features: &mut HashMap<&'d str, FeatureDependency<'d>, S>, named_using_dep_syntax: Option, dep_kind: Kind, only_for_target: Option<&'d str>, key: &'a str, dep: &'d Dependency) where 'a: 'd { let is_optional = dep.optional(); let entry = deps_for_features.entry(key); if !is_optional && named_using_dep_syntax.is_none() && matches!(entry, Entry::Vacant(_)) { return; } let entry = entry.or_insert_with(move || FeatureDependency { crate_name: dep.package().unwrap_or(key), targets: BTreeMap::new(), }); entry.targets.entry(TargetKey { kind: dep_kind, target: only_for_target }).or_insert(dep); // explicit features never overlap with deps, unless using "dep:" syntax. // We need to know about all deps referenced by features, or all optional ones. // We won't see optional build or dev dep feature, if there's normal non-optional dep. Not a big deal? // if we added one for normal, then keep adding build and dev. if is_optional && named_using_dep_syntax != Some(true) { features.entry(key).or_insert_with(move || Feature { key, enables_features: BTreeSet::default(), enables_deps: BTreeMap::from_iter([(key, DepAction { is_dep_only: false, is_conditional: false, dep_features: BTreeSet::default(), })]), explicit: false, enabled_by: BTreeSet::new(), // will do later required_by_bins: vec![], }); } } /// find which names are affected by use of the `dep:` syntax and supress implicit features #[inline(never)] fn named_using_dep_syntax(features: &HashMap<&'a str, Feature<'a>, S>) -> HashMap<&'a str, bool, S> { // explicit features exist, even if their name clashes with a `dep:name`. let mut named_using_dep_syntax: HashMap<_, _, S> = features.keys().map(|&k| (k, false)).collect(); for f in features.values() { f.enables_deps.iter().for_each(|(&dep_key, a)| { named_using_dep_syntax.entry(dep_key) .and_modify(|v| *v |= a.is_dep_only) .or_insert(a.is_dep_only); }); } named_using_dep_syntax } fn add_dependencies(features: &mut HashMap<&'a str, Feature<'a>, S>, dependencies: &'a DepsSet, build_dependencies: &'a DepsSet, dev_dependencies: &'a DepsSet, target: &'a TargetDepsSet) -> HashMap<&'a str, FeatureDependency<'a>, S> { let named_using_dep_syntax = Self::named_using_dep_syntax(features); // First one wins, so order is important let mut all_deps = HashMap::<_, _, S>::default(); Self::add_implied_optional_deps(features, &mut all_deps, dependencies, &named_using_dep_syntax, Kind::Normal, None); Self::add_implied_optional_deps(features, &mut all_deps, build_dependencies, &named_using_dep_syntax, Kind::Build, None); for (target_cfg, target_deps) in target { Self::add_implied_optional_deps(features, &mut all_deps, &target_deps.dependencies, &named_using_dep_syntax, Kind::Normal, Some(target_cfg)); Self::add_implied_optional_deps(features, &mut all_deps, &target_deps.build_dependencies, &named_using_dep_syntax, Kind::Build, Some(target_cfg)); } Self::add_implied_optional_deps(features, &mut all_deps, dev_dependencies, &named_using_dep_syntax, Kind::Dev, None); for (target_cfg, target_deps) in target { Self::add_implied_optional_deps(features, &mut all_deps, &target_deps.dev_dependencies, &named_using_dep_syntax, Kind::Dev, Some(target_cfg)); } all_deps } #[inline(never)] fn set_required_by_bins(features: &mut HashMap<&'a str, Feature<'a>, S>, bin: &'a [Product], package_name: &'a str) { for bin in bin { for f in &bin.required_features { let bin_name = bin.name.as_deref().unwrap_or(package_name); if let Some(f) = features.get_mut(f.as_str()) { // fallback to package name is not quite accurate, because Cargo normalizes exe names slightly f.required_by_bins.push(bin_name); } } } } #[inline(never)] fn remove_redundant_dep_action_features(features: &mut HashMap<&str, Feature<'_>, S>, dependencies: &HashMap<&str, FeatureDependency<'_>, S>) { features.values_mut() .flat_map(|f| &mut f.enables_deps) .filter(|(_, action)| !action.dep_features.is_empty()) .for_each(|(dep_key, action)| { if let Some(dep) = dependencies.get(dep_key).and_then(|d| d.dep().detail()) { action.dep_features.retain(move |dep_f| { let dep_f = &**dep_f; (!dep.default_features || dep_f != "default") && !dep.features.iter().any(|k| k == dep_f) }); } }); } #[inline(never)] fn set_enabled_by(features: &mut HashMap<&'a str, Feature<'a>, S>) { let mut all_enabled_by = HashMap::<_, _, S>::default(); for (&feature_key, f) in features.iter() { f.enables_features.iter().copied().for_each(|action_key| if action_key != feature_key { all_enabled_by.entry(action_key).or_insert_with(BTreeSet::new).insert(feature_key); }); f.enables_deps.iter().for_each(|(&action_key, action)| if !action.is_conditional && !action.is_dep_only && action_key != feature_key { all_enabled_by.entry(action_key).or_insert_with(BTreeSet::new).insert(feature_key); }); } for (key, enabled_by) in all_enabled_by { if let Some(f) = features.get_mut(key) { f.enabled_by = enabled_by; } } } /// find `__features` and inline them #[inline(never)] fn remove_hidden_features(&self, features: &mut HashMap<&'a str, Feature<'a>, S>) -> HashMap<&'a str, BTreeSet<&'a str>, S> { let features_to_remove: BTreeSet<_> = features.keys().copied().filter(|&k| { k.starts_with('_') && !self.always_keep.is_some_and(|cb| (cb)(k)) // if user thinks that is useful info }).collect(); let mut removed_mapping = HashMap::<_, _, S>::default(); if features_to_remove.is_empty() { return removed_mapping; } features_to_remove.into_iter().for_each(|key| { let Some(mut janky) = features.remove(key) else { return }; janky.enabled_by.iter().for_each(|&parent_key| if let Some(parent) = features.get_mut(parent_key) { parent.enabled_by.remove(janky.key); // just in case it's circular // the filter tries to avoid adding new redundant enables_features, but it's order-dependent parent.enables_features.extend(&janky.enables_features); parent.enables_features.remove(janky.key); janky.enables_deps.iter().for_each(|(&k, ja)| { parent.enables_deps.entry(k) .and_modify(|old| { if !ja.is_conditional { old.is_conditional = false; } if ja.is_dep_only { old.is_dep_only = true; } old.dep_features.extend(ja.dep_features.iter().cloned()); }) .or_insert_with(|| ja.clone()); }); }); janky.enables_features.iter().for_each(|&f| { if let Some(child) = features.get_mut(f) { // this list is sometimes a bit redundant, // but the hidden feature cleanup is not recursive, so it needs to contain all possible places child.enabled_by.extend(&janky.enabled_by); child.enabled_by.remove(janky.key); janky.required_by_bins.iter().take(10).for_each(|&bin| { if !child.required_by_bins.contains(&bin) { child.required_by_bins.push(bin); } }); } }); janky.enables_deps.iter().filter(|&(k, a)| !a.is_dep_only && !janky.enables_features.contains(k)).for_each(|(&d, _)| { if let Some(d) = features.get_mut(d) { d.enabled_by.extend(&janky.enabled_by); d.enabled_by.remove(janky.key); } }); removed_mapping.entry(janky.key).or_default().append(&mut janky.enables_features); }); removed_mapping } } #[test] fn features_test() { let m = crate::Manifest::from_str(r#" [package] name = "foo" [[bin]] name = "thebin" required-features = ["__hidden2", "loop3"] [dependencies] not_optional = { path = "..", package = "actual_pkg", features = ["f1", "f2"] } depend = { version = "1.0.0", package = "actual_pkg", optional = true, default-features = false } implied_standalone = { version = "1.0.0", optional = true } implied_referenced = { version = "1.0.0", optional = true } not_relevant = "2" feature_for_hidden = { version = "2", optional = true } __hidden_dep = { version = "2", optional = true } [build-dependencies] a_dep = { version = "1.0.0", optional = true } [features] default = ["x"] a = [] b = ["a", "implied_referenced/with_feature", "depend/default", "depend/default"] c = ["__hidden"] x = ["__hidden", "c", "not_optional/f2", "not_optional/f3", "not_optional/default"] __hidden = ["__hidden2"] __hidden2 = ["dep:depend", "depend/with_x", "depend?/with_y", "__hidden0"] __hidden0 = ["a"] enables_hidden = ["__hidden0"] __feature_for_hidden = ["feature_for_hidden"] loop1 = ["loop2"] loop2 = ["loop3", "a_dep?/maybe", "a_dep?/maybe_too", "depend/with_loop"] loop3 = ["loop1", "implied_referenced/from_loop_3"] "#).unwrap(); let r = Resolver::new().parse(&m); let f = r.features; let d = r.dependencies; assert!(r.removed_hidden_features); // __hidden completely removed assert!(!f.iter().any(|(&k, f)| { k.starts_with('_') || f.enables_features.iter().any(|&k| k.starts_with('_')) || f.enables_deps.iter().any(|(&k, _)| k.starts_with('_')) || f.enabled_by.iter().any(|&k| k.starts_with('_')) })); assert!(!d.keys().any(|&k| k.starts_with('_') && k != "__hidden_dep")); assert!(!f.contains_key("__hidden_dep")); assert_eq!(f.len(), 13); assert!(!f.contains_key("depend")); assert_eq!(d.len(), 7); assert!(!d.contains_key("not_relevant")); assert!(!f.contains_key("not_relevant")); assert!(!f.contains_key("not_optional")); assert_eq!(d["not_optional"].crate_name, "actual_pkg"); assert_eq!(d["implied_standalone"].crate_name, "implied_standalone"); assert!(d["implied_standalone"].detail().0.optional()); assert!(d["a_dep"].targets.keys().all(|t| t.kind == Kind::Build)); assert!(!f["implied_standalone"].explicit); assert!(!f["implied_referenced"].explicit); assert!(!f["a_dep"].explicit); assert!(!f["feature_for_hidden"].is_referenced()); assert_eq!(f["x"].enables_deps.keys().copied().collect::>(), &["depend", "not_optional"]); assert_eq!(f["x"].enables_features.iter().copied().collect::>(), &["a", "c"]); assert_eq!(f["a"].enabled_by.iter().copied().collect::>(), &["b", "c", "enables_hidden", "x"]); assert!(f["a"].enables_deps.is_empty()); assert!(f["a"].enables_features.is_empty()); assert_eq!(f["loop1"].enabled_by.iter().copied().collect::>(), &["loop3"]); assert_eq!(f["loop2"].enabled_by.iter().copied().collect::>(), &["loop1"]); assert_eq!(f["loop3"].enabled_by.iter().copied().collect::>(), &["loop2"]); assert!(f["loop1"].enables_deps.is_empty()); assert_eq!(f["loop2"].enables_deps.keys().copied().collect::>(), &["a_dep", "depend"]); assert!(f["loop2"].enables_deps["a_dep"].is_conditional); assert!(!f["loop2"].enables_deps["depend"].is_conditional); assert_eq!(f["loop3"].enables_deps.keys().copied().collect::>(), &["implied_referenced"]); assert_eq!(f["loop1"].enables_features.iter().copied().collect::>(), &["loop2"]); assert_eq!(f["loop2"].enables_features.iter().copied().collect::>(), &["loop3"]); assert_eq!(f["loop3"].enables_features.iter().copied().collect::>(), &["loop1"]); let (rf, rd) = f["loop1"].enables_recursive(&f); assert_eq!(rf["loop1"].key, "loop1"); assert_eq!(rf["loop2"].key, "loop2"); assert_eq!(rf["loop3"].key, "loop3"); assert_eq!(rd["implied_referenced"][0].0, "loop3"); assert_eq!(rd["depend"][0].0, "loop2"); assert!(!rd.contains_key("a_dep")); } cargo_toml-0.22.3/src/inheritable.rs000064400000000000000000000114521046102023000154640ustar 00000000000000use std::collections::BTreeMap; use crate::Error; use serde::{Deserialize, Serialize, Serializer}; /// Placeholder for a property that may be missing from its package, and needs to be copied from a `Workspace`. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[serde(untagged, try_from = "InheritableSerdeParser")] pub enum Inheritable { Set(T), #[serde(serialize_with = "workspace_true")] Inherited, } impl TryFrom> for Inheritable { type Error = String; fn try_from(parsed: InheritableSerdeParser) -> Result { match parsed { InheritableSerdeParser::Set(v) => Ok(Self::Set(v)), InheritableSerdeParser::Inherited { workspace: true } => Ok(Self::Inherited), InheritableSerdeParser::Inherited { workspace: false } => Err("inherited field with `workspace = false` is not allowed".into()), InheritableSerdeParser::ParseErrorFallback(s) => Err(format!("Error parsing field content. Expected to deserialize {}, found {s}", std::any::type_name::())), } } } fn workspace_true(serializer: S) -> Result { #[derive(Serialize)] struct Inherited { workspace: bool, } Inherited { workspace: true }.serialize(serializer) } #[derive(Deserialize)] #[serde(untagged)] pub enum InheritableSerdeParser { Set(T), Inherited { /// Always `true` (this is for serde) workspace: bool, }, ParseErrorFallback(toml::Value), } impl PartialEq for Inheritable { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Set(a), Self::Set(b)) => a.eq(b), _ => false, } } } impl Inheritable { pub fn as_ref(&self) -> Inheritable<&T> { match self { Self::Set(t) => Inheritable::Set(t), Self::Inherited => Inheritable::Inherited, } } /// You can read the value pub fn is_set(&self) -> bool { matches!(self, Self::Set(_)) } pub fn get(&self) -> Result<&T, Error> { match self { Self::Set(t) => Ok(t), Self::Inherited => Err(Error::InheritedUnknownValue), } } pub fn set(&mut self, val: T) { *self = Self::Set(val); } pub fn as_mut(&mut self) -> Inheritable<&mut T> { match self { Self::Set(t) => Inheritable::Set(t), Self::Inherited => Inheritable::Inherited, } } /// Fails if inherited pub fn get_mut(&mut self) -> Result<&mut T, Error> { match self { Self::Set(t) => Ok(t), Self::Inherited => Err(Error::InheritedUnknownValue), } } /// Panics if inherited #[track_caller] pub fn unwrap(self) -> T { match self { Self::Set(t) => t, Self::Inherited => panic!("inherited workspace value"), } } /// Copy from workspace if needed pub fn inherit(&mut self, other: &T) where T: Clone { if matches!(self, Self::Inherited) { *self = Self::Set(other.clone()); } } } impl Default for Inheritable { fn default() -> Self { Self::Set(T::default()) } } impl Inheritable> { /// False if inherited and unknown #[must_use] pub fn is_empty(&self) -> bool { match self { Self::Inherited => false, Self::Set(v) => v.is_empty(), } } } impl Inheritable> { /// False if inherited and unknown #[must_use] pub fn is_empty(&self) -> bool { match self { Self::Inherited => false, Self::Set(v) => v.is_empty(), } } } impl Inheritable { /// False if inherited and unknown pub fn is_default(&self) -> bool { match self { Self::Inherited => false, Self::Set(v) => T::default() == *v, } } } impl From> for Inheritable { /// Inherits if `None` fn from(val: Option) -> Self { match val { Some(val) => Self::Set(val), None => Self::Inherited, } } } impl From> for Option { /// `None` if inherited fn from(val: Inheritable) -> Self { match val { Inheritable::Inherited => None, Inheritable::Set(val) => Some(val), } } } #[test] fn serializes() { #[derive(Serialize)] struct Foo { bar: Inheritable<&'static str>, } let s = toml::to_string(&Foo { bar: Inheritable::Inherited, }).unwrap(); assert_eq!(s, "[bar]\nworkspace = true\n"); let s = toml::to_string(&Foo { bar: Inheritable::Set("hello"), }).unwrap(); assert_eq!(s, "bar = \"hello\"\n"); }