@@ -8,11 +8,7 @@ use validation::RawVersionInfo;
88
99use serde:: { Deserialize , Serialize } ;
1010
11- #[ cfg( feature = "from_metadata" ) ]
12- use std:: convert:: TryFrom ;
1311use std:: str:: FromStr ;
14- #[ cfg( feature = "from_metadata" ) ]
15- use std:: { cmp:: min, cmp:: Ordering :: * , collections:: HashMap , error:: Error , fmt:: Display } ;
1612
1713/// Dependency tree embedded in the binary.
1814///
@@ -35,19 +31,6 @@ use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::D
3531///
3632/// If deserialization succeeds, it is guaranteed that there is only one root package,
3733/// and that are no cyclic dependencies.
38- ///
39- /// ## Optional features
40- ///
41- /// If the `from_metadata` feature is enabled, a conversion from
42- /// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html)
43- /// is possible via the `TryFrom` trait. This is the preferred way to construct this structure.
44- /// An example demonstrating that can be found
45- /// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/from-metadata.rs).
46- ///
47- /// If the `toml` feature is enabled, a conversion into the [`cargo_lock::Lockfile`](https://docs.rs/cargo-lock/)
48- /// struct is possible via the `TryFrom` trait. This can be useful if you need to interoperate with tooling
49- /// that consumes the `Cargo.lock` file format. An example demonstrating it can be found
50- /// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/json-to-toml.rs).
5134#[ derive( Serialize , Deserialize , Debug , PartialEq , Eq , PartialOrd , Ord , Clone ) ]
5235#[ serde( try_from = "RawVersionInfo" ) ]
5336#[ cfg_attr( feature = "schema" , derive( schemars:: JsonSchema ) ) ]
@@ -123,21 +106,6 @@ impl From<Source> for String {
123106 }
124107}
125108
126- #[ cfg( feature = "from_metadata" ) ]
127- impl From < & cargo_metadata:: Source > for Source {
128- fn from ( meta_source : & cargo_metadata:: Source ) -> Self {
129- match meta_source. repr . as_str ( ) {
130- "registry+https://github.com/rust-lang/crates.io-index" => Source :: CratesIo ,
131- source => Source :: from (
132- source
133- . split ( '+' )
134- . next ( )
135- . expect ( "Encoding of source strings in `cargo metadata` has changed!" ) ,
136- ) ,
137- }
138- }
139- }
140-
141109#[ derive( Serialize , Deserialize , Debug , PartialEq , Eq , PartialOrd , Ord , Copy , Clone , Default ) ]
142110#[ cfg_attr( feature = "schema" , derive( schemars:: JsonSchema ) ) ]
143111pub enum DependencyKind {
@@ -149,28 +117,6 @@ pub enum DependencyKind {
149117 Runtime ,
150118}
151119
152- /// The values are ordered from weakest to strongest so that casting to integer would make sense
153- #[ cfg( feature = "from_metadata" ) ]
154- #[ derive( Debug , PartialEq , Eq , PartialOrd , Ord , Copy , Clone ) ]
155- enum PrivateDepKind {
156- Development ,
157- Build ,
158- Runtime ,
159- }
160-
161- #[ cfg( feature = "from_metadata" ) ]
162- impl From < PrivateDepKind > for DependencyKind {
163- fn from ( priv_kind : PrivateDepKind ) -> Self {
164- match priv_kind {
165- PrivateDepKind :: Development => {
166- panic ! ( "Cannot convert development dependency to serializable format" )
167- }
168- PrivateDepKind :: Build => DependencyKind :: Build ,
169- PrivateDepKind :: Runtime => DependencyKind :: Runtime ,
170- }
171- }
172- }
173-
174120fn is_default < T : Default + PartialEq > ( value : & T ) -> bool {
175121 let default_value = T :: default ( ) ;
176122 value == & default_value
@@ -183,191 +129,6 @@ impl FromStr for VersionInfo {
183129 }
184130}
185131
186- #[ cfg( feature = "from_metadata" ) ]
187- impl From < & cargo_metadata:: DependencyKind > for PrivateDepKind {
188- fn from ( kind : & cargo_metadata:: DependencyKind ) -> Self {
189- match kind {
190- cargo_metadata:: DependencyKind :: Normal => PrivateDepKind :: Runtime ,
191- cargo_metadata:: DependencyKind :: Development => PrivateDepKind :: Development ,
192- cargo_metadata:: DependencyKind :: Build => PrivateDepKind :: Build ,
193- _ => panic ! ( "Unknown dependency kind" ) ,
194- }
195- }
196- }
197-
198- /// Error returned by the conversion from
199- /// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html)
200- #[ cfg( feature = "from_metadata" ) ]
201- #[ derive( Debug , Copy , Clone , Eq , PartialEq ) ]
202- pub enum InsufficientMetadata {
203- NoDeps ,
204- VirtualWorkspace ,
205- }
206-
207- #[ cfg( feature = "from_metadata" ) ]
208- impl Display for InsufficientMetadata {
209- fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
210- match self {
211- InsufficientMetadata :: NoDeps => {
212- write ! ( f, "Missing dependency information! Please call 'cargo metadata' without '--no-deps' flag." )
213- }
214- InsufficientMetadata :: VirtualWorkspace => {
215- write ! ( f, "Missing root crate! Please call this from a package directory, not workspace root." )
216- }
217- }
218- }
219- }
220-
221- #[ cfg( feature = "from_metadata" ) ]
222- impl Error for InsufficientMetadata { }
223-
224- #[ cfg( feature = "from_metadata" ) ]
225- impl TryFrom < & cargo_metadata:: Metadata > for VersionInfo {
226- type Error = InsufficientMetadata ;
227- fn try_from ( metadata : & cargo_metadata:: Metadata ) -> Result < Self , Self :: Error > {
228- let toplevel_crate_id = metadata
229- . resolve
230- . as_ref ( )
231- . ok_or ( InsufficientMetadata :: NoDeps ) ?
232- . root
233- . as_ref ( )
234- . ok_or ( InsufficientMetadata :: VirtualWorkspace ) ?
235- . repr
236- . as_str ( ) ;
237-
238- // Walk the dependency tree and resolve dependency kinds for each package.
239- // We need this because there may be several different paths to the same package
240- // and we need to aggregate dependency types across all of them.
241- // Moreover, `cargo metadata` doesn't propagate dependency information:
242- // A runtime dependency of a build dependency of your package should be recorded
243- // as *build* dependency, but Cargo flags it as a runtime dependency.
244- // Hoo boy, here I go hand-rolling BFS again!
245- let nodes = & metadata. resolve . as_ref ( ) . unwrap ( ) . nodes ;
246- let id_to_node: HashMap < & str , & cargo_metadata:: Node > =
247- nodes. iter ( ) . map ( |n| ( n. id . repr . as_str ( ) , n) ) . collect ( ) ;
248- let mut id_to_dep_kind: HashMap < & str , PrivateDepKind > = HashMap :: new ( ) ;
249- id_to_dep_kind. insert ( toplevel_crate_id, PrivateDepKind :: Runtime ) ;
250- let mut current_queue: Vec < & cargo_metadata:: Node > = vec ! [ id_to_node[ toplevel_crate_id] ] ;
251- let mut next_step_queue: Vec < & cargo_metadata:: Node > = Vec :: new ( ) ;
252- while !current_queue. is_empty ( ) {
253- for parent in current_queue. drain ( ..) {
254- let parent_dep_kind = id_to_dep_kind[ parent. id . repr . as_str ( ) ] ;
255- for child in & parent. deps {
256- let child_id = child. pkg . repr . as_str ( ) ;
257- let dep_kind = strongest_dep_kind ( child. dep_kinds . as_slice ( ) ) ;
258- let dep_kind = min ( dep_kind, parent_dep_kind) ;
259- let dep_kind_on_previous_visit = id_to_dep_kind. get ( child_id) ;
260- if dep_kind_on_previous_visit. is_none ( )
261- || & dep_kind > dep_kind_on_previous_visit. unwrap ( )
262- {
263- // if we haven't visited this node in dependency graph yet
264- // or if we've visited it with a weaker dependency type,
265- // records its new dependency type and add it to the queue to visit its dependencies
266- id_to_dep_kind. insert ( child_id, dep_kind) ;
267- next_step_queue. push ( id_to_node[ child_id] ) ;
268- }
269- }
270- }
271- std:: mem:: swap ( & mut next_step_queue, & mut current_queue) ;
272- }
273-
274- let metadata_package_dep_kind = |p : & cargo_metadata:: Package | {
275- let package_id = p. id . repr . as_str ( ) ;
276- id_to_dep_kind. get ( package_id)
277- } ;
278-
279- // Remove dev-only dependencies from the package list and collect them to Vec
280- let mut packages: Vec < & cargo_metadata:: Package > = metadata
281- . packages
282- . iter ( )
283- . filter ( |p| {
284- let dep_kind = metadata_package_dep_kind ( p) ;
285- // Dependencies that are present in the workspace but not used by the current root crate
286- // will not be in the map we've built by traversing the root crate's dependencies.
287- // In this case they will not be in the map at all. We skip them, along with dev-dependencies.
288- dep_kind. is_some ( ) && dep_kind. unwrap ( ) != & PrivateDepKind :: Development
289- } )
290- . collect ( ) ;
291-
292- // This function is the simplest place to introduce sorting, since
293- // it contains enough data to distinguish between equal-looking packages
294- // and provide a stable sorting that might not be possible
295- // using the data from VersionInfo struct alone.
296- //
297- // We use sort_unstable here because there is no point in
298- // not reordering equal elements, since they're supplied by
299- // in arbitrary order by cargo-metadata anyway
300- // and the order even varies between executions.
301- packages. sort_unstable_by ( |a, b| {
302- // This is a workaround for Package not implementing Ord.
303- // Deriving it in cargo_metadata might be more reliable?
304- let names_order = a. name . cmp ( & b. name ) ;
305- if names_order != Equal {
306- return names_order;
307- }
308- let versions_order = a. name . cmp ( & b. name ) ;
309- if versions_order != Equal {
310- return versions_order;
311- }
312- // IDs are unique so comparing them should be sufficient
313- a. id . repr . cmp ( & b. id . repr )
314- } ) ;
315-
316- // Build a mapping from package ID to the index of that package in the Vec
317- // because serializable representation doesn't store IDs
318- let mut id_to_index = HashMap :: new ( ) ;
319- for ( index, package) in packages. iter ( ) . enumerate ( ) {
320- id_to_index. insert ( package. id . repr . as_str ( ) , index) ;
321- }
322-
323- // Convert packages from cargo-metadata representation to our representation
324- let mut packages: Vec < Package > = packages
325- . into_iter ( )
326- . map ( |p| Package {
327- name : p. name . to_owned ( ) ,
328- version : p. version . clone ( ) ,
329- source : p. source . as_ref ( ) . map_or ( Source :: Local , Source :: from) ,
330- kind : ( * metadata_package_dep_kind ( p) . unwrap ( ) ) . into ( ) ,
331- dependencies : Vec :: new ( ) ,
332- root : p. id . repr == toplevel_crate_id,
333- } )
334- . collect ( ) ;
335-
336- // Fill in dependency info from resolved dependency graph
337- for node in metadata. resolve . as_ref ( ) . unwrap ( ) . nodes . iter ( ) {
338- let package_id = node. id . repr . as_str ( ) ;
339- if id_to_index. contains_key ( package_id) {
340- // dev-dependencies are not included
341- let package: & mut Package = & mut packages[ id_to_index[ package_id] ] ;
342- // Dependencies
343- for dep in node. deps . iter ( ) {
344- // Omit the graph edge if this is a development dependency
345- // to fix https://github.com/rustsec/rustsec/issues/1043
346- // It is possible that something that we depend on normally
347- // is also a dev-dependency for something,
348- // and dev-dependencies are allowed to have cycles,
349- // so we may end up encoding cyclic graph if we don't handle that.
350- let dep_id = dep. pkg . repr . as_str ( ) ;
351- if strongest_dep_kind ( & dep. dep_kinds ) != PrivateDepKind :: Development {
352- package. dependencies . push ( id_to_index[ dep_id] ) ;
353- }
354- }
355- // .sort_unstable() is fine because they're all integers
356- package. dependencies . sort_unstable ( ) ;
357- }
358- }
359- Ok ( VersionInfo { packages } )
360- }
361- }
362-
363- #[ cfg( feature = "from_metadata" ) ]
364- fn strongest_dep_kind ( deps : & [ cargo_metadata:: DepKindInfo ] ) -> PrivateDepKind {
365- deps. iter ( )
366- . map ( |d| PrivateDepKind :: from ( & d. kind ) )
367- . max ( )
368- . unwrap_or ( PrivateDepKind :: Runtime ) // for compatibility with Rust earlier than 1.41
369- }
370-
371132#[ cfg( test) ]
372133mod tests {
373134 #![ allow( unused_imports) ] // otherwise conditional compilation emits warnings
@@ -378,24 +139,6 @@ mod tests {
378139 path:: { Path , PathBuf } ,
379140 } ;
380141
381- #[ cfg( feature = "from_metadata" ) ]
382- fn load_metadata ( cargo_toml_path : & Path ) -> cargo_metadata:: Metadata {
383- let mut cmd = cargo_metadata:: MetadataCommand :: new ( ) ;
384- cmd. manifest_path ( cargo_toml_path) ;
385- cmd. exec ( ) . unwrap ( )
386- }
387-
388- #[ test]
389- #[ cfg( feature = "from_metadata" ) ]
390- fn dependency_cycle ( ) {
391- let cargo_toml_path = PathBuf :: from ( std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap ( ) )
392- . join ( "tests/fixtures/cargo-audit-dep-cycle/Cargo.toml" ) ;
393- let metadata = load_metadata ( & cargo_toml_path) ;
394- let version_info_struct: VersionInfo = ( & metadata) . try_into ( ) . unwrap ( ) ;
395- let json = serde_json:: to_string ( & version_info_struct) . unwrap ( ) ;
396- VersionInfo :: from_str ( & json) . unwrap ( ) ; // <- the part we care about succeeding
397- }
398-
399142 #[ cfg( feature = "schema" ) ]
400143 /// Generate a JsonSchema for VersionInfo
401144 fn generate_schema ( ) -> schemars:: schema:: RootSchema {
0 commit comments