Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions assets/test/package5.6/Package.resolved

This file was deleted.

20 changes: 0 additions & 20 deletions assets/test/package5.6/Package.swift

This file was deleted.

6 changes: 0 additions & 6 deletions assets/test/package5.6/Sources/package5.6/package5_6.swift

This file was deleted.

38 changes: 21 additions & 17 deletions src/FolderContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ export class FolderContext implements vscode.Disposable {
) {
this.packageWatcher = new PackageWatcher(this, workspaceContext.logger);
this.backgroundCompilation = new BackgroundCompilation(this);
this.taskQueue = new TaskQueue(this);
this.taskQueue = new TaskQueue(
workspaceContext.tasks,
workspaceContext.statusItem,
workspaceContext.logger,
this.name
);
this.testRunManager = new TestRunManager();

// In order to track down why a FolderContext may be created when we don't want one,
Expand Down Expand Up @@ -138,11 +143,7 @@ export class FolderContext implements vscode.Disposable {
const { linuxMain, swiftPackage } =
await workspaceContext.statusItem.showStatusWhileRunning(statusItemText, async () => {
const linuxMain = await LinuxMain.create(folder);
const swiftPackage = await SwiftPackage.create(
folder,
toolchain,
configuration.disableSwiftPMIntegration
);
const swiftPackage = await SwiftPackage.create(folder);
return { linuxMain, swiftPackage };
});
workspaceContext.statusItem.end(statusItemText);
Expand All @@ -156,16 +157,19 @@ export class FolderContext implements vscode.Disposable {
workspaceContext
);

const error = await swiftPackage.error;
if (error) {
void vscode.window.showErrorMessage(
`Failed to load ${folderContext.name}/Package.swift: ${error.message}`
);
workspaceContext.logger.info(
`Failed to load Package.swift: ${error.message}`,
folderContext.name
);
}
// List the package's dependencies without blocking folder creation
void swiftPackage.loadPackageState(folderContext).then(async () => {
const error = await swiftPackage.error;
if (error) {
void vscode.window.showErrorMessage(
`Failed to load ${folderContext.name}/Package.swift: ${error.message}`
);
workspaceContext.logger.info(
`Failed to load Package.swift: ${error.message}`,
folderContext.name
);
}
});

// Start watching for changes to Package.swift, Package.resolved and .swift-version
await folderContext.packageWatcher.install();
Expand Down Expand Up @@ -200,7 +204,7 @@ export class FolderContext implements vscode.Disposable {

/** reload swift package for this folder */
async reload() {
await this.swiftPackage.reload(this.toolchain, configuration.disableSwiftPMIntegration);
await this.swiftPackage.reload(this, configuration.disableSwiftPMIntegration);
}

/** reload Package.resolved for this folder */
Expand Down
4 changes: 0 additions & 4 deletions src/PackageWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,6 @@ export class PackageWatcher {
*/
private async handleWorkspaceStateChange() {
await this.folderContext.reloadWorkspaceState();
// TODO: Remove this
this.logger.info(
`Package watcher state updated workspace-state.json: ${JSON.stringify(this.folderContext.swiftPackage.workspaceState, null, 2)}`
);
await this.folderContext.fireEvent(FolderOperation.workspaceStateUpdated);
}
}
96 changes: 53 additions & 43 deletions src/SwiftPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import * as fs from "fs/promises";
import * as path from "path";
import * as vscode from "vscode";

import { FolderContext } from "./FolderContext";
import { describePackage } from "./commands/dependencies/describe";
import { showPackageDependencies } from "./commands/dependencies/show";
import { SwiftLogger } from "./logging/SwiftLogger";
import { BuildFlags } from "./toolchain/BuildFlags";
import { SwiftToolchain } from "./toolchain/toolchain";
Expand All @@ -23,7 +26,7 @@ import { lineBreakRegex } from "./utilities/tasks";
import { execSwift, getErrorDescription, hashString } from "./utilities/utilities";

/** Swift Package Manager contents */
interface PackageContents {
export interface PackageContents {
name: string;
products: Product[];
dependencies: Dependency[];
Expand Down Expand Up @@ -192,7 +195,11 @@ function isError(state: SwiftPackageState): state is Error {
*/
export class SwiftPackage {
public plugins: PackagePlugin[] = [];

private _contents: SwiftPackageState | undefined;
private contentsPromise: Promise<SwiftPackageState>;
private contentsResolve: (value: SwiftPackageState | PromiseLike<SwiftPackageState>) => void;
private contentsReject: (reason?: unknown) => void;

/**
* SwiftPackage Constructor
Expand All @@ -202,11 +209,19 @@ export class SwiftPackage {
*/
private constructor(
readonly folder: vscode.Uri,
private contentsPromise: Promise<SwiftPackageState>,
public resolved: PackageResolved | undefined,
// TODO: Make private again
public workspaceState: WorkspaceState | undefined
) {}
) {
let res: (value: SwiftPackageState | PromiseLike<SwiftPackageState>) => void;
let rej: (reason?: unknown) => void;
this.contentsPromise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
this.contentsResolve = res!;
this.contentsReject = rej!;
}

/**
* Create a SwiftPackage from a folder
Expand All @@ -215,21 +230,12 @@ export class SwiftPackage {
* @param disableSwiftPMIntegration Whether to disable SwiftPM integration
* @returns new SwiftPackage
*/
public static async create(
folder: vscode.Uri,
toolchain: SwiftToolchain,
disableSwiftPMIntegration: boolean = false
): Promise<SwiftPackage> {
public static async create(folder: vscode.Uri): Promise<SwiftPackage> {
const [resolved, workspaceState] = await Promise.all([
SwiftPackage.loadPackageResolved(folder),
SwiftPackage.loadWorkspaceState(folder),
]);
return new SwiftPackage(
folder,
SwiftPackage.loadPackage(folder, toolchain, disableSwiftPMIntegration),
resolved,
workspaceState
);
return new SwiftPackage(folder, resolved, workspaceState);
}

/**
Expand Down Expand Up @@ -257,52 +263,49 @@ export class SwiftPackage {
* @param disableSwiftPMIntegration Whether to disable SwiftPM integration
* @returns results of `swift package describe`
*/
static async loadPackage(
folder: vscode.Uri,
toolchain: SwiftToolchain,
public async loadPackageState(
folderContext: FolderContext,
disableSwiftPMIntegration: boolean = false
): Promise<SwiftPackageState> {
// When SwiftPM integration is disabled, return empty package structure
if (disableSwiftPMIntegration) {
return {
name: path.basename(folder.fsPath),
const empty = {
name: path.basename(folderContext.folder.fsPath),
products: [],
dependencies: [],
targets: [],
};
this.contentsResolve(empty);
return empty;
}

try {
// Use swift package describe to describe the package targets, products, and platforms
// Use swift package show-dependencies to get the dependencies in a tree format
const [describe, dependencies] = await Promise.all([
execSwift(["package", "describe", "--type", "json"], toolchain, {
cwd: folder.fsPath,
}),
execSwift(["package", "show-dependencies", "--format", "json"], toolchain, {
cwd: folder.fsPath,
}),
]);

// Each of these locks the folder so we can't run them in parallel, so just serially run them.
const describe = await describePackage(folderContext);
const dependencies = await showPackageDependencies(folderContext);
const packageState = {
...(JSON.parse(SwiftPackage.trimStdout(describe.stdout)) as PackageContents),
dependencies: JSON.parse(SwiftPackage.trimStdout(dependencies.stdout)).dependencies,
...(describe as PackageContents),
dependencies: dependencies,
};

this.contentsResolve(packageState);
return packageState;
} catch (error) {
const execError = error as { stderr: string };
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
// if caught error and it begins with "error: root manifest" then there is no Package.swift
if (
execError.stderr !== undefined &&
(execError.stderr.startsWith("error: root manifest") ||
execError.stderr.startsWith("error: Could not find Package.swift"))
errorMessage.startsWith("error: root manifest") ||
errorMessage.startsWith("error: Could not find Package.swift")
) {
this.contentsResolve(undefined);
return undefined;
} else {
// otherwise it is an error loading the Package.swift so return `null` indicating
// we have a package but we failed to load it
return Error(getErrorDescription(error));
// we have a package but we failed to load it. Calling resolve instead of reject is intent
this.contentsResolve(Error(getErrorDescription(error)));
return undefined;
}
}
}
Expand Down Expand Up @@ -377,14 +380,21 @@ export class SwiftPackage {
}

/** Reload swift package */
public async reload(toolchain: SwiftToolchain, disableSwiftPMIntegration: boolean = false) {
const loadedContents = await SwiftPackage.loadPackage(
this.folder,
toolchain,
public async reload(folderContext: FolderContext, disableSwiftPMIntegration: boolean = false) {
let res: (value: SwiftPackageState | PromiseLike<SwiftPackageState>) => void;
let rej: (reason?: unknown) => void;
this.contentsPromise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
this.contentsResolve = res!;
this.contentsReject = rej!;
const loadedContents = await this.loadPackageState(
folderContext,
disableSwiftPMIntegration
);

this._contents = loadedContents;
this.contentsPromise = Promise.resolve(loadedContents);
}

/** Reload Package.resolved file */
Expand Down Expand Up @@ -572,7 +582,7 @@ export class SwiftPackage {
);
}

private static trimStdout(stdout: string): string {
static trimStdout(stdout: string): string {
// remove lines from `swift package describe` until we find a "{"
while (!stdout.startsWith("{")) {
const firstNewLine = stdout.indexOf("\n");
Expand Down
Loading
Loading