Skip to content

Add quick pick to set Xcode DEVELOPER_DIR #384

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 4 commits into from
Aug 15, 2022
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
19 changes: 14 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@
"title": "Clean Build",
"category": "Swift"
},
{
"command": "swift.switchPlatform",
"title": "Switch Target",
"category": "Swift"
},
{
"command": "swift.resetPackage",
"title": "Reset Package Dependencies",
Expand Down Expand Up @@ -89,6 +84,16 @@
"command": "swift.openPackage",
"title": "Open Package.swift",
"category": "Swift"
},
{
"command": "swift.switchPlatform",
"title": "Select Target Platform",
"category": "Swift"
},
{
"command": "swift.selectXcodeDeveloperDir",
"title": "Select Xcode Developer Dir",
"category": "Swift"
}
],
"configuration": [
Expand Down Expand Up @@ -276,6 +281,10 @@
{
"command": "swift.openExternal",
"when": "false"
},
{
"command": "swift.selectXcodeDeveloperDir",
"when": "isMac"
}
],
"view/title": [
Expand Down
1 change: 1 addition & 0 deletions src/SwiftTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export class SwiftTaskProvider implements vscode.TaskProvider {
"swift",
new vscode.ShellExecution(swift, task.definition.args, {
cwd: fullCwd,
env: { ...configuration.swiftEnvironmentVariables, ...swiftRuntimeEnv() },
}),
task.problemMatchers
);
Expand Down
76 changes: 58 additions & 18 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as vscode from "vscode";
import * as fs from "fs/promises";
import * as path from "path";
import configuration from "./configuration";
import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
import { createSwiftTask, SwiftTaskProvider } from "./SwiftTaskProvider";
import { FolderContext } from "./FolderContext";
Expand All @@ -23,7 +24,6 @@ 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:
Expand Down Expand Up @@ -431,31 +431,68 @@ 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<DarwinQuickPickTarget>(
await withQuickPick(
"Select a new target",
[
{ value: DarwinCompatibleTarget.macOS, label: "macOS" },
{ value: DarwinCompatibleTarget.iOS, label: "iOS" },
],
onSelect
async picked => {
const sdkForTarget = await SwiftToolchain.getSdkForTarget(picked.value);
if (sdkForTarget) {
configuration.sdk = sdkForTarget;
} else {
vscode.window.showErrorMessage("Unable to obtain requested SDK path");
}
}
);
}

/**
* Choose DEVELOPER_DIR
* @param workspaceContext
*/
async function selectXcodeDeveloperDir() {
const defaultXcode = await SwiftToolchain.getXcodeDeveloperDir();
const selectedXcode = configuration.swiftEnvironmentVariables.DEVELOPER_DIR;
const xcodes = await SwiftToolchain.getXcodeInstalls();
await withQuickPick(
selectedXcode ?? defaultXcode,
xcodes.map(xcode => {
const developerDir = `${xcode}/Contents/Developer`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nits] the UI and the actual value is a bit different
the list in UI is Xcode paths
but the actual values are the developer paths
it's a little bit different, not sure if engineers would get confused

return {
label: developerDir === defaultXcode ? `${xcode} (default)` : xcode,
folder: developerDir === defaultXcode ? undefined : developerDir,
};
}),
async selected => {
let swiftEnv = configuration.swiftEnvironmentVariables;
const previousDeveloperDir = swiftEnv.DEVELOPER_DIR ?? defaultXcode;
if (selected.folder) {
swiftEnv.DEVELOPER_DIR = selected.folder;
} else if (swiftEnv.DEVELOPER_DIR) {
// if DEVELOPER_DIR was set and the new folder is the default then
// delete variable
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { DEVELOPER_DIR, ...rest } = swiftEnv;
swiftEnv = rest;
}
configuration.swiftEnvironmentVariables = swiftEnv;
// if SDK is inside previous DEVELOPER_DIR then move to new DEVELOPER_DIR
if (
configuration.sdk.length > 0 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is convenient enough

but I'm wondering if it's possible for the setting of swift.SDK could be like either way

  1. swift.SDK: "SDKs/MacOSX.sdk" (DEVELOPER_DIR is the automatic prefix for it)
  2. swift.SDK: "$DEVELOPER_DIR/SDKs/MacOSX.sdk". (environment variable can be used in arguments too)

a bit out of scope here. you may find some ideas here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEVELOPER_DIR doesn't exist on Linux or Windows. Also the SDK can exist outside the DEVELOPER_DIR if you are cross compiling for another platform.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your answer

configuration.sdk.startsWith(previousDeveloperDir)
) {
configuration.sdk = configuration.sdk.replace(
previousDeveloperDir,
selected.folder ?? defaultXcode
);
}
}
);
}

Expand Down Expand Up @@ -510,6 +547,9 @@ export function register(ctx: WorkspaceContext) {
if (item instanceof PackageNode) {
openInExternalEditor(item);
}
})
}),
vscode.commands.registerCommand("swift.selectXcodeDeveloperDir", () =>
selectXcodeDeveloperDir()
)
);
}
3 changes: 3 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ const configuration = {
.getConfiguration("swift")
.get<{ [key: string]: string }>("swiftEnvironmentVariables", {});
},
set swiftEnvironmentVariables(vars: { [key: string]: string }) {
vscode.workspace.getConfiguration("swift").update("swiftEnvironmentVariables", vars);
},
/** include build errors in problems view */
get problemMatchCompileErrors(): boolean {
return vscode.workspace
Expand Down
72 changes: 46 additions & 26 deletions src/toolchain/toolchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import * as fs from "fs/promises";
import * as path from "path";
import { normalize } from "path";
import * as plist from "plist";
import * as vscode from "vscode";
import configuration from "../configuration";
Expand Down Expand Up @@ -95,6 +96,49 @@ export class SwiftToolchain {
);
}

/**
* Get active developer dir for Xcode
*/
public static async getXcodeDeveloperDir(): Promise<string> {
const { stdout } = await execFile("xcode-select", ["-p"]);
return stdout.trimEnd();
}

/**
* @param target Target to obtain the SDK path for
* @returns path to the SDK for the target
*/
public static async getSdkForTarget(
target: DarwinCompatibleTarget
): Promise<string | undefined> {
let sdkType: string;
switch (target) {
case DarwinCompatibleTarget.macOS:
// macOS is the default target, so lets not update the SDK
return undefined;
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());
}

/**
* Get list of Xcode versions intalled on mac
* @returns Folders for each Xcode install
*/
public static async getXcodeInstalls(): Promise<string[]> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nits] getInstalledXcodes may be easier to understand?

const { stdout: xcodes } = await execFile("mdfind", [
`kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'`,
]);
return xcodes.trimEnd().split("\n");
}

logDiagnostics(channel: SwiftOutputChannel) {
channel.logDiagnostic(`Swift Path: ${this.swiftFolderPath}`);
channel.logDiagnostic(`Toolchain Path: ${this.toolchainPath}`);
Expand Down Expand Up @@ -214,30 +258,6 @@ 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<string | undefined> {
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
*/
Expand All @@ -260,8 +280,8 @@ export class SwiftToolchain {
): Promise<string | undefined> {
switch (process.platform) {
case "darwin": {
const { stdout } = await execFile("xcode-select", ["-p"]);
return path.join(stdout.trimEnd(), "usr", "bin");
const developerDir = await this.getXcodeDeveloperDir();
return path.join(developerDir, "usr", "bin");
}
case "win32": {
// look up runtime library directory for XCTest alternatively
Expand Down