-
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
(#1244)
- Loading branch information
1 parent
8139b11
commit 5044a3d
Showing
3 changed files
with
170 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,75 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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(function () { | ||
// Xcode only exists on macOS, so the SelectedXcodeWatcher is macOS-only. | ||
if (process.platform !== "darwin") { | ||
this.skip(); | ||
} | ||
|
||
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" | ||
); | ||
}); | ||
}); |