diff --git a/package.json b/package.json index e523706ec..af21171d8 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,11 @@ "title": "Clean Build", "category": "Swift" }, + { + "command": "swift.switchPlatform", + "title": "Switch Target", + "category": "Swift" + }, { "command": "swift.resetPackage", "title": "Reset Package Dependencies", @@ -245,6 +250,10 @@ "command": "swift.cleanBuild", "when": "swift.hasPackage" }, + { + "command": "swift.switchPlatform", + "when": "isMac" + }, { "command": "swift.resetPackage", "when": "swift.hasPackage" diff --git a/src/commands.ts b/src/commands.ts index cb9425f2c..521c484dc 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -19,8 +19,11 @@ import { FolderEvent, WorkspaceContext } from "./WorkspaceContext"; import { createSwiftTask, SwiftTaskProvider } from "./SwiftTaskProvider"; import { FolderContext } from "./FolderContext"; import { PackageNode } from "./ui/PackageDependencyProvider"; +import { withQuickPick } from "./ui/QuickPick"; import { execSwift } from "./utilities/utilities"; import { Version } from "./utilities/version"; +import { DarwinCompatibleTarget, SwiftToolchain } from "./toolchain/toolchain"; +import configuration from "./configuration"; /** * References: @@ -428,6 +431,34 @@ function openInExternalEditor(packageNode: PackageNode) { } } +interface DarwinQuickPickTarget extends vscode.QuickPickItem { + value: DarwinCompatibleTarget; + label: string; +} + +/** + * Switches the target SDK to the platform selected in a QuickPick UI. + */ +async function switchPlatform() { + const onSelect = async (picked: DarwinQuickPickTarget) => { + const sdkForTarget = await SwiftToolchain.getSdkForTarget(picked.value); + if (sdkForTarget) { + configuration.sdk = sdkForTarget; + } else { + vscode.window.showErrorMessage("Unable to obtain requested SDK path"); + } + }; + + await withQuickPick( + "Select a new target", + [ + { value: DarwinCompatibleTarget.macOS, label: "macOS" }, + { value: DarwinCompatibleTarget.iOS, label: "iOS" }, + ], + onSelect + ); +} + function updateAfterError(result: boolean, folderContext: FolderContext) { const triggerResolvedUpdatedEvent = folderContext.hasResolveErrors; // set has resolve errors flag @@ -450,6 +481,8 @@ export function register(ctx: WorkspaceContext) { ), vscode.commands.registerCommand("swift.updateDependencies", () => updateDependencies(ctx)), vscode.commands.registerCommand("swift.cleanBuild", () => cleanBuild(ctx)), + // 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()), vscode.commands.registerCommand("swift.resetPackage", () => resetPackage(ctx)), vscode.commands.registerCommand("swift.runSingle", () => runSingleFile(ctx)), vscode.commands.registerCommand("swift.openPackage", () => openPackage(ctx)), diff --git a/src/configuration.ts b/src/configuration.ts index 050ca135b..2c27ad6f5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -73,6 +73,9 @@ const configuration = { get sdk(): string { return vscode.workspace.getConfiguration("swift").get("SDK", ""); }, + set sdk(value: string) { + vscode.workspace.getConfiguration("swift").update("SDK", value); + }, /** swift build arguments */ get buildArguments(): string[] { return vscode.workspace.getConfiguration("swift").get("buildArguments", []); diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index ab730d59c..36923a03b 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -23,6 +23,7 @@ import { swiftDriverSDKFlags, buildPathFlags, swiftRuntimeEnv, + swiftDriverTargetFlags, } from "../utilities/utilities"; import { Version } from "../utilities/version"; import { FolderEvent, WorkspaceContext } from "../WorkspaceContext"; @@ -345,6 +346,7 @@ export class LanguageClientManager { serverPathConfig.length > 0 ? serverPathConfig : getSwiftExecutable("sourcekit-lsp"); const sdkArguments = [ ...swiftDriverSDKFlags(true), + ...swiftDriverTargetFlags(true), ...filterArguments( configuration.buildArguments.concat(buildPathFlags()), LanguageClientManager.buildArgumentFilter diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 2181549e6..39df3bfd2 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -46,6 +46,15 @@ interface SwiftTargetInfo { [name: string]: string | object | undefined; } +/** + * A Swift compilation target that can be compiled to + * from macOS. These are similar to XCode's target list. + */ +export enum DarwinCompatibleTarget { + iOS, + macOS, +} + export class SwiftToolchain { constructor( public swiftFolderPath: string, @@ -195,8 +204,8 @@ export class SwiftToolchain { if (process.env.SDKROOT) { return process.env.SDKROOT; } - const { stdout } = await execFile("xcrun", ["--sdk", "macosx", "--show-sdk-path"]); - return path.join(stdout.trimEnd()); + + return this.getSdkForTarget(DarwinCompatibleTarget.macOS); } case "win32": { return process.env.SDKROOT; @@ -205,6 +214,30 @@ export class SwiftToolchain { return undefined; } + /** + * @param target Target to obtain the SDK path for + * @returns path to the SDK for the target + */ + public static async getSdkForTarget( + target: DarwinCompatibleTarget + ): Promise { + let sdkType: string; + switch (target) { + case DarwinCompatibleTarget.macOS: + sdkType = "macosx"; + break; + case DarwinCompatibleTarget.iOS: + sdkType = "iphoneos"; + break; + } + + // Include custom variables so that non-standard XCode installs can be better supported. + const { stdout } = await execFile("xcrun", ["--sdk", sdkType, "--show-sdk-path"], { + env: { ...process.env, ...configuration.swiftEnvironmentVariables }, + }); + return path.join(stdout.trimEnd()); + } + /** * @returns path to custom SDK */ diff --git a/src/ui/QuickPick.ts b/src/ui/QuickPick.ts new file mode 100644 index 000000000..c3bd251b7 --- /dev/null +++ b/src/ui/QuickPick.ts @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VSCode Swift open source project +// +// Copyright (c) 2022 the VSCode Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VSCode Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; + +/** + * Displays a QuickPick UI with the parameters and passes the result + * to the provided closure. + */ +export async function withQuickPick( + placeholder: string, + options: T[], + onSelect: (picked: T) => Promise +) { + const picker = vscode.window.createQuickPick(); + + picker.placeholder = placeholder; + picker.items = options; + + picker.show(); + + const pickedItem = await new Promise(resolve => { + picker.onDidAccept(() => resolve(picker.selectedItems[0])); + picker.onDidHide(() => resolve(undefined)); + }); + + picker.busy = true; + + if (pickedItem) { + await onSelect(pickedItem); + picker.busy = false; + } + + picker.dispose(); +} diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts index 756def2e7..034dc88cf 100644 --- a/src/utilities/utilities.ts +++ b/src/utilities/utilities.ts @@ -251,6 +251,36 @@ export function swiftDriverSDKFlags(indirect = false): string[] { return indirect ? args.flatMap(arg => ["-Xswiftc", arg]) : args; } +/** + * Get target flags for swiftc + * + * @param indirect whether to pass the flags by -Xswiftc + */ +export function swiftDriverTargetFlags(indirect = false): string[] { + const IPHONE_SDK_KIND = "iPhoneOS"; + + let args: string[] = []; + + if (configuration.sdk === "") { + return args; + } + + const sdkKindParts = configuration.sdk.split("/"); + const sdkKind = sdkKindParts[sdkKindParts.length - 1]; + if (sdkKind.includes(IPHONE_SDK_KIND)) { + // Obtain the iOS version of the SDK. + const version = sdkKind.substring( + // Trim the prefix + sdkKind.length - IPHONE_SDK_KIND.length, + // Trim the `.sdk` suffix + sdkKind.length - 4 + ); + args = ["-target", `arm64-apple-ios${version}`]; + } + + return indirect ? args.flatMap(arg => ["-Xswiftc", arg]) : args; +} + /** * Get the file name of executable *