Skip to content

Commit 9ba47c6

Browse files
authored
Add quick pick to set Xcode DEVELOPER_DIR (#384)
* Add quick pick to select Xcode toolchain to use * Fix deleting DEVELOPER_DIR key * Resolved tasks should use same environment * Remove DarwinQuickPickTarget. We don't need it
1 parent 70f7585 commit 9ba47c6

File tree

5 files changed

+122
-49
lines changed

5 files changed

+122
-49
lines changed

package.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@
4444
"title": "Clean Build",
4545
"category": "Swift"
4646
},
47-
{
48-
"command": "swift.switchPlatform",
49-
"title": "Switch Target",
50-
"category": "Swift"
51-
},
5247
{
5348
"command": "swift.resetPackage",
5449
"title": "Reset Package Dependencies",
@@ -89,6 +84,16 @@
8984
"command": "swift.openPackage",
9085
"title": "Open Package.swift",
9186
"category": "Swift"
87+
},
88+
{
89+
"command": "swift.switchPlatform",
90+
"title": "Select Target Platform",
91+
"category": "Swift"
92+
},
93+
{
94+
"command": "swift.selectXcodeDeveloperDir",
95+
"title": "Select Xcode Developer Dir",
96+
"category": "Swift"
9297
}
9398
],
9499
"configuration": [
@@ -276,6 +281,10 @@
276281
{
277282
"command": "swift.openExternal",
278283
"when": "false"
284+
},
285+
{
286+
"command": "swift.selectXcodeDeveloperDir",
287+
"when": "isMac"
279288
}
280289
],
281290
"view/title": [

src/SwiftTaskProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ export class SwiftTaskProvider implements vscode.TaskProvider {
294294
"swift",
295295
new vscode.ShellExecution(swift, task.definition.args, {
296296
cwd: fullCwd,
297+
env: { ...configuration.swiftEnvironmentVariables, ...swiftRuntimeEnv() },
297298
}),
298299
task.problemMatchers
299300
);

src/commands.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import * as vscode from "vscode";
1616
import * as fs from "fs/promises";
1717
import * as path from "path";
18+
import configuration from "./configuration";
1819
import { FolderEvent, WorkspaceContext } from "./WorkspaceContext";
1920
import { createSwiftTask, SwiftTaskProvider } from "./SwiftTaskProvider";
2021
import { FolderContext } from "./FolderContext";
@@ -23,7 +24,6 @@ import { withQuickPick } from "./ui/QuickPick";
2324
import { execSwift } from "./utilities/utilities";
2425
import { Version } from "./utilities/version";
2526
import { DarwinCompatibleTarget, SwiftToolchain } from "./toolchain/toolchain";
26-
import configuration from "./configuration";
2727

2828
/**
2929
* References:
@@ -431,31 +431,68 @@ function openInExternalEditor(packageNode: PackageNode) {
431431
}
432432
}
433433

434-
interface DarwinQuickPickTarget extends vscode.QuickPickItem {
435-
value: DarwinCompatibleTarget;
436-
label: string;
437-
}
438-
439434
/**
440435
* Switches the target SDK to the platform selected in a QuickPick UI.
441436
*/
442437
async function switchPlatform() {
443-
const onSelect = async (picked: DarwinQuickPickTarget) => {
444-
const sdkForTarget = await SwiftToolchain.getSdkForTarget(picked.value);
445-
if (sdkForTarget) {
446-
configuration.sdk = sdkForTarget;
447-
} else {
448-
vscode.window.showErrorMessage("Unable to obtain requested SDK path");
449-
}
450-
};
451-
452-
await withQuickPick<DarwinQuickPickTarget>(
438+
await withQuickPick(
453439
"Select a new target",
454440
[
455441
{ value: DarwinCompatibleTarget.macOS, label: "macOS" },
456442
{ value: DarwinCompatibleTarget.iOS, label: "iOS" },
457443
],
458-
onSelect
444+
async picked => {
445+
const sdkForTarget = await SwiftToolchain.getSdkForTarget(picked.value);
446+
if (sdkForTarget) {
447+
configuration.sdk = sdkForTarget;
448+
} else {
449+
vscode.window.showErrorMessage("Unable to obtain requested SDK path");
450+
}
451+
}
452+
);
453+
}
454+
455+
/**
456+
* Choose DEVELOPER_DIR
457+
* @param workspaceContext
458+
*/
459+
async function selectXcodeDeveloperDir() {
460+
const defaultXcode = await SwiftToolchain.getXcodeDeveloperDir();
461+
const selectedXcode = configuration.swiftEnvironmentVariables.DEVELOPER_DIR;
462+
const xcodes = await SwiftToolchain.getXcodeInstalls();
463+
await withQuickPick(
464+
selectedXcode ?? defaultXcode,
465+
xcodes.map(xcode => {
466+
const developerDir = `${xcode}/Contents/Developer`;
467+
return {
468+
label: developerDir === defaultXcode ? `${xcode} (default)` : xcode,
469+
folder: developerDir === defaultXcode ? undefined : developerDir,
470+
};
471+
}),
472+
async selected => {
473+
let swiftEnv = configuration.swiftEnvironmentVariables;
474+
const previousDeveloperDir = swiftEnv.DEVELOPER_DIR ?? defaultXcode;
475+
if (selected.folder) {
476+
swiftEnv.DEVELOPER_DIR = selected.folder;
477+
} else if (swiftEnv.DEVELOPER_DIR) {
478+
// if DEVELOPER_DIR was set and the new folder is the default then
479+
// delete variable
480+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
481+
const { DEVELOPER_DIR, ...rest } = swiftEnv;
482+
swiftEnv = rest;
483+
}
484+
configuration.swiftEnvironmentVariables = swiftEnv;
485+
// if SDK is inside previous DEVELOPER_DIR then move to new DEVELOPER_DIR
486+
if (
487+
configuration.sdk.length > 0 &&
488+
configuration.sdk.startsWith(previousDeveloperDir)
489+
) {
490+
configuration.sdk = configuration.sdk.replace(
491+
previousDeveloperDir,
492+
selected.folder ?? defaultXcode
493+
);
494+
}
495+
}
459496
);
460497
}
461498

@@ -510,6 +547,9 @@ export function register(ctx: WorkspaceContext) {
510547
if (item instanceof PackageNode) {
511548
openInExternalEditor(item);
512549
}
513-
})
550+
}),
551+
vscode.commands.registerCommand("swift.selectXcodeDeveloperDir", () =>
552+
selectXcodeDeveloperDir()
553+
)
514554
);
515555
}

src/configuration.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ const configuration = {
8989
.getConfiguration("swift")
9090
.get<{ [key: string]: string }>("swiftEnvironmentVariables", {});
9191
},
92+
set swiftEnvironmentVariables(vars: { [key: string]: string }) {
93+
vscode.workspace.getConfiguration("swift").update("swiftEnvironmentVariables", vars);
94+
},
9295
/** include build errors in problems view */
9396
get problemMatchCompileErrors(): boolean {
9497
return vscode.workspace

src/toolchain/toolchain.ts

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

1515
import * as fs from "fs/promises";
1616
import * as path from "path";
17+
import { normalize } from "path";
1718
import * as plist from "plist";
1819
import * as vscode from "vscode";
1920
import configuration from "../configuration";
@@ -95,6 +96,49 @@ export class SwiftToolchain {
9596
);
9697
}
9798

99+
/**
100+
* Get active developer dir for Xcode
101+
*/
102+
public static async getXcodeDeveloperDir(): Promise<string> {
103+
const { stdout } = await execFile("xcode-select", ["-p"]);
104+
return stdout.trimEnd();
105+
}
106+
107+
/**
108+
* @param target Target to obtain the SDK path for
109+
* @returns path to the SDK for the target
110+
*/
111+
public static async getSdkForTarget(
112+
target: DarwinCompatibleTarget
113+
): Promise<string | undefined> {
114+
let sdkType: string;
115+
switch (target) {
116+
case DarwinCompatibleTarget.macOS:
117+
// macOS is the default target, so lets not update the SDK
118+
return undefined;
119+
case DarwinCompatibleTarget.iOS:
120+
sdkType = "iphoneos";
121+
break;
122+
}
123+
124+
// Include custom variables so that non-standard XCode installs can be better supported.
125+
const { stdout } = await execFile("xcrun", ["--sdk", sdkType, "--show-sdk-path"], {
126+
env: { ...process.env, ...configuration.swiftEnvironmentVariables },
127+
});
128+
return path.join(stdout.trimEnd());
129+
}
130+
131+
/**
132+
* Get list of Xcode versions intalled on mac
133+
* @returns Folders for each Xcode install
134+
*/
135+
public static async getXcodeInstalls(): Promise<string[]> {
136+
const { stdout: xcodes } = await execFile("mdfind", [
137+
`kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'`,
138+
]);
139+
return xcodes.trimEnd().split("\n");
140+
}
141+
98142
logDiagnostics(channel: SwiftOutputChannel) {
99143
channel.logDiagnostic(`Swift Path: ${this.swiftFolderPath}`);
100144
channel.logDiagnostic(`Toolchain Path: ${this.toolchainPath}`);
@@ -214,30 +258,6 @@ export class SwiftToolchain {
214258
return undefined;
215259
}
216260

217-
/**
218-
* @param target Target to obtain the SDK path for
219-
* @returns path to the SDK for the target
220-
*/
221-
public static async getSdkForTarget(
222-
target: DarwinCompatibleTarget
223-
): Promise<string | undefined> {
224-
let sdkType: string;
225-
switch (target) {
226-
case DarwinCompatibleTarget.macOS:
227-
sdkType = "macosx";
228-
break;
229-
case DarwinCompatibleTarget.iOS:
230-
sdkType = "iphoneos";
231-
break;
232-
}
233-
234-
// Include custom variables so that non-standard XCode installs can be better supported.
235-
const { stdout } = await execFile("xcrun", ["--sdk", sdkType, "--show-sdk-path"], {
236-
env: { ...process.env, ...configuration.swiftEnvironmentVariables },
237-
});
238-
return path.join(stdout.trimEnd());
239-
}
240-
241261
/**
242262
* @returns path to custom SDK
243263
*/
@@ -260,8 +280,8 @@ export class SwiftToolchain {
260280
): Promise<string | undefined> {
261281
switch (process.platform) {
262282
case "darwin": {
263-
const { stdout } = await execFile("xcode-select", ["-p"]);
264-
return path.join(stdout.trimEnd(), "usr", "bin");
283+
const developerDir = await this.getXcodeDeveloperDir();
284+
return path.join(developerDir, "usr", "bin");
265285
}
266286
case "win32": {
267287
// look up runtime library directory for XCTest alternatively

0 commit comments

Comments
 (0)