1
1
import abc
2
+ import enum
2
3
import functools
3
4
import itertools
4
5
import json
5
6
import pathlib
6
7
import shutil
7
8
import subprocess
9
+ from collections import defaultdict
10
+ from enum import Enum
8
11
from logging import getLogger
9
12
from subprocess import PIPE
10
13
from typing import *
@@ -69,23 +72,33 @@ def _list_dependencies_by_crate(path: pathlib.Path, *, basedir: pathlib.Path, ca
69
72
70
73
packages_by_id = {p ['id' ]: p for p in metadata ['packages' ]}
71
74
75
+ class DependencyNamespace (Enum ):
76
+ NORMAL_DEVELOPMENT = enum .auto ()
77
+ BUILD = enum .auto ()
78
+
79
+ @classmethod
80
+ def from_dep_kind (cls , kind : str ):
81
+ if kind == 'build' :
82
+ return cls .BUILD
83
+ return cls .NORMAL_DEVELOPMENT
84
+
72
85
# Collect the `(|dev-|build-)dependencies` into a <is a `build-dependency`> → (<"extern crate name"> → <package>) dictionary.
73
- dependencies : Dict [ bool , Dict [str , Dict [str , Any ]]] = { False : {}, True : {}}
86
+ dependencies : DefaultDict [ DependencyNamespace , Dict [str , Dict [str , Any ]]] = defaultdict ( dict )
74
87
for dep in next (n ['deps' ] for n in metadata ['resolve' ]['nodes' ] if n ['id' ] == main_package ['id' ]):
75
- if any (k ['kind' ] == ['build' ] for k in dep ['dep_kinds' ]):
76
- dependencies [True ][dep ['name' ]] = packages_by_id [dep ['pkg' ]]
77
88
if _need_dev_deps (main_target ) or any (k ['kind' ] is None for k in dep ['dep_kinds' ]):
78
- dependencies [False ][dep ['name' ]] = packages_by_id [dep ['pkg' ]]
89
+ dependencies [DependencyNamespace .NORMAL_DEVELOPMENT ][dep ['name' ]] = packages_by_id [dep ['pkg' ]]
90
+ if any (k ['kind' ] == ['build' ] for k in dep ['dep_kinds' ]):
91
+ dependencies [DependencyNamespace .BUILD ][dep ['name' ]] = packages_by_id [dep ['pkg' ]]
79
92
80
93
# Decides whether to include `main_package` itself.
81
94
# Note that cargo-udeps does not detect it if it is unused.
82
95
# https://github.com/est31/cargo-udeps/pull/35
83
96
depends_on_main_package_itself = not _is_lib_or_proc_macro (main_target ) and any (map (_is_lib_or_proc_macro , main_package ['targets' ]))
84
97
85
98
# If `cargo_udeps_toolchain` is present, collects packages that are "unused" by `target`.
86
- unused_packages : Dict [ bool , Set [ str ]] = { False : set (), True : set ()}
99
+ unused_packages = defaultdict ( set )
87
100
if cargo_udeps_toolchain is not None :
88
- explicit_names_in_toml = {(d ['kind' ] == 'build' , d ['rename' ]) for d in main_package ['dependencies' ] if d ['rename' ]}
101
+ explicit_names_in_toml = {(DependencyNamespace . from_dep_kind ( d ['kind' ]) , d ['rename' ]) for d in main_package ['dependencies' ] if d ['rename' ]}
89
102
if not shutil .which ('cargo-udeps' ):
90
103
raise RuntimeError ('`cargo-udeps` not in $PATH' )
91
104
unused_deps = json .loads (subprocess .run (
@@ -96,23 +109,25 @@ def _list_dependencies_by_crate(path: pathlib.Path, *, basedir: pathlib.Path, ca
96
109
).stdout .decode ())['unused_deps' ].values ()
97
110
unused_dep = next ((u for u in unused_deps if u ['manifest_path' ] == main_package ['manifest_path' ]), None )
98
111
if unused_dep :
99
- for is_build , name_in_toml in [* [(False , n ) for n in [* unused_dep ['normal' ], * unused_dep ['development' ]]], * [(True , n ) for n in unused_dep ['build' ]]]:
100
- if (is_build , name_in_toml ) in explicit_names_in_toml :
112
+ names_in_toml = [(DependencyNamespace .NORMAL_DEVELOPMENT , name_in_toml ) for name_in_toml in [* unused_dep ['normal' ], * unused_dep ['development' ]]]
113
+ names_in_toml .extend ((DependencyNamespace .BUILD , name_in_toml ) for name_in_toml in unused_dep ['build' ])
114
+ for dependency_namespace , name_in_toml in names_in_toml :
115
+ if (dependency_namespace , name_in_toml ) in explicit_names_in_toml :
101
116
# If the `name_in_toml` is explicitly renamed one, it equals to the `extern_crate_name`.
102
- unused_packages [is_build ].add (dependencies [is_build ][name_in_toml ]['id' ])
117
+ unused_packages [dependency_namespace ].add (dependencies [dependency_namespace ][name_in_toml ]['id' ])
103
118
else :
104
119
# Otherwise, it equals to the `package.name`.
105
- unused_packages [is_build ].add (next (p ['id' ] for p in dependencies [is_build ].values () if p ['name' ] == name_in_toml ))
120
+ unused_packages [dependency_namespace ].add (next (p ['id' ] for p in dependencies [dependency_namespace ].values () if p ['name' ] == name_in_toml ))
106
121
107
122
# Finally, adds source files related to the depended crates except:
108
123
#
109
124
# - those detected by cargo-udeps
110
125
# - those come from Crates.io or Git repositories (e.g. `proconio`, other people's libraries including `ac-library-rs`)
111
126
ret = common_result
112
- depended_packages = [(False , main_package )] if depends_on_main_package_itself else []
113
- depended_packages .extend ((is_build , package ) for is_build , dependencies in dependencies .items () for package in dependencies .values ())
114
- for is_build , depended_package in depended_packages :
115
- if depended_package ['id' ] in unused_packages [is_build ] or depended_package ['source' ]:
127
+ depended_packages = [(DependencyNamespace . NORMAL_DEVELOPMENT , main_package )] if depends_on_main_package_itself else []
128
+ depended_packages .extend ((dependency_namespace , package ) for dependency_namespace , dependencies in dependencies .items () for package in dependencies .values ())
129
+ for dependency_namespace , depended_package in depended_packages :
130
+ if depended_package ['id' ] in unused_packages [dependency_namespace ] or depended_package ['source' ]:
116
131
continue
117
132
depended_target = next (filter (_is_lib_or_proc_macro , depended_package ['targets' ]), None )
118
133
if depended_target :
0 commit comments