11use cargo:: core:: dependency:: DepKind ;
2+ use cargo:: core:: PackageIdSpec ;
23use cargo:: core:: Workspace ;
34use cargo:: ops:: cargo_remove:: remove;
45use cargo:: ops:: cargo_remove:: RemoveOptions ;
56use cargo:: ops:: resolve_ws;
67use cargo:: util:: command_prelude:: * ;
78use cargo:: util:: print_available_packages;
9+ use cargo:: util:: toml_mut:: dependency:: Dependency ;
10+ use cargo:: util:: toml_mut:: dependency:: MaybeWorkspace ;
11+ use cargo:: util:: toml_mut:: dependency:: Source ;
812use cargo:: util:: toml_mut:: manifest:: DepTable ;
913use cargo:: util:: toml_mut:: manifest:: LocalManifest ;
1014use cargo:: CargoResult ;
@@ -86,7 +90,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
8690 . get_many :: < String > ( "dependencies" )
8791 . expect ( "required(true)" )
8892 . cloned ( )
89- . collect ( ) ;
93+ . collect :: < Vec < _ > > ( ) ;
9094
9195 let section = parse_section ( args) ;
9296
@@ -100,8 +104,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
100104 remove ( & options) ?;
101105
102106 if !dry_run {
103- // Clean up workspace dependencies
104- gc_workspace ( & workspace, & options . dependencies ) ?;
107+ // Clean up the workspace
108+ gc_workspace ( & workspace) ?;
105109
106110 // Reload the workspace since we've changed dependencies
107111 let ws = args. workspace ( config) ?;
@@ -133,49 +137,203 @@ fn parse_section(args: &ArgMatches) -> DepTable {
133137 table
134138}
135139
136- /// Clean up workspace dependencies which no longer have a reference to them.
137- fn gc_workspace ( workspace : & Workspace < ' _ > , dependencies : & [ String ] ) -> CargoResult < ( ) > {
140+ /// Clean up the workspace.dependencies, profile, patch, and replace sections of the root manifest
141+ /// by removing dependencies which no longer have a reference to them.
142+ fn gc_workspace ( workspace : & Workspace < ' _ > ) -> CargoResult < ( ) > {
138143 let mut manifest: toml_edit:: Document =
139144 cargo_util:: paths:: read ( workspace. root_manifest ( ) ) ?. parse ( ) ?;
145+ let mut is_modified = true ;
140146
141147 let members = workspace
142148 . members ( )
143149 . map ( |p| LocalManifest :: try_new ( p. manifest_path ( ) ) )
144150 . collect :: < CargoResult < Vec < _ > > > ( ) ?;
145151
146- for dep in dependencies {
147- if !dep_in_workspace ( dep, & members) {
148- remove_workspace_dep ( dep, & mut manifest) ;
152+ let mut dependencies = members
153+ . iter ( )
154+ . flat_map ( |manifest| {
155+ manifest. get_sections ( ) . into_iter ( ) . flat_map ( |( _, table) | {
156+ table
157+ . as_table_like ( )
158+ . unwrap ( )
159+ . iter ( )
160+ . map ( |( key, item) | Dependency :: from_toml ( & manifest. path , key, item) )
161+ . collect :: < Vec < _ > > ( )
162+ } )
163+ } )
164+ . collect :: < CargoResult < Vec < _ > > > ( ) ?;
165+
166+ // Clean up the workspace.dependencies section and replace instances of
167+ // workspace dependencies with their definitions
168+ if let Some ( toml_edit:: Item :: Table ( deps_table) ) = manifest
169+ . get_mut ( "workspace" )
170+ . and_then ( |t| t. get_mut ( "dependencies" ) )
171+ {
172+ deps_table. set_implicit ( true ) ;
173+ for ( key, item) in deps_table. iter_mut ( ) {
174+ let ws_dep = Dependency :: from_toml ( & workspace. root ( ) , key. get ( ) , item) ?;
175+
176+ // search for uses of this workspace dependency
177+ let mut is_used = false ;
178+ for dep in dependencies. iter_mut ( ) . filter ( |d| {
179+ d. toml_key ( ) == key. get ( ) && matches ! ( d. source( ) , Some ( Source :: Workspace ( _) ) )
180+ } ) {
181+ // replace the reference with the definition
182+ * dep = ws_dep. clone ( ) ;
183+
184+ is_used = true ;
185+ }
186+
187+ if !is_used {
188+ * item = toml_edit:: Item :: None ;
189+ is_modified = true ;
190+ }
149191 }
150192 }
151193
152- cargo_util:: paths:: write ( workspace. root_manifest ( ) , manifest. to_string ( ) . as_bytes ( ) ) ?;
194+ // Clean up the profile section
195+ //
196+ // Example tables:
197+ // - profile.dev.package.foo
198+ // - profile.release.package."*"
199+ // - profile.release.package."foo:2.1.0"
200+ if let Some ( toml_edit:: Item :: Table ( profile_section_table) ) = manifest. get_mut ( "profile" ) {
201+ profile_section_table. set_implicit ( true ) ;
202+
203+ for ( _, item) in profile_section_table. iter_mut ( ) {
204+ if let toml_edit:: Item :: Table ( profile_table) = item {
205+ profile_table. set_implicit ( true ) ;
206+
207+ if let Some ( toml_edit:: Item :: Table ( package_table) ) =
208+ profile_table. get_mut ( "package" )
209+ {
210+ package_table. set_implicit ( true ) ;
211+
212+ for ( key, item) in package_table. iter_mut ( ) {
213+ if !spec_has_match (
214+ & PackageIdSpec :: parse ( key. get ( ) ) ?,
215+ & dependencies,
216+ workspace. config ( ) ,
217+ ) ? {
218+ * item = toml_edit:: Item :: None ;
219+ is_modified = true ;
220+ }
221+ }
222+ }
223+ }
224+ }
225+ }
226+
227+ // Clean up the patch section
228+ if let Some ( toml_edit:: Item :: Table ( patch_section_table) ) = manifest. get_mut ( "patch" ) {
229+ patch_section_table. set_implicit ( true ) ;
230+
231+ // The key in each of the subtables is a source (either a registry or a URL)
232+ for ( source, item) in patch_section_table. iter_mut ( ) {
233+ if let toml_edit:: Item :: Table ( patch_table) = item {
234+ patch_table. set_implicit ( true ) ;
235+
236+ for ( key, item) in patch_table. iter_mut ( ) {
237+ let package_name =
238+ Dependency :: from_toml ( & workspace. root_manifest ( ) , key. get ( ) , item) ?. name ;
239+ if !source_has_match (
240+ & package_name,
241+ source. get ( ) ,
242+ & dependencies,
243+ workspace. config ( ) ,
244+ ) ? {
245+ * item = toml_edit:: Item :: None ;
246+ }
247+ }
248+ }
249+ }
250+ }
251+
252+ // Clean up the replace section
253+ if let Some ( toml_edit:: Item :: Table ( table) ) = manifest. get_mut ( "replace" ) {
254+ table. set_implicit ( true ) ;
255+
256+ for ( key, item) in table. iter_mut ( ) {
257+ if !spec_has_match (
258+ & PackageIdSpec :: parse ( key. get ( ) ) ?,
259+ & dependencies,
260+ workspace. config ( ) ,
261+ ) ? {
262+ * item = toml_edit:: Item :: None ;
263+ is_modified = true ;
264+ }
265+ }
266+ }
267+
268+ if is_modified {
269+ cargo_util:: paths:: write ( workspace. root_manifest ( ) , manifest. to_string ( ) . as_bytes ( ) ) ?;
270+ }
153271
154272 Ok ( ( ) )
155273}
156274
157- /// Get whether or not a dependency is depended upon in a workspace.
158- fn dep_in_workspace ( dep : & str , members : & [ LocalManifest ] ) -> bool {
159- members. iter ( ) . any ( |manifest| {
160- manifest. get_sections ( ) . iter ( ) . any ( |( _, table) | {
161- table
162- . as_table_like ( )
163- . unwrap ( )
164- . get ( dep)
165- . and_then ( |t| t. get ( "workspace" ) )
166- . and_then ( |v| v. as_bool ( ) )
167- . unwrap_or ( false )
168- } )
169- } )
275+ /// Check whether or not a package ID spec matches any non-workspace dependencies.
276+ fn spec_has_match (
277+ spec : & PackageIdSpec ,
278+ dependencies : & [ Dependency ] ,
279+ config : & Config ,
280+ ) -> CargoResult < bool > {
281+ for dep in dependencies {
282+ if spec. name ( ) . as_str ( ) != & dep. name {
283+ continue ;
284+ }
285+
286+ let version_matches = match ( spec. version ( ) , dep. version ( ) ) {
287+ ( Some ( v) , Some ( vq) ) => semver:: VersionReq :: parse ( vq) ?. matches ( v) ,
288+ ( Some ( _) , None ) => false ,
289+ ( None , None | Some ( _) ) => true ,
290+ } ;
291+ if !version_matches {
292+ continue ;
293+ }
294+
295+ match dep. source_id ( config) ? {
296+ MaybeWorkspace :: Other ( source_id) => {
297+ if spec. url ( ) . map ( |u| u == source_id. url ( ) ) . unwrap_or ( true ) {
298+ return Ok ( true ) ;
299+ }
300+ }
301+ MaybeWorkspace :: Workspace ( _) => { }
302+ }
303+ }
304+
305+ Ok ( false )
170306}
171307
172- /// Remove a dependency from a workspace manifest.
173- fn remove_workspace_dep ( dep : & str , ws_manifest : & mut toml_edit:: Document ) {
174- if let Some ( toml_edit:: Item :: Table ( table) ) = ws_manifest
175- . get_mut ( "workspace" )
176- . and_then ( |t| t. get_mut ( "dependencies" ) )
177- {
178- table. set_implicit ( true ) ;
179- table. remove ( dep) ;
308+ /// Check whether or not a source (URL or registry name) matches any non-workspace dependencies.
309+ fn source_has_match (
310+ name : & str ,
311+ source : & str ,
312+ dependencies : & [ Dependency ] ,
313+ config : & Config ,
314+ ) -> CargoResult < bool > {
315+ for dep in dependencies {
316+ if & dep. name != name {
317+ continue ;
318+ }
319+
320+ match dep. source_id ( config) ? {
321+ MaybeWorkspace :: Other ( source_id) => {
322+ if source_id. is_registry ( ) {
323+ if source_id. display_registry_name ( ) == source
324+ || source_id. url ( ) . as_str ( ) == source
325+ {
326+ return Ok ( true ) ;
327+ }
328+ } else if source_id. is_git ( ) {
329+ if source_id. url ( ) . as_str ( ) == source {
330+ return Ok ( true ) ;
331+ }
332+ }
333+ }
334+ MaybeWorkspace :: Workspace ( _) => { }
335+ }
180336 }
337+
338+ Ok ( false )
181339}
0 commit comments