forked from swiftlang/vscode-swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutilities.ts
319 lines (303 loc) · 10.2 KB
/
utilities.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2021 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 * as cp from "child_process";
import * as path from "path";
import * as Stream from "stream";
import configuration from "../configuration";
import { FolderContext } from "../FolderContext";
import { SwiftToolchain } from "../toolchain/toolchain";
/**
* Get required environment variable for Swift product
*
* @param base base environment configuration
* @returns minimal required environment for Swift product
*/
export function swiftRuntimeEnv(
base: NodeJS.ProcessEnv | boolean = process.env
): { [key: string]: string } | undefined {
if (configuration.runtimePath === "") {
return undefined;
}
const runtimePath = configuration.runtimePath;
const key = swiftLibraryPathKey();
const separator = process.platform === "win32" ? ";" : ":";
switch (base) {
case false:
return { [key]: runtimePath };
case true:
return { [key]: `${runtimePath}${separator}\${env:${key}}` };
default:
return base[key]
? { [key]: `${runtimePath}${separator}${base[key]}` }
: { [key]: runtimePath };
}
}
/** Return environment variable to update for runtime library search path */
export function swiftLibraryPathKey(): string {
switch (process.platform) {
case "win32":
return "Path";
case "darwin":
return "DYLD_LIBRARY_PATH";
default:
return "LD_LIBRARY_PATH";
}
}
/**
* Asynchronous wrapper around {@link cp.execFile child_process.execFile}.
*
* Assumes output will be a string
*
* @param executable name of executable to run
* @param args arguments to be passed to executable
* @param options execution options
*/
export async function execFile(
executable: string,
args: string[],
options: cp.ExecFileOptions = {},
folderContext?: FolderContext,
customSwiftRuntime = true
): Promise<{ stdout: string; stderr: string }> {
folderContext?.workspaceContext.outputChannel.logDiagnostic(
`Exec: ${executable} ${args.join(" ")}`,
folderContext.name
);
if (customSwiftRuntime) {
const runtimeEnv = swiftRuntimeEnv(options.env);
if (runtimeEnv && Object.keys(runtimeEnv).length > 0) {
options.env = { ...(options.env ?? process.env), ...runtimeEnv };
}
}
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) =>
cp.execFile(executable, args, options, (error, stdout, stderr) => {
if (error) {
reject({ error, stdout, stderr, toString: () => error.message });
}
resolve({ stdout, stderr });
})
);
}
export async function execFileStreamOutput(
executable: string,
args: string[],
stdout: Stream.Writable | null,
stderr: Stream.Writable | null,
token: vscode.CancellationToken | null,
options: cp.ExecFileOptions = {},
folderContext?: FolderContext,
customSwiftRuntime = true,
killSignal: NodeJS.Signals = "SIGTERM"
): Promise<void> {
folderContext?.workspaceContext.outputChannel.logDiagnostic(
`Exec: ${executable} ${args.join(" ")}`,
folderContext.name
);
if (customSwiftRuntime) {
const runtimeEnv = swiftRuntimeEnv(options.env);
if (runtimeEnv && Object.keys(runtimeEnv).length > 0) {
options.env = { ...(options.env ?? process.env), ...runtimeEnv };
}
}
return new Promise<void>((resolve, reject) => {
let cancellation: vscode.Disposable;
const p = cp.execFile(executable, args, options, error => {
if (error) {
reject(error);
} else {
resolve();
}
if (cancellation) {
cancellation.dispose();
}
});
if (stdout) {
p.stdout?.pipe(stdout);
}
if (stderr) {
p.stderr?.pipe(stderr);
}
if (token) {
cancellation = token.onCancellationRequested(() => {
p.kill(killSignal);
});
}
});
}
/**
* Asynchronous wrapper around {@link cp.execFile child_process.execFile} running
* swift executable
*
* @param args array of arguments to pass to swift executable
* @param options execution options
* @param setSDKFlags whether to set SDK flags
*/
export async function execSwift(
args: string[],
toolchain: SwiftToolchain | "default",
options: cp.ExecFileOptions = {},
folderContext?: FolderContext
): Promise<{ stdout: string; stderr: string }> {
let swift: string;
if (toolchain === "default") {
swift = getSwiftExecutable();
} else {
swift = toolchain.getToolchainExecutable("swift");
}
if (toolchain !== "default") {
args = toolchain.buildFlags.withSwiftSDKFlags(args);
}
if (Object.keys(configuration.swiftEnvironmentVariables).length > 0) {
// when adding environment vars we either combine with vars passed
// into the function or the process environment vars
options.env = {
...(options.env ?? process.env),
...configuration.swiftEnvironmentVariables,
};
}
return await execFile(swift, args, options, folderContext);
}
/**
* Keep calling a function until it returns true
* @param fn function to test
* @param everyMilliseconds Time period between each call of the function
*/
export async function poll(fn: () => boolean, everyMilliseconds: number) {
while (!fn()) {
await wait(everyMilliseconds);
}
}
/**
* Wait for amount of time
* @param milliseconds Amount of time to wait
*/
export function wait(milliseconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
/**
* Get path to swift executable, or executable in swift bin folder
*
* @param exe name of executable to return
*/
export function getSwiftExecutable(exe = "swift"): string {
// should we add `.exe` at the end of the executable name
const windowsExeSuffix = process.platform === "win32" ? ".exe" : "";
return path.join(configuration.path, `${exe}${windowsExeSuffix}`);
}
/**
* Extracts the base name of a repository from its URL.
*
* The base name is the last path component of the URL, without the extension `.git`,
* and without an optional trailing slash.
*/
export function getRepositoryName(url: string): string {
// This regular expression consists of:
// - any number of characters that aren't a slash: ([^/]*)
// - optionally followed by a trailing slash: \/?
// - at the end of the URL: $
const pattern = /([^/]*)\/?$/;
// The capture group in this pattern will match the last path component of the URL.
let lastPathComponent = url.match(pattern)![1];
// Trim the optional .git extension.
if (lastPathComponent.endsWith(".git")) {
lastPathComponent = lastPathComponent.replace(/\.git$/, "");
}
return lastPathComponent;
}
/**
* Return random string
* @param length Length of string to return (max 16)
* @returns Random string
*/
export function randomString(length = 8): string {
return Math.random().toString(16).substring(2, length);
}
/**
* Return string description of Error object
* @param error Error object
* @returns String description of error
*/
export function getErrorDescription(error: unknown): string {
if (!error) {
return "No error provided";
} else if ((error as { stderr: string }).stderr) {
return (error as { stderr: string }).stderr;
} else if ((error as { error: string }).error) {
return JSON.stringify((error as { error: string }).error);
} else if (error instanceof Error) {
return error.message;
} else {
return JSON.stringify(error);
}
}
/**
* Convert array of strings into phrase eg "a, b and c"
* @param strings Array of strings
* @returns phrase
*/
export function stringArrayInEnglish(strings: string[]): string {
return strings.length === 1
? strings[0]
: [strings.slice(0, -1).join(", "), strings[strings.length - 1]].join(" and ");
}
/**
* String hashing function taken from https://stackoverflow.com/a/52171480/7831758
* @param str String to hash
* @param seed Seed for hash function
* @returns Hash of string
*/
export function hashString(str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
/**
* Transforms a file, line and optional column in to a vscode.Location.
* The line numbers are expected to start at 1, not 0.
* @param string A file path
* @param line A line number, starting at 1
* @param column An optional column
*/
export function sourceLocationToVSCodeLocation(
file: string,
line: number,
column?: number
): vscode.Location {
return new vscode.Location(vscode.Uri.file(file), new vscode.Position(line - 1, column ?? 0));
}
const regexEscapedCharacters = new Set(["(", ")", "[", "]", ".", "$", "^", "?", "|", "/", ":"]);
/**
* Escapes regular expression special characters with a backslash.
* @param string A string to escape
* @returns The escaped string
*/
export function regexEscapedString(string: string, omitting?: Set<string>): string {
let result = "";
for (const c of string) {
if (regexEscapedCharacters.has(c) && (!omitting || !omitting.has(c))) {
result += `\\${c}`;
} else {
result += c;
}
}
return result;
}