Skip to content

Commit 92977c2

Browse files
Warn users about lack of symlink privileges on Windows (#888)
* warn users about lack of symlink privileges on Windows * fix spelling of privilege Co-authored-by: award999 <[email protected]> * move notification code into its own file under src/ui --------- Co-authored-by: award999 <[email protected]>
1 parent 9b190a6 commit 92977c2

File tree

4 files changed

+92
-2
lines changed

4 files changed

+92
-2
lines changed

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,13 @@
357357
"Show the Swift build status as a progress notification."
358358
],
359359
"order": 17
360+
},
361+
"swift.warnAboutSymlinkCreation": {
362+
"type": "boolean",
363+
"default": true,
364+
"description": "Controls whether or not the extension will warn about being unable to create symlinks. (Windows only)",
365+
"scope": "application",
366+
"order": 18
360367
}
361368
}
362369
},
@@ -1187,4 +1194,4 @@
11871194
"vscode-languageclient": "^9.0.1",
11881195
"xml2js": "^0.6.2"
11891196
}
1190-
}
1197+
}

src/configuration.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,17 @@ const configuration = {
272272
.getConfiguration("swift")
273273
.get<OpenAfterCreateNewProjectOptions>("openAfterCreateNewProject", "prompt");
274274
},
275+
/** Whether or not the extension should warn about being unable to create symlinks on Windows */
276+
get warnAboutSymlinkCreation(): boolean {
277+
return vscode.workspace
278+
.getConfiguration("swift")
279+
.get<boolean>("warnAboutSymlinkCreation", true);
280+
},
281+
set warnAboutSymlinkCreation(value: boolean) {
282+
vscode.workspace
283+
.getConfiguration("swift")
284+
.update("warnAboutSymlinkCreation", value, vscode.ConfigurationTarget.Global);
285+
},
275286
};
276287

277288
export default configuration;

src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { showToolchainError } from "./ui/ToolchainSelection";
3434
import { SwiftToolchain } from "./toolchain/toolchain";
3535
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
3636
import { showReloadExtensionNotification } from "./ui/ReloadExtension";
37+
import { checkAndWarnAboutWindowsSymlinks } from "./ui/win32";
3738

3839
/**
3940
* External API as exposed by the extension. Can be queried by other extensions
@@ -49,8 +50,10 @@ export interface Api {
4950
export async function activate(context: vscode.ExtensionContext): Promise<Api | undefined> {
5051
try {
5152
console.debug("Activating Swift for Visual Studio Code...");
52-
5353
const outputChannel = new SwiftOutputChannel();
54+
55+
checkAndWarnAboutWindowsSymlinks(outputChannel);
56+
5457
const toolchain: SwiftToolchain | undefined = await SwiftToolchain.create()
5558
.then(toolchain => {
5659
outputChannel.log(toolchain.swiftVersionString);

src/ui/win32.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2024 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as fs from "fs/promises";
16+
import { SwiftOutputChannel } from "./SwiftOutputChannel";
17+
import { TemporaryFolder } from "../utilities/tempFolder";
18+
import configuration from "../configuration";
19+
import * as vscode from "vscode";
20+
21+
/**
22+
* Warns the user about lack of symbolic link support on Windows. Performs the
23+
* check in the background to avoid extending extension startup times.
24+
*
25+
* @param outputChannel The Swift output channel to log any errors to
26+
*/
27+
export function checkAndWarnAboutWindowsSymlinks(outputChannel: SwiftOutputChannel) {
28+
if (process.platform === "win32" && configuration.warnAboutSymlinkCreation) {
29+
isSymlinkAllowed(outputChannel).then(async canCreateSymlink => {
30+
if (canCreateSymlink) {
31+
return;
32+
}
33+
const selected = await vscode.window.showWarningMessage(
34+
"The Swift extension is unable to create symbolic links on your system and some features may not work correctly. Please either enable Developer Mode or allow symlink creation via Windows privileges.",
35+
"Learn More",
36+
"Don't Show Again"
37+
);
38+
if (selected === "Learn More") {
39+
return vscode.env.openExternal(
40+
vscode.Uri.parse(
41+
"https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
42+
)
43+
);
44+
} else if (selected === "Don't Show Again") {
45+
configuration.warnAboutSymlinkCreation = false;
46+
}
47+
});
48+
}
49+
}
50+
51+
/**
52+
* Checks to see if the platform allows creating symlinks.
53+
*
54+
* @returns whether or not a symlink can be created
55+
*/
56+
export async function isSymlinkAllowed(outputChannel?: SwiftOutputChannel): Promise<boolean> {
57+
const temporaryFolder = await TemporaryFolder.create();
58+
return await temporaryFolder.withTemporaryFile("", async testFilePath => {
59+
const testSymlinkPath = temporaryFolder.filename("symlink-");
60+
try {
61+
await fs.symlink(testFilePath, testSymlinkPath, "file");
62+
await fs.unlink(testSymlinkPath);
63+
return true;
64+
} catch (error) {
65+
outputChannel?.log(`${error}`);
66+
return false;
67+
}
68+
});
69+
}

0 commit comments

Comments
 (0)