diff options
Diffstat (limited to 'third_party/rust/cargo_metadata')
-rw-r--r-- | third_party/rust/cargo_metadata/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/CHANGELOG.md | 38 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/Cargo.toml | 55 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/LICENSE-MIT | 23 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/README.md | 12 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/src/dependency.rs | 90 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/src/diagnostic.rs | 160 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/src/errors.rs | 52 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/src/lib.rs | 792 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/src/messages.rs | 175 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/tests/selftest.rs | 163 | ||||
-rw-r--r-- | third_party/rust/cargo_metadata/tests/test_samples.rs | 647 |
12 files changed, 2208 insertions, 0 deletions
diff --git a/third_party/rust/cargo_metadata/.cargo-checksum.json b/third_party/rust/cargo_metadata/.cargo-checksum.json new file mode 100644 index 0000000000..7838a4a29d --- /dev/null +++ b/third_party/rust/cargo_metadata/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"8b3e29799cdedf02f169bb519072ace2e2b6b9413f4ce8fa0666c2d1d964084e","Cargo.toml":"57d432cd172cc87ee4c31b0e4c21c52d06ba1a48da9decd34581b2671c47d71d","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"d51a5b3347bed2441b20986be81bfd4611ca2c5614f950116b273199a9bcf2de","src/dependency.rs":"c593ddc73d863c5712e2aba58b5f4d9bd915a5ac0bc17df71642aa79aa93bfdc","src/diagnostic.rs":"fee47d27390f1026ff99ffade5dfd2ab3e9b9839c3f33ce91a7dcde875551374","src/errors.rs":"797afd61efdd843ae570d9e972dd2425d33823d4a78c0c488028493dffb45c7a","src/lib.rs":"5ec701f3589c5d71c152b5abe7ad5f222aee4d4a5f9992bced1d357bad36e227","src/messages.rs":"a8e3ee31dc8cce5762b4b085be29fe4d7189a789f3a149ef2b6c17604d94528b","tests/selftest.rs":"73afd494c1bf7dd4e1a99971e9ff66a0e21fc7bf3e327663df15d2350dcdfc70","tests/test_samples.rs":"ee2b4737adfa1930c1610bb3ec0fc94b7f1a3691bb09545da69044eef2f5ba6b"},"package":"08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07"}
\ No newline at end of file diff --git a/third_party/rust/cargo_metadata/CHANGELOG.md b/third_party/rust/cargo_metadata/CHANGELOG.md new file mode 100644 index 0000000000..fcc0b26426 --- /dev/null +++ b/third_party/rust/cargo_metadata/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +## Unreleased + +### Added + +- Re-exported `semver` crate directly. + +### Changed + +- Made `parse_stream` more versatile by accepting anything that implements `Read`. + +### Removed + +- Removed re-exports for `BuildMetadata` and `Prerelease` from `semver` crate. + +### Fixed + +- Added missing `manifest_path` field to `Artifact`. Fixes #187. + +## [0.15.0] - 2022-06-22 + +### Added + +- Re-exported `BuildMetadata` and `Prerelease` from `semver` crate. +- Added `workspace_packages` function. +- Added `Edition` enum to better parse edition field. +- Added `rust-version` field to Cargo manifest. + +### Changed + +- Bumped msrv from `1.40.0` to `1.42.0`. + +### Internal Changes + +- Updated `derive_builder` to the latest version. +- Made use of `matches!` macros where possible. +- Fixed some tests diff --git a/third_party/rust/cargo_metadata/Cargo.toml b/third_party/rust/cargo_metadata/Cargo.toml new file mode 100644 index 0000000000..25a28ef4f6 --- /dev/null +++ b/third_party/rust/cargo_metadata/Cargo.toml @@ -0,0 +1,55 @@ +# 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 = "2018" +rust-version = "1.42.0" +name = "cargo_metadata" +version = "0.15.3" +authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"] +description = "structured access to the output of `cargo metadata`" +readme = "README.md" +license = "MIT" +repository = "https://github.com/oli-obk/cargo_metadata" + +[package.metadata.cargo_metadata_test] +some_field = true +other_field = "foo" + +[dependencies.camino] +version = "1.0.7" +features = ["serde1"] + +[dependencies.cargo-platform] +version = "0.1.2" + +[dependencies.derive_builder] +version = "0.11.1" +optional = true + +[dependencies.semver] +version = "1.0.7" +features = ["serde"] + +[dependencies.serde] +version = "1.0.136" +features = ["derive"] + +[dependencies.serde_json] +version = "1.0.79" +features = ["unbounded_depth"] + +[dependencies.thiserror] +version = "1.0.31" + +[features] +builder = ["derive_builder"] +default = [] diff --git a/third_party/rust/cargo_metadata/LICENSE-MIT b/third_party/rust/cargo_metadata/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/third_party/rust/cargo_metadata/LICENSE-MIT @@ -0,0 +1,23 @@ +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. diff --git a/third_party/rust/cargo_metadata/README.md b/third_party/rust/cargo_metadata/README.md new file mode 100644 index 0000000000..03743f31bc --- /dev/null +++ b/third_party/rust/cargo_metadata/README.md @@ -0,0 +1,12 @@ +# cargo_metadata + +Structured access to the output of `cargo metadata`. Usually used from within a `cargo-*` executable. + +Also supports serialization to aid in implementing `--message-format=json`-like +output generation in `cargo-*` subcommands, since some of the types in what +`cargo --message-format=json` emits are exactly the same as the ones from `cargo metadata`. + +[![Build Status](https://api.travis-ci.org/oli-obk/cargo_metadata.svg?branch=master)](https://travis-ci.org/oli-obk/cargo_metadata) +[![crates.io](https://img.shields.io/crates/v/cargo_metadata.svg)](https://crates.io/crates/cargo_metadata) + +[Documentation](https://docs.rs/cargo_metadata/) diff --git a/third_party/rust/cargo_metadata/src/dependency.rs b/third_party/rust/cargo_metadata/src/dependency.rs new file mode 100644 index 0000000000..89bcf9f2f1 --- /dev/null +++ b/third_party/rust/cargo_metadata/src/dependency.rs @@ -0,0 +1,90 @@ +//! This module contains `Dependency` and the types/functions it uses for deserialization. + +use std::fmt; + +use camino::Utf8PathBuf; +#[cfg(feature = "builder")] +use derive_builder::Builder; +use semver::VersionReq; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Eq, PartialEq, Clone, Debug, Copy, Hash, Serialize, Deserialize)] +/// Dependencies can come in three kinds +pub enum DependencyKind { + #[serde(rename = "normal")] + /// The 'normal' kind + Normal, + #[serde(rename = "dev")] + /// Those used in tests only + Development, + #[serde(rename = "build")] + /// Those used in build scripts only + Build, + #[doc(hidden)] + #[serde(other)] + Unknown, +} + +impl Default for DependencyKind { + fn default() -> DependencyKind { + DependencyKind::Normal + } +} + +impl fmt::Display for DependencyKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = serde_json::to_string(self).unwrap(); + // skip opening and closing quotes + f.write_str(&s[1..s.len() - 1]) + } +} + +/// The `kind` can be `null`, which is interpreted as the default - `Normal`. +pub(super) fn parse_dependency_kind<'de, D>(d: D) -> Result<DependencyKind, D::Error> +where + D: Deserializer<'de>, +{ + Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default()) +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// A dependency of the main crate +pub struct Dependency { + /// Name as given in the `Cargo.toml` + pub name: String, + /// The source of dependency + pub source: Option<String>, + /// The required version + pub req: VersionReq, + /// The kind of dependency this is + #[serde(deserialize_with = "parse_dependency_kind")] + pub kind: DependencyKind, + /// Whether this dependency is required or optional + pub optional: bool, + /// Whether the default features in this dependency are used. + pub uses_default_features: bool, + /// The list of features enabled for this dependency. + pub features: Vec<String>, + /// The target this dependency is specific to. + /// + /// Use the [`Display`] trait to access the contents. + /// + /// [`Display`]: std::fmt::Display + pub target: Option<Platform>, + /// If the dependency is renamed, this is the new name for the dependency + /// as a string. None if it is not renamed. + pub rename: Option<String>, + /// The URL of the index of the registry where this dependency is from. + /// + /// If None, the dependency is from crates.io. + pub registry: Option<String>, + /// The file system path for a local path dependency. + /// + /// Only produced on cargo 1.51+ + pub path: Option<Utf8PathBuf>, +} + +pub use cargo_platform::Platform; diff --git a/third_party/rust/cargo_metadata/src/diagnostic.rs b/third_party/rust/cargo_metadata/src/diagnostic.rs new file mode 100644 index 0000000000..dbe21128a0 --- /dev/null +++ b/third_party/rust/cargo_metadata/src/diagnostic.rs @@ -0,0 +1,160 @@ +//! This module contains `Diagnostic` and the types/functions it uses for deserialization. + +#[cfg(feature = "builder")] +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// The error code associated to this diagnostic. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct DiagnosticCode { + /// The code itself. + pub code: String, + /// An explanation for the code + pub explanation: Option<String>, +} + +/// A line of code associated with the Diagnostic +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct DiagnosticSpanLine { + /// The line of code associated with the error + pub text: String, + /// Start of the section of the line to highlight. 1-based, character offset in self.text + pub highlight_start: usize, + /// End of the section of the line to highlight. 1-based, character offset in self.text + pub highlight_end: usize, +} + +/// Macro expansion information associated with a diagnostic. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct DiagnosticSpanMacroExpansion { + /// span where macro was applied to generate this code; note that + /// this may itself derive from a macro (if + /// `span.expansion.is_some()`) + pub span: DiagnosticSpan, + + /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") + pub macro_decl_name: String, + + /// span where macro was defined (if known) + pub def_site_span: Option<DiagnosticSpan>, +} + +/// A section of the source code associated with a Diagnostic +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct DiagnosticSpan { + /// The file name or the macro name this diagnostic comes from. + pub file_name: String, + /// The byte offset in the file where this diagnostic starts from. + pub byte_start: u32, + /// The byte offset in the file where this diagnostic ends. + pub byte_end: u32, + /// 1-based. The line in the file. + pub line_start: usize, + /// 1-based. The line in the file. + pub line_end: usize, + /// 1-based, character offset. + pub column_start: usize, + /// 1-based, character offset. + pub column_end: usize, + /// Is this a "primary" span -- meaning the point, or one of the points, + /// where the error occurred? + /// + /// There are rare cases where multiple spans are marked as primary, + /// e.g. "immutable borrow occurs here" and "mutable borrow ends here" can + /// be two separate spans both "primary". Top (parent) messages should + /// always have at least one primary span, unless it has 0 spans. Child + /// messages may have 0 or more primary spans. + pub is_primary: bool, + /// Source text from the start of line_start to the end of line_end. + pub text: Vec<DiagnosticSpanLine>, + /// Label that should be placed at this location (if any) + pub label: Option<String>, + /// If we are suggesting a replacement, this will contain text + /// that should be sliced in atop this span. + pub suggested_replacement: Option<String>, + /// If the suggestion is approximate + pub suggestion_applicability: Option<Applicability>, + /// Macro invocations that created the code at this span, if any. + pub expansion: Option<Box<DiagnosticSpanMacroExpansion>>, +} + +/// Whether a suggestion can be safely applied. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Applicability { + /// The suggested replacement can be applied automatically safely + MachineApplicable, + /// The suggested replacement has placeholders that will need to be manually + /// replaced. + HasPlaceholders, + /// The suggested replacement may be incorrect in some circumstances. Needs + /// human review. + MaybeIncorrect, + /// The suggested replacement will probably not work. + Unspecified, +} + +/// The diagnostic level +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[non_exhaustive] +#[serde(rename_all = "lowercase")] +pub enum DiagnosticLevel { + /// Internal compiler error + #[serde(rename = "error: internal compiler error")] + Ice, + /// Error + Error, + /// Warning + Warning, + /// Failure note + #[serde(rename = "failure-note")] + FailureNote, + /// Note + Note, + /// Help + Help, +} + +/// A diagnostic message generated by rustc +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct Diagnostic { + /// The error message of this diagnostic. + pub message: String, + /// The associated error code for this diagnostic + pub code: Option<DiagnosticCode>, + /// "error: internal compiler error", "error", "warning", "note", "help" + pub level: DiagnosticLevel, + /// A list of source code spans this diagnostic is associated with. + pub spans: Vec<DiagnosticSpan>, + /// Associated diagnostic messages. + pub children: Vec<Diagnostic>, + /// The message as rustc would render it + pub rendered: Option<String>, +} + +impl fmt::Display for Diagnostic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref rendered) = self.rendered { + f.write_str(rendered)?; + } else { + f.write_str("cargo didn't render this message")?; + } + Ok(()) + } +} diff --git a/third_party/rust/cargo_metadata/src/errors.rs b/third_party/rust/cargo_metadata/src/errors.rs new file mode 100644 index 0000000000..4d08200c83 --- /dev/null +++ b/third_party/rust/cargo_metadata/src/errors.rs @@ -0,0 +1,52 @@ +use std::{io, str::Utf8Error, string::FromUtf8Error}; + +/// Custom result type for `cargo_metadata::Error` +pub type Result<T> = ::std::result::Result<T, Error>; + +/// Error returned when executing/parsing `cargo metadata` fails. +/// +/// # Note about Backtraces +/// +/// This error type does not contain backtraces, but each error variant +/// comes from _one_ specific place, so it's not really needed for the +/// inside of this crate. If you need a backtrace down to, but not inside +/// of, a failed call of `cargo_metadata` you can do one of multiple thinks: +/// +/// 1. Convert it to a `failure::Error` (possible using the `?` operator), +/// which is similar to a `Box<::std::error::Error + 'static + Send + Sync>`. +/// 2. Have appropriate variants in your own error type. E.g. you could wrap +/// a `failure::Context<Error>` or add a `failure::Backtrace` field (which +/// is empty if `RUST_BACKTRACE` is not set, so it's simple to use). +/// 3. You still can place a failure based error into a `error_chain` if you +/// really want to. (Either through foreign_links or by making it a field +/// value of a `ErrorKind` variant). +/// +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Error during execution of `cargo metadata` + #[error("`cargo metadata` exited with an error: {stderr}")] + CargoMetadata { + /// stderr returned by the `cargo metadata` command + stderr: String, + }, + + /// IO Error during execution of `cargo metadata` + #[error("failed to start `cargo metadata`: {0}")] + Io(#[from] io::Error), + + /// Output of `cargo metadata` was not valid utf8 + #[error("cannot convert the stdout of `cargo metadata`: {0}")] + Utf8(#[from] Utf8Error), + + /// Error output of `cargo metadata` was not valid utf8 + #[error("cannot convert the stderr of `cargo metadata`: {0}")] + ErrUtf8(#[from] FromUtf8Error), + + /// Deserialization error (structure of json did not match expected structure) + #[error("failed to interpret `cargo metadata`'s json: {0}")] + Json(#[from] ::serde_json::Error), + + /// The output did not contain any json + #[error("could not find any json in the output of `cargo metadata`")] + NoJson, +} diff --git a/third_party/rust/cargo_metadata/src/lib.rs b/third_party/rust/cargo_metadata/src/lib.rs new file mode 100644 index 0000000000..27c72da0d3 --- /dev/null +++ b/third_party/rust/cargo_metadata/src/lib.rs @@ -0,0 +1,792 @@ +#![deny(missing_docs)] +//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`. +//! Usually used from within a `cargo-*` executable +//! +//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for +//! details on cargo itself. +//! +//! ## Examples +//! +//! ```rust +//! # extern crate cargo_metadata; +//! # use std::path::Path; +//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path")); +//! +//! let mut cmd = cargo_metadata::MetadataCommand::new(); +//! let manifest_path = match args.next() { +//! Some(ref p) if p == "--manifest-path" => { +//! cmd.manifest_path(args.next().unwrap()); +//! } +//! Some(p) => { +//! cmd.manifest_path(p.trim_start_matches("--manifest-path=")); +//! } +//! None => {} +//! }; +//! +//! let _metadata = cmd.exec().unwrap(); +//! ``` +//! +//! Pass features flags +//! +//! ```rust +//! # // This should be kept in sync with the equivalent example in the readme. +//! # extern crate cargo_metadata; +//! # use std::path::Path; +//! # fn main() { +//! use cargo_metadata::{MetadataCommand, CargoOpt}; +//! +//! let _metadata = MetadataCommand::new() +//! .manifest_path("./Cargo.toml") +//! .features(CargoOpt::AllFeatures) +//! .exec() +//! .unwrap(); +//! # } +//! ``` +//! +//! Parse message-format output: +//! +//! ``` +//! # extern crate cargo_metadata; +//! use std::process::{Stdio, Command}; +//! use cargo_metadata::Message; +//! +//! let mut command = Command::new("cargo") +//! .args(&["build", "--message-format=json-render-diagnostics"]) +//! .stdout(Stdio::piped()) +//! .spawn() +//! .unwrap(); +//! +//! let reader = std::io::BufReader::new(command.stdout.take().unwrap()); +//! for message in cargo_metadata::Message::parse_stream(reader) { +//! match message.unwrap() { +//! Message::CompilerMessage(msg) => { +//! println!("{:?}", msg); +//! }, +//! Message::CompilerArtifact(artifact) => { +//! println!("{:?}", artifact); +//! }, +//! Message::BuildScriptExecuted(script) => { +//! println!("{:?}", script); +//! }, +//! Message::BuildFinished(finished) => { +//! println!("{:?}", finished); +//! }, +//! _ => () // Unknown message +//! } +//! } +//! +//! let output = command.wait().expect("Couldn't get cargo's exit status"); +//! ``` + +use camino::Utf8PathBuf; +#[cfg(feature = "builder")] +use derive_builder::Builder; +use std::collections::HashMap; +use std::env; +use std::ffi::OsString; +use std::fmt; +use std::hash::Hash; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::str::from_utf8; + +pub use camino; +pub use semver; +use semver::{Version, VersionReq}; + +pub use dependency::{Dependency, DependencyKind}; +use diagnostic::Diagnostic; +pub use errors::{Error, Result}; +#[allow(deprecated)] +pub use messages::parse_messages; +pub use messages::{ + Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter, +}; +use serde::{Deserialize, Serialize}; + +mod dependency; +pub mod diagnostic; +mod errors; +mod messages; + +/// An "opaque" identifier for a package. +/// It is possible to inspect the `repr` field, if the need arises, but its +/// precise format is an implementation detail and is subject to change. +/// +/// `Metadata` can be indexed by `PackageId`. +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] +pub struct PackageId { + /// The underlying string representation of id. + pub repr: String, +} + +impl std::fmt::Display for PackageId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.repr, f) + } +} + +/// Helpers for default metadata fields +fn is_null(value: &serde_json::Value) -> bool { + matches!(value, serde_json::Value::Null) +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// Starting point for metadata returned by `cargo metadata` +pub struct Metadata { + /// A list of all crates referenced by this crate (and the crate itself) + pub packages: Vec<Package>, + /// A list of all workspace members + pub workspace_members: Vec<PackageId>, + /// Dependencies graph + pub resolve: Option<Resolve>, + /// Workspace root + pub workspace_root: Utf8PathBuf, + /// Build directory + pub target_directory: Utf8PathBuf, + /// The workspace-level metadata object. Null if non-existent. + #[serde(rename = "metadata", default, skip_serializing_if = "is_null")] + pub workspace_metadata: serde_json::Value, + /// The metadata format version + version: usize, +} + +impl Metadata { + /// Get the workspace's root package of this metadata instance. + pub fn root_package(&self) -> Option<&Package> { + match &self.resolve { + Some(resolve) => { + // if dependencies are resolved, use Cargo's answer + let root = resolve.root.as_ref()?; + self.packages.iter().find(|pkg| &pkg.id == root) + } + None => { + // if dependencies aren't resolved, check for a root package manually + let root_manifest_path = self.workspace_root.join("Cargo.toml"); + self.packages + .iter() + .find(|pkg| pkg.manifest_path == root_manifest_path) + } + } + } + + /// Get the workspace packages. + pub fn workspace_packages(&self) -> Vec<&Package> { + self.packages + .iter() + .filter(|&p| self.workspace_members.contains(&p.id)) + .collect() + } +} + +impl<'a> std::ops::Index<&'a PackageId> for Metadata { + type Output = Package; + + fn index(&self, idx: &'a PackageId) -> &Package { + self.packages + .iter() + .find(|p| p.id == *idx) + .unwrap_or_else(|| panic!("no package with this id: {:?}", idx)) + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// A dependency graph +pub struct Resolve { + /// Nodes in a dependencies graph + pub nodes: Vec<Node>, + + /// The crate for which the metadata was read. + pub root: Option<PackageId>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// A node in a dependencies graph +pub struct Node { + /// An opaque identifier for a package + pub id: PackageId, + /// Dependencies in a structured format. + /// + /// `deps` handles renamed dependencies whereas `dependencies` does not. + #[serde(default)] + pub deps: Vec<NodeDep>, + + /// List of opaque identifiers for this node's dependencies. + /// It doesn't support renamed dependencies. See `deps`. + pub dependencies: Vec<PackageId>, + + /// Features enabled on the crate + #[serde(default)] + pub features: Vec<String>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// A dependency in a node +pub struct NodeDep { + /// The name of the dependency's library target. + /// If the crate was renamed, it is the new name. + pub name: String, + /// Package ID (opaque unique identifier) + pub pkg: PackageId, + /// The kinds of dependencies. + /// + /// This field was added in Rust 1.41. + #[serde(default)] + pub dep_kinds: Vec<DepKindInfo>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// Information about a dependency kind. +pub struct DepKindInfo { + /// The kind of dependency. + #[serde(deserialize_with = "dependency::parse_dependency_kind")] + pub kind: DependencyKind, + /// The target platform for the dependency. + /// + /// This is `None` if it is not a target dependency. + /// + /// Use the [`Display`] trait to access the contents. + /// + /// By default all platform dependencies are included in the resolve + /// graph. Use Cargo's `--filter-platform` flag if you only want to + /// include dependencies for a specific platform. + /// + /// [`Display`]: std::fmt::Display + pub target: Option<dependency::Platform>, +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +/// One or more crates described by a single `Cargo.toml` +/// +/// Each [`target`][Package::targets] of a `Package` will be built as a crate. +/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>. +pub struct Package { + /// Name as given in the `Cargo.toml` + pub name: String, + /// Version given in the `Cargo.toml` + pub version: Version, + /// Authors given in the `Cargo.toml` + #[serde(default)] + pub authors: Vec<String>, + /// An opaque identifier for a package + pub id: PackageId, + /// The source of the package, e.g. + /// crates.io or `None` for local projects. + pub source: Option<Source>, + /// Description as given in the `Cargo.toml` + pub description: Option<String>, + /// List of dependencies of this particular package + pub dependencies: Vec<Dependency>, + /// License as given in the `Cargo.toml` + pub license: Option<String>, + /// If the package is using a nonstandard license, this key may be specified instead of + /// `license`, and must point to a file relative to the manifest. + pub license_file: Option<Utf8PathBuf>, + /// Targets provided by the crate (lib, bin, example, test, ...) + pub targets: Vec<Target>, + /// Features provided by the crate, mapped to the features required by that feature. + pub features: HashMap<String, Vec<String>>, + /// Path containing the `Cargo.toml` + pub manifest_path: Utf8PathBuf, + /// Categories as given in the `Cargo.toml` + #[serde(default)] + pub categories: Vec<String>, + /// Keywords as given in the `Cargo.toml` + #[serde(default)] + pub keywords: Vec<String>, + /// Readme as given in the `Cargo.toml` + pub readme: Option<Utf8PathBuf>, + /// Repository as given in the `Cargo.toml` + // can't use `url::Url` because that requires a more recent stable compiler + pub repository: Option<String>, + /// Homepage as given in the `Cargo.toml` + /// + /// On versions of cargo before 1.49, this will always be [`None`]. + pub homepage: Option<String>, + /// Documentation URL as given in the `Cargo.toml` + /// + /// On versions of cargo before 1.49, this will always be [`None`]. + pub documentation: Option<String>, + /// Default Rust edition for the package + /// + /// Beware that individual targets may specify their own edition in + /// [`Target::edition`]. + #[serde(default)] + pub edition: Edition, + /// Contents of the free form package.metadata section + /// + /// This contents can be serialized to a struct using serde: + /// + /// ```rust + /// use serde::Deserialize; + /// use serde_json::json; + /// + /// #[derive(Debug, Deserialize)] + /// struct SomePackageMetadata { + /// some_value: i32, + /// } + /// + /// fn main() { + /// let value = json!({ + /// "some_value": 42, + /// }); + /// + /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap(); + /// assert_eq!(package_metadata.some_value, 42); + /// } + /// + /// ``` + #[serde(default, skip_serializing_if = "is_null")] + pub metadata: serde_json::Value, + /// The name of a native library the package is linking to. + pub links: Option<String>, + /// List of registries to which this package may be published. + /// + /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty. + /// + /// This is always `None` if running with a version of Cargo older than 1.39. + pub publish: Option<Vec<String>>, + /// The default binary to run by `cargo run`. + /// + /// This is always `None` if running with a version of Cargo older than 1.55. + pub default_run: Option<String>, + /// The minimum supported Rust version of this package. + /// + /// This is always `None` if running with a version of Cargo older than 1.58. + pub rust_version: Option<VersionReq>, +} + +impl Package { + /// Full path to the license file if one is present in the manifest + pub fn license_file(&self) -> Option<Utf8PathBuf> { + self.license_file.as_ref().map(|file| { + self.manifest_path + .parent() + .unwrap_or(&self.manifest_path) + .join(file) + }) + } + + /// Full path to the readme file if one is present in the manifest + pub fn readme(&self) -> Option<Utf8PathBuf> { + self.readme.as_ref().map(|file| { + self.manifest_path + .parent() + .unwrap_or(&self.manifest_path) + .join(file) + }) + } +} + +/// The source of a package such as crates.io. +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[serde(transparent)] +pub struct Source { + /// The underlying string representation of a source. + pub repr: String, +} + +impl Source { + /// Returns true if the source is crates.io. + pub fn is_crates_io(&self) -> bool { + self.repr == "registry+https://github.com/rust-lang/crates.io-index" + } +} + +impl std::fmt::Display for Source { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.repr, f) + } +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +#[non_exhaustive] +/// A single target (lib, bin, example, ...) provided by a crate +pub struct Target { + /// Name as given in the `Cargo.toml` or generated from the file name + pub name: String, + /// Kind of target ("bin", "example", "test", "bench", "lib", "custom-build") + pub kind: Vec<String>, + /// Almost the same as `kind`, except when an example is a library instead of an executable. + /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example` + #[serde(default)] + #[cfg_attr(feature = "builder", builder(default))] + pub crate_types: Vec<String>, + + #[serde(default)] + #[cfg_attr(feature = "builder", builder(default))] + #[serde(rename = "required-features")] + /// This target is built only if these features are enabled. + /// It doesn't apply to `lib` targets. + pub required_features: Vec<String>, + /// Path to the main source file of the target + pub src_path: Utf8PathBuf, + /// Rust edition for this target + #[serde(default)] + #[cfg_attr(feature = "builder", builder(default))] + pub edition: Edition, + /// Whether or not this target has doc tests enabled, and the target is + /// compatible with doc testing. + /// + /// This is always `true` if running with a version of Cargo older than 1.37. + #[serde(default = "default_true")] + #[cfg_attr(feature = "builder", builder(default = "true"))] + pub doctest: bool, + /// Whether or not this target is tested by default by `cargo test`. + /// + /// This is always `true` if running with a version of Cargo older than 1.47. + #[serde(default = "default_true")] + #[cfg_attr(feature = "builder", builder(default = "true"))] + pub test: bool, + /// Whether or not this target is documented by `cargo doc`. + /// + /// This is always `true` if running with a version of Cargo older than 1.50. + #[serde(default = "default_true")] + #[cfg_attr(feature = "builder", builder(default = "true"))] + pub doc: bool, +} + +impl Target { + fn is_kind(&self, name: &str) -> bool { + self.kind.iter().any(|kind| kind == name) + } + + /// Return true if this target is of kind "lib". + pub fn is_lib(&self) -> bool { + self.is_kind("lib") + } + + /// Return true if this target is of kind "bin". + pub fn is_bin(&self) -> bool { + self.is_kind("bin") + } + + /// Return true if this target is of kind "example". + pub fn is_example(&self) -> bool { + self.is_kind("example") + } + + /// Return true if this target is of kind "test". + pub fn is_test(&self) -> bool { + self.is_kind("test") + } + + /// Return true if this target is of kind "bench". + pub fn is_bench(&self) -> bool { + self.is_kind("bench") + } + + /// Return true if this target is of kind "custom-build". + pub fn is_custom_build(&self) -> bool { + self.is_kind("custom-build") + } +} + +/// The Rust edition +/// +/// As of writing this comment rust editions 2024, 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum Edition { + /// Edition 2015 + #[serde(rename = "2015")] + E2015, + /// Edition 2018 + #[serde(rename = "2018")] + E2018, + /// Edition 2021 + #[serde(rename = "2021")] + E2021, + #[doc(hidden)] + #[serde(rename = "2024")] + _E2024, + #[doc(hidden)] + #[serde(rename = "2027")] + _E2027, + #[doc(hidden)] + #[serde(rename = "2030")] + _E2030, +} + +impl Edition { + /// Return the string representation of the edition + pub fn as_str(&self) -> &'static str { + use Edition::*; + match self { + E2015 => "2015", + E2018 => "2018", + E2021 => "2021", + _E2024 => "2024", + _E2027 => "2027", + _E2030 => "2030", + } + } +} + +impl fmt::Display for Edition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl Default for Edition { + fn default() -> Self { + Self::E2015 + } +} + +fn default_true() -> bool { + true +} + +/// Cargo features flags +#[derive(Debug, Clone)] +pub enum CargoOpt { + /// Run cargo with `--features-all` + AllFeatures, + /// Run cargo with `--no-default-features` + NoDefaultFeatures, + /// Run cargo with `--features <FEATURES>` + SomeFeatures(Vec<String>), +} + +/// A builder for configurating `cargo metadata` invocation. +#[derive(Debug, Clone, Default)] +pub struct MetadataCommand { + /// Path to `cargo` executable. If not set, this will use the + /// the `$CARGO` environment variable, and if that is not set, will + /// simply be `cargo`. + cargo_path: Option<PathBuf>, + /// Path to `Cargo.toml` + manifest_path: Option<PathBuf>, + /// Current directory of the `cargo metadata` process. + current_dir: Option<PathBuf>, + /// Output information only about workspace members and don't fetch dependencies. + no_deps: bool, + /// Collections of `CargoOpt::SomeFeatures(..)` + features: Vec<String>, + /// Latched `CargoOpt::AllFeatures` + all_features: bool, + /// Latched `CargoOpt::NoDefaultFeatures` + no_default_features: bool, + /// Arbitrary command line flags to pass to `cargo`. These will be added + /// to the end of the command line invocation. + other_options: Vec<String>, + /// Arbitrary environment variables to set when running `cargo`. These will be merged into + /// the calling environment, overriding any which clash. + env: HashMap<OsString, OsString>, + /// Show stderr + verbose: bool, +} + +impl MetadataCommand { + /// Creates a default `cargo metadata` command, which will look for + /// `Cargo.toml` in the ancestors of the current directory. + pub fn new() -> MetadataCommand { + MetadataCommand::default() + } + /// Path to `cargo` executable. If not set, this will use the + /// the `$CARGO` environment variable, and if that is not set, will + /// simply be `cargo`. + pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand { + self.cargo_path = Some(path.into()); + self + } + /// Path to `Cargo.toml` + pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand { + self.manifest_path = Some(path.into()); + self + } + /// Current directory of the `cargo metadata` process. + pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand { + self.current_dir = Some(path.into()); + self + } + /// Output information only about workspace members and don't fetch dependencies. + pub fn no_deps(&mut self) -> &mut MetadataCommand { + self.no_deps = true; + self + } + /// Which features to include. + /// + /// Call this multiple times to specify advanced feature configurations: + /// + /// ```no_run + /// # use cargo_metadata::{CargoOpt, MetadataCommand}; + /// MetadataCommand::new() + /// .features(CargoOpt::NoDefaultFeatures) + /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()])) + /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()])) + /// // ... + /// # ; + /// ``` + /// + /// # Panics + /// + /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()` + /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`: + /// + /// ```should_panic + /// # use cargo_metadata::{CargoOpt, MetadataCommand}; + /// MetadataCommand::new() + /// .features(CargoOpt::NoDefaultFeatures) + /// .features(CargoOpt::NoDefaultFeatures) // <-- panic! + /// // ... + /// # ; + /// ``` + /// + /// The method also panics for multiple `CargoOpt::AllFeatures` arguments: + /// + /// ```should_panic + /// # use cargo_metadata::{CargoOpt, MetadataCommand}; + /// MetadataCommand::new() + /// .features(CargoOpt::AllFeatures) + /// .features(CargoOpt::AllFeatures) // <-- panic! + /// // ... + /// # ; + /// ``` + pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand { + match features { + CargoOpt::SomeFeatures(features) => self.features.extend(features), + CargoOpt::NoDefaultFeatures => { + assert!( + !self.no_default_features, + "Do not supply CargoOpt::NoDefaultFeatures more than once!" + ); + self.no_default_features = true; + } + CargoOpt::AllFeatures => { + assert!( + !self.all_features, + "Do not supply CargoOpt::AllFeatures more than once!" + ); + self.all_features = true; + } + } + self + } + /// Arbitrary command line flags to pass to `cargo`. These will be added + /// to the end of the command line invocation. + pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand { + self.other_options = options.into(); + self + } + + /// Arbitrary environment variables to set when running `cargo`. These will be merged into + /// the calling environment, overriding any which clash. + /// + /// Some examples of when you may want to use this: + /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set + /// `CARGO_NET_GIT_FETCH_WITH_CLI=true` + /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in + /// the way cargo expects by default. + /// + /// ```no_run + /// # use cargo_metadata::{CargoOpt, MetadataCommand}; + /// MetadataCommand::new() + /// .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true") + /// .env("RUSTC", "/path/to/rustc") + /// // ... + /// # ; + /// ``` + pub fn env<K: Into<OsString>, V: Into<OsString>>( + &mut self, + key: K, + val: V, + ) -> &mut MetadataCommand { + self.env.insert(key.into(), val.into()); + self + } + + /// Set whether to show stderr + pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand { + self.verbose = verbose; + self + } + + /// Builds a command for `cargo metadata`. This is the first + /// part of the work of `exec`. + pub fn cargo_command(&self) -> Command { + let cargo = self + .cargo_path + .clone() + .or_else(|| env::var("CARGO").map(PathBuf::from).ok()) + .unwrap_or_else(|| PathBuf::from("cargo")); + let mut cmd = Command::new(cargo); + cmd.args(&["metadata", "--format-version", "1"]); + + if self.no_deps { + cmd.arg("--no-deps"); + } + + if let Some(path) = self.current_dir.as_ref() { + cmd.current_dir(path); + } + + if !self.features.is_empty() { + cmd.arg("--features").arg(self.features.join(",")); + } + if self.all_features { + cmd.arg("--all-features"); + } + if self.no_default_features { + cmd.arg("--no-default-features"); + } + + if let Some(manifest_path) = &self.manifest_path { + cmd.arg("--manifest-path").arg(manifest_path.as_os_str()); + } + cmd.args(&self.other_options); + + cmd.envs(&self.env); + + cmd + } + + /// Parses `cargo metadata` output. `data` must have been + /// produced by a command built with `cargo_command`. + pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> { + let meta = serde_json::from_str(data.as_ref())?; + Ok(meta) + } + + /// Runs configured `cargo metadata` and returns parsed `Metadata`. + pub fn exec(&self) -> Result<Metadata> { + let mut command = self.cargo_command(); + if self.verbose { + command.stderr(Stdio::inherit()); + } + let output = command.output()?; + if !output.status.success() { + return Err(Error::CargoMetadata { + stderr: String::from_utf8(output.stderr)?, + }); + } + let stdout = from_utf8(&output.stdout)? + .lines() + .find(|line| line.starts_with('{')) + .ok_or(Error::NoJson)?; + Self::parse(stdout) + } +} diff --git a/third_party/rust/cargo_metadata/src/messages.rs b/third_party/rust/cargo_metadata/src/messages.rs new file mode 100644 index 0000000000..ea2abd2508 --- /dev/null +++ b/third_party/rust/cargo_metadata/src/messages.rs @@ -0,0 +1,175 @@ +use super::{Diagnostic, PackageId, Target}; +use camino::Utf8PathBuf; +#[cfg(feature = "builder")] +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::io::{self, BufRead, Read}; + +/// Profile settings used to determine which compiler flags to use for a +/// target. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct ArtifactProfile { + /// Optimization level. Possible values are 0-3, s or z. + pub opt_level: String, + /// The amount of debug info. 0 for none, 1 for limited, 2 for full + pub debuginfo: Option<u32>, + /// State of the `cfg(debug_assertions)` directive, enabling macros like + /// `debug_assert!` + pub debug_assertions: bool, + /// State of the overflow checks. + pub overflow_checks: bool, + /// Whether this profile is a test + pub test: bool, +} + +/// A compiler-generated file. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct Artifact { + /// The package this artifact belongs to + pub package_id: PackageId, + /// Path to the `Cargo.toml` file + #[serde(default)] + pub manifest_path: Utf8PathBuf, + /// The target this artifact was compiled for + pub target: Target, + /// The profile this artifact was compiled with + pub profile: ArtifactProfile, + /// The enabled features for this artifact + pub features: Vec<String>, + /// The full paths to the generated artifacts + /// (e.g. binary file and separate debug info) + pub filenames: Vec<Utf8PathBuf>, + /// Path to the executable file + pub executable: Option<Utf8PathBuf>, + /// If true, then the files were already generated + pub fresh: bool, +} + +/// Message left by the compiler +// TODO: Better name. This one comes from machine_message.rs +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct CompilerMessage { + /// The package this message belongs to + pub package_id: PackageId, + /// The target this message is aimed at + pub target: Target, + /// The message the compiler sent. + pub message: Diagnostic, +} + +/// Output of a build script execution. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct BuildScript { + /// The package this build script execution belongs to + pub package_id: PackageId, + /// The libs to link + pub linked_libs: Vec<Utf8PathBuf>, + /// The paths to search when resolving libs + pub linked_paths: Vec<Utf8PathBuf>, + /// Various `--cfg` flags to pass to the compiler + pub cfgs: Vec<String>, + /// The environment variables to add to the compilation + pub env: Vec<(String, String)>, + /// The `OUT_DIR` environment variable where this script places its output + /// + /// Added in Rust 1.41. + #[serde(default)] + pub out_dir: Utf8PathBuf, +} + +/// Final result of a build. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[non_exhaustive] +#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] +pub struct BuildFinished { + /// Whether or not the build finished successfully. + pub success: bool, +} + +/// A cargo message +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[non_exhaustive] +#[serde(tag = "reason", rename_all = "kebab-case")] +pub enum Message { + /// The compiler generated an artifact + CompilerArtifact(Artifact), + /// The compiler wants to display a message + CompilerMessage(CompilerMessage), + /// A build script successfully executed. + BuildScriptExecuted(BuildScript), + /// The build has finished. + /// + /// This is emitted at the end of the build as the last message. + /// Added in Rust 1.44. + BuildFinished(BuildFinished), + /// A line of text which isn't a cargo or compiler message. + /// Line separator is not included + #[serde(skip)] + TextLine(String), +} + +impl Message { + /// Creates an iterator of Message from a Read outputting a stream of JSON + /// messages. For usage information, look at the top-level documentation. + pub fn parse_stream<R: Read>(input: R) -> MessageIter<R> { + MessageIter { input } + } +} + +impl fmt::Display for CompilerMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +/// An iterator of Messages. +pub struct MessageIter<R> { + input: R, +} + +impl<R: BufRead> Iterator for MessageIter<R> { + type Item = io::Result<Message>; + fn next(&mut self) -> Option<Self::Item> { + let mut line = String::new(); + self.input + .read_line(&mut line) + .map(|n| { + if n == 0 { + None + } else { + if line.ends_with('\n') { + line.truncate(line.len() - 1); + } + let mut deserializer = serde_json::Deserializer::from_str(&line); + deserializer.disable_recursion_limit(); + Some(Message::deserialize(&mut deserializer).unwrap_or(Message::TextLine(line))) + } + }) + .transpose() + } +} + +/// An iterator of Message. +type MessageIterator<R> = + serde_json::StreamDeserializer<'static, serde_json::de::IoRead<R>, Message>; + +/// Creates an iterator of Message from a Read outputting a stream of JSON +/// messages. For usage information, look at the top-level documentation. +#[deprecated(note = "Use Message::parse_stream instead")] +pub fn parse_messages<R: Read>(input: R) -> MessageIterator<R> { + serde_json::Deserializer::from_reader(input).into_iter::<Message>() +} diff --git a/third_party/rust/cargo_metadata/tests/selftest.rs b/third_party/rust/cargo_metadata/tests/selftest.rs new file mode 100644 index 0000000000..d6ab61832d --- /dev/null +++ b/third_party/rust/cargo_metadata/tests/selftest.rs @@ -0,0 +1,163 @@ +use std::env::current_dir; +use std::path::PathBuf; + +use semver::Version; + +use cargo_metadata::{CargoOpt, Error, MetadataCommand}; +use serde::Deserialize; + +#[derive(Debug, PartialEq, Eq, Deserialize)] +struct TestPackageMetadata { + some_field: bool, + other_field: String, +} + +#[test] +fn metadata() { + let metadata = MetadataCommand::new().no_deps().exec().unwrap(); + + let this = &metadata.packages[0]; + assert_eq!(this.name, "cargo_metadata"); + assert_eq!(this.targets.len(), 3); + + let lib = this + .targets + .iter() + .find(|t| t.name == "cargo_metadata") + .unwrap(); + assert_eq!(lib.kind[0], "lib"); + assert_eq!(lib.crate_types[0], "lib"); + + let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); + assert_eq!(selftest.name, "selftest"); + assert_eq!(selftest.kind[0], "test"); + assert_eq!(selftest.crate_types[0], "bin"); + + let package_metadata = &metadata.packages[0] + .metadata + .as_object() + .expect("package.metadata must be a table."); + assert_eq!(package_metadata.len(), 1); + + let value = package_metadata.get("cargo_metadata_test").unwrap(); + let test_package_metadata: TestPackageMetadata = serde_json::from_value(value.clone()).unwrap(); + assert_eq!( + test_package_metadata, + TestPackageMetadata { + some_field: true, + other_field: "foo".into(), + } + ); +} + +#[test] +fn builder_interface() { + let _ = MetadataCommand::new() + .manifest_path("Cargo.toml") + .exec() + .unwrap(); + let _ = MetadataCommand::new() + .manifest_path(String::from("Cargo.toml")) + .exec() + .unwrap(); + let _ = MetadataCommand::new() + .manifest_path(PathBuf::from("Cargo.toml")) + .exec() + .unwrap(); + let _ = MetadataCommand::new() + .manifest_path("Cargo.toml") + .no_deps() + .exec() + .unwrap(); + let _ = MetadataCommand::new() + .manifest_path("Cargo.toml") + .features(CargoOpt::AllFeatures) + .exec() + .unwrap(); + let _ = MetadataCommand::new() + .manifest_path("Cargo.toml") + .current_dir(current_dir().unwrap()) + .exec() + .unwrap(); +} + +#[test] +fn error1() { + match MetadataCommand::new().manifest_path("foo").exec() { + Err(Error::CargoMetadata { stderr }) => assert_eq!( + stderr.trim(), + "error: the manifest-path must be a path to a Cargo.toml file" + ), + _ => unreachable!(), + } +} + +#[test] +fn error2() { + match MetadataCommand::new() + .manifest_path("foo/Cargo.toml") + .exec() + { + Err(Error::CargoMetadata { stderr }) => assert_eq!( + stderr.trim(), + "error: manifest path `foo/Cargo.toml` does not exist" + ), + _ => unreachable!(), + } +} + +#[test] +fn cargo_path() { + match MetadataCommand::new() + .cargo_path("this does not exist") + .exec() + { + Err(Error::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound), + _ => unreachable!(), + } +} + +#[test] +fn metadata_deps() { + std::env::set_var("CARGO_PROFILE", "3"); + let metadata = MetadataCommand::new() + .manifest_path("Cargo.toml") + .exec() + .unwrap(); + let this_id = metadata + .workspace_members + .first() + .expect("Did not find ourselves"); + let this = &metadata[this_id]; + + assert_eq!(this.name, "cargo_metadata"); + + let workspace_packages = metadata.workspace_packages(); + assert_eq!(workspace_packages.len(), 1); + assert_eq!(&workspace_packages[0].id, this_id); + + let lib = this + .targets + .iter() + .find(|t| t.name == "cargo_metadata") + .unwrap(); + assert_eq!(lib.kind[0], "lib"); + assert_eq!(lib.crate_types[0], "lib"); + + let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); + assert_eq!(selftest.name, "selftest"); + assert_eq!(selftest.kind[0], "test"); + assert_eq!(selftest.crate_types[0], "bin"); + + let dependencies = &this.dependencies; + + let serde = dependencies + .iter() + .find(|dep| dep.name == "serde") + .expect("Did not find serde dependency"); + + assert_eq!(serde.kind, cargo_metadata::DependencyKind::Normal); + assert!(!serde.req.matches(&Version::parse("1.0.0").unwrap())); + assert!(serde.req.matches(&Version::parse("1.99.99").unwrap())); + assert!(!serde.req.matches(&Version::parse("2.0.0").unwrap())); +} diff --git a/third_party/rust/cargo_metadata/tests/test_samples.rs b/third_party/rust/cargo_metadata/tests/test_samples.rs new file mode 100644 index 0000000000..3c747c595a --- /dev/null +++ b/third_party/rust/cargo_metadata/tests/test_samples.rs @@ -0,0 +1,647 @@ +extern crate cargo_metadata; +extern crate semver; +#[macro_use] +extern crate serde_json; + +use camino::Utf8PathBuf; +use cargo_metadata::{CargoOpt, DependencyKind, Edition, Metadata, MetadataCommand}; + +#[test] +fn old_minimal() { + // Output from oldest supported version (1.24). + // This intentionally has as many null fields as possible. + // 1.8 is when metadata was introduced. + // Older versions not supported because the following are required: + // - `workspace_members` added in 1.13 + // - `target_directory` added in 1.19 + // - `workspace_root` added in 1.24 + let json = r#" +{ + "packages": [ + { + "name": "foo", + "version": "0.1.0", + "id": "foo 0.1.0 (path+file:///foo)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "somedep", + "source": null, + "req": "^1.0", + "kind": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null + } + ], + "targets": [ + { + "kind": [ + "bin" + ], + "crate_types": [ + "bin" + ], + "name": "foo", + "src_path": "/foo/src/main.rs" + } + ], + "features": {}, + "manifest_path": "/foo/Cargo.toml" + } + ], + "workspace_members": [ + "foo 0.1.0 (path+file:///foo)" + ], + "resolve": null, + "target_directory": "/foo/target", + "version": 1, + "workspace_root": "/foo" +} +"#; + let meta: Metadata = serde_json::from_str(json).unwrap(); + assert_eq!(meta.packages.len(), 1); + let pkg = &meta.packages[0]; + assert_eq!(pkg.name, "foo"); + assert_eq!(pkg.version, semver::Version::parse("0.1.0").unwrap()); + assert_eq!(pkg.authors.len(), 0); + assert_eq!(pkg.id.to_string(), "foo 0.1.0 (path+file:///foo)"); + assert_eq!(pkg.description, None); + assert_eq!(pkg.license, None); + assert_eq!(pkg.license_file, None); + assert_eq!(pkg.default_run, None); + assert_eq!(pkg.rust_version, None); + assert_eq!(pkg.dependencies.len(), 1); + let dep = &pkg.dependencies[0]; + assert_eq!(dep.name, "somedep"); + assert_eq!(dep.source, None); + assert_eq!(dep.req, semver::VersionReq::parse("^1.0").unwrap()); + assert_eq!(dep.kind, DependencyKind::Normal); + assert!(!dep.optional); + assert!(dep.uses_default_features); + assert_eq!(dep.features.len(), 0); + assert!(dep.target.is_none()); + assert_eq!(dep.rename, None); + assert_eq!(dep.registry, None); + assert_eq!(pkg.targets.len(), 1); + let target = &pkg.targets[0]; + assert_eq!(target.name, "foo"); + assert_eq!(target.kind, vec!["bin"]); + assert_eq!(target.crate_types, vec!["bin"]); + assert_eq!(target.required_features.len(), 0); + assert_eq!(target.src_path, "/foo/src/main.rs"); + assert_eq!(target.edition, Edition::E2015); + assert!(target.doctest); + assert!(target.test); + assert!(target.doc); + assert_eq!(pkg.features.len(), 0); + assert_eq!(pkg.manifest_path, "/foo/Cargo.toml"); + assert_eq!(pkg.categories.len(), 0); + assert_eq!(pkg.keywords.len(), 0); + assert_eq!(pkg.readme, None); + assert_eq!(pkg.repository, None); + assert_eq!(pkg.homepage, None); + assert_eq!(pkg.documentation, None); + assert_eq!(pkg.edition, Edition::E2015); + assert_eq!(pkg.metadata, serde_json::Value::Null); + assert_eq!(pkg.links, None); + assert_eq!(pkg.publish, None); + assert_eq!(meta.workspace_members.len(), 1); + assert_eq!( + meta.workspace_members[0].to_string(), + "foo 0.1.0 (path+file:///foo)" + ); + assert!(meta.resolve.is_none()); + assert_eq!(meta.workspace_root, "/foo"); + assert_eq!(meta.workspace_metadata, serde_json::Value::Null); + assert_eq!(meta.target_directory, "/foo/target"); +} + +macro_rules! sorted { + ($e:expr) => {{ + let mut v = $e.clone(); + v.sort(); + v + }}; +} + +fn cargo_version() -> semver::Version { + let output = std::process::Command::new("cargo") + .arg("-V") + .output() + .expect("Failed to exec cargo."); + let out = std::str::from_utf8(&output.stdout) + .expect("invalid utf8") + .trim(); + let split: Vec<&str> = out.split_whitespace().collect(); + assert!(split.len() >= 2, "cargo -V output is unexpected: {}", out); + let mut ver = semver::Version::parse(split[1]).expect("cargo -V semver could not be parsed"); + // Don't care about metadata, it is awkward to compare. + ver.pre = semver::Prerelease::EMPTY; + ver.build = semver::BuildMetadata::EMPTY; + ver +} + +#[derive(serde::Deserialize, PartialEq, Eq, Debug)] +struct WorkspaceMetadata { + testobject: TestObject, +} + +#[derive(serde::Deserialize, PartialEq, Eq, Debug)] +struct TestObject { + myvalue: String, +} + +#[test] +fn all_the_fields() { + // All the fields currently generated as of 1.60. This tries to exercise as + // much as possible. + let ver = cargo_version(); + let minimum = semver::Version::parse("1.56.0").unwrap(); + if ver < minimum { + // edition added in 1.30 + // rename added in 1.31 + // links added in 1.33 + // doctest added in 1.37 + // publish added in 1.39 + // dep_kinds added in 1.41 + // test added in 1.47 + // homepage added in 1.49 + // documentation added in 1.49 + // doc added in 1.50 + // path added in 1.51 + // default_run added in 1.55 + // rust_version added in 1.58 + eprintln!("Skipping all_the_fields test, cargo {} is too old.", ver); + return; + } + let meta = MetadataCommand::new() + .manifest_path("tests/all/Cargo.toml") + .exec() + .unwrap(); + assert_eq!(meta.workspace_root.file_name().unwrap(), "all"); + assert_eq!( + serde_json::from_value::<WorkspaceMetadata>(meta.workspace_metadata).unwrap(), + WorkspaceMetadata { + testobject: TestObject { + myvalue: "abc".to_string() + } + } + ); + assert_eq!(meta.workspace_members.len(), 1); + assert!(meta.workspace_members[0].to_string().starts_with("all")); + + assert_eq!(meta.packages.len(), 9); + let all = meta.packages.iter().find(|p| p.name == "all").unwrap(); + assert_eq!(all.version, semver::Version::parse("0.1.0").unwrap()); + assert_eq!(all.authors, vec!["Jane Doe <user@example.com>"]); + assert!(all.id.to_string().starts_with("all")); + assert_eq!(all.description, Some("Package description.".to_string())); + assert_eq!(all.license, Some("MIT/Apache-2.0".to_string())); + assert_eq!(all.license_file, Some(Utf8PathBuf::from("LICENSE"))); + assert!(all.license_file().unwrap().ends_with("tests/all/LICENSE")); + assert_eq!(all.publish, Some(vec![])); + assert_eq!(all.links, Some("foo".to_string())); + assert_eq!(all.default_run, Some("otherbin".to_string())); + if ver >= semver::Version::parse("1.58.0").unwrap() { + assert_eq!( + all.rust_version, + Some(semver::VersionReq::parse("1.56").unwrap()) + ); + } + + assert_eq!(all.dependencies.len(), 8); + let bitflags = all + .dependencies + .iter() + .find(|d| d.name == "bitflags") + .unwrap(); + assert_eq!( + bitflags.source, + Some("registry+https://github.com/rust-lang/crates.io-index".to_string()) + ); + assert!(bitflags.optional); + assert_eq!(bitflags.req, semver::VersionReq::parse("^1.0").unwrap()); + + let path_dep = all + .dependencies + .iter() + .find(|d| d.name == "path-dep") + .unwrap(); + assert_eq!(path_dep.source, None); + assert_eq!(path_dep.kind, DependencyKind::Normal); + assert_eq!(path_dep.req, semver::VersionReq::parse("*").unwrap()); + assert_eq!( + path_dep.path.as_ref().map(|p| p.ends_with("path-dep")), + Some(true), + ); + + all.dependencies + .iter() + .find(|d| d.name == "namedep") + .unwrap(); + + let featdep = all + .dependencies + .iter() + .find(|d| d.name == "featdep") + .unwrap(); + assert_eq!(featdep.features, vec!["i128"]); + assert!(!featdep.uses_default_features); + + let renamed = all + .dependencies + .iter() + .find(|d| d.name == "oldname") + .unwrap(); + assert_eq!(renamed.rename, Some("newname".to_string())); + + let devdep = all + .dependencies + .iter() + .find(|d| d.name == "devdep") + .unwrap(); + assert_eq!(devdep.kind, DependencyKind::Development); + + let bdep = all.dependencies.iter().find(|d| d.name == "bdep").unwrap(); + assert_eq!(bdep.kind, DependencyKind::Build); + + let windep = all + .dependencies + .iter() + .find(|d| d.name == "windep") + .unwrap(); + assert_eq!( + windep.target.as_ref().map(|x| x.to_string()), + Some("cfg(windows)".to_string()) + ); + + macro_rules! get_file_name { + ($v:expr) => { + all.targets + .iter() + .find(|t| t.src_path.file_name().unwrap() == $v) + .unwrap() + }; + } + assert_eq!(all.targets.len(), 8); + let lib = get_file_name!("lib.rs"); + assert_eq!(lib.name, "all"); + assert_eq!(sorted!(lib.kind), vec!["cdylib", "rlib", "staticlib"]); + assert_eq!( + sorted!(lib.crate_types), + vec!["cdylib", "rlib", "staticlib"] + ); + assert_eq!(lib.required_features.len(), 0); + assert_eq!(lib.edition, Edition::E2018); + assert!(lib.doctest); + assert!(lib.test); + assert!(lib.doc); + + let main = get_file_name!("main.rs"); + assert_eq!(main.crate_types, vec!["bin"]); + assert_eq!(main.kind, vec!["bin"]); + assert!(!main.doctest); + assert!(main.test); + assert!(main.doc); + + let otherbin = get_file_name!("otherbin.rs"); + assert_eq!(otherbin.edition, Edition::E2015); + assert!(!otherbin.doc); + + let reqfeat = get_file_name!("reqfeat.rs"); + assert_eq!(reqfeat.required_features, vec!["feat2"]); + + let ex1 = get_file_name!("ex1.rs"); + assert_eq!(ex1.kind, vec!["example"]); + assert!(!ex1.test); + + let t1 = get_file_name!("t1.rs"); + assert_eq!(t1.kind, vec!["test"]); + + let b1 = get_file_name!("b1.rs"); + assert_eq!(b1.kind, vec!["bench"]); + + let build = get_file_name!("build.rs"); + assert_eq!(build.kind, vec!["custom-build"]); + + if ver >= semver::Version::parse("1.60.0").unwrap() { + // 1.60 now reports optional dependencies within the features table + assert_eq!(all.features.len(), 4); + assert_eq!(all.features["bitflags"], vec!["dep:bitflags"]); + } else { + assert_eq!(all.features.len(), 3); + } + assert_eq!(all.features["feat1"].len(), 0); + assert_eq!(all.features["feat2"].len(), 0); + assert_eq!(sorted!(all.features["default"]), vec!["bitflags", "feat1"]); + + assert!(all.manifest_path.ends_with("all/Cargo.toml")); + assert_eq!(all.categories, vec!["command-line-utilities"]); + assert_eq!(all.keywords, vec!["cli"]); + assert_eq!(all.readme, Some(Utf8PathBuf::from("README.md"))); + assert!(all.readme().unwrap().ends_with("tests/all/README.md")); + assert_eq!( + all.repository, + Some("https://github.com/oli-obk/cargo_metadata/".to_string()) + ); + assert_eq!( + all.homepage, + Some("https://github.com/oli-obk/cargo_metadata/".to_string()) + ); + assert_eq!( + all.documentation, + Some("https://docs.rs/cargo_metadata/".to_string()) + ); + assert_eq!(all.edition, Edition::E2018); + assert_eq!( + all.metadata, + json!({ + "docs": { + "rs": { + "all-features": true, + "default-target": "x86_64-unknown-linux-gnu", + "rustc-args": ["--example-rustc-arg"] + } + } + }) + ); + + let resolve = meta.resolve.as_ref().unwrap(); + assert!(resolve + .root + .as_ref() + .unwrap() + .to_string() + .starts_with("all")); + + assert_eq!(resolve.nodes.len(), 9); + let path_dep = resolve + .nodes + .iter() + .find(|n| n.id.to_string().starts_with("path-dep")) + .unwrap(); + assert_eq!(path_dep.deps.len(), 0); + assert_eq!(path_dep.dependencies.len(), 0); + assert_eq!(path_dep.features.len(), 0); + + let bitflags = resolve + .nodes + .iter() + .find(|n| n.id.to_string().starts_with("bitflags")) + .unwrap(); + assert_eq!(bitflags.features, vec!["default"]); + + let featdep = resolve + .nodes + .iter() + .find(|n| n.id.to_string().starts_with("featdep")) + .unwrap(); + assert_eq!(featdep.features, vec!["i128"]); + + let all = resolve + .nodes + .iter() + .find(|n| n.id.to_string().starts_with("all")) + .unwrap(); + assert_eq!(all.dependencies.len(), 8); + assert_eq!(all.deps.len(), 8); + let newname = all.deps.iter().find(|d| d.name == "newname").unwrap(); + assert!(newname.pkg.to_string().starts_with("oldname")); + // Note the underscore here. + let path_dep = all.deps.iter().find(|d| d.name == "path_dep").unwrap(); + assert!(path_dep.pkg.to_string().starts_with("path-dep")); + assert_eq!(path_dep.dep_kinds.len(), 1); + let kind = &path_dep.dep_kinds[0]; + assert_eq!(kind.kind, DependencyKind::Normal); + assert!(kind.target.is_none()); + + let namedep = all + .deps + .iter() + .find(|d| d.name == "different_name") + .unwrap(); + assert!(namedep.pkg.to_string().starts_with("namedep")); + assert_eq!(sorted!(all.features), vec!["bitflags", "default", "feat1"]); + + let bdep = all.deps.iter().find(|d| d.name == "bdep").unwrap(); + assert_eq!(bdep.dep_kinds.len(), 1); + let kind = &bdep.dep_kinds[0]; + assert_eq!(kind.kind, DependencyKind::Build); + assert!(kind.target.is_none()); + + let devdep = all.deps.iter().find(|d| d.name == "devdep").unwrap(); + assert_eq!(devdep.dep_kinds.len(), 1); + let kind = &devdep.dep_kinds[0]; + assert_eq!(kind.kind, DependencyKind::Development); + assert!(kind.target.is_none()); + + let windep = all.deps.iter().find(|d| d.name == "windep").unwrap(); + assert_eq!(windep.dep_kinds.len(), 1); + let kind = &windep.dep_kinds[0]; + assert_eq!(kind.kind, DependencyKind::Normal); + assert_eq!( + kind.target.as_ref().map(|x| x.to_string()), + Some("cfg(windows)".to_string()) + ); +} + +#[test] +fn alt_registry() { + // This is difficult to test (would need to set up a custom index). + // Just manually check the JSON is handled. + let json = r#" +{ + "packages": [ + { + "name": "alt", + "version": "0.1.0", + "id": "alt 0.1.0 (path+file:///alt)", + "source": null, + "dependencies": [ + { + "name": "alt2", + "source": "registry+https://example.com", + "req": "^0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": "https://example.com" + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "alt", + "src_path": "/alt/src/lib.rs", + "edition": "2018" + } + ], + "features": {}, + "manifest_path": "/alt/Cargo.toml", + "metadata": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2018", + "links": null + } + ], + "workspace_members": [ + "alt 0.1.0 (path+file:///alt)" + ], + "resolve": null, + "target_directory": "/alt/target", + "version": 1, + "workspace_root": "/alt" +} +"#; + let meta: Metadata = serde_json::from_str(json).unwrap(); + assert_eq!(meta.packages.len(), 1); + let alt = &meta.packages[0]; + let deps = &alt.dependencies; + assert_eq!(deps.len(), 1); + let dep = &deps[0]; + assert_eq!(dep.registry, Some("https://example.com".to_string())); +} + +#[test] +fn current_dir() { + let meta = MetadataCommand::new() + .current_dir("tests/all/namedep") + .exec() + .unwrap(); + let namedep = meta.packages.iter().find(|p| p.name == "namedep").unwrap(); + assert!(namedep.name.starts_with("namedep")); +} + +#[test] +fn parse_stream_is_robust() { + // Proc macros can print stuff to stdout, which naturally breaks JSON messages. + // Let's check that we don't die horribly in this case, and report an error. + let json_output = r##"{"reason":"compiler-artifact","package_id":"chatty 0.1.0 (path+file:///chatty-macro/chatty)","manifest_path":"chatty-macro/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"chatty","src_path":"/chatty-macro/chatty/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/deps/libchatty-f2adcff24cdf3bb2.so"],"executable":null,"fresh":false} +Evil proc macro was here! +{"reason":"compiler-artifact","package_id":"chatty-macro 0.1.0 (path+file:///chatty-macro)","manifest_path":"chatty-macro/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"chatty-macro","src_path":"/chatty-macro/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/libchatty_macro.rlib","/chatty-macro/target/debug/deps/libchatty_macro-cb5956ed52a11fb6.rmeta"],"executable":null,"fresh":false} +"##; + let mut n_messages = 0; + let mut text = String::new(); + for message in cargo_metadata::Message::parse_stream(json_output.as_bytes()) { + let message = message.unwrap(); + match message { + cargo_metadata::Message::TextLine(line) => text = line, + _ => n_messages += 1, + } + } + assert_eq!(n_messages, 2); + assert_eq!(text, "Evil proc macro was here!"); +} + +#[test] +fn advanced_feature_configuration() { + fn build_features<F: FnOnce(&mut MetadataCommand) -> &mut MetadataCommand>( + func: F, + ) -> Vec<String> { + let mut meta = MetadataCommand::new(); + let meta = meta.manifest_path("tests/all/Cargo.toml"); + + let meta = func(meta); + let meta = meta.exec().unwrap(); + + let resolve = meta.resolve.as_ref().unwrap(); + + let all = resolve + .nodes + .iter() + .find(|n| n.id.to_string().starts_with("all")) + .unwrap(); + + all.features.clone() + } + + // Default behavior; tested above + let default_features = build_features(|meta| meta); + assert_eq!( + sorted!(default_features), + vec!["bitflags", "default", "feat1"] + ); + + // Manually specify the same default features + let manual_features = build_features(|meta| { + meta.features(CargoOpt::NoDefaultFeatures) + .features(CargoOpt::SomeFeatures(vec![ + "feat1".into(), + "bitflags".into(), + ])) + }); + assert_eq!(sorted!(manual_features), vec!["bitflags", "feat1"]); + + // Multiple SomeFeatures is same as one longer SomeFeatures + let manual_features = build_features(|meta| { + meta.features(CargoOpt::NoDefaultFeatures) + .features(CargoOpt::SomeFeatures(vec!["feat1".into()])) + .features(CargoOpt::SomeFeatures(vec!["feat2".into()])) + }); + assert_eq!(sorted!(manual_features), vec!["feat1", "feat2"]); + + // No features + All features == All features + let all_features = build_features(|meta| { + meta.features(CargoOpt::AllFeatures) + .features(CargoOpt::NoDefaultFeatures) + }); + assert_eq!( + sorted!(all_features), + vec!["bitflags", "default", "feat1", "feat2"] + ); + + // The '--all-features' flag supersedes other feature flags + let all_flag_variants = build_features(|meta| { + meta.features(CargoOpt::SomeFeatures(vec!["feat2".into()])) + .features(CargoOpt::NoDefaultFeatures) + .features(CargoOpt::AllFeatures) + }); + assert_eq!(sorted!(all_flag_variants), sorted!(all_features)); +} + +#[test] +fn depkind_to_string() { + assert_eq!(DependencyKind::Normal.to_string(), "normal"); + assert_eq!(DependencyKind::Development.to_string(), "dev"); + assert_eq!(DependencyKind::Build.to_string(), "build"); + assert_eq!(DependencyKind::Unknown.to_string(), "Unknown"); +} + +#[test] +fn basic_workspace_root_package_exists() { + // First try with dependencies + let meta = MetadataCommand::new() + .manifest_path("tests/basic_workspace/Cargo.toml") + .exec() + .unwrap(); + assert_eq!(meta.root_package().unwrap().name, "ex_bin"); + // Now with no_deps, it should still work exactly the same + let meta = MetadataCommand::new() + .manifest_path("tests/basic_workspace/Cargo.toml") + .no_deps() + .exec() + .unwrap(); + assert_eq!( + meta.root_package() + .expect("workspace root still exists when no_deps used") + .name, + "ex_bin" + ); +} |