-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prompt to restart extension on
xcode-select
If the user performs an `xcode-select` and doesn't have a `swift.path` set in their VS Code settings, prompt them to restart the extension to pick up the new Xcode path. This only applies on macOS where `xcode-select` is possible.
- Loading branch information
1 parent
3c116a4
commit 8d6c0db
Showing
3 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the VS Code Swift open source project | ||
// | ||
// Copyright (c) 2021-2024 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 * as fs from "fs/promises"; | ||
import * as vscode from "vscode"; | ||
import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; | ||
import { showReloadExtensionNotification } from "../ui/ReloadExtension"; | ||
import configuration from "../configuration"; | ||
|
||
export class SelectedXcodeWatcher implements vscode.Disposable { | ||
private xcodePath: string | undefined; | ||
private disposed: boolean = false; | ||
private interval: NodeJS.Timeout | undefined; | ||
private checkIntervalMs: number; | ||
private xcodeSymlink: () => Promise<string | undefined>; | ||
|
||
private static DEFAULT_CHECK_INTERVAL_MS = 2000; | ||
private static XCODE_SYMLINK_LOCATION = "/var/select/developer_dir"; | ||
|
||
constructor( | ||
private outputChannel: SwiftOutputChannel, | ||
testDependencies?: { | ||
checkIntervalMs?: number; | ||
xcodeSymlink?: () => Promise<string | undefined>; | ||
} | ||
) { | ||
this.checkIntervalMs = | ||
testDependencies?.checkIntervalMs || SelectedXcodeWatcher.DEFAULT_CHECK_INTERVAL_MS; | ||
this.xcodeSymlink = | ||
testDependencies?.xcodeSymlink || | ||
(async () => { | ||
try { | ||
return await fs.readlink(SelectedXcodeWatcher.XCODE_SYMLINK_LOCATION); | ||
} catch (e) { | ||
return undefined; | ||
} | ||
}); | ||
|
||
if (!this.isValidXcodePlatform()) { | ||
return; | ||
} | ||
|
||
// Deliberately not awaiting this, as we don't want to block the extension activation. | ||
this.setup(); | ||
} | ||
|
||
dispose() { | ||
this.disposed = true; | ||
clearInterval(this.interval); | ||
} | ||
|
||
/** | ||
* Polls the Xcode symlink location checking if it has changed. | ||
* If the user has `swift.path` set in their settings this check is skipped. | ||
*/ | ||
private async setup() { | ||
this.xcodePath = await this.xcodeSymlink(); | ||
this.interval = setInterval(async () => { | ||
if (this.disposed) { | ||
return clearInterval(this.interval); | ||
} | ||
|
||
const newXcodePath = await this.xcodeSymlink(); | ||
if (!configuration.path && newXcodePath && this.xcodePath !== newXcodePath) { | ||
this.outputChannel.appendLine( | ||
`Selected Xcode changed from ${this.xcodePath} to ${newXcodePath}` | ||
); | ||
showReloadExtensionNotification( | ||
"The Swift Extension has detected a change in the selected Xcode. Please reload the extension to apply the changes." | ||
); | ||
this.xcodePath = newXcodePath; | ||
} | ||
}, this.checkIntervalMs); | ||
} | ||
|
||
/** | ||
* Xcode selection is a macOS only concept. | ||
*/ | ||
private isValidXcodePlatform() { | ||
return process.platform === "darwin"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the VS Code Swift open source project | ||
// | ||
// Copyright (c) 2024 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 * as vscode from "vscode"; | ||
import { expect } from "chai"; | ||
import { SelectedXcodeWatcher } from "../../../src/toolchain/SelectedXcodeWatcher"; | ||
import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; | ||
import { instance, MockedObject, mockFn, mockGlobalObject, mockObject } from "../../MockUtils"; | ||
|
||
suite("Selected Xcode Watcher", () => { | ||
const mockedVSCodeWindow = mockGlobalObject(vscode, "window"); | ||
let mockOutputChannel: MockedObject<SwiftOutputChannel>; | ||
|
||
setup(() => { | ||
mockOutputChannel = mockObject<SwiftOutputChannel>({ | ||
appendLine: mockFn(), | ||
}); | ||
}); | ||
|
||
async function run(symLinksOnCallback: (string | undefined)[]) { | ||
return new Promise<void>(resolve => { | ||
let ctr = 0; | ||
const watcher = new SelectedXcodeWatcher(instance(mockOutputChannel), { | ||
checkIntervalMs: 1, | ||
xcodeSymlink: async () => { | ||
if (ctr >= symLinksOnCallback.length) { | ||
watcher.dispose(); | ||
resolve(); | ||
return; | ||
} | ||
const response = symLinksOnCallback[ctr]; | ||
ctr += 1; | ||
return response; | ||
}, | ||
}); | ||
}); | ||
} | ||
|
||
test("Does nothing when the symlink is undefined", async () => { | ||
await run([undefined, undefined]); | ||
|
||
expect(mockedVSCodeWindow.showWarningMessage).to.have.not.been.called; | ||
}); | ||
|
||
test("Does nothing when the symlink is identical", async () => { | ||
await run(["/foo", "/foo"]); | ||
|
||
expect(mockedVSCodeWindow.showWarningMessage).to.have.not.been.called; | ||
}); | ||
|
||
test("Prompts to restart when the symlink changes", async () => { | ||
await run(["/foo", "/bar"]); | ||
|
||
expect(mockedVSCodeWindow.showWarningMessage).to.have.been.calledOnceWithExactly( | ||
"The Swift Extension has detected a change in the selected Xcode. Please reload the extension to apply the changes.", | ||
"Reload Extensions" | ||
); | ||
}); | ||
}); |