Skip to content

Set swift.swiftSDK for target platforms #1390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 14, 2025
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The extension adds the following commands, available via the command palette.

The following command is only available on macOS:

- **Select Target Platform**: This is an experimental command that offers code completion for iOS and tvOS projects.
- **Select Target Platform**: This is an experimental command that offers code editing support for iOS, tvOS, watchOS and visionOS projects.

#### Building and Debugging

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@
},
{
"command": "swift.switchPlatform",
"when": "swift.isActivated && isMac"
"when": "swift.isActivated && isMac && swift.switchPlatformAvailable"
},
{
"command": "swift.insertFunctionComment",
Expand Down
6 changes: 4 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
return runTestMultipleTimes(ctx.currentFolder, item, true);
}
}),
// Note: This is only available on macOS (gated in `package.json`) because its the only OS that has the iOS SDK available.
vscode.commands.registerCommand("swift.switchPlatform", () => switchPlatform()),
// Note: switchPlatform is only available on macOS and Swift 6.1 or later
// (gated in `package.json`) because it's the only OS and toolchain combination that
// has Darwin SDKs available and supports code editing with SourceKit-LSP
vscode.commands.registerCommand("swift.switchPlatform", () => switchPlatform(ctx)),
vscode.commands.registerCommand(Commands.RESET_PACKAGE, () => resetPackage(ctx)),
vscode.commands.registerCommand("swift.runScript", () => runSwiftScript(ctx)),
vscode.commands.registerCommand("swift.openPackage", () => {
Expand Down
47 changes: 29 additions & 18 deletions src/commands/switchPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { DarwinCompatibleTarget, SwiftToolchain } from "../toolchain/toolchain";
import {
DarwinCompatibleTarget,
SwiftToolchain,
getDarwinTargetTriple,
} from "../toolchain/toolchain";
import configuration from "../configuration";
import { WorkspaceContext } from "../WorkspaceContext";

/**
* Switches the target SDK to the platform selected in a QuickPick UI.
* Switches the appropriate SDK setting to the platform selected in a QuickPick UI.
*/
export async function switchPlatform() {
export async function switchPlatform(ctx: WorkspaceContext) {
const picked = await vscode.window.showQuickPick(
[
{ value: undefined, label: "macOS" },
Expand All @@ -29,28 +34,34 @@ export async function switchPlatform() {
{ value: DarwinCompatibleTarget.visionOS, label: "visionOS" },
],
{
placeHolder: "Select a new target",
placeHolder: "Select a new target platform",
}
);
if (picked) {
// show a status item as getSDKForTarget can sometimes take a long while to run xcrun to find the SDK
const statusItemText = `Setting target platform to ${picked.label}`;
ctx.statusItem.start(statusItemText);
try {
const sdkForTarget = picked.value
? await SwiftToolchain.getSDKForTarget(picked.value)
: "";
if (sdkForTarget !== undefined) {
if (sdkForTarget !== "") {
configuration.sdk = sdkForTarget;
vscode.window.showWarningMessage(
`Selecting the ${picked.label} SDK will provide code editing support, but compiling with this SDK will have undefined results.`
);
} else {
configuration.sdk = undefined;
}
if (picked.value) {
// verify that the SDK for the platform actually exists
await SwiftToolchain.getSDKForTarget(picked.value);
}
const swiftSDKTriple = picked.value ? getDarwinTargetTriple(picked.value) : "";
if (swiftSDKTriple !== "") {
// set a swiftSDK for non-macOS Darwin platforms so that SourceKit-LSP can provide syntax highlighting
configuration.swiftSDK = swiftSDKTriple;
vscode.window.showWarningMessage(
`Selecting the ${picked.label} target platform will provide code editing support, but compiling with a ${picked.label} SDK will have undefined results.`
);
} else {
vscode.window.showErrorMessage("Unable to obtain requested SDK path");
// set swiftSDK to an empty string for macOS and other platforms
configuration.swiftSDK = "";
}
} catch {
vscode.window.showErrorMessage("Unable to obtain requested SDK path");
vscode.window.showErrorMessage(
`Unable set the Swift SDK setting to ${picked.label}, verify that the SDK exists`
);
}
ctx.statusItem.end(statusItemText);
}
}
15 changes: 15 additions & 0 deletions src/contextKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ interface ContextKeys {
* Whether the SourceKit-LSP server supports documentation live preview.
*/
supportsDocumentationLivePreview: boolean;

/**
* Whether the swift.switchPlatform command is available.
*/
switchPlatformAvailable: boolean;
}

/** Creates the getters and setters for the VS Code Swift extension's context keys. */
Expand All @@ -92,6 +97,7 @@ function createContextKeys(): ContextKeys {
let createNewProjectAvailable: boolean = false;
let supportsReindexing: boolean = false;
let supportsDocumentationLivePreview: boolean = false;
let switchPlatformAvailable: boolean = false;

return {
get isActivated() {
Expand Down Expand Up @@ -200,6 +206,15 @@ function createContextKeys(): ContextKeys {
value
);
},

get switchPlatformAvailable() {
return switchPlatformAvailable;
},

set switchPlatformAvailable(value: boolean) {
switchPlatformAvailable = value;
vscode.commands.executeCommand("setContext", "swift.switchPlatformAvailable", value);
},
};
}

Expand Down
10 changes: 5 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
contextKeys.createNewProjectAvailable = toolchain.swiftVersion.isGreaterThanOrEqual(
new Version(5, 8, 0)
);
contextKeys.switchPlatformAvailable = toolchain.swiftVersion.isGreaterThanOrEqual(
new Version(6, 1, 0)
);
return toolchain;
})
.catch(error => {
outputChannel.log("Failed to discover Swift toolchain");
outputChannel.log(error);
contextKeys.createNewProjectAvailable = false;
contextKeys.switchPlatformAvailable = false;
return undefined;
});

Expand All @@ -102,11 +106,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
event.affectsConfiguration("swift.SDK") ||
event.affectsConfiguration("swift.swiftSDK")
) {
// FIXME: There is a bug stopping us from restarting SourceKit-LSP directly.
// As long as it's fixed we won't need to reload on newer versions.
showReloadExtensionNotification(
"Changing the Swift SDK path requires the project be reloaded."
);
vscode.commands.executeCommand("swift.restartLSPServer");
} else if (event.affectsConfiguration("swift.swiftEnvironmentVariables")) {
showReloadExtensionNotification(
"Changing environment variables requires the project be reloaded."
Expand Down
85 changes: 85 additions & 0 deletions test/unit-tests/commands/switchPlatform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2021-2025 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import { expect } from "chai";
import * as vscode from "vscode";
import {
mockObject,
mockGlobalObject,
mockGlobalModule,
MockedObject,
mockFn,
instance,
} from "../../MockUtils";
import {
DarwinCompatibleTarget,
SwiftToolchain,
getDarwinTargetTriple,
} from "../../../src/toolchain/toolchain";
import { WorkspaceContext } from "../../../src/WorkspaceContext";
import { switchPlatform } from "../../../src/commands/switchPlatform";
import { StatusItem } from "../../../src/ui/StatusItem";
import configuration from "../../../src/configuration";

suite("Switch Target Platform Unit Tests", () => {
const mockedConfiguration = mockGlobalModule(configuration);
const windowMock = mockGlobalObject(vscode, "window");
const mockSwiftToolchain = mockGlobalModule(SwiftToolchain);
let mockContext: MockedObject<WorkspaceContext>;
let mockedStatusItem: MockedObject<StatusItem>;

setup(() => {
mockedStatusItem = mockObject<StatusItem>({
start: mockFn(),
end: mockFn(),
});
mockContext = mockObject<WorkspaceContext>({
statusItem: instance(mockedStatusItem),
});
});

test("Call Switch Platform and switch to iOS", async () => {
const selectedItem = { value: DarwinCompatibleTarget.iOS, label: "iOS" };
windowMock.showQuickPick.resolves(selectedItem);
mockSwiftToolchain.getSDKForTarget.resolves("");
expect(mockedConfiguration.swiftSDK).to.equal("");

await switchPlatform(instance(mockContext));

expect(windowMock.showQuickPick).to.have.been.calledOnce;
expect(windowMock.showWarningMessage).to.have.been.calledOnceWithExactly(
"Selecting the iOS target platform will provide code editing support, but compiling with a iOS SDK will have undefined results."
);
expect(mockedStatusItem.start).to.have.been.called;
expect(mockedStatusItem.end).to.have.been.called;
expect(mockedConfiguration.swiftSDK).to.equal(
getDarwinTargetTriple(DarwinCompatibleTarget.iOS)
);
});

test("Call Switch Platform and switch to macOS", async () => {
const selectedItem = { value: undefined, label: "macOS" };
windowMock.showQuickPick.resolves(selectedItem);
mockSwiftToolchain.getSDKForTarget.resolves("");
expect(mockedConfiguration.swiftSDK).to.equal("");

await switchPlatform(instance(mockContext));

expect(windowMock.showQuickPick).to.have.been.calledOnce;
expect(windowMock.showWarningMessage).to.not.have.been.called;
expect(mockedStatusItem.start).to.have.been.called;
expect(mockedStatusItem.end).to.have.been.called;
expect(mockedConfiguration.swiftSDK).to.equal("");
});
});
Loading