diff --git a/src/lib.rs b/src/lib.rs index 94a9fd4e..80068a9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,8 @@ pub use errors::{Error, Result}; #[allow(deprecated)] pub use messages::parse_messages; pub use messages::{ - Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter, + Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, + Message, MessageIter, }; #[cfg(feature = "builder")] pub use messages::{ diff --git a/src/messages.rs b/src/messages.rs index ea2abd25..028cd726 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -2,8 +2,8 @@ use super::{Diagnostic, PackageId, Target}; use camino::Utf8PathBuf; #[cfg(feature = "builder")] use derive_builder::Builder; -use serde::{Deserialize, Serialize}; -use std::fmt; +use serde::{de, ser, Deserialize, Serialize}; +use std::fmt::{self, Write}; use std::io::{self, BufRead, Read}; /// Profile settings used to determine which compiler flags to use for a @@ -15,8 +15,9 @@ use std::io::{self, BufRead, Read}; 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, + /// The kind of debug information. + #[serde(default)] + pub debuginfo: ArtifactDebuginfo, /// State of the `cfg(debug_assertions)` directive, enabling macros like /// `debug_assert!` pub debug_assertions: bool, @@ -26,6 +27,132 @@ pub struct ArtifactProfile { pub test: bool, } +/// The kind of debug information included in the artifact. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum ArtifactDebuginfo { + /// No debug information. + None, + /// Line directives only. + LineDirectivesOnly, + /// Line tables only. + LineTablesOnly, + /// Debug information without type or variable-level information. + Limited, + /// Full debug information. + Full, + /// An unknown integer level. + /// + /// This may be produced by a version of rustc in the future that has + /// additional levels represented by an integer that are not known by this + /// version of `cargo_metadata`. + UnknownInt(i64), + /// An unknown string level. + /// + /// This may be produced by a version of rustc in the future that has + /// additional levels represented by a string that are not known by this + /// version of `cargo_metadata`. + UnknownString(String), +} + +impl Default for ArtifactDebuginfo { + fn default() -> Self { + ArtifactDebuginfo::None + } +} + +impl ser::Serialize for ArtifactDebuginfo { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + match self { + Self::None => 0.serialize(serializer), + Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), + Self::LineTablesOnly => "line-tables-only".serialize(serializer), + Self::Limited => 1.serialize(serializer), + Self::Full => 2.serialize(serializer), + Self::UnknownInt(n) => n.serialize(serializer), + Self::UnknownString(s) => s.serialize(serializer), + } + } +} + +impl<'de> de::Deserialize<'de> for ArtifactDebuginfo { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = ArtifactDebuginfo; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an integer or string") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + let debuginfo = match value { + 0 => ArtifactDebuginfo::None, + 1 => ArtifactDebuginfo::Limited, + 2 => ArtifactDebuginfo::Full, + n => ArtifactDebuginfo::UnknownInt(n), + }; + Ok(debuginfo) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + self.visit_i64(value as i64) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let debuginfo = match value { + "none" => ArtifactDebuginfo::None, + "limited" => ArtifactDebuginfo::Limited, + "full" => ArtifactDebuginfo::Full, + "line-directives-only" => ArtifactDebuginfo::LineDirectivesOnly, + "line-tables-only" => ArtifactDebuginfo::LineTablesOnly, + s => ArtifactDebuginfo::UnknownString(s.to_string()), + }; + Ok(debuginfo) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(ArtifactDebuginfo::None) + } + } + + d.deserialize_any(Visitor) + } +} + +impl fmt::Display for ArtifactDebuginfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ArtifactDebuginfo::None => f.write_char('0'), + ArtifactDebuginfo::Limited => f.write_char('1'), + ArtifactDebuginfo::Full => f.write_char('2'), + ArtifactDebuginfo::LineDirectivesOnly => f.write_str("line-directives-only"), + ArtifactDebuginfo::LineTablesOnly => f.write_str("line-tables-only"), + ArtifactDebuginfo::UnknownInt(n) => write!(f, "{}", n), + ArtifactDebuginfo::UnknownString(s) => f.write_str(s), + } + } +} + /// A compiler-generated file. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[cfg_attr(feature = "builder", derive(Builder))] diff --git a/tests/test_samples.rs b/tests/test_samples.rs index 3c747c59..91d06d65 100644 --- a/tests/test_samples.rs +++ b/tests/test_samples.rs @@ -4,7 +4,9 @@ extern crate semver; extern crate serde_json; use camino::Utf8PathBuf; -use cargo_metadata::{CargoOpt, DependencyKind, Edition, Metadata, MetadataCommand}; +use cargo_metadata::{ + ArtifactDebuginfo, CargoOpt, DependencyKind, Edition, Message, Metadata, MetadataCommand, +}; #[test] fn old_minimal() { @@ -645,3 +647,46 @@ fn basic_workspace_root_package_exists() { "ex_bin" ); } + +#[test] +fn debuginfo_variants() { + // Checks behavior for the different debuginfo variants. + let variants = [ + ("0", ArtifactDebuginfo::None), + ("1", ArtifactDebuginfo::Limited), + ("2", ArtifactDebuginfo::Full), + ( + "\"line-directives-only\"", + ArtifactDebuginfo::LineDirectivesOnly, + ), + ("\"line-tables-only\"", ArtifactDebuginfo::LineTablesOnly), + ("3", ArtifactDebuginfo::UnknownInt(3)), + ( + "\"abc\"", + ArtifactDebuginfo::UnknownString("abc".to_string()), + ), + ("null", ArtifactDebuginfo::None), + ]; + for (value, expected) in variants { + let s = r#"{"reason":"compiler-artifact","package_id":"cargo_metadata 0.16.0 (path+file:////cargo_metadata)","manifest_path":"/cargo_metadata/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo_metadata","src_path":"/cargo_metadata/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":DEBUGINFO,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/cargo_metadata/target/debug/deps/libcargo_metadata-27f582f7187b9a2c.rmeta"],"executable":null,"fresh":false}"#; + let message: Message = serde_json::from_str(&s.replace("DEBUGINFO", value)).unwrap(); + match message { + Message::CompilerArtifact(artifact) => { + assert_eq!(artifact.profile.debuginfo, expected); + let de_s = serde_json::to_string(&artifact.profile.debuginfo).unwrap(); + // Note: Roundtrip does not retain null value. + if value == "null" { + assert_eq!(artifact.profile.debuginfo.to_string(), "0"); + assert_eq!(de_s, "0"); + } else { + assert_eq!( + artifact.profile.debuginfo.to_string(), + value.trim_matches('"') + ); + assert_eq!(de_s, value); + } + } + _ => panic!("unexpected {:?}", message), + } + } +}