Skip to content

Commit 803567d

Browse files
committed
resolve dependency graph with recursive swift package describe
1 parent 603bbb1 commit 803567d

File tree

7 files changed

+182
-137
lines changed

7 files changed

+182
-137
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,6 @@
416416
"dev-package": "vsce package -o swift-lang-development.vsix"
417417
},
418418
"devDependencies": {
419-
"@types/checksum": "^0.1.33",
420419
"@types/glob": "^7.1.4",
421420
"@types/mocha": "^9.0.0",
422421
"@types/node": "14.x",

src/FolderContext.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,8 @@ export class FolderContext implements vscode.Disposable {
163163
}
164164

165165
/** Get list of all packages */
166-
async getWorkspaceDependencies(): Promise<WorkspaceStateDependency[]> {
167-
const workspaceState = await this.swiftPackage.loadWorkspaceState();
168-
return workspaceState?.object?.dependencies ?? [];
166+
async resolveDependencyGraph(): Promise<WorkspaceStateDependency[]> {
167+
return await this.swiftPackage.resolveDependencyGraph();
169168
}
170169

171170
static uriName(uri: vscode.Uri): string {

src/PackageWatcher.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import * as vscode from "vscode";
1616
import { FolderContext } from "./FolderContext";
17+
import { buildDirectoryFromWorkspacePath } from "./utilities/utilities";
1718
import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
1819

1920
/**
@@ -25,6 +26,7 @@ import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
2526
export class PackageWatcher {
2627
private packageFileWatcher?: vscode.FileSystemWatcher;
2728
private resolvedFileWatcher?: vscode.FileSystemWatcher;
29+
private workspaceStateFileWatcher?: vscode.FileSystemWatcher;
2830

2931
constructor(private folderContext: FolderContext, private workspaceContext: WorkspaceContext) {}
3032

@@ -35,6 +37,7 @@ export class PackageWatcher {
3537
install() {
3638
this.packageFileWatcher = this.createPackageFileWatcher();
3739
this.resolvedFileWatcher = this.createResolvedFileWatcher();
40+
this.workspaceStateFileWatcher = this.createWorkspaceStateFileWatcher();
3841
}
3942

4043
/**
@@ -44,6 +47,7 @@ export class PackageWatcher {
4447
dispose() {
4548
this.packageFileWatcher?.dispose();
4649
this.resolvedFileWatcher?.dispose();
50+
this.workspaceStateFileWatcher?.dispose();
4751
}
4852

4953
private createPackageFileWatcher(): vscode.FileSystemWatcher {
@@ -60,9 +64,23 @@ export class PackageWatcher {
6064
const watcher = vscode.workspace.createFileSystemWatcher(
6165
new vscode.RelativePattern(this.folderContext.folder, "Package.resolved")
6266
);
63-
// watcher.onDidCreate(async () => await this.handlePackageResolvedChange());
64-
watcher.onDidChange(async () => await this.handlePackageResolvedChange("change"));
65-
watcher.onDidDelete(async () => await this.handlePackageResolvedChange("delete"));
67+
watcher.onDidCreate(async () => await this.handlePackageResolvedChange());
68+
watcher.onDidChange(async () => await this.handlePackageResolvedChange());
69+
watcher.onDidDelete(async () => await this.handlePackageResolvedChange());
70+
return watcher;
71+
}
72+
73+
private createWorkspaceStateFileWatcher(): vscode.FileSystemWatcher {
74+
const uri = vscode.Uri.file(
75+
buildDirectoryFromWorkspacePath(this.folderContext.folder.fsPath, true)
76+
);
77+
78+
const watcher = vscode.workspace.createFileSystemWatcher(
79+
new vscode.RelativePattern(uri, "workspace-state.json")
80+
);
81+
watcher.onDidCreate(async () => await this.handleWorkspaceStateChange());
82+
watcher.onDidChange(async () => await this.handleWorkspaceStateChange());
83+
watcher.onDidDelete(async () => await this.handleWorkspaceStateChange());
6684
return watcher;
6785
}
6886

@@ -84,17 +102,17 @@ export class PackageWatcher {
84102
*
85103
* This will resolve any changes in the Package.resolved.
86104
*/
87-
private async handlePackageResolvedChange(action: "change" | "delete") {
88-
// if Package.resolved is modified, we need to resolve the dependencies
89-
// after resolving is done, it will MODIFY the Package.resolved again
90-
// BUT, the file content is not actually changed
91-
// we don't want to resolve the dependencies again
92-
const hasChanges = await this.folderContext.swiftPackage.packageResovledHasChanged(action);
93-
if (!hasChanges) {
94-
return;
95-
}
96-
97-
await this.folderContext.swiftPackage.reloadPackageResolved();
105+
private async handlePackageResolvedChange() {
106+
await this.folderContext.reloadPackageResolved();
98107
this.workspaceContext.fireEvent(this.folderContext, FolderEvent.resolvedUpdated);
99108
}
109+
110+
/**
111+
* Handles a create or change event for **workspace-state.json**.
112+
*
113+
* This will resolve any changes in the workspace-state.json
114+
*/
115+
private async handleWorkspaceStateChange() {
116+
this.workspaceContext.fireEvent(this.folderContext, FolderEvent.workspaceStateUpdated);
117+
}
100118
}

src/SwiftPackage.ts

Lines changed: 128 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
import * as vscode from "vscode";
1616
import * as fs from "fs/promises";
17+
import * as path from "path";
1718
import {
1819
buildDirectoryFromWorkspacePath,
1920
execSwift,
2021
getErrorDescription,
2122
} from "./utilities/utilities";
22-
import checksum = require("checksum");
2323

2424
/** Swift Package Manager contents */
2525
export interface PackageContents {
@@ -152,6 +152,61 @@ function isError(state: SwiftPackageState): state is Error {
152152
return state instanceof Error;
153153
}
154154

155+
/**
156+
* Get version of WorkspaceStateDependency for displaying in the tree
157+
* @param dependency
158+
* @return real version | edited | local
159+
*/
160+
export function dependencyVersion(dependency: WorkspaceStateDependency): string {
161+
return dependency.packageRef.kind === "fileSystem"
162+
? "local"
163+
: dependency.state.checkoutState?.version ??
164+
dependency.state.checkoutState?.branch ??
165+
"edited";
166+
}
167+
168+
/**
169+
* Get type of WorkspaceStateDependency for displaying in the tree: real version | edited | local
170+
* @param dependency
171+
* @return "local" | "remote" | "edited"
172+
*/
173+
export function dependencyType(
174+
dependency: WorkspaceStateDependency
175+
): "local" | "remote" | "edited" {
176+
return dependency.state.name === "edited"
177+
? "edited"
178+
: dependency.packageRef.kind === "fileSystem"
179+
? "local"
180+
: "remote";
181+
}
182+
183+
/**
184+
* Get type of WorkspaceStateDependency for displaying in the tree: real version | edited | local
185+
* `edited`: dependency.state.path ?? workspacePath + Packages/ + dependency.subpath
186+
* `local`: dependency.packageRef.location
187+
* `remote`: buildDirectory + checkouts + dependency.packageRef.location
188+
* @param dependency
189+
* @return the package path based on the type
190+
*/
191+
export function dependencyPackagePath(
192+
dependency: WorkspaceStateDependency,
193+
workspaceFolder: string
194+
): string {
195+
const type = dependencyType(dependency);
196+
let packagePath = "";
197+
if (type === "edited") {
198+
packagePath =
199+
dependency.state.path ?? path.join(workspaceFolder, "Packages", dependency.subpath);
200+
} else if (type === "local") {
201+
packagePath = dependency.state.path ?? dependency.packageRef.location;
202+
} else {
203+
// remote
204+
const buildDirectory = buildDirectoryFromWorkspacePath(workspaceFolder, true);
205+
packagePath = path.join(buildDirectory, "checkouts", dependency.subpath);
206+
}
207+
return packagePath;
208+
}
209+
155210
/**
156211
* Class holding Swift Package Manager Package
157212
*/
@@ -166,9 +221,7 @@ export class SwiftPackage implements PackageContents {
166221
readonly folder: vscode.Uri,
167222
private contents: SwiftPackageState,
168223
public resolved: PackageResolved | undefined,
169-
public plugins: PackagePlugin[],
170-
public resolvedContent: string | undefined,
171-
public dependencySet: Set<string>
224+
public plugins: PackagePlugin[]
172225
) {}
173226

174227
/**
@@ -180,8 +233,7 @@ export class SwiftPackage implements PackageContents {
180233
const contents = await SwiftPackage.loadPackage(folder);
181234
const resolved = await SwiftPackage.loadPackageResolved(folder);
182235
const plugins = await SwiftPackage.loadPlugins(folder);
183-
const resolvedContent = await SwiftPackage.loadPackageResolvedFile(folder);
184-
return new SwiftPackage(folder, contents, resolved, plugins, resolvedContent, new Set());
236+
return new SwiftPackage(folder, contents, resolved, plugins);
185237
}
186238

187239
/**
@@ -217,19 +269,10 @@ export class SwiftPackage implements PackageContents {
217269
}
218270
}
219271

220-
static async loadPackageResolvedFile(folder: vscode.Uri): Promise<string | undefined> {
272+
static async loadPackageResolved(folder: vscode.Uri): Promise<PackageResolved | undefined> {
221273
try {
222274
const uri = vscode.Uri.joinPath(folder, "Package.resolved");
223275
const contents = await fs.readFile(uri.fsPath, "utf8");
224-
return contents;
225-
} catch {
226-
return undefined;
227-
}
228-
}
229-
230-
static async loadPackageResolved(folder: vscode.Uri): Promise<PackageResolved | undefined> {
231-
try {
232-
const contents = await SwiftPackage.loadPackageResolvedFile(folder);
233276
if (contents === undefined) {
234277
return undefined;
235278
}
@@ -273,34 +316,6 @@ export class SwiftPackage implements PackageContents {
273316
}
274317
}
275318

276-
async packageResovledHasChanged(action: any): Promise<boolean> {
277-
console.log(action);
278-
const resolvedContent = await SwiftPackage.loadPackageResolvedFile(this.folder);
279-
280-
console.log(resolvedContent);
281-
console.log(this.resolvedContent);
282-
283-
if (resolvedContent === undefined) {
284-
return false;
285-
}
286-
// deletion --> creation --> modification
287-
// r undefined exists exists
288-
// pr exists undefined exists
289-
290-
if (this.resolvedContent === undefined) {
291-
return true;
292-
}
293-
294-
const oldChecksum = checksum(resolvedContent);
295-
const newChecksum = checksum(this.resolvedContent);
296-
297-
if (oldChecksum !== newChecksum) {
298-
return true;
299-
}
300-
301-
return false;
302-
}
303-
304319
/**
305320
* Load workspace-state.json file for swift package
306321
* @returns Workspace state
@@ -320,24 +335,79 @@ export class SwiftPackage implements PackageContents {
320335
}
321336

322337
/**
323-
* Run `swift package describe` and return results
324-
* @param folder folder package is in
325-
* @returns results of `swift package describe`
338+
* tranverse the dependency tree
339+
* in each node, call `swift package describe` to get the child dependencies and do it recursively
340+
* @returns all dependencies in flat list
326341
*/
327-
async resolveDependencyGraph(): Promise<Set<string>> {
328-
return this.dependencySet;
329-
}
342+
async resolveDependencyGraph(): Promise<WorkspaceStateDependency[]> {
343+
const workspaceState = await this.loadWorkspaceState();
344+
const workspaceStateDependencies = workspaceState?.object.dependencies ?? [];
345+
346+
if (workspaceStateDependencies.length === 0) {
347+
return [];
348+
}
330349

331-
updateDependencySetWithStdout(stdout: string): void {
332-
const lines = stdout
333-
.split("\n")
334-
.filter(item => item !== "")
335-
.map(item => item.trim());
336-
this.updateDependencySet(new Set(lines ?? []));
350+
const contents = this.contents as PackageContents;
351+
console.log("== resolve graph begin");
352+
const showingDependencies = new Set<string>();
353+
await this.getChildDependencies(contents, workspaceStateDependencies, showingDependencies);
354+
355+
console.log("== resolve graph done");
356+
357+
// filter workspaceStateDependencies that in showingDependencies
358+
return workspaceStateDependencies.filter(dependency =>
359+
showingDependencies.has(dependency.packageRef.identity)
360+
);
361+
362+
// this can filter out dependencies that are not in the workspace state
363+
// filter workspaceStateDependencies that not in showingDependencies
364+
//const unusedPackages = workspaceStateDependencies.filter(
365+
// dependency => !showingDependencies.has(dependency.packageRef.identity)
366+
//);
337367
}
338368

339-
updateDependencySet(dependencySet: Set<string>): void {
340-
this.dependencySet = dependencySet;
369+
/**
370+
* tranverse the dependency tree
371+
* @param dependency current node
372+
* @param workspaceStateDependencies all dependencies in workspace-state.json
373+
* @param showingDependencies result of dependencies that are showing in the tree
374+
* @returns
375+
*/
376+
private async getChildDependencies(
377+
dependency: PackageContents,
378+
workspaceStateDependencies: WorkspaceStateDependency[],
379+
showingDependencies: Set<string>
380+
) {
381+
for (let i = 0; i < dependency.dependencies.length; i++) {
382+
const childDependency = dependency.dependencies[i];
383+
if (showingDependencies.has(childDependency.identity)) {
384+
return;
385+
}
386+
showingDependencies.add(childDependency.identity);
387+
const workspaceStateDependency = workspaceStateDependencies.find(
388+
workspaceStateDependency =>
389+
workspaceStateDependency.packageRef.identity === childDependency.identity
390+
);
391+
if (workspaceStateDependency) {
392+
showingDependencies.add(workspaceStateDependency.packageRef.identity);
393+
}
394+
395+
if (workspaceStateDependency === undefined) {
396+
return;
397+
}
398+
399+
const packagePath = dependencyPackagePath(workspaceStateDependency, this.folder.fsPath);
400+
401+
const childDependencyContents = (await SwiftPackage.loadPackage(
402+
vscode.Uri.file(packagePath)
403+
)) as PackageContents;
404+
405+
await this.getChildDependencies(
406+
childDependencyContents,
407+
workspaceStateDependencies,
408+
showingDependencies
409+
);
410+
}
341411
}
342412

343413
/** Reload swift package */
@@ -347,7 +417,6 @@ export class SwiftPackage implements PackageContents {
347417

348418
/** Reload Package.resolved file */
349419
public async reloadPackageResolved() {
350-
this.resolvedContent = await SwiftPackage.loadPackageResolvedFile(this.folder);
351420
this.resolved = await SwiftPackage.loadPackageResolved(this.folder);
352421
}
353422

src/WorkspaceContext.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class WorkspaceContext implements vscode.Disposable {
132132
break;
133133
case FolderEvent.resolvedUpdated:
134134
break;
135-
case FolderEvent.resolveDone:
135+
case FolderEvent.workspaceStateUpdated:
136136
if (folder === this.currentFolder) {
137137
this.updateContextKeys(folder);
138138
}
@@ -494,8 +494,8 @@ export enum FolderEvent {
494494
packageUpdated = "packageUpdated",
495495
// Package.resolved has been updated
496496
resolvedUpdated = "resolvedUpdated",
497-
// `swift package resolve` is done
498-
resolveDone = "resolveDone",
497+
// `workspace-state.json` is updated, update dependency tree only by this event
498+
workspaceStateUpdated = "workspaceStateUpdated",
499499
}
500500

501501
/** Workspace Folder observer function */

0 commit comments

Comments
 (0)