@@ -47,10 +47,18 @@ export interface Target {
47
47
/** Swift Package Manager dependency */
48
48
export interface Dependency {
49
49
identity : string ;
50
- type ?: string ; // fileSystem, sourceControl or registry
50
+ type ?: string ;
51
51
requirement ?: object ;
52
52
url ?: string ;
53
53
path ?: string ;
54
+ dependencies : Dependency [ ] ;
55
+ }
56
+
57
+ export interface ResolvedDependency extends Omit < Omit < Dependency , "type" > , "path" > {
58
+ version : string ;
59
+ type : string ;
60
+ path : string ;
61
+ location : string ;
54
62
}
55
63
56
64
/** Swift Package.resolved file */
@@ -187,18 +195,23 @@ export class SwiftPackage implements PackageContents {
187
195
private constructor (
188
196
readonly folder : vscode . Uri ,
189
197
private contents : SwiftPackageState ,
190
- public resolved : PackageResolved | undefined
198
+ public resolved : PackageResolved | undefined ,
199
+ private workspaceState : WorkspaceState | undefined
191
200
) { }
192
201
193
202
/**
194
203
* Create a SwiftPackage from a folder
195
204
* @param folder folder package is in
196
205
* @returns new SwiftPackage
197
206
*/
198
- static async create ( folder : vscode . Uri , toolchain : SwiftToolchain ) : Promise < SwiftPackage > {
207
+ public static async create (
208
+ folder : vscode . Uri ,
209
+ toolchain : SwiftToolchain
210
+ ) : Promise < SwiftPackage > {
199
211
const contents = await SwiftPackage . loadPackage ( folder , toolchain ) ;
200
212
const resolved = await SwiftPackage . loadPackageResolved ( folder ) ;
201
- return new SwiftPackage ( folder , contents , resolved ) ;
213
+ const workspaceState = await SwiftPackage . loadWorkspaceState ( folder ) ;
214
+ return new SwiftPackage ( folder , contents , resolved , workspaceState ) ;
202
215
}
203
216
204
217
/**
@@ -211,15 +224,28 @@ export class SwiftPackage implements PackageContents {
211
224
toolchain : SwiftToolchain
212
225
) : Promise < SwiftPackageState > {
213
226
try {
214
- let { stdout } = await execSwift ( [ "package" , "describe" , "--type" , "json" ] , toolchain , {
227
+ // Use swift package describe to describe the package targets, products, and platforms
228
+ const describe = await execSwift ( [ "package" , "describe" , "--type" , "json" ] , toolchain , {
215
229
cwd : folder . fsPath ,
216
230
} ) ;
217
- // remove lines from `swift package describe` until we find a "{"
218
- while ( ! stdout . startsWith ( "{" ) ) {
219
- const firstNewLine = stdout . indexOf ( "\n" ) ;
220
- stdout = stdout . slice ( firstNewLine + 1 ) ;
221
- }
222
- return JSON . parse ( stdout ) ;
231
+ const packageState = JSON . parse (
232
+ SwiftPackage . trimStdout ( describe . stdout )
233
+ ) as PackageContents ;
234
+
235
+ // Use swift package show-dependencies to get the dependencies in a tree format
236
+ const dependencies = await execSwift (
237
+ [ "package" , "show-dependencies" , "--format" , "json" ] ,
238
+ toolchain ,
239
+ {
240
+ cwd : folder . fsPath ,
241
+ }
242
+ ) ;
243
+
244
+ packageState . dependencies = JSON . parse (
245
+ SwiftPackage . trimStdout ( dependencies . stdout )
246
+ ) . dependencies ;
247
+
248
+ return packageState ;
223
249
} catch ( error ) {
224
250
const execError = error as { stderr : string } ;
225
251
// if caught error and it begins with "error: root manifest" then there is no Package.swift
@@ -237,7 +263,9 @@ export class SwiftPackage implements PackageContents {
237
263
}
238
264
}
239
265
240
- static async loadPackageResolved ( folder : vscode . Uri ) : Promise < PackageResolved | undefined > {
266
+ private static async loadPackageResolved (
267
+ folder : vscode . Uri
268
+ ) : Promise < PackageResolved | undefined > {
241
269
try {
242
270
const uri = vscode . Uri . joinPath ( folder , "Package.resolved" ) ;
243
271
const contents = await fs . readFile ( uri . fsPath , "utf8" ) ;
@@ -248,7 +276,7 @@ export class SwiftPackage implements PackageContents {
248
276
}
249
277
}
250
278
251
- static async loadPlugins (
279
+ private static async loadPlugins (
252
280
folder : vscode . Uri ,
253
281
toolchain : SwiftToolchain
254
282
) : Promise < PackagePlugin [ ] > {
@@ -280,12 +308,12 @@ export class SwiftPackage implements PackageContents {
280
308
* Load workspace-state.json file for swift package
281
309
* @returns Workspace state
282
310
*/
283
- public async loadWorkspaceState ( ) : Promise < WorkspaceState | undefined > {
311
+ private static async loadWorkspaceState (
312
+ folder : vscode . Uri
313
+ ) : Promise < WorkspaceState | undefined > {
284
314
try {
285
315
const uri = vscode . Uri . joinPath (
286
- vscode . Uri . file (
287
- BuildFlags . buildDirectoryFromWorkspacePath ( this . folder . fsPath , true )
288
- ) ,
316
+ vscode . Uri . file ( BuildFlags . buildDirectoryFromWorkspacePath ( folder . fsPath , true ) ) ,
289
317
"workspace-state.json"
290
318
) ;
291
319
const contents = await fs . readFile ( uri . fsPath , "utf8" ) ;
@@ -306,6 +334,14 @@ export class SwiftPackage implements PackageContents {
306
334
this . resolved = await SwiftPackage . loadPackageResolved ( this . folder ) ;
307
335
}
308
336
337
+ public async reloadWorkspaceState ( ) {
338
+ this . workspaceState = await SwiftPackage . loadWorkspaceState ( this . folder ) ;
339
+ }
340
+
341
+ public async loadSwiftPlugins ( toolchain : SwiftToolchain ) {
342
+ this . plugins = await SwiftPackage . loadPlugins ( this . folder , toolchain ) ;
343
+ }
344
+
309
345
/** Return if has valid contents */
310
346
public get isValid ( ) : boolean {
311
347
return isPackage ( this . contents ) ;
@@ -325,6 +361,88 @@ export class SwiftPackage implements PackageContents {
325
361
return this . contents !== undefined ;
326
362
}
327
363
364
+ public rootDependencies ( ) : ResolvedDependency [ ] {
365
+ // Correlate the root dependencies found in the Package.swift with their
366
+ // checked out versions in the workspace-state.json.
367
+ const result = this . dependencies . map ( dependency =>
368
+ this . resolveDependencyAgainstWorkspaceState ( dependency )
369
+ ) ;
370
+ return result ;
371
+ }
372
+
373
+ private resolveDependencyAgainstWorkspaceState ( dependency : Dependency ) : ResolvedDependency {
374
+ const workspaceStateDep = this . workspaceState ?. object . dependencies . find (
375
+ dep => dep . packageRef . identity === dependency . identity
376
+ ) ;
377
+ return {
378
+ ...dependency ,
379
+ version : workspaceStateDep ?. state . checkoutState ?. version ?? "" ,
380
+ path : workspaceStateDep
381
+ ? this . dependencyPackagePath ( workspaceStateDep , this . folder . fsPath )
382
+ : "" ,
383
+ type : workspaceStateDep ? this . dependencyType ( workspaceStateDep ) : "" ,
384
+ location : workspaceStateDep ? workspaceStateDep . packageRef . location : "" ,
385
+ } ;
386
+ }
387
+
388
+ public async childDependencies ( dependency : Dependency ) : Promise < ResolvedDependency [ ] > {
389
+ return dependency . dependencies . map ( dep => this . resolveDependencyAgainstWorkspaceState ( dep ) ) ;
390
+ }
391
+
392
+ /**
393
+ * * Get package source path of dependency
394
+ * `editing`: dependency.state.path ?? workspacePath + Packages/ + dependency.subpath
395
+ * `local`: dependency.packageRef.location
396
+ * `remote`: buildDirectory + checkouts + dependency.packageRef.location
397
+ * @param dependency
398
+ * @param workspaceFolder
399
+ * @return the package path based on the type
400
+ */
401
+ private dependencyPackagePath (
402
+ dependency : WorkspaceStateDependency ,
403
+ workspaceFolder : string
404
+ ) : string {
405
+ const type = this . dependencyType ( dependency ) ;
406
+ if ( type === "editing" ) {
407
+ return (
408
+ dependency . state . path ?? path . join ( workspaceFolder , "Packages" , dependency . subpath )
409
+ ) ;
410
+ } else if ( type === "local" ) {
411
+ return dependency . state . path ?? dependency . packageRef . location ;
412
+ } else {
413
+ // remote
414
+ const buildDirectory = BuildFlags . buildDirectoryFromWorkspacePath (
415
+ workspaceFolder ,
416
+ true
417
+ ) ;
418
+ if ( dependency . packageRef . kind === "registry" ) {
419
+ return path . join ( buildDirectory , "registry" , "downloads" , dependency . subpath ) ;
420
+ } else {
421
+ return path . join ( buildDirectory , "checkouts" , dependency . subpath ) ;
422
+ }
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Get type of WorkspaceStateDependency for displaying in the tree: real version | edited | local
428
+ * @param dependency
429
+ * @return "local" | "remote" | "editing"
430
+ */
431
+ private dependencyType ( dependency : WorkspaceStateDependency ) : "local" | "remote" | "editing" {
432
+ if ( dependency . state . name === "edited" ) {
433
+ return "editing" ;
434
+ } else if (
435
+ dependency . packageRef . kind === "local" ||
436
+ dependency . packageRef . kind === "fileSystem"
437
+ ) {
438
+ // need to check for both "local" and "fileSystem" as swift 5.5 and earlier
439
+ // use "local" while 5.6 and later use "fileSystem"
440
+ return "local" ;
441
+ } else {
442
+ return "remote" ;
443
+ }
444
+ }
445
+
328
446
/** name of Swift Package */
329
447
get name ( ) : string {
330
448
return ( this . contents as PackageContents ) ?. name ?? "" ;
@@ -375,6 +493,15 @@ export class SwiftPackage implements PackageContents {
375
493
const filePath = path . relative ( this . folder . fsPath , file ) ;
376
494
return this . targets . find ( target => isPathInsidePath ( filePath , target . path ) ) ;
377
495
}
496
+
497
+ private static trimStdout ( stdout : string ) : string {
498
+ // remove lines from `swift package describe` until we find a "{"
499
+ while ( ! stdout . startsWith ( "{" ) ) {
500
+ const firstNewLine = stdout . indexOf ( "\n" ) ;
501
+ stdout = stdout . slice ( firstNewLine + 1 ) ;
502
+ }
503
+ return stdout ;
504
+ }
378
505
}
379
506
380
507
export enum TargetType {
0 commit comments