Skip to content

Commit 7fe1d91

Browse files
authored
Notify user if the xcode-selected Xcode doesn't match setting (#1563)
* Notify user if the `xcode-select`ed Xcode doesn't match setting If the Xcode selected via xcode-select changes and the user has a "swift.path" setting configured, show warning notification suggesting they remove the setting or "Select Toolchain" Also trigger this warning on startup if the toolchain path is not under the selected Xcode.app Issue: #1472 * Fix lint error
1 parent 2647a6b commit 7fe1d91

File tree

4 files changed

+87
-11
lines changed

4 files changed

+87
-11
lines changed

src/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export enum Commands {
9999
COVER_ALL_TESTS = "swift.coverAllTests",
100100
OPEN_MANIFEST = "swift.openManifest",
101101
RESTART_LSP = "swift.restartLSPServer",
102+
SELECT_TOOLCHAIN = "swift.selectToolchain",
102103
}
103104

104105
/**

src/toolchain/SelectedXcodeWatcher.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as vscode from "vscode";
1717
import { SwiftOutputChannel } from "../ui/SwiftOutputChannel";
1818
import { showReloadExtensionNotification } from "../ui/ReloadExtension";
1919
import configuration from "../configuration";
20+
import { removeToolchainPath, selectToolchain } from "../ui/ToolchainSelection";
2021

2122
export class SelectedXcodeWatcher implements vscode.Disposable {
2223
private xcodePath: string | undefined;
@@ -66,20 +67,40 @@ export class SelectedXcodeWatcher implements vscode.Disposable {
6667
*/
6768
private async setup() {
6869
this.xcodePath = await this.xcodeSymlink();
70+
if (
71+
this.xcodePath &&
72+
configuration.path &&
73+
!configuration.path.startsWith(this.xcodePath)
74+
) {
75+
this.xcodePath = undefined; // Notify user when initially launching that xcode changed since last session
76+
}
6977
this.interval = setInterval(async () => {
7078
if (this.disposed) {
7179
return clearInterval(this.interval);
7280
}
7381

7482
const newXcodePath = await this.xcodeSymlink();
75-
if (!configuration.path && newXcodePath && this.xcodePath !== newXcodePath) {
83+
if (newXcodePath && this.xcodePath !== newXcodePath) {
7684
this.outputChannel.appendLine(
7785
`Selected Xcode changed from ${this.xcodePath} to ${newXcodePath}`
7886
);
7987
this.xcodePath = newXcodePath;
80-
await showReloadExtensionNotification(
81-
"The Swift Extension has detected a change in the selected Xcode. Please reload the extension to apply the changes."
82-
);
88+
if (!configuration.path) {
89+
await showReloadExtensionNotification(
90+
"The Swift Extension has detected a change in the selected Xcode. Please reload the extension to apply the changes."
91+
);
92+
} else {
93+
const selected = await vscode.window.showWarningMessage(
94+
'The Swift Extension has detected a change in the selected Xcode which does not match the value of your "swift.path" setting. Would you like to update your configured "swift.path" setting?',
95+
"Remove From Settings",
96+
"Select Toolchain"
97+
);
98+
if (selected === "Remove From Settings") {
99+
await removeToolchainPath();
100+
} else if (selected === "Select Toolchain") {
101+
await selectToolchain();
102+
}
103+
}
83104
}
84105
}, this.checkIntervalMs);
85106
}

src/ui/ToolchainSelection.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as path from "path";
1717
import { showReloadExtensionNotification } from "./ReloadExtension";
1818
import { SwiftToolchain } from "../toolchain/toolchain";
1919
import configuration from "../configuration";
20+
import { Commands } from "../commands";
2021

2122
/**
2223
* Open the installation page on Swift.org
@@ -28,7 +29,7 @@ export async function downloadToolchain() {
2829
"Select Toolchain"
2930
);
3031
if (selected === "Select Toolchain") {
31-
await vscode.commands.executeCommand("swift.selectToolchain");
32+
await selectToolchain();
3233
}
3334
}
3435
}
@@ -43,7 +44,7 @@ export async function installSwiftly() {
4344
"Select Toolchain"
4445
);
4546
if (selected === "Select Toolchain") {
46-
await vscode.commands.executeCommand("swift.selectToolchain");
47+
await selectToolchain();
4748
}
4849
}
4950
}
@@ -87,10 +88,14 @@ export async function showToolchainError(): Promise<void> {
8788
if (selected === "Remove From Settings") {
8889
await removeToolchainPath();
8990
} else if (selected === "Select Toolchain") {
90-
await vscode.commands.executeCommand("swift.selectToolchain");
91+
await selectToolchain();
9192
}
9293
}
9394

95+
export async function selectToolchain() {
96+
await vscode.commands.executeCommand(Commands.SELECT_TOOLCHAIN);
97+
}
98+
9499
/** A {@link vscode.QuickPickItem} that contains the path to an installed Swift toolchain */
95100
type SwiftToolchainItem = PublicSwiftToolchainItem | XcodeToolchainItem;
96101

@@ -351,7 +356,7 @@ async function showDeveloperDirQuickPick(xcodePaths: string[]): Promise<string |
351356
/**
352357
* Delete all set Swift path settings.
353358
*/
354-
async function removeToolchainPath() {
359+
export async function removeToolchainPath() {
355360
const swiftSettings = vscode.workspace.getConfiguration("swift");
356361
const swiftEnvironmentSettings = swiftSettings.inspect("swiftEnvironmentVariables");
357362
if (swiftEnvironmentSettings?.globalValue) {

test/unit-tests/toolchain/SelectedXcodeWatcher.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ import {
2525
mockObject,
2626
} from "../../MockUtils";
2727
import configuration from "../../../src/configuration";
28+
import { Commands } from "../../../src/commands";
2829

2930
suite("Selected Xcode Watcher", () => {
3031
const mockedVSCodeWindow = mockGlobalObject(vscode, "window");
3132
let mockOutputChannel: MockedObject<SwiftOutputChannel>;
3233
const pathConfig = mockGlobalValue(configuration, "path");
34+
const mockWorkspace = mockGlobalObject(vscode, "workspace");
35+
const mockCommands = mockGlobalObject(vscode, "commands");
36+
let mockSwiftConfig: MockedObject<vscode.WorkspaceConfiguration>;
3337

3438
setup(function () {
3539
// Xcode only exists on macOS, so the SelectedXcodeWatcher is macOS-only.
@@ -42,6 +46,12 @@ suite("Selected Xcode Watcher", () => {
4246
});
4347

4448
pathConfig.setValue("");
49+
50+
mockSwiftConfig = mockObject<vscode.WorkspaceConfiguration>({
51+
inspect: mockFn(),
52+
update: mockFn(),
53+
});
54+
mockWorkspace.getConfiguration.returns(instance(mockSwiftConfig));
4555
});
4656

4757
async function run(symLinksOnCallback: (string | undefined)[]) {
@@ -84,11 +94,50 @@ suite("Selected Xcode Watcher", () => {
8494
);
8595
});
8696

87-
test("Ignores when path is explicitly set", async () => {
97+
test("Warns that setting is out of date", async () => {
8898
pathConfig.setValue("/path/to/swift/bin");
8999

90-
await run(["/foo", "/bar"]);
100+
await run(["/path/to/swift/bin", "/foo", "/foo"]);
91101

92-
expect(mockedVSCodeWindow.showWarningMessage).to.have.not.been.called;
102+
expect(mockedVSCodeWindow.showWarningMessage).to.have.been.calledOnceWithExactly(
103+
'The Swift Extension has detected a change in the selected Xcode which does not match the value of your "swift.path" setting. Would you like to update your configured "swift.path" setting?',
104+
"Remove From Settings",
105+
"Select Toolchain"
106+
);
107+
});
108+
109+
test("Warns that setting is out of date on startup", async () => {
110+
pathConfig.setValue("/path/to/swift/bin");
111+
112+
await run(["/foo", "/foo"]);
113+
114+
expect(mockedVSCodeWindow.showWarningMessage).to.have.been.calledOnceWithExactly(
115+
'The Swift Extension has detected a change in the selected Xcode which does not match the value of your "swift.path" setting. Would you like to update your configured "swift.path" setting?',
116+
"Remove From Settings",
117+
"Select Toolchain"
118+
);
119+
});
120+
121+
test("Remove setting", async () => {
122+
pathConfig.setValue("/path/to/swift/bin");
123+
124+
mockedVSCodeWindow.showWarningMessage.resolves("Remove From Settings" as any);
125+
126+
await run(["/foo", "/foo"]);
127+
128+
expect(mockSwiftConfig.update.args).to.deep.equal([
129+
["path", undefined, vscode.ConfigurationTarget.Global],
130+
["path", undefined, vscode.ConfigurationTarget.Workspace],
131+
]);
132+
});
133+
134+
test("Select toolchain", async () => {
135+
pathConfig.setValue("/path/to/swift/bin");
136+
137+
mockedVSCodeWindow.showWarningMessage.resolves("Select Toolchain" as any);
138+
139+
await run(["/foo", "/foo"]);
140+
141+
expect(mockCommands.executeCommand).to.have.been.calledOnceWith(Commands.SELECT_TOOLCHAIN);
93142
});
94143
});

0 commit comments

Comments
 (0)