1
1
use cargo:: core:: dependency:: DepKind ;
2
+ use cargo:: core:: PackageIdSpec ;
2
3
use cargo:: core:: Workspace ;
3
4
use cargo:: ops:: cargo_remove:: remove;
4
5
use cargo:: ops:: cargo_remove:: RemoveOptions ;
5
6
use cargo:: ops:: resolve_ws;
6
7
use cargo:: util:: command_prelude:: * ;
7
8
use 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 ;
8
12
use cargo:: util:: toml_mut:: manifest:: DepTable ;
9
13
use cargo:: util:: toml_mut:: manifest:: LocalManifest ;
10
14
use cargo:: CargoResult ;
@@ -86,7 +90,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
86
90
. get_many :: < String > ( "dependencies" )
87
91
. expect ( "required(true)" )
88
92
. cloned ( )
89
- . collect ( ) ;
93
+ . collect :: < Vec < _ > > ( ) ;
90
94
91
95
let section = parse_section ( args) ;
92
96
@@ -100,8 +104,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
100
104
remove ( & options) ?;
101
105
102
106
if !dry_run {
103
- // Clean up workspace dependencies
104
- gc_workspace ( & workspace, & options . dependencies ) ?;
107
+ // Clean up the workspace
108
+ gc_workspace ( & workspace) ?;
105
109
106
110
// Reload the workspace since we've changed dependencies
107
111
let ws = args. workspace ( config) ?;
@@ -133,49 +137,208 @@ fn parse_section(args: &ArgMatches) -> DepTable {
133
137
table
134
138
}
135
139
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 < ( ) > {
138
143
let mut manifest: toml_edit:: Document =
139
144
cargo_util:: paths:: read ( workspace. root_manifest ( ) ) ?. parse ( ) ?;
145
+ let mut is_modified = true ;
140
146
141
147
let members = workspace
142
148
. members ( )
143
149
. map ( |p| LocalManifest :: try_new ( p. manifest_path ( ) ) )
144
150
. collect :: < CargoResult < Vec < _ > > > ( ) ?;
145
151
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
+ // HACK: Replace workspace references in `dependencies` to simplify later GC steps:
182
+ // 1. Avoid having to look it up again to determine the dependency source / spec
183
+ // 2. The entry might get deleted, preventing us from looking it up again
184
+ //
185
+ // This does lose extra information, like features enabled, but that shouldn't be a
186
+ // problem for GC
187
+ * dep = ws_dep. clone ( ) ;
188
+
189
+ is_used = true ;
190
+ }
191
+
192
+ if !is_used {
193
+ * item = toml_edit:: Item :: None ;
194
+ is_modified = true ;
195
+ }
149
196
}
150
197
}
151
198
152
- cargo_util:: paths:: write ( workspace. root_manifest ( ) , manifest. to_string ( ) . as_bytes ( ) ) ?;
199
+ // Clean up the profile section
200
+ //
201
+ // Example tables:
202
+ // - profile.dev.package.foo
203
+ // - profile.release.package."*"
204
+ // - profile.release.package."foo:2.1.0"
205
+ if let Some ( toml_edit:: Item :: Table ( profile_section_table) ) = manifest. get_mut ( "profile" ) {
206
+ profile_section_table. set_implicit ( true ) ;
207
+
208
+ for ( _, item) in profile_section_table. iter_mut ( ) {
209
+ if let toml_edit:: Item :: Table ( profile_table) = item {
210
+ profile_table. set_implicit ( true ) ;
211
+
212
+ if let Some ( toml_edit:: Item :: Table ( package_table) ) =
213
+ profile_table. get_mut ( "package" )
214
+ {
215
+ package_table. set_implicit ( true ) ;
216
+
217
+ for ( key, item) in package_table. iter_mut ( ) {
218
+ if !spec_has_match (
219
+ & PackageIdSpec :: parse ( key. get ( ) ) ?,
220
+ & dependencies,
221
+ workspace. config ( ) ,
222
+ ) ? {
223
+ * item = toml_edit:: Item :: None ;
224
+ is_modified = true ;
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ // Clean up the patch section
233
+ if let Some ( toml_edit:: Item :: Table ( patch_section_table) ) = manifest. get_mut ( "patch" ) {
234
+ patch_section_table. set_implicit ( true ) ;
235
+
236
+ // The key in each of the subtables is a source (either a registry or a URL)
237
+ for ( source, item) in patch_section_table. iter_mut ( ) {
238
+ if let toml_edit:: Item :: Table ( patch_table) = item {
239
+ patch_table. set_implicit ( true ) ;
240
+
241
+ for ( key, item) in patch_table. iter_mut ( ) {
242
+ let package_name =
243
+ Dependency :: from_toml ( & workspace. root_manifest ( ) , key. get ( ) , item) ?. name ;
244
+ if !source_has_match (
245
+ & package_name,
246
+ source. get ( ) ,
247
+ & dependencies,
248
+ workspace. config ( ) ,
249
+ ) ? {
250
+ * item = toml_edit:: Item :: None ;
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // Clean up the replace section
258
+ if let Some ( toml_edit:: Item :: Table ( table) ) = manifest. get_mut ( "replace" ) {
259
+ table. set_implicit ( true ) ;
260
+
261
+ for ( key, item) in table. iter_mut ( ) {
262
+ if !spec_has_match (
263
+ & PackageIdSpec :: parse ( key. get ( ) ) ?,
264
+ & dependencies,
265
+ workspace. config ( ) ,
266
+ ) ? {
267
+ * item = toml_edit:: Item :: None ;
268
+ is_modified = true ;
269
+ }
270
+ }
271
+ }
272
+
273
+ if is_modified {
274
+ cargo_util:: paths:: write ( workspace. root_manifest ( ) , manifest. to_string ( ) . as_bytes ( ) ) ?;
275
+ }
153
276
154
277
Ok ( ( ) )
155
278
}
156
279
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
- } )
280
+ /// Check whether or not a package ID spec matches any non-workspace dependencies.
281
+ fn spec_has_match (
282
+ spec : & PackageIdSpec ,
283
+ dependencies : & [ Dependency ] ,
284
+ config : & Config ,
285
+ ) -> CargoResult < bool > {
286
+ for dep in dependencies {
287
+ if spec. name ( ) . as_str ( ) != & dep. name {
288
+ continue ;
289
+ }
290
+
291
+ let version_matches = match ( spec. version ( ) , dep. version ( ) ) {
292
+ ( Some ( v) , Some ( vq) ) => semver:: VersionReq :: parse ( vq) ?. matches ( v) ,
293
+ ( Some ( _) , None ) => false ,
294
+ ( None , None | Some ( _) ) => true ,
295
+ } ;
296
+ if !version_matches {
297
+ continue ;
298
+ }
299
+
300
+ match dep. source_id ( config) ? {
301
+ MaybeWorkspace :: Other ( source_id) => {
302
+ if spec. url ( ) . map ( |u| u == source_id. url ( ) ) . unwrap_or ( true ) {
303
+ return Ok ( true ) ;
304
+ }
305
+ }
306
+ MaybeWorkspace :: Workspace ( _) => { }
307
+ }
308
+ }
309
+
310
+ Ok ( false )
170
311
}
171
312
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) ;
313
+ /// Check whether or not a source (URL or registry name) matches any non-workspace dependencies.
314
+ fn source_has_match (
315
+ name : & str ,
316
+ source : & str ,
317
+ dependencies : & [ Dependency ] ,
318
+ config : & Config ,
319
+ ) -> CargoResult < bool > {
320
+ for dep in dependencies {
321
+ if & dep. name != name {
322
+ continue ;
323
+ }
324
+
325
+ match dep. source_id ( config) ? {
326
+ MaybeWorkspace :: Other ( source_id) => {
327
+ if source_id. is_registry ( ) {
328
+ if source_id. display_registry_name ( ) == source
329
+ || source_id. url ( ) . as_str ( ) == source
330
+ {
331
+ return Ok ( true ) ;
332
+ }
333
+ } else if source_id. is_git ( ) {
334
+ if source_id. url ( ) . as_str ( ) == source {
335
+ return Ok ( true ) ;
336
+ }
337
+ }
338
+ }
339
+ MaybeWorkspace :: Workspace ( _) => { }
340
+ }
180
341
}
342
+
343
+ Ok ( false )
181
344
}
0 commit comments