From 4e9af558d42d1e44ae02ed583a2f8a47f8f4a73b Mon Sep 17 00:00:00 2001 From: Jan Jurzitza Date: Fri, 5 Nov 2021 14:52:57 +0100 Subject: [PATCH 1/8] start fix #370 --- package.json | 44 +++++++++++++++++++++++++ res/walkthroughs/debugProject.md | 1 + res/walkthroughs/installCompiler.md | 51 +++++++++++++++++++++++++++++ res/walkthroughs/userSettings.md | 20 +++++++++++ 4 files changed, 116 insertions(+) create mode 100644 res/walkthroughs/debugProject.md create mode 100644 res/walkthroughs/installCompiler.md create mode 100644 res/walkthroughs/userSettings.md diff --git a/package.json b/package.json index e672fa9..47eba0c 100644 --- a/package.json +++ b/package.json @@ -1181,6 +1181,50 @@ "message": 5 } } + ], + "walkthroughs": [ + { + "id": "code-d.welcome", + "title": "Get Started with D Development", + "description": "Setup your development experience for the D programming language.", + "steps": [ + { + "id": "code-d.welcome.installCompiler", + "title": "Install and choose a D compiler (DMD, LDC or GCC)", + "description": "Run the 'Setup D Compiler' command to choose which D compiler to use with code-d and DUB. You can find available compilers on https://dlang.org/download.html", + "completionEvents": [ + "onCommand:code-d.setupCompiler" + ], + "media": { + "markdown": "res/walkthroughs/installCompiler.md" + } + }, + { + "id": "code-d.welcome.userSettings", + "title": "Configure code-d", + "description": "Review some common user settings", + "media": { + "markdown": "res/walkthroughs/userSettings.md" + } + }, + { + "id": "code-d.welcome.checkUserGuide", + "title": "Check out user guide", + "description": "The included code-d user guide contains tutorials and a description on all features of code-d. Make sure you read it!", + "completionEvents": [ + "onCommand:code-d.viewUserGuide" + ] + }, + { + "id": "code-d.welcome.debugProject", + "title": "Run and Debug your first project", + "description": "Code-D includes debugging plugins to improve the D debugging experience. Get started with a project and debug it from code-d.", + "media": { + "markdown": "res/walkthroughs/debugProject.md" + } + } + ] + } ] }, "scripts": { diff --git a/res/walkthroughs/debugProject.md b/res/walkthroughs/debugProject.md new file mode 100644 index 0000000..ec6e92c --- /dev/null +++ b/res/walkthroughs/debugProject.md @@ -0,0 +1 @@ +# Debugging Projects diff --git a/res/walkthroughs/installCompiler.md b/res/walkthroughs/installCompiler.md new file mode 100644 index 0000000..418796b --- /dev/null +++ b/res/walkthroughs/installCompiler.md @@ -0,0 +1,51 @@ +# Installing a D Compiler + +To run D code you first need to build it. To build it a compiler must be +installed on the system. + +There are several D compilers to choose from, you can find a list of them under +https://dlang.org/download.html + +## DMD + +DMD is the official reference compiler for D. It always implements the latest +features and the latest standard library + runtime. Additionally it is the +fastest compiler out of the 3, although it doesn't produce the fastest +executables. It's a good compiler for prototyping and quick scripts and is the +recommended compiler to get if you don't know which one to get. + +Download: https://dlang.org/download.html#dmd + +## LDC + +LDC is an LLVM-based (like clang) D compiler. It supports a variety of operating +systems and target architectures and has very frequent releases, usually being +up-to-date to the DMD reference compiler within days. It takes longer to compile +executables but results in much better optimized executables than with DMD. LDC +is a good compiler to be using in production to create executables. + +Installation (package manager): https://github.com/ldc-developers/ldc#installation + +Download (executables): https://github.com/ldc-developers/ldc/releases + +## GDC + +GDC is an GCC-based D compiler. Other than the other compilers it comes built-in +into GCC and does not require a separate installation. It supports a variety of +operating systems and target architectures. Being tied to GCC's release schedule +it is often behind in new features but receives bug fix backports from later +versions in minor updates. GDC could be called the most stable compiler as it is +tied to specific D frontend versions for a while, only fixing issues without big +changes. GDC is a good compiler to be using in production to create executables. + +GDC is included in GCC since GCC 9.0, installing a recent GCC version should +have it included by default. + +For Linux distributions packaging the backends in different packages or Windows +downloads see https://www.gdcproject.org/downloads + +--- + +The installation packages of DMD and LDC come with various utilities (DUB, rdmd) +installed. If you don't have these utilities because you used your Operating +System's package manager or installed GDC, code-d will download the executables. diff --git a/res/walkthroughs/userSettings.md b/res/walkthroughs/userSettings.md new file mode 100644 index 0000000..82acf08 --- /dev/null +++ b/res/walkthroughs/userSettings.md @@ -0,0 +1,20 @@ +# Configuring Code-D + +Code-D uses the standard VSCode user settings. Open the user settings and check +out the section `Extensions > D` to view or edit all the settings. + +In the JSON settings editor the settings all start with `"d."` for D related +settings, `"dfmt."` for formatter related settings and `"dscanner."` for linting +related settings. + +Some features are disabled by default because they are not fully ready for all +use-cases yet or may significantly increase resource usage on low-spec machines. +If you want to, do give these features a try and report issues on +[GitHub](https://github.com/Pure-D/code-d/issues). + +Code-D is using a language server protocol called Serve-D to implement all of +its features. Serve-D gets more frequent updates than Code-D and these will +automatically be downloaded whenever a new stable release gets released. If you +don't want to receive automatic updates or want to receive more frequent beta or +nightly updates, configure the `d.servedReleaseChannel` user setting to your +liking and reload the window. From fdc468897dfd5995fc881004b2379076b86626e6 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Thu, 18 Nov 2021 22:53:45 +0100 Subject: [PATCH 2/8] add compiler detection & setup command supports dmd, ldc, gdc --- package-lock.json | 6 +- package.json | 36 +++-- src/compilers.ts | 345 ++++++++++++++++++++++++++++++++++++++++++++++ src/extension.ts | 83 +++++------ 4 files changed, 407 insertions(+), 63 deletions(-) create mode 100644 src/compilers.ts diff --git a/package-lock.json b/package-lock.json index 6aebdb8..e6b1a10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "code-d", - "version": "0.23.0-beta.2", + "version": "0.23.0-beta.3", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.23.0-beta.2", + "name": "code-d", + "version": "0.23.0-beta.3", "license": "MIT", "dependencies": { "adm-zip": "^0.4.13", @@ -1688,6 +1689,7 @@ "resolved": "https://registry.npmjs.org/oniguruma/-/oniguruma-7.2.1.tgz", "integrity": "sha512-WPS/e1uzhswPtJSe+Zls/kAj27+lEqZjCmRSjnYk/Z4L2Mu+lJC2JWtkZhPJe4kZeTQfz7ClcLyXlI4J68MG2w==", "dev": true, + "hasInstallScript": true, "dependencies": { "nan": "^2.14.0" } diff --git a/package.json b/package.json index 47eba0c..80df3b8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-d", "displayName": "D Programming Language (code-d)", "description": "auto-complete, snippets, linter and formatter for dlang", - "version": "0.22.0", + "version": "0.23.0-beta.3", "publisher": "webfreak", "repository": { "type": "git", @@ -46,7 +46,8 @@ "onCommand:code-d.addImport", "onCommand:code-d.searchDocs", "onCommand:code-d.openDocsAtCursor", - "onCommand:code-d.convertDubRecipe" + "onCommand:code-d.convertDubRecipe", + "onCommand:code-d.setupCompiler" ], "main": "./out/extension", "keywords": [ @@ -1055,6 +1056,11 @@ "command": "code-d.viewUserGuide", "title": "Open User Guide / Documentation", "category": "code-d" + }, + { + "command": "code-d.setupCompiler", + "title": "Setup D Compiler", + "category": "code-d" } ], "jsonValidation": [ @@ -1184,14 +1190,14 @@ ], "walkthroughs": [ { - "id": "code-d.welcome", + "id": "welcome", "title": "Get Started with D Development", "description": "Setup your development experience for the D programming language.", "steps": [ { - "id": "code-d.welcome.installCompiler", - "title": "Install and choose a D compiler (DMD, LDC or GCC)", - "description": "Run the 'Setup D Compiler' command to choose which D compiler to use with code-d and DUB. You can find available compilers on https://dlang.org/download.html", + "id": "welcome.installCompiler", + "title": "Install and choose a D compiler (DMD, LDC or GDC)", + "description": "Run the 'Setup D Compiler' command to choose which D compiler to use with code-d and DUB. You can find available compilers on [https://dlang.org/download.html](https://dlang.org/download.html)\n[Setup D Compiler](command:code-d.setupCompiler)", "completionEvents": [ "onCommand:code-d.setupCompiler" ], @@ -1200,7 +1206,7 @@ } }, { - "id": "code-d.welcome.userSettings", + "id": "welcome.userSettings", "title": "Configure code-d", "description": "Review some common user settings", "media": { @@ -1208,15 +1214,19 @@ } }, { - "id": "code-d.welcome.checkUserGuide", - "title": "Check out user guide", - "description": "The included code-d user guide contains tutorials and a description on all features of code-d. Make sure you read it!", + "id": "welcome.checkUserGuide", + "title": "Check out the user guide", + "description": "The included code-d user guide contains tutorials and a description on all features of code-d. Make sure you read it!\n[Open User Guide / Documentation](command:code-d.viewUserGuide)", "completionEvents": [ - "onCommand:code-d.viewUserGuide" - ] + "onCommand:code-d.viewUserGuide", + "onStepSelected" + ], + "media": { + "markdown": "docs/index.md" + } }, { - "id": "code-d.welcome.debugProject", + "id": "welcome.debugProject", "title": "Run and Debug your first project", "description": "Code-D includes debugging plugins to improve the D debugging experience. Get started with a project and debug it from code-d.", "media": { diff --git a/src/compilers.ts b/src/compilers.ts new file mode 100644 index 0000000..10b3ca2 --- /dev/null +++ b/src/compilers.ts @@ -0,0 +1,345 @@ +import * as vscode from 'vscode'; +import * as which from "which"; +import * as fs from "fs"; +import * as path from "path"; +import * as ChildProcess from "child_process"; +import { config } from './extension'; + +export interface DetectedCompiler { + /** + * `false` if not a valid executable compiler, set to the compiler name + * (dmd, ldc or gdc) otherwise. + */ + has: "dmd" | "ldc" | "gdc" | false; + version?: string; + frontendVersion?: string; + path?: string; + inPath?: boolean; + importPaths?: string[]; +}; + +export function registerCompilerInstaller(): vscode.Disposable { + return vscode.commands.registerCommand("code-d.setupCompiler", (args) => { + setupCompilersUI(); + }); +} + +type UIQuickPickItem = vscode.QuickPickItem & { kind?: number, installInfo?: any, action?: Function }; +export async function setupCompilersUI() { + const introQuickPick = vscode.window.createQuickPick(); + introQuickPick.title = "Setup auto-detected compiler or manually configure compiler"; + const compilers: DetectedCompiler[] = await listCompilers(); + let items: UIQuickPickItem[] = []; + for (let i = 0; i < compilers.length; i++) { + if (i == 0) { + items.push({ + label: "$(find-expanded) Detected installations", + kind: 2, // proposed type for separators: https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts + }); + } + + const compiler = compilers[i]; + if (compiler.has && compiler.path) { + let versionStrings: string[] = []; + if (compiler.version) { + if (compiler.has == "gdc") + versionStrings.push("gcc " + compiler.version); + else + versionStrings.push(compiler.version); + } + if (compiler.frontendVersion && compiler.frontendVersion != compiler.version) + versionStrings.push("spec version " + compiler.frontendVersion); + items.push({ + label: compiler.has, + description: versionStrings.length > 0 ? versionStrings.join(" ・ ") : undefined, + installInfo: compiler + }); + } + } + items.push({ + label: "$(find-expanded) Manual configuration", + kind: 2, + }); + let manualSelect: vscode.QuickPickItem; + let dmdItem: vscode.QuickPickItem; + let ldcItem: vscode.QuickPickItem; + let gdcItem: vscode.QuickPickItem; + items.push(dmdItem = { + label: "DMD", + description: "The reference D compiler ・ latest features, fast compilation" + }); + if (compilers.length == 0) + dmdItem.detail = "$(getting-started-beginner) Recommended for beginners"; + items.push(ldcItem = { + label: "LDC", + description: "LLVM-based D compiler ・ recent features, great optimization" + }); + items.push(gdcItem = { + label: "GDC", + description: "GCC-based D compiler ・ stable, great optimization" + }); + items.push(manualSelect = { + label: "Select installation folder", + description: "if you have already installed a D compiler that is not being picked up" + }); + introQuickPick.items = items; + introQuickPick.show(); + + introQuickPick.onDidAccept((e) => { + let selection = introQuickPick.selectedItems[0]; + if (selection.kind === 2) + return; + + introQuickPick.hide(); + if (selection.installInfo) { + showDetectedCompilerInstallPrompt(selection.installInfo); + } else { + switch (selection) { + case dmdItem: + break; + case ldcItem: + break; + case gdcItem: + break; + case manualSelect: + break; + default: + console.error("invalid selection"); + introQuickPick.show(); + break; + } + } + }); +} + +export async function showDetectedCompilerInstallPrompt(compiler: DetectedCompiler) { + const installPrompt = vscode.window.createQuickPick(); + installPrompt.title = "Configure " + compiler.has + " compiler"; + let items: UIQuickPickItem[] = []; + let checked: UIQuickPickItem[] = []; + + if (!compiler.path) + throw new Error("Missing compiler path"); + + function makeSettingButton(label: string, settings: [string, any][], detail?: string): UIQuickPickItem { + return { + label: label, + description: "$(settings) " + settings.map(setting => "\"d." + setting[0] + "\": " + JSON.stringify(setting[1])).join(", "), + detail: detail, + action: function() { + settings.forEach(setting => { + config(null).update(setting[0], setting[1], vscode.ConfigurationTarget.Global); + }); + } + }; + } + function check(b: UIQuickPickItem): UIQuickPickItem { + checked.push(b); + return b; + } + + items.push(check(makeSettingButton( + "Configure for auto completion and tasks", + [["dubCompiler", compiler.inPath ? path.basename(compiler.path) : compiler.path], ["stdlibPath", compiler.importPaths || "auto"]], + "This setting is needed for auto completion and build and debug tasks" + ))); + + let dir = path.dirname(compiler.path); + let dubExe = path.join(dir, process.platform == "win32" ? "dub.exe" : "dub"); + if (fs.existsSync(dubExe)) { + items.push(check(makeSettingButton( + "Use included DUB executable", + [["dubPath", dubExe]], + "DUB is used for building the project through build tasks and debugging" + ))); + } + + if (compiler.has == "dmd") { + items.push(makeSettingButton( + "Enable import timing code lens", + [["dmdPath", compiler.path], ["enableDMDImportTiming", true]], + "[EXPERIMENTAL] This is an experimental feature to see how imports affect compilation speed" + )); + } + + installPrompt.items = items; + installPrompt.selectedItems = checked; + installPrompt.canSelectMany = true; + installPrompt.buttons = [vscode.QuickInputButtons.Back]; + installPrompt.show(); + + installPrompt.onDidAccept((e) => { + let selection = installPrompt.selectedItems; + installPrompt.hide(); + for (let i = 0; i < selection.length; i++) { + const btn = selection[i]; + if (btn.action) + btn.action(); + } + }); + installPrompt.onDidTriggerButton(async (e) => { + if (e == vscode.QuickInputButtons.Back) { + await setupCompilersUI(); + installPrompt.hide(); + } + }) +} + +export async function checkCompilers(): Promise { + const compilers = await listCompilers(); + let dmdIndex = -1; + let ldcIndex = -1; + let gdcIndex = -1; + let fallbackPath: string | undefined = undefined; + for (let i = 0; i < compilers.length; i++) { + const compiler = compilers[i]; + if (compiler.has) { + switch (compiler.has) { + case "dmd": dmdIndex = i; break; + case "ldc": ldcIndex = i; break; + case "gdc": gdcIndex = i; break; + default: console.error("unexpected state in code-d?!"); break; + } + } + fallbackPath = fallbackPath || compiler.path; + } + if (dmdIndex != -1) + return compilers[dmdIndex]; + else if (ldcIndex != -1) + return compilers[ldcIndex]; + else if (gdcIndex != -1) + return compilers[gdcIndex]; + else + return { has: false, path: fallbackPath }; +} + +let listCompilersCache: DetectedCompiler[] | undefined = undefined; +export async function listCompilers(): Promise { + if (listCompilersCache !== undefined) + return listCompilersCache; + else + return listCompilersCache = await listCompilersImpl(); +} + +export async function listCompilersImpl(): Promise { + const compilers = ["dmd", "ldc2", "ldc", "gdc", "gcc"]; + let ret: DetectedCompiler[] = []; + let fallbackPath: string | undefined = undefined; + for (let i = 0; i < compilers.length; i++) { + const check = compilers[i]; + let result = await checkCompiler(check); + fallbackPath = fallbackPath || result.path; + if (result && result.has) { + result.has = check == "ldc2" ? "ldc" : check; + ret.push(result); + if (check == "ldc2" || check == "gdc") + i++; // skip ldc / gcc + } + } + if (ret.length == 0 && fallbackPath) + ret.push({ has: false, path: fallbackPath }); + return ret; +} + +const gdcVersionRegex = /^gcc version\s+v?(\d+(?:\.\d+)+)/gm; +const gdcFeVersionRegex = /^version\s+v?(\d+(?:\.\d+)+)/gm; +const gdcImportPathRegex = /^import path\s*\[\d+\]\s*=\s*(.+)/gm; +const ldcVersionRegex = /^LDC - the LLVM D compiler \(v?(\d+(?:\.\d+)+).*\)/gim; +const ldcFeVersionRegex = /based on DMD v?(\d+(?:\.\d+)+)/gim; +const dmdVersionRegex = /^DMD(?:32|64) D Compiler v?(\d+(?:\.\d+)+)/gim; +async function checkCompiler(compiler: "dmd" | "ldc" | "ldc2" | "gdc" | "gcc", compilerPath?: string): Promise { + const isGDC = compiler == "gdc" || compiler == "gcc"; + let inPath = false; + try { + if (!compilerPath) { + compilerPath = await which(compiler); + inPath = true; + } + } catch (e) { + return { has: false }; + } + + if (!compilerPath || !fs.existsSync(compilerPath)) + return { has: false }; + + let versionArgs = ["--version"]; + if (isGDC) + versionArgs = ["-xd", "-fsyntax-only", "-v", "-"]; + + let proc: ChildProcess.ChildProcess; + try { + proc = ChildProcess.spawn(compilerPath, versionArgs, { + stdio: [isGDC ? "pipe" : "ignore", "pipe", isGDC ? "pipe" : "ignore"] + }); + } catch (err) { + return { has: false, path: compilerPath }; + } + + return await new Promise((resolve) => { + let stdout: string = ""; + proc.stdout!.on("data", (chunk) => { + stdout += chunk.toString(); + }); + if (isGDC) { + proc.stderr!.on("data", (chunk) => { + stdout += chunk.toString(); + }); + proc.stdin!.end(); + } + proc.on("error", function () { + resolve({ has: false, path: compilerPath }); + }).on("exit", function () { + let beVersionRegex: RegExp | undefined; + let feVersionRegex: RegExp | undefined; + let importRegex: RegExp | undefined; + let has: string | boolean; + switch (compiler) { + case "dmd": + beVersionRegex = feVersionRegex = dmdVersionRegex; + has = "dmd"; + break; + case "gdc": + case "gcc": + beVersionRegex = gdcVersionRegex; + feVersionRegex = gdcFeVersionRegex; + importRegex = gdcImportPathRegex; + has = "gdc"; + break; + case "ldc": + case "ldc2": + feVersionRegex = ldcFeVersionRegex; + beVersionRegex = ldcVersionRegex; + has = "ldc"; + break; + default: + has = true; + break; + } + let ret: DetectedCompiler = { + has: has, + path: compilerPath, + inPath: inPath + }; + let m: RegExpMatchArray | null | undefined; + if (beVersionRegex) beVersionRegex.lastIndex = 0; + if (m = beVersionRegex?.exec(stdout)) { + ret.version = m[1]; + } + if (feVersionRegex) feVersionRegex.lastIndex = 0; + if (m = feVersionRegex?.exec(stdout)) { + ret.frontendVersion = m[1]; + } + if (importRegex) { + importRegex.lastIndex = 0; + let imports: string[] = []; + let importMatch: RegExpExecArray | null; + while ((importMatch = importRegex.exec(stdout)) != null) { + imports.push(importMatch[1]); + } + if (imports.length > 0) + ret.importPaths = imports; + } + resolve(ret); + }); + }); +} diff --git a/src/extension.ts b/src/extension.ts index 9b722c7..6d97f68 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,6 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, DocumentFilter, N import { setContext, installServeD, compileServeD, getInstallOutput, downloadFileInteractive, findLatestServeD, cmpSemver, extractServedBuiltDate, Release, updateAndInstallServeD } from "./installer"; import { EventEmitter } from "events"; import * as ChildProcess from "child_process"; -import * as which from "which"; import { TestHub, testExplorerExtensionId, TestController, TestAdapter } from 'vscode-test-adapter-api'; import * as mode from "./dmode"; @@ -25,6 +24,7 @@ import { restoreCreateProjectPackageBackup } from "./project-creator"; import { TestAdapterGenerator, UnittestProject } from "./testprovider"; import { registerDebuggers, linkDebuggersWithServed } from "./debug"; import { DubTasksProvider } from "./dub-tasks"; +import { checkCompilers, DetectedCompiler, registerCompilerInstaller } from "./compilers"; class CustomErrorHandler implements ErrorHandler { private restarts: number[]; @@ -416,6 +416,8 @@ export function activate(context: vscode.ExtensionContext): CodedAPI { context.subscriptions.push(addSDLProviders()); context.subscriptions.push(addJSONProviders()); + context.subscriptions.push(registerCompilerInstaller()); + registerCommands(context); registerDebuggers(context); @@ -470,15 +472,40 @@ async function preStartup(context: vscode.ExtensionContext) { await restoreCreateProjectPackageBackup(context); - let presentCompiler: { has: string | false, path?: string } | undefined; - if (!context.globalState.get("checkedCompiler", false)) { + let presentCompiler: DetectedCompiler | undefined; + if (context.globalState.get("checkedCompiler", 0) != 2) { console.log("Checking if compiler is present"); presentCompiler = await checkCompilers(); - context.globalState.update("checkedCompiler", true); - if (!presentCompiler.has) - vscode.env.openExternal(vscode.Uri.parse("https://dlang.org/download.html")).then(() => { - vscode.window.showInformationMessage("Please install a D compiler from dlang.org and reload the window once done."); - }); + context.globalState.update("checkedCompiler", 2); + let setupDCompiler = "Change D Compiler"; + let gettingStarted = "Getting Started"; + if (presentCompiler && presentCompiler.has) { + let compilerSpec = presentCompiler.has; + if (presentCompiler.version) + compilerSpec += " " + presentCompiler.version; + vscode.window.showInformationMessage("code-d has auto-detected " + compilerSpec + " and preconfigured it. " + + "If you would like to use another compiler, please click the button below.", + setupDCompiler, gettingStarted) + .then(btn => { + if (btn == setupDCompiler) { + vscode.commands.executeCommand("code-d.setupCompiler"); + } else if (btn == gettingStarted) { + vscode.commands.executeCommand("workbench.action.openWalkthrough", "webfreak.code-d#welcome"); + } + }); + } else { + gettingStarted = "First time setup"; + vscode.window.showWarningMessage( + "code-d has not detected any compatible D compiler. Please click the button below to install and configure " + + "a D compiler on your system or just for code-d. Auto completion will not contain any standard " + + "library symbols and building projects will not work until then.", + gettingStarted) + .then(btn => { + if (btn == gettingStarted) { + vscode.commands.executeCommand("workbench.action.openWalkthrough", "webfreak.code-d#welcome"); + } + }); + } } async function checkDub(dubPath: string | undefined, updateSetting: boolean = false): Promise { @@ -708,46 +735,6 @@ async function preStartup(context: vscode.ExtensionContext) { } } -async function checkCompiler(compiler: string): Promise<{ has: boolean, path?: string }> { - let compilerPath: string; - try { - compilerPath = await which(compiler); - } catch (e) { - return { has: false }; - } - - if (!compilerPath) - return { has: false }; - - let proc: ChildProcess.ChildProcessWithoutNullStreams; - try { - proc = ChildProcess.spawn(compilerPath, ["--version"]); - } catch (err) { - return { has: false, path: compilerPath }; - } - - return await new Promise((resolve) => { - proc.on("error", function () { - resolve({ has: false, path: compilerPath }); - }).on("exit", function () { - resolve({ has: true, path: compilerPath }); - }); - }); -} - -async function checkCompilers(): Promise<{ has: string | false, path?: string }> { - const compilers = ["dmd", "ldc", "ldc2", "gdc"]; - let fallbackPath: string | undefined = undefined; - for (let i = 0; i < compilers.length; i++) { - const check = compilers[i]; - let result = await checkCompiler(check); - fallbackPath = fallbackPath || result.path; - if (result && result.has) - return { has: check, path: result.path }; - } - return { has: false, path: fallbackPath }; -} - function spawnOneShotCheck(program: string, args: string[], captureOutput: boolean = false, options: any = undefined): Promise { let proc: ChildProcess.ChildProcessWithoutNullStreams; try { From aefaba5335846ed8e575f1fabecb8c36d3fd4cb0 Mon Sep 17 00:00:00 2001 From: Jan Jurzitza Date: Fri, 19 Nov 2021 14:50:17 +0100 Subject: [PATCH 3/8] bundle install.sh, first installer buttons fixes startup detection not actually configuring compiler --- res/exe/install.sh | 1303 ++++++++++++++++++++++++++++++++++++++++++++ src/compilers.ts | 259 +++++++-- src/extension.ts | 10 +- src/installer.ts | 5 +- 4 files changed, 1538 insertions(+), 39 deletions(-) create mode 100644 res/exe/install.sh diff --git a/res/exe/install.sh b/res/exe/install.sh new file mode 100644 index 0000000..92635f1 --- /dev/null +++ b/res/exe/install.sh @@ -0,0 +1,1303 @@ +#!/usr/bin/env bash +# +# Copyright: Copyright Martin Nowak 2015 -. +# License: Boost License 1.0 (www.boost.org/LICENSE_1_0.txt) +# Authors: Martin Nowak +# Documentation: https://dlang.org/install.html + +_() { + +# Returns false if the script is invoked from a Windows command prompt. +posix_terminal() { + # If run from a POSIX terminal (including under MSYS2 or Cygwin) the TERM + # variable is defined to something like "xterm". If run from a Windows + # command prompt through bash.exe from an MSYS installation, TERM keeps + # its default value, which is "cygwin". + if [[ "${TERM:-noterm}" == "cygwin" ]]; then + false + else + true + fi +} + +if ! posix_terminal; then + # We have been invoked from Windows cmd. Run the login script. + # (Cannot use --login bash option, as that runs /etc/bash.bash_logout + # afterwards which typically clears the screen, removing any output.) + if [ -r /etc/profile ]; then + # shellcheck disable=SC1091 + source /etc/profile + else + fatal "Failed to source /etc/profile."; + fi +fi + +# Earliest opportunity for security settings, tolerate insecure /etc/profile. +set -ueo pipefail + +# ------------------------------------------------------------------------------ + +log_() { + local tilde='~' + echo "${@//$HOME/$tilde}" +} + +log() { + if [ "$VERBOSITY" -gt 0 ]; then + log_ "$@" + fi +} + +logV() { + if [ "$VERBOSITY" -gt 1 ]; then + log_ "$@" + fi +} + +logE() { + log_ "$@" >&2 +} + +fatal() { + logE "$@" + exit 1 +} + +# ------------------------------------------------------------------------------ +curl2() { + : "${CURL_USER_AGENT:="installer/install.sh $(command curl --version | head -n 1)"}" + TIMEOUT_ARGS=(--connect-timeout 5 --speed-time 30 --speed-limit 1024) + command curl --fail "${TIMEOUT_ARGS[@]}" -L -A "$CURL_USER_AGENT" "$@" +} + +curl() { + if [ "$VERBOSITY" -gt 0 ]; then + curl2 -# "$@" + else + curl2 -sS "$@" + fi +} + +retry() { + for i in {0..4}; do + if "$@"; then + break + elif [ $i -lt 4 ]; then + sleep $((1 << i)) + else + fatal "Failed to download '$url'" + fi + done +} + +# path, verify (0/1), urls... +# the gpg signature is assumed to be url+.sig +download() { + local path do_verify mirrors + path="$1" + do_verify="$2" + mirrors=("${@:3}") + + try_all_mirrors() { + for i in "${!mirrors[@]}" ; do + if [ "$i" -gt 0 ] ; then + log "Falling back to mirror: ${mirrors[$i]}" + fi + if curl "${mirrors[$i]}" -o "$path" ; then + return + fi + done + return 1 + } + retry try_all_mirrors + if [ "$do_verify" -eq 1 ]; then + verify "$path" "${mirrors[@]/%/.sig}" + fi +} + +# path, urls... +download_with_verify() { + download "$1" 1 "${@:2}" +} + +# path, urls... +download_without_verify() { + download "$1" 0 "${@:2}" +} + +# urls... +fetch() { + local mirrors path + path=$(mktemp "$TMP_ROOT/XXXXXX") + mirrors=("$@") + + try_all_mirrors() { + for mirror in "${mirrors[@]}" ; do + if curl2 -sS "$mirror" -o "$path" ; then + return + fi + done + return 1 + } + retry try_all_mirrors + cat "$path" + rm "$path" +} + +# ------------------------------------------------------------------------------ + +HAVE_CYGPATH=no +if command -v cygpath &>/dev/null; then + HAVE_CYGPATH=yes +fi +posix_path() { + if [[ "$HAVE_CYGPATH" == "yes" ]]; then + cygpath "$1" + else + echo "$1" + fi +} +display_path() { + if [[ "$HAVE_CYGPATH" == "yes" ]]; then + if posix_terminal; then + cygpath "$1" + else + cygpath -w "$1" + fi + else + echo "$1" + fi +} + +COMMAND= +COMPILER=dmd +VERBOSITY=1 +GET_PATH_AUTO_INSTALL=0 +GET_PATH_COMPILER=dc +# Set a default install path depending on the POSIX/Windows environment. +if posix_terminal; then + ROOT=~/dlang +else + # Default to a ROOT that is outside the POSIX-like environment. + if [ -z "$USERPROFILE" ]; then + fatal '%USERPROFILE% should not be empty on Windows.'; + fi + ROOT=$(posix_path "$USERPROFILE")/dlang +fi +TMP_ROOT= +DUB_VERSION= +DUB_BIN_PATH= +case $(uname -s) in + Darwin) OS=osx;; + Linux) OS=linux;; + FreeBSD) OS=freebsd;; + *_NT-*) OS=windows;; + *) + fatal "Unsupported OS $(uname -s)" + ;; +esac + +check_tools() { + while [[ $# -gt 0 ]]; do + if ! command -v "$1" &>/dev/null && + # detect OSX' liblzma support in libarchive + ! { [ "$OS-$1" == osx-xz ] && otool -L /usr/lib/libarchive.*.dylib | grep -qF liblzma; }; then + local msg="Required tool $1 not found, please install it." + case $OS-$1 in + osx-xz) msg="$msg http://macpkg.sourceforge.net";; + esac + fatal "$msg" + fi + shift + done +} + +# ------------------------------------------------------------------------------ + +mkdtemp() { + mktemp -d "$TMP_ROOT/XXXXXX" +} + +cleanup() { + if [[ -n $TMP_ROOT ]]; then + rm -rf "$TMP_ROOT"; + fi +} +trap cleanup EXIT + +# ------------------------------------------------------------------------------ + +usage() { + log 'Usage + + install.sh [] [] + +Commands + + install Install a D compiler (default command) + uninstall Remove an installed D compiler + list List all installed D compilers + update Update this dlang script + get-path Path of the installed compiler executable + +Options + + -h --help Show this help + -p --path Install location + POSIX default: ~/dlang + Windows default: %USERPROFILE%\dlang + -v --verbose Verbose output + +Run "install.sh --help to get help for a specific command, or consult +https://dlang.org/install.html for documentation. +If no arguments are provided, the latest DMD compiler will be installed. +' +} + +command_help() { + if [ -z "${1-}" ]; then + usage + return + fi + + local _compiler='Compiler + + dmd|gdc|ldc latest version of a compiler + dmd|gdc|ldc- specific version of a compiler (e.g. dmd-2.071.1, ldc-1.1.0-beta2) + dmd|ldc-beta latest beta version of a compiler + dmd-nightly latest dmd nightly + ldc-latest-ci latest ldc CI build (with assertions enabled) + dmd-2016-08-08 specific dmd nightly +' + + case $1 in + install) + log 'Usage + + install.sh install + +Description + + Download and install a D compiler. + By default the latest release of the DMD compiler is selected. + +Options + + -a --activate Only print the path to the activate script + +Examples + + install.sh + install.sh dmd + install.sh install dmd + install.sh install dmd-2.071.1 + install.sh install ldc-1.1.0-beta2 +' + log "$_compiler" + ;; + + uninstall) + log 'Usage + + install.sh uninstall + +Description + + Uninstall a D compiler. + +Examples + + install.sh uninstall dmd + install.sh uninstall dmd-2.071.1 + install.sh uninstall ldc-1.1.0-beta2 +' + log "$_compiler" + ;; + + list) + log 'Usage + + install.sh list + +Description + + List all installed D compilers. +' + ;; + + update) + log 'Usage + + install.sh update + +Description + + Update the dlang installer itself and the keyring. +' + ;; + + get-path) + log 'Usage + + install.sh get-path + +Description + + Find the path of an installed D compiler. + +Options + + --install Install the compiler if it is not installed + --dmd Find the DMD-alike interface instead + --dub Find the DUB instance + +Examples + + install.sh + install.sh get-path dmd + install.sh get-path dmd-2.093.0 --install + install.sh get-path --dmd ldc-1.19.0 + install.sh get-path --dub ldc-1.19.0 + install.sh get-path ldc-1.19.0 --install + install.sh get-path --dub dub-1.21.0 +' + log "$_compiler" + ;; + + esac +} + +# ------------------------------------------------------------------------------ + +parse_args() { + local _help= + local _installAction= + local _getPathAction= + local _autoInstallFlag= + + while [[ $# -gt 0 ]]; do + case "$1" in + -h | --help) + _help=1 + ;; + + -p | --path) + if [ -z "${2:-}" ]; then + fatal '-p|--path must be followed by a path.'; + fi + shift + ROOT="$(posix_path "$1")"; + ;; + + -v | --verbose) + VERBOSITY=2 + ;; + + -a | --activate) + if [ -n "$_installAction" ]; then + fatal "$1 conflicts with --${_installAction}" + fi + _installAction="activate" + ;; + + --dmd) + if [ -n "$_getPathAction" ]; then + fatal "$1 conflicts with --${_getPathAction}" + fi + _getPathAction="dmd" + ;; + + --dub) + if [ -n "$_getPathAction" ]; then + fatal "$1 conflicts with --${_getPathAction}" + fi + _getPathAction="dub" + ;; + + --install) + _autoInstallFlag=1 + ;; + + use | install | uninstall | list | update) + COMMAND=$1 + ;; + + get-path) + COMMAND=$1 + ;; + + remove) + COMMAND=uninstall + ;; + + dmd | dmd-* | gdc | gdc-* | ldc | ldc-* | dub | dub-* | \ + dmd,* | ldc,* | gdc,* ) + COMPILER=$1 + ;; + + *) + usage + fatal "Unrecognized command-line parameter: $1" + ;; + esac + shift + done + + mkdir -p "$ROOT" + TMP_ROOT=$(mktemp -d "$ROOT/.installer_tmp_XXXXXX") + + if [ -n "$_help" ]; then + command_help $COMMAND + exit 0 + fi + local command_="${COMMAND:-install}" + if [ -n "$_installAction" ] ; then + if [ "$command_" == "install" ]; then + VERBOSITY=0 + else + logE "ERROR: --activate is not allowed for ${command_}." + command_help $COMMAND + exit 1 + fi + fi + if [ -n "$_autoInstallFlag" ] ; then + if [ "$command_" == "get-path" ]; then + GET_PATH_AUTO_INSTALL=1 + else + logE "ERROR: --install is not allowed for ${command_}." + command_help $COMMAND + exit 1 + fi + fi + if [ -n "$_getPathAction" ] ; then + if [ "$command_" == "get-path" ]; then + case "$_getPathAction" in + dmd|dub) + GET_PATH_COMPILER=$_getPathAction + ;; + *) + fatal "Internal Error. Invalid get-path: $_getPathAction" + esac + else + logE "ERROR: --${_getPathAction} is not allowed for ${command_}." + command_help $COMMAND + exit 1 + fi + fi + + IFS="," read -r _compiler _dub <<< "$COMPILER" + if [ -n "${_dub:-}" ] ; then + COMPILER="$_compiler" + DUB="$_dub" + fi +} + +# ------------------------------------------------------------------------------ + +# run_command command [compiler] +run_command() { + case $1 in + install) + install_d "$1" "${2:-}" + if posix_terminal; then + if [ "$(basename "$SHELL")" = fish ]; then + local suffix=.fish + fi + if [ "$VERBOSITY" -eq 0 ]; then + echo "$ROOT/$2/activate${suffix:-}" + else + log " +Run \`source $ROOT/$2/activate${suffix:-}\` in your shell to use $2. +This will setup PATH, LIBRARY_PATH, LD_LIBRARY_PATH, DMD, DC, and PS1. +Run \`deactivate\` later on to restore your environment." + fi + else + if [ "$VERBOSITY" -eq 0 ]; then + display_path "$ROOT/$2/activate.bat" + else + log " +Run \`$(display_path "$ROOT/$2/activate.bat")\` to add $2 to your PATH." + fi + fi + ;; + + get-path) + if [ $GET_PATH_AUTO_INSTALL -eq 0 ] ; then + if [ ! -d "$ROOT/$2" ]; then + fatal "Requested $2 is not installed. Install or rerun with --install."; + fi + if [ "$GET_PATH_COMPILER" == "dub" ] ; then + local dubBinPath + dubBinPath="$(binexec_for_dub_compiler "$2")" + if [ ! -f "$dubBinPath" ] ; then + fatal "Requested DUB is not installed. Install or rerun with --install."; + fi + fi + fi + previousVerbosity=$VERBOSITY + VERBOSITY=-1 + install_d "$1" "${2:-}" + VERBOSITY=$previousVerbosity + case "$GET_PATH_COMPILER" in + dmd) + if [[ $COMPILER =~ ^dub ]] ; then + fatal "ERROR: DUB is not a compiler." + fi + echo "$ROOT/$2/$(binexec_for_dmd_compiler "$2")" + ;; + dc) + if [[ $COMPILER =~ ^dub ]] ; then + fatal "ERROR: DUB is not a compiler." + fi + echo "$ROOT/$2/$(binexec_for_dc_compiler "$2")" + ;; + dub) + binexec_for_dub_compiler "$2" + ;; + *) + fatal "Unknown value for GET_PATH_COMPILER encountered." + esac + ;; + + uninstall) + if [ -z "${2:-}" ]; then + fatal "Missing compiler argument for $1 command."; + fi + uninstall_compiler "$2" + ;; + + list) + list_compilers + ;; + + update) + install_dlang_installer + ;; + esac +} + +install_d() { + local commandName="$1" + if [ -z "${2:-}" ]; then + fatal "Missing compiler argument for $commandName command."; + fi + local compilerName="$2" + check_tools curl + if [ ! -f "$ROOT/install.sh" ] || [ ! -f "$ROOT/d-keyring.gpg" ] ; then + install_dlang_installer + fi + if [ -d "$ROOT/$compilerName" ]; then + log "$compilerName already installed"; + else + install_compiler "$compilerName" + fi + + # Only try to install dub if it wasn't the main compiler + if ! [[ $COMPILER =~ ^dub ]] ; then + if [ -n "${DUB:-}" ] ; then + # A dub version was explicitly specified with ,dub-1.8.0 + DUB_BIN_PATH="${ROOT}/${DUB}" + # Check whether the latest dub version needs to be resolved + if ! [[ $DUB =~ ^dub- ]] ; then + resolve_latest "$DUB" + install_dub "dub-$DUB_VERSION" + else + install_dub "$DUB" + fi + else + # compiler was installed without requesting dub + local -r binpath=$(binpath_for_compiler "$compilerName") + if [ -f "$ROOT/$2/$binpath/dub" ]; then + if [[ $("$ROOT/$2/$binpath/dub" --version) =~ ([0-9]+\.[0-9]+\.[0-9]+(-[^, ]+)?) ]]; then + log "Using dub ${BASH_REMATCH[1]} shipped with $compilerName" + else + log "Using dub shipped with $compilerName" + fi + else + # no dub bundled - manually installing + DUB_BIN_PATH="${ROOT}/dub" + resolve_latest dub + install_dub "dub-$DUB_VERSION" + fi + fi + fi + + write_env_vars "$compilerName" +} + +install_dlang_installer() { + local tmp mirrors + tmp=$(mkdtemp) + local mirrors=( + "https://dlang.org/install.sh" + "https://s3-us-west-2.amazonaws.com/downloads.dlang.org/other/install.sh" + ) + local keyring_mirrors=( + "https://dlang.org/d-keyring.gpg" + "https://s3-us-west-2.amazonaws.com/downloads.dlang.org/other/d-keyring.gpg" + ) + + mkdir -p "$ROOT" + log "Downloading https://dlang.org/d-keyring.gpg" + if [ ! -f "$ROOT/d-keyring.gpg" ]; then + download_without_verify "$tmp/d-keyring.gpg" "${keyring_mirrors[@]}" + else + download_with_verify "$tmp/d-keyring.gpg" "${keyring_mirrors[@]}" + fi + mv "$tmp/d-keyring.gpg" "$ROOT/d-keyring.gpg" + log "Downloading ${mirrors[0]}" + download_with_verify "$tmp/install.sh" "${mirrors[@]}" + mv "$tmp/install.sh" "$ROOT/install.sh" + rmdir "$tmp" + chmod +x "$ROOT/install.sh" + log "The latest version of this script was installed as $ROOT/install.sh. +It can be used it to install further D compilers. +Run \`$ROOT/install.sh --help\` for usage information. +" +} + +resolve_latest() { + local input_compiler=${1:-$COMPILER} + case $input_compiler in + dmd) + local mirrors=( + "http://downloads.dlang.org/releases/LATEST" + "http://ftp.digitalmars.com/LATEST" + ) + logV "Determing latest dmd version (${mirrors[0]})." + COMPILER="dmd-$(fetch "${mirrors[@]}")" + ;; + dmd-beta) + local mirrors=( + "http://downloads.dlang.org/pre-releases/LATEST" + "http://ftp.digitalmars.com/LATEST_BETA" + ) + logV "Determing latest dmd-beta version (${mirrors[0]})." + COMPILER="dmd-$(fetch "${mirrors[@]}")" + ;; + dmd-nightly) + COMPILER="dmd-nightly" + ;; + dmd-*) # nightly master or feature branch + # dmd-nightly, dmd-master, dmd-branch + # but not: dmd-2016-10-19 or dmd-branch-2016-10-20 + # dmd-2.068.0 or dmd-2.068.2-5 + # dmd-2.064 or dmd-2.064-0 + if [[ ! $input_compiler =~ -[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && + [[ ! $input_compiler =~ -[0-9][.][0-9]{3}[.][0-9]{1,3}(-[0-9]{1,3})? ]] && + [[ ! $input_compiler =~ -[0-9][.][0-9]{3}(.[0-9]{1,3})? ]]; then + local url=http://downloads.dlang.org/nightlies/$input_compiler/LATEST + logV "Determing latest $input_compiler version ($url)." + COMPILER="dmd-$(fetch "$url")" + # rewrite dmd-2016-10-19 -> dmd-master-2016-10-19 (default branch for nightlies) + elif [[ $COMPILER =~ ^dmd-([0-9]{4}-[0-9]{2}-[0-9]{2})$ ]]; then + COMPILER="dmd-master-${BASH_REMATCH[1]}" + fi + ;; + ldc) + local url=https://ldc-developers.github.io/LATEST + logV "Determing latest ldc version ($url)." + COMPILER="ldc-$(fetch $url)" + ;; + ldc-beta) + local url=https://ldc-developers.github.io/LATEST_BETA + logV "Determining latest ldc-beta version ($url)." + COMPILER="ldc-$(fetch $url)" + ;; + ldc-latest-ci) + local url=https://thecybershadow.net/d/github-ldc + logV "Finding latest ldc CI binary package (at $url)." + local package + package="$(fetch $url)" + if [[ $package =~ ldc2-([0-9a-f]*)-$OS-$ARCH. ]]; then + COMPILER="ldc-${BASH_REMATCH[1]}" + else + fatal "Could not find ldc CI binaries (OS: $OS, arch: $ARCH)" + fi + ;; + ldc-*) + ;; + gdc) + local url=https://gdcproject.org/downloads/LATEST + logV "Determing latest gdc version ($url)." + COMPILER="gdc-$(fetch $url)" + ;; + gdc-*) + ;; + dub) + local mirrors=( + "https://dlang.github.io/dub/LATEST" + "https://code.dlang.org/download/LATEST" + ) + logV "Determining latest dub version (${mirrors[0]})." + DUB_VERSION="$(fetch "${mirrors[@]}" | sed 's/^v//')" + local DUB="dub-${DUB_VERSION}" + if [ "$COMPILER" == "dub" ] ; then + COMPILER="$DUB" + fi + ;; + dub-*) + ;; + *) + fatal "Invalid compiler: $COMPILER" + esac +} + +install_compiler() { + local compiler="$1" + # dmd-2.065, dmd-2.068.0, dmd-2.068.1-b1 + if [[ $1 =~ ^dmd-2\.([0-9]{3})(\.[0-9])?(-.*)?$ ]]; then + local basename="dmd.2.${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}" + local ver="2.${BASH_REMATCH[1]}" + + if [[ $ver > "2.064z" ]]; then + basename="$basename.$OS" + ver="$ver${BASH_REMATCH[2]}" + if [ $OS = freebsd ]; then + basename="$basename-$MODEL" + fi + fi + + if [[ $OS == windows && $ver > "2.068.0" ]] && command -v 7z &>/dev/null; then + local ext=".7z" + elif [[ $ver > "2.068.0z" && $OS != windows ]]; then + local ext=".tar.xz" + else + local ext=".zip" + fi + + if [[ $ARCH != x86* ]]; then + fatal "no DMD binaries available for $ARCH" + fi + + local mirrors + if [ -n "${BASH_REMATCH[3]}" ]; then # pre-release + mirrors=( + "http://downloads.dlang.org/pre-releases/2.x/$ver/$basename$ext" + "http://ftp.digitalmars.com/$basename$ext" + ) + else + mirrors=( + "http://downloads.dlang.org/releases/2.x/$ver/$basename$ext" + "http://ftp.digitalmars.com/$basename$ext" + ) + fi + + download_and_unpack_with_verify "$ROOT/$compiler" "${mirrors[@]}" + + # dmd-2015-11-20, dmd-feature_branch-2016-10-20 + elif [[ $1 =~ ^dmd(-(.*))?-[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + local branch=${BASH_REMATCH[2]:-master} + local basename="dmd.$branch.$OS" + if [ $OS = freebsd ]; then + basename="$basename-$MODEL" + fi + if [[ $ARCH != x86* ]]; then + fatal "no DMD binaries available for $ARCH" + fi + local ext + test $OS = windows && ext=.7z || ext=.tar.xz + local url="http://downloads.dlang.org/nightlies/$1/$basename$ext" + + download_and_unpack_with_verify "$ROOT/$compiler" "$url" + + # Nightlies uploaded to the DMD repo + elif [[ $1 == dmd-nightly ]]; then + local baseUrl="https://github.com/dlang/dmd/releases/download/nightly/dmd.master" + local ext + test $OS = windows && ext=7z || ext=tar.xz + local mod= + test $OS = freebsd && mod=-64 + + download_and_unpack_without_verify "$ROOT/$compiler" "$baseUrl.$OS$mod.$ext" + + # Create symlink `dmd-master => dmd-nightly` to be compatible with the old behaviour + ln -s "$ROOT/$compiler" "$ROOT/dmd-master" + + # ldc-0.12.1 or ldc-0.15.0-alpha1 + elif [[ $1 =~ ^ldc-(([0-9]+)\.([0-9]+)\.[0-9]+(-.*)?)$ ]]; then + local ver=${BASH_REMATCH[1]} + local vernum=$((BASH_REMATCH[2] * 1000 + BASH_REMATCH[3])) + local ext + test $OS = windows && ext=.7z || ext=.tar.xz + local url="https://github.com/ldc-developers/ldc/releases/download/v$ver/ldc2-$ver-$OS-$ARCH$ext" + if [[ $OS == windows && $vernum -lt 1007 ]]; then + url="https://github.com/ldc-developers/ldc/releases/download/v$ver/ldc2-$ver-win$MODEL-msvc.zip" + fi + + download_and_unpack_without_verify "$ROOT/$compiler" "$url" + + # ldc-latest-ci: ldc-8c0abd52 + elif [[ $1 =~ ^ldc-([0-9a-f]+) ]]; then + local package_hash=${BASH_REMATCH[1]} + local ext + test $OS = windows && ext=.7z || ext=.tar.xz + local url="https://github.com/ldc-developers/ldc/releases/download/CI/ldc2-$package_hash-$OS-$ARCH$ext" + + # Install into 'ldc-8c0abd52-20171222' directory. + download_and_unpack_without_verify "$ROOT/$compiler" "$url" + + # gdc-4.8.2, gdc-4.9.0-alpha1, gdc-5.2, or gdc-5.2-alpha1 + elif [[ $1 =~ ^gdc-([0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?)$ ]]; then + local name=${BASH_REMATCH[0]} + if [ $OS != linux ]; then + fatal "no gdc binaries available for $OS" + fi + case $ARCH in + x86_64) local triplet=x86_64-linux-gnu;; + x86) local triplet=i686-linux-gnu;; + esac + local url="https://gdcproject.org/downloads/binaries/$triplet/$name.tar.xz" + + download_and_unpack_without_verify "$ROOT/$compiler" "$url" + + url=https://raw.githubusercontent.com/D-Programming-GDC/GDMD/a67179d54611ae8cfb1d791cf7ab8e36c3224b76/dmd-script + log "Downloading gdmd $url" + download_without_verify "$ROOT/$1/bin/gdmd" "$url" + chmod +x "$ROOT/$1/bin/gdmd" + + elif [[ $1 =~ ^dub-v?([0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?)$ ]]; then + install_dub "$1" + else + fatal "Unknown compiler '$compiler'" + fi +} + +find_gpg() { + if command -v gpg2 &>/dev/null; then + echo gpg2 + elif command -v gpg &>/dev/null; then + echo gpg + else + echo "Warning: No gpg tool found to verify downloads." >&2 + fi +} + +unpack_zip() { + local zip=$1 + local dir=$2 + + ( + # Avoid invoking Windows programs with absolute paths, as + # POSIX environments on Windows are unreliable in converting + # POSIX paths to Windows paths on programs' command lines. + # Use relative paths instead. + cd "$ROOT" + zip=${zip/$ROOT\//} + dir=${dir/$ROOT\//} + + if command -v 7z &>/dev/null; then + 7z x -o"$dir" "$zip" 1>&2 + else + # Allow unzip to exit with code 1, which it uses to signal warnings + # such as ".zip appears to use backslashes as path separators". + unzip -q -d "$dir" "$zip" || [ $? -le 1 ] + fi + ) +} + +# path, verify (0/1), urls... +# the gpg signature is assumed to be url+.sig +download_and_unpack() { + local path do_verify urls + path="$1" + do_verify="$2" + urls=("${@:3}") + local tmp name + tmp=$(mkdtemp) + name="$(basename "${urls[0]}")" + + check_tools curl + if [[ $name =~ \.tar\.xz$ ]]; then + check_tools tar xz + elif [[ $name =~ \.7z$ ]]; then + check_tools 7z + elif [[ $name =~ \.zip$ ]]; then + if ! command -v 7z &>/dev/null; then + check_tools unzip + fi + fi + + log "Downloading and unpacking ${urls[0]}" + download "$tmp/$name" "$do_verify" "${urls[@]}" + if [[ $name =~ \.tar\.xz$ ]]; then + tar --strip-components=1 -C "$tmp" -Jxf "$tmp/$name" + elif [[ $name =~ \.tar\.gz$ ]]; then + tar --strip-components=1 -C "$tmp" -xf "$tmp/$name" + else + mkdir "$tmp"/target + unpack_zip "$tmp/$name" "$tmp"/target + + local files=("$tmp"/target/*) + if [[ "${#files[@]}" -eq 1 && -d "${files[0]}" ]]; then + # Single directory at the top of the archive, + # as is common with .tar archives. Move it out. + find "$tmp"/target -mindepth 2 -maxdepth 2 -exec sh -c "exec mv "\$@" '$tmp'" sh {} \+ + rmdir "$tmp"/target/* + else + find "$tmp"/target -mindepth 1 -maxdepth 1 -exec sh -c "exec mv "\$@" '$tmp'" sh {} \+ + fi + rmdir "$tmp"/target + fi + rm "$tmp/$name" + mv "$tmp" "$path" +} + +# path, urls... +download_and_unpack_with_verify() { + download_and_unpack "$1" 1 "${@:2}" +} + +# path, urls... +download_and_unpack_without_verify() { + download_and_unpack "$1" 0 "${@:2}" +} + +# path, urls... +verify() { + local path urls + path="$1" + urls=("${@:2}") + : "${GPG:=$(find_gpg)}" + if [ -z "$GPG" ]; then + return + fi + if ! $GPG --list-keys >/dev/null; then + fatal "Broken GPG installation" + fi + local out + if ! out=$(fetch "${urls[@]}" | $GPG -q --verify --keyring "$ROOT/d-keyring.gpg" --no-default-keyring - "$path" 2>&1); then + rm "$path" # delete invalid files + logE "$out" + fatal "Invalid signature ${urls[0]}" + fi +} + +binpath_for_compiler() { + case $1 in + dmd*) + local suffix= + [ $OS = osx ] || [ $OS = windows ] || suffix=$MODEL + local -r binpath=$OS/bin$suffix + ;; + + ldc*) + local -r binpath=bin + ;; + + gdc*) + local -r binpath=bin + ;; + dub*) + local -r binpath= + ;; + esac + echo "$binpath" +} + +# Path to the compiler executable with a DMD-compatible interface +binexec_for_dmd_compiler() { + local binPath + binPath=$(binpath_for_compiler "$1")/ + case $1 in + dmd*) + binPath+=dmd + ;; + ldc*) + binPath+=ldmd2 + ;; + gdc*) + binPath+=gdmd + ;; + esac + echo "$binPath" +} + +# Path to the compiler executable with its custom interface +binexec_for_dc_compiler() { + local binPath + binPath=$(binpath_for_compiler "$1")/ + case $1 in + dmd*) + binPath+=dmd + ;; + ldc*) + binPath+=ldc2 + ;; + gdc*) + binPath+=gdc + ;; + esac + echo "$binPath" +} + +binexec_for_dub_compiler() { + local dub binPath + dub="${DUB:-}" + if [[ "$dub" =~ ^dub- ]] ; then + binPath="$ROOT/$DUB/dub" + elif [ "$dub" == "dub" ] ; then + binPath="$ROOT/$DUB/dub" + else + binPath="$ROOT/$1/$(binpath_for_compiler "$1")" + case $1 in + dub*) + binPath+=dub + ;; + dmd*|ldc*|gdc*) + binPath+=/dub + ;; + esac + # check for old compiler which ship without dub + if [ ! -f "$binPath" ]; then + binPath="$ROOT/dub/dub" + fi + fi + echo "$binPath" +} + +write_env_vars() { + local -r binpath=$(binpath_for_compiler "$1") + case $1 in + dmd*) + local suffix= + [ $OS = osx ] || suffix=$MODEL + local libpath=$OS/lib$suffix + local dc=dmd + local dmd=dmd + ;; + + ldc*) + local libpath=lib + local dc=ldc2 + local dmd=ldmd2 + ;; + + gdc*) + if [ -d "$ROOT/$1/lib$MODEL" ]; then + local libpath=lib$MODEL + else + # older gdc releases only ship 64-bit libs + local libpath=lib + fi + local dc=gdc + local dmd=gdmd + ;; + dub*) + local libpath= + local dc= + local dmd= + ;; + esac + + logV "Writing environment variables to $ROOT/$1/activate" +{ + # when activate is called twice, deactivate the prior context first + echo "if [ ! -z \${_OLD_D_PATH+x} ] ; then deactivate; fi" + echo + echo "deactivate() {" + echo " export PATH=\"\$_OLD_D_PATH\"" + if [ -n "$libpath" ] ; then + echo " export LIBRARY_PATH=\"\$_OLD_D_LIBRARY_PATH\"" + echo " export LD_LIBRARY_PATH=\"\$_OLD_D_LD_LIBRARY_PATH\"" + echo " unset _OLD_D_LIBRARY_PATH" + echo " unset _OLD_D_LD_LIBRARY_PATH" + fi + if [ -n "$dmd" ] ; then + echo " unset DMD" + echo " unset DC" + fi + + echo " export PS1=\"\$_OLD_D_PS1\"" + echo " unset _OLD_D_PATH" + echo " unset _OLD_D_PS1" + echo " unset -f deactivate" + echo "}" + echo + echo "_OLD_D_PATH=\"\${PATH:-}\"" + + if [ -n "$libpath" ] ; then + echo "_OLD_D_LIBRARY_PATH=\"\${LIBRARY_PATH:-}\"" + echo "_OLD_D_LD_LIBRARY_PATH=\"\${LD_LIBRARY_PATH:-}\"" + echo "export LIBRARY_PATH=\"$ROOT/$1/$libpath\${LIBRARY_PATH:+:}\${LIBRARY_PATH:-}\"" + echo "export LD_LIBRARY_PATH=\"$ROOT/$1/$libpath\${LD_LIBRARY_PATH:+:}\${LD_LIBRARY_PATH:-}\"" + fi + + echo "_OLD_D_PATH=\"\${PATH:-}\"" + echo "_OLD_D_PS1=\"\${PS1:-}\"" + echo "export PS1=\"($1)\${PS1:-}\"" + echo "export PATH=\"${DUB_BIN_PATH}${DUB_BIN_PATH:+:}$ROOT/$1/$binpath\${PATH:+:}\${PATH:-}\"" + + if [ -n "$dmd" ] ; then + echo "export DMD=$dmd" + echo "export DC=$dc" + fi +} > "$ROOT/$1/activate" + + logV "Writing environment variables to $ROOT/$1/activate.fish" +{ + echo "function deactivate" + echo " set -gx PATH \$_OLD_D_PATH" + + if [ -n "$libpath" ] ; then + echo " set -gx LIBRARY_PATH \$_OLD_D_LIBRARY_PATH" + echo " set -gx LD_LIBRARY_PATH \$_OLD_D_LD_LIBRARY_PATH" + echo " set -e _OLD_D_LIBRARY_PATH" + echo " set -e _OLD_D_LD_LIBRARY_PATH" + fi + + echo " functions -e fish_prompt" + echo " functions -c _old_d_fish_prompt fish_prompt" + echo " functions -e _old_d_fish_prompt" + echo + echo " set -e _OLD_D_PATH" + + if [ -n "$dmd" ] ; then + echo " set -e DMD" + echo " set -e DC" + fi + echo " functions -e deactivate" + echo "end" + echo + + echo "set -g _OLD_D_PATH \$PATH" + echo "set -g _OLD_D_PS1 \$PS1" + echo + echo "set -gx PATH ${DUB_BIN_PATH:+\'}${DUB_BIN_PATH}${DUB_BIN_PATH:+\' }'$ROOT/$1/$binpath' \$PATH" + + if [ -n "$libpath" ] ; then + echo "set -g _OLD_D_LIBRARY_PATH \$LIBRARY_PATH" + echo "set -g _OLD_D_LD_LIBRARY_PATH \$LD_LIBRARY_PATH" + echo "set -gx LIBRARY_PATH '$ROOT/$1/$libpath' \$LIBRARY_PATH" + echo "set -gx LD_LIBRARY_PATH '$ROOT/$1/$libpath' \$LD_LIBRARY_PATH" + fi + + if [ -n "$dmd" ] ; then + echo "set -gx DMD $dmd" + echo "set -gx DC $dc" + fi + + echo "functions -c fish_prompt _old_d_fish_prompt" + echo "function fish_prompt" + echo " printf '($1)%s' (_old_d_fish_prompt)" + echo "end" +} > "$ROOT/$1/activate.fish" + + if [[ $OS == windows ]]; then + logV "Writing environment variables to $ROOT/$1/activate.bat" +{ + local -r winpath=$(cygpath -w "$ROOT/$1/$binpath") + echo "@echo off" + echo "if not \"%PATH:${winpath}=%\"==\"%PATH%\" (" + echo " echo $1 is already active." + echo " goto :EOF" + echo ")" + echo "echo Adding $1 to your PATH." + echo "echo Run \`set PATH=%%_OLD_D_PATH%%\` later on to restore your PATH." + echo "set _OLD_D_PATH=%PATH%" + echo "set PATH=${DUB_BIN_PATH:+$(cygpath -w "${DUB_BIN_PATH}");}${winpath};%PATH%" + if [[ $PROCESSOR_ARCHITECTURE != x86 ]]; then + echo "set PATH=${winpath}64;%PATH%" + fi +} > "$ROOT/$1/activate.bat" + fi +} + +uninstall_compiler() { + if [ ! -d "$ROOT/$1" ]; then + fatal "$1 is not installed in $ROOT" + fi + log "Removing $(display_path "$ROOT/$1")" + rm -rf "${ROOT:?}/$1" + + # Remove the compatibility symlink for the nightlies + if [ "$1" == "dmd-nightly" ]; then + rm -rf "${ROOT:?}/dmd-master" + fi +} + +list_compilers() { + check_tools find + if [ -d "$ROOT" ]; then + find "$ROOT" \ + -mindepth 1 \ + -maxdepth 1 \ + -not -name 'dub*' \ + -not -name install.sh \ + -not -name d-keyring.gpg \ + -not -name '.*' \ + -exec basename {} \; | \ + grep . # fail if none found + fi +} + +install_dub() { + if [ $OS != linux ] && [ $OS != windows ] && [ $OS != osx ]; then + log "no dub binaries available for $OS" + return + fi + local dub="$1" + if [ -d "$ROOT/$dub" ]; then + log "$dub already installed" + return + fi + local tmp url + tmp=$(mkdtemp) + dubVersion=${dub##dub-} + local ext=.tar.gz arch=$ARCH + if [[ $OS == windows ]]; then + ext=.zip + if [[ "$dubVersion" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + local vernum=$((BASH_REMATCH[1] * 1000000 + BASH_REMATCH[2] * 1000 + BASH_REMATCH[3])) + if [[ $vernum -lt 1014000 ]]; then + # Older releases, pre-1.14.0, were on code.dlang.org, + # and did not have x86_64 versions. + arch=x86 + elif [[ $vernum -lt 1020000 ]]; then + # Releases since 1.14.0 have x86_64 archives on GitHub. + # However, these are corrupted (https://github.com/dlang/dub/issues/1795). + # At this point it's unclear if the archives are going to be fixed, + # so just install them for now. + : + fi + fi + fi + local mirrors=( + "https://github.com/dlang/dub/releases/download/v${dubVersion}/dub-v${dubVersion}-$OS-$arch$ext" + "https://code.dlang.org/files/$dub-$OS-$arch$ext" + ) + log "Downloading and unpacking ${mirrors[0]}" + download_without_verify "$tmp/dub$ext" "${mirrors[@]}" + if [[ $ext == .tar.gz ]]; then + tar -C "$tmp" -zxf "$tmp/dub$ext" + else + unpack_zip "$tmp/dub$ext" "$tmp" + fi + logV "Removing old dub symlink" + rm -rf "$ROOT/dub" + mv "$tmp" "$ROOT/$dub" + logV "Linking $ROOT/dub -> $ROOT/$dub" + ln -s "$dub" "$ROOT/dub" +} + +# ------------------------------------------------------------------------------ + +parse_args "$@" + +case $(uname -m) in + x86_64|amd64) ARCH=x86_64; MODEL=64;; + aarch64) ARCH=aarch64; MODEL=64;; + i*86) ARCH=x86; MODEL=32;; + *) + fatal "Unsupported Arch $(uname -m)" + ;; +esac +if [[ $OS-$ARCH = windows-x86_64 && $COMPILER = ldc* ]]; then + ARCH=x64 +fi + +resolve_latest "$COMPILER" +run_command ${COMMAND:-install} "$COMPILER" +} + +_ "$@" diff --git a/src/compilers.ts b/src/compilers.ts index 10b3ca2..adbdb23 100644 --- a/src/compilers.ts +++ b/src/compilers.ts @@ -4,6 +4,7 @@ import * as fs from "fs"; import * as path from "path"; import * as ChildProcess from "child_process"; import { config } from './extension'; +import { determineOutputFolder, downloadFileInteractive } from './installer'; export interface DetectedCompiler { /** @@ -18,7 +19,9 @@ export interface DetectedCompiler { importPaths?: string[]; }; -export function registerCompilerInstaller(): vscode.Disposable { +let codedContext: vscode.ExtensionContext; +export function registerCompilerInstaller(context: vscode.ExtensionContext): vscode.Disposable { + codedContext = context; return vscode.commands.registerCommand("code-d.setupCompiler", (args) => { setupCompilersUI(); }); @@ -95,26 +98,209 @@ export async function setupCompilersUI() { showDetectedCompilerInstallPrompt(selection.installInfo); } else { switch (selection) { - case dmdItem: - break; - case ldcItem: - break; - case gdcItem: - break; - case manualSelect: - break; - default: - console.error("invalid selection"); - introQuickPick.show(); - break; + case dmdItem: + showCompilerInstallationPrompt("DMD", [ + { label: "See releases", website: "https://dlang.org/download.html#dmd" }, + { platform: "win32", label: "Run installer", downloadAndRun: "https://s3.us-west-2.amazonaws.com/downloads.dlang.org/releases/2021/dmd-2.098.0.exe" }, + { label: "Portable install", installSh: "install dmd,dub", binTest: "bash" }, + { platform: "linux", label: "System install", command: "pacman -S dlang-dmd", binTest: "pacman" }, + { platform: "linux", label: "System install", command: "layman -a dlang", binTest: "layman" }, + { platform: "darwin", label: "System install", command: "brew install dmd", binTest: "brew" }, + { platform: "linux", label: "System install", command: "nix-env -iA nixpkgs.dmd", binTest: "nix-env" }, + { platform: "linux", label: "System install", command: "zypper install dmd", binTest: "zypper" }, + ]); + break; + case ldcItem: + showCompilerInstallationPrompt("LDC", [ + { label: "See releases", website: "https://github.com/ldc-developers/ldc/releases" }, + { platform: "win32", label: "Run installer", downloadAndRun: "https://github.com/ldc-developers/ldc/releases/download/v1.28.0/ldc2-1.28.0-windows-multilib.exe" }, + { label: "Portable install", installSh: "install ldc,dub", binTest: "bash" }, + { label: "System install", command: "brew install ldc", binTest: "brew" }, + { platform: "linux", label: "System install", command: "apk add ldc", binTest: "apk" }, + { platform: "linux", label: "System install", command: "pacman -S dlang-ldc", binTest: "pacman" }, + { platform: "win32", label: "System install", command: "choco install ldc", binTest: "choco" }, + { platform: "linux", label: "System install", command: "apt install ldc", binTest: "apt" }, + { platform: "linux", label: "System install", command: "dnf install ldc", binTest: "dnf" }, + { platform: "freebsd", label: "System install", command: "pkg install ldc", binTest: "pkg" }, + { platform: "linux", label: "System install", command: "layman -a ldc", binTest: "layman" }, + { platform: "darwin", label: "System install", command: "brew install ldc", binTest: "brew" }, + { platform: "linux", label: "System install", command: "nix-env -i ldc", binTest: "nix-env" }, + ]); + break; + case gdcItem: + showCompilerInstallationPrompt("GDC", [ + { label: "View Project website", website: "https://gdcproject.org/downloads" }, + { platform: "win32", label: "Install through WinLibs", website: "https://winlibs.com" }, + { platform: "linux", label: "Portable install", installSh: "install gdc,dub" }, + { platform: "linux", label: "System install", command: "pacman -S gcc-d", binTest: "pacman" }, + { platform: "linux", label: "System install", command: "apt install gdc", binTest: "apt" }, + ]); + break; + case manualSelect: + break; + default: + console.error("invalid selection"); + introQuickPick.show(); + break; } } }); } +type LabelWebsiteButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, website: string }; +type LabelDownloadButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, downloadAndRun: string }; +type LabelCommandButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, command: string }; +type LabelInstallShButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, installSh: string }; + +type InstallButtonType = LabelWebsiteButton | LabelDownloadButton | LabelCommandButton | LabelInstallShButton; +type InstallQuickPickItem = vscode.QuickPickItem & { button: InstallButtonType }; + +async function showCompilerInstallationPrompt(name: string, buttons: InstallButtonType[]) { + const installPrompt = vscode.window.createQuickPick(); + installPrompt.title = "Install " + name + " compiler"; + let items: InstallQuickPickItem[] = []; + for (let i = 0; i < buttons.length; i++) { + const button = buttons[i]; + if (button.platform) { + if (typeof button.platform == "function") { + if (!button.platform()) + continue; + } else if (process.platform != button.platform) { + continue; + } + } + if (button.binTest && !await testBinExists(button.binTest)) + continue; + let detail: string | undefined; + if ((button).website) { + detail = "$(ports-open-browser-icon) " + (button).website; + } else if ((button).downloadAndRun) { + detail = "$(cloud-download) " + (button).downloadAndRun; + } else if ((button).command) { + detail = "$(terminal) " + (button).command; + } else if ((button).installSh) { + detail = "$(terminal) install.sh " + (button).installSh; + } + + items.push({ + label: button.label, + description: detail, + button: button + }); + } + installPrompt.items = items; + installPrompt.buttons = [vscode.QuickInputButtons.Back]; + installPrompt.show(); + + installPrompt.onDidAccept(async (e) => { + function runTerminal(shell: string) { + let terminal = vscode.window.createTerminal("code-d compiler installation"); + terminal.show(); + terminal.sendText(shell, true); + } + + let selection = (installPrompt.selectedItems[0])?.button; + installPrompt.hide(); + if (selection) { + if ((selection).website) { + vscode.env.openExternal(vscode.Uri.parse((selection).website)); + } else if ((selection).downloadAndRun) { + let link = (selection).downloadAndRun; + let aborted = false; + let outputFolder = determineOutputFolder(); + let fileLocation = link.lastIndexOf('/'); + let dstFile = path.join(outputFolder, fileLocation == -1 ? "compiler_dl.exe" : link.substr(fileLocation + 1)); + console.log("Downloading " + link + " to " + dstFile); + downloadFileInteractive(link, "Downloading Compiler installer", () => { + aborted = true; + }).then(stream => stream.pipe(fs.createWriteStream(dstFile)).on("finish", () => { + if (!aborted) { + // note: if not using an information prompt, add a timeout so on windows it doesn't fail with EBUSY here + let installBtn = "Run Installer"; + vscode.window.showInformationMessage("Executable is ready for install!", installBtn).then(btn => { + if (btn == installBtn) { + try { + let spawnProc = dstFile; + let args: string[] | undefined; + if (process.platform != "win32") { + fs.chmodSync(dstFile, 0o755); + } else { + spawnProc = "cmd.exe"; + args = ["/c", dstFile]; + } + + if (args?.length) { + ChildProcess.spawn(spawnProc, args, { + stdio: "ignore", + windowsHide: false + }); + } else { + ChildProcess.spawn(spawnProc, { + stdio: "ignore", + windowsHide: false + }); + } + + let reloadBtn = "Reload Window"; + vscode.window.showInformationMessage("When finished installing, reload the window and setup the compiler in the getting started guide.", reloadBtn) + .then(async btn => { + if (btn == reloadBtn) { + await vscode.commands.executeCommand("workbench.action.openWalkthrough", "webfreak.code-d#welcome"); + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + }) + } catch (e) { + vscode.window.showErrorMessage("Installation failled " + e); + } + } + }) + } + })); + } else if ((selection).command) { + runTerminal((selection).command); + } else if ((selection).installSh) { + let installSh = codedContext.asAbsolutePath("res/exe/install.sh").replace(/\\/g, '\\\\'); + runTerminal((await testBinExists("bash")) + " \"" + installSh + "\" " + (selection).installSh); + } + } + }); + installPrompt.onDidTriggerButton(async (e) => { + if (e == vscode.QuickInputButtons.Back) { + await setupCompilersUI(); + installPrompt.hide(); + } + }); +} + export async function showDetectedCompilerInstallPrompt(compiler: DetectedCompiler) { const installPrompt = vscode.window.createQuickPick(); installPrompt.title = "Configure " + compiler.has + " compiler"; + + let [items, checked] = makeCompilerInstallButtons(compiler); + installPrompt.items = items; + installPrompt.selectedItems = checked; + installPrompt.canSelectMany = true; + installPrompt.buttons = [vscode.QuickInputButtons.Back]; + installPrompt.show(); + + installPrompt.onDidAccept((e) => { + let selection = installPrompt.selectedItems; + installPrompt.hide(); + for (let i = 0; i < selection.length; i++) { + const btn = selection[i]; + if (btn.action) + btn.action(); + } + }); + installPrompt.onDidTriggerButton(async (e) => { + if (e == vscode.QuickInputButtons.Back) { + await setupCompilersUI(); + installPrompt.hide(); + } + }); +} + +export function makeCompilerInstallButtons(compiler: DetectedCompiler): [UIQuickPickItem[], UIQuickPickItem[]] { let items: UIQuickPickItem[] = []; let checked: UIQuickPickItem[] = []; @@ -126,7 +312,7 @@ export async function showDetectedCompilerInstallPrompt(compiler: DetectedCompil label: label, description: "$(settings) " + settings.map(setting => "\"d." + setting[0] + "\": " + JSON.stringify(setting[1])).join(", "), detail: detail, - action: function() { + action: function () { settings.forEach(setting => { config(null).update(setting[0], setting[1], vscode.ConfigurationTarget.Global); }); @@ -162,27 +348,7 @@ export async function showDetectedCompilerInstallPrompt(compiler: DetectedCompil )); } - installPrompt.items = items; - installPrompt.selectedItems = checked; - installPrompt.canSelectMany = true; - installPrompt.buttons = [vscode.QuickInputButtons.Back]; - installPrompt.show(); - - installPrompt.onDidAccept((e) => { - let selection = installPrompt.selectedItems; - installPrompt.hide(); - for (let i = 0; i < selection.length; i++) { - const btn = selection[i]; - if (btn.action) - btn.action(); - } - }); - installPrompt.onDidTriggerButton(async (e) => { - if (e == vscode.QuickInputButtons.Back) { - await setupCompilersUI(); - installPrompt.hide(); - } - }) + return [items, checked]; } export async function checkCompilers(): Promise { @@ -343,3 +509,26 @@ async function checkCompiler(compiler: "dmd" | "ldc" | "ldc2" | "gdc" | "gcc", c }); }); } + +let binExistsCache: { [index: string]: string | false } = {}; +async function testBinExists(binary: string): Promise { + if (binExistsCache[binary] !== undefined) + return binExistsCache[binary]; + + try { + let founds = await which(binary, { + all: true + }); + for (let i = 0; i < founds.length; i++) { + const found = founds[i]; + + if (process.platform == "win32" && found.toUpperCase() == "C:\\WINDOWS\\SYSTEM32\\BASH.EXE") + continue; // this is WSL bash - not what we want! + + return binExistsCache[binary] = found; + } + } catch (e) { + } + return binExistsCache[binary] = false; +} + diff --git a/src/extension.ts b/src/extension.ts index 6d97f68..c6b594e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,7 +24,7 @@ import { restoreCreateProjectPackageBackup } from "./project-creator"; import { TestAdapterGenerator, UnittestProject } from "./testprovider"; import { registerDebuggers, linkDebuggersWithServed } from "./debug"; import { DubTasksProvider } from "./dub-tasks"; -import { checkCompilers, DetectedCompiler, registerCompilerInstaller } from "./compilers"; +import { checkCompilers, DetectedCompiler, makeCompilerInstallButtons, registerCompilerInstaller } from "./compilers"; class CustomErrorHandler implements ErrorHandler { private restarts: number[]; @@ -416,7 +416,7 @@ export function activate(context: vscode.ExtensionContext): CodedAPI { context.subscriptions.push(addSDLProviders()); context.subscriptions.push(addJSONProviders()); - context.subscriptions.push(registerCompilerInstaller()); + context.subscriptions.push(registerCompilerInstaller(context)); registerCommands(context); @@ -483,6 +483,12 @@ async function preStartup(context: vscode.ExtensionContext) { let compilerSpec = presentCompiler.has; if (presentCompiler.version) compilerSpec += " " + presentCompiler.version; + let [_, checked] = makeCompilerInstallButtons(presentCompiler); + for (let i = 0; i < checked.length; i++) { + let action = checked[i].action; + if (action !== undefined) + action(); + } vscode.window.showInformationMessage("code-d has auto-detected " + compilerSpec + " and preconfigured it. " + "If you would like to use another compiler, please click the button below.", setupDCompiler, gettingStarted) diff --git a/src/installer.ts b/src/installer.ts index 578adb4..f4ac3fe 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -24,7 +24,7 @@ export function setContext(context: vscode.ExtensionContext) { extensionContext = context; } -function determineOutputFolder(): string { +export function determineOutputFolder(): string { if (process.platform == "linux") { if (fs.existsSync(path.join(process.env.HOME!, ".local", "share"))) return path.join(process.env.HOME!, ".local", "share", "code-d", "bin"); @@ -90,7 +90,8 @@ export function downloadFileInteractive(url: string, title: string, aborted: Fun else done = false; - installationLog.appendLine("Finished downloading"); + if (installationLog) + installationLog.appendLine("Finished downloading"); }); }); From e7d3a73cff2fd4de86d8f2f07a5b74841c5d1f5c Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Fri, 19 Nov 2021 20:09:20 +0100 Subject: [PATCH 4/8] fallback output folder on missing env --- src/installer.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index f4ac3fe..fd19b61 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -25,14 +25,14 @@ export function setContext(context: vscode.ExtensionContext) { } export function determineOutputFolder(): string { - if (process.platform == "linux") { - if (fs.existsSync(path.join(process.env.HOME!, ".local", "share"))) - return path.join(process.env.HOME!, ".local", "share", "code-d", "bin"); + if (process.platform == "linux" && process.env.HOME) { + if (fs.existsSync(path.join(process.env.HOME, ".local", "share"))) + return path.join(process.env.HOME, ".local", "share", "code-d", "bin"); else - return path.join(process.env.HOME!, ".code-d", "bin"); + return path.join(process.env.HOME, ".code-d", "bin"); } - else if (process.platform == "win32") { - return path.join(process.env.APPDATA!, "code-d", "bin"); + else if (process.platform == "win32" && process.env.APPDATA) { + return path.join(process.env.APPDATA, "code-d", "bin"); } else { return path.join(extensionContext.extensionPath, "bin"); From fba25aef9fdcc87f65c9f70f0f2872c580ea3841 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Fri, 19 Nov 2021 20:09:28 +0100 Subject: [PATCH 5/8] make install.sh executable --- res/exe/install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 res/exe/install.sh diff --git a/res/exe/install.sh b/res/exe/install.sh old mode 100644 new mode 100755 From 453cc8a84b4b77146e6ec930ee5815af04f0093b Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Fri, 19 Nov 2021 20:09:53 +0100 Subject: [PATCH 6/8] finish installation UI --- src/compilers.ts | 232 ++++++++++++++++++++++++++++++++++++++++++++--- src/util.ts | 12 +-- 2 files changed, 226 insertions(+), 18 deletions(-) diff --git a/src/compilers.ts b/src/compilers.ts index adbdb23..8559dbc 100644 --- a/src/compilers.ts +++ b/src/compilers.ts @@ -5,6 +5,7 @@ import * as path from "path"; import * as ChildProcess from "child_process"; import { config } from './extension'; import { determineOutputFolder, downloadFileInteractive } from './installer'; +import { reqText } from './util'; export interface DetectedCompiler { /** @@ -31,6 +32,9 @@ type UIQuickPickItem = vscode.QuickPickItem & { kind?: number, installInfo?: any export async function setupCompilersUI() { const introQuickPick = vscode.window.createQuickPick(); introQuickPick.title = "Setup auto-detected compiler or manually configure compiler"; + introQuickPick.busy = true; + introQuickPick.items = [{ label: "Detecting compilers..." }]; + introQuickPick.show(); const compilers: DetectedCompiler[] = await listCompilers(); let items: UIQuickPickItem[] = []; for (let i = 0; i < compilers.length; i++) { @@ -52,6 +56,8 @@ export async function setupCompilersUI() { } if (compiler.frontendVersion && compiler.frontendVersion != compiler.version) versionStrings.push("spec version " + compiler.frontendVersion); + if (!compiler.inPath && compiler.path) + versionStrings.push(compiler.path); items.push({ label: compiler.has, description: versionStrings.length > 0 ? versionStrings.join(" ・ ") : undefined, @@ -82,13 +88,13 @@ export async function setupCompilersUI() { description: "GCC-based D compiler ・ stable, great optimization" }); items.push(manualSelect = { - label: "Select installation folder", + label: "Select installed executable", description: "if you have already installed a D compiler that is not being picked up" }); introQuickPick.items = items; - introQuickPick.show(); + introQuickPick.busy = false; - introQuickPick.onDidAccept((e) => { + introQuickPick.onDidAccept(async (e) => { let selection = introQuickPick.selectedItems[0]; if (selection.kind === 2) return; @@ -97,23 +103,33 @@ export async function setupCompilersUI() { if (selection.installInfo) { showDetectedCompilerInstallPrompt(selection.installInfo); } else { + function isGlobalInstallSh() { + let dir = getDefaultInstallShDir(); + return dir && fs.existsSync(dir); + } + let latest; switch (selection) { case dmdItem: + latest = process.platform == "win32" && await readHTTP("http://downloads.dlang.org/releases/LATEST"); showCompilerInstallationPrompt("DMD", [ { label: "See releases", website: "https://dlang.org/download.html#dmd" }, - { platform: "win32", label: "Run installer", downloadAndRun: "https://s3.us-west-2.amazonaws.com/downloads.dlang.org/releases/2021/dmd-2.098.0.exe" }, + latest && { platform: "win32", label: "Run installer", downloadAndRun: "http://downloads.dlang.org/releases/2.x/" + latest + "/dmd-" + latest + ".exe" }, + { label: "Portable install (in existing ~/dlang)", installSh: "install dmd,dub", binTest: "bash", global: true, platform: isGlobalInstallSh }, { label: "Portable install", installSh: "install dmd,dub", binTest: "bash" }, { platform: "linux", label: "System install", command: "pacman -S dlang-dmd", binTest: "pacman" }, { platform: "linux", label: "System install", command: "layman -a dlang", binTest: "layman" }, { platform: "darwin", label: "System install", command: "brew install dmd", binTest: "brew" }, { platform: "linux", label: "System install", command: "nix-env -iA nixpkgs.dmd", binTest: "nix-env" }, { platform: "linux", label: "System install", command: "zypper install dmd", binTest: "zypper" }, + { platform: "linux", label: "System install", command: "xbps-install -S dmd", binTest: "xbps-install" }, ]); break; case ldcItem: + latest = process.platform == "win32" && await readHTTP("http://ldc-developers.github.io/LATEST"); showCompilerInstallationPrompt("LDC", [ { label: "See releases", website: "https://github.com/ldc-developers/ldc/releases" }, - { platform: "win32", label: "Run installer", downloadAndRun: "https://github.com/ldc-developers/ldc/releases/download/v1.28.0/ldc2-1.28.0-windows-multilib.exe" }, + latest && { platform: "win32", label: "Run installer", downloadAndRun: "https://github.com/ldc-developers/ldc/releases/download/v" + latest + "/ldc2-" + latest + "-windows-multilib.exe" }, + { label: "Portable install (in existing ~/dlang)", installSh: "install ldc,dub", binTest: "bash", global: true, platform: isGlobalInstallSh }, { label: "Portable install", installSh: "install ldc,dub", binTest: "bash" }, { label: "System install", command: "brew install ldc", binTest: "brew" }, { platform: "linux", label: "System install", command: "apk add ldc", binTest: "apk" }, @@ -125,18 +141,22 @@ export async function setupCompilersUI() { { platform: "linux", label: "System install", command: "layman -a ldc", binTest: "layman" }, { platform: "darwin", label: "System install", command: "brew install ldc", binTest: "brew" }, { platform: "linux", label: "System install", command: "nix-env -i ldc", binTest: "nix-env" }, + { platform: "linux", label: "System install", command: "xbps-install -S ldc", binTest: "xbps-install" }, ]); break; case gdcItem: showCompilerInstallationPrompt("GDC", [ { label: "View Project website", website: "https://gdcproject.org/downloads" }, { platform: "win32", label: "Install through WinLibs", website: "https://winlibs.com" }, - { platform: "linux", label: "Portable install", installSh: "install gdc,dub" }, + // no install.sh for GDC because the version is ancient! (installing gcc 4.8.5, FE 2.068.2) + // { platform: () => isGlobalInstallSh() && process.platform == "linux", label: "Portable install (in existing ~/dlang)", installSh: "install gdc,dub", global: true }, + // { platform: "linux", label: "Portable install", installSh: "install gdc,dub" }, { platform: "linux", label: "System install", command: "pacman -S gcc-d", binTest: "pacman" }, { platform: "linux", label: "System install", command: "apt install gdc", binTest: "apt" }, ]); break; case manualSelect: + doManualSelect(); break; default: console.error("invalid selection"); @@ -147,20 +167,75 @@ export async function setupCompilersUI() { }); } +async function readHTTP(uri: string): Promise { + try { + return (await reqText(undefined, 3000).get(uri)).data; + } catch (e) { + console.log("could not fetch", uri, e); + return undefined; + } +} + +async function doManualSelect(): Promise { + let files = await vscode.window.showOpenDialog({ + title: "Select compiler executable" + }); + if (files && files.length > 0) { + if (files.length > 1) { + vscode.window.showWarningMessage("ignoring more than 1 file"); + } + let selectedPath = files[0].fsPath; + let filename = path.basename(selectedPath); + let type = getCompilerTypeFromPrefix(filename); + if (!type) { + let tryAgain = "Try Again"; + vscode.window.showErrorMessage("Could not detect compiler type from executable name (tested for DMD, LDC and GDC) - make sure you open the compiler executable and name it correctly!", tryAgain) + .then(b => { + if (b == tryAgain) + doManualSelect(); + }); + } else { + let result = await checkCompiler(type, selectedPath); + if (!result.has) { + let tryAgain = "Try Again"; + vscode.window.showErrorMessage("The selected file was not executable or did not work with. Is the selected file a DMD, LDC or GDB executable?", tryAgain) + .then(b => { + if (b == tryAgain) + doManualSelect(); + }); + return; + } + + if (!result.version && !result.frontendVersion) { + let tryAgain = "Try Again"; + let ignore = "Ignore"; + let choice = await vscode.window.showWarningMessage("Could not detect the compiler version from the executable. Is the selected file a DMD, LDC or GDB executable?", tryAgain); + if (choice == tryAgain) + return doManualSelect(); + else if (choice != ignore) + return; + } + + await showDetectedCompilerInstallPrompt(result); + } + } +} + type LabelWebsiteButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, website: string }; type LabelDownloadButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, downloadAndRun: string }; type LabelCommandButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, command: string }; -type LabelInstallShButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, installSh: string }; +type LabelInstallShButton = { label: string, platform?: NodeJS.Platform | Function, binTest?: string, installSh: string, global?: boolean }; type InstallButtonType = LabelWebsiteButton | LabelDownloadButton | LabelCommandButton | LabelInstallShButton; type InstallQuickPickItem = vscode.QuickPickItem & { button: InstallButtonType }; -async function showCompilerInstallationPrompt(name: string, buttons: InstallButtonType[]) { +async function showCompilerInstallationPrompt(name: string, buttons: (InstallButtonType | false | null | undefined | "")[]) { const installPrompt = vscode.window.createQuickPick(); installPrompt.title = "Install " + name + " compiler"; let items: InstallQuickPickItem[] = []; for (let i = 0; i < buttons.length; i++) { const button = buttons[i]; + if (!button) continue; if (button.platform) { if (typeof button.platform == "function") { if (!button.platform()) @@ -260,7 +335,8 @@ async function showCompilerInstallationPrompt(name: string, buttons: InstallButt runTerminal((selection).command); } else if ((selection).installSh) { let installSh = codedContext.asAbsolutePath("res/exe/install.sh").replace(/\\/g, '\\\\'); - runTerminal((await testBinExists("bash")) + " \"" + installSh + "\" " + (selection).installSh); + let installDir = getLocalCompilersDir().replace(/\\/g, '\\\\'); + runTerminal(`${await testBinExists("bash")} \"${installSh}\" -p ${installDir} ${(selection).installSh}`); } } }); @@ -360,10 +436,16 @@ export async function checkCompilers(): Promise { for (let i = 0; i < compilers.length; i++) { const compiler = compilers[i]; if (compiler.has) { + function isBetterVer(vs: number) { + if (vs == -1) return true; + var a = compilers[i].frontendVersion || compilers[i].version || "0"; + var b = compilers[vs].frontendVersion || compilers[vs].version || "0"; + return cmpVerGeneric(a, b) > 0; + } switch (compiler.has) { - case "dmd": dmdIndex = i; break; - case "ldc": ldcIndex = i; break; - case "gdc": gdcIndex = i; break; + case "dmd": if (isBetterVer(dmdIndex)) dmdIndex = i; break; + case "ldc": if (isBetterVer(ldcIndex)) ldcIndex = i; break; + case "gdc": if (isBetterVer(gdcIndex)) gdcIndex = i; break; default: console.error("unexpected state in code-d?!"); break; } } @@ -379,6 +461,26 @@ export async function checkCompilers(): Promise { return { has: false, path: fallbackPath }; } +function cmpVerGeneric(a: string, b: string): number { + var as = a.split(/[\s\.\-]+/g).map(i => parseInt(i)).filter(n => isFinite(n)); + var bs = b.split(/[\s\.\-]+/g).map(i => parseInt(i)).filter(n => isFinite(n)); + return as < bs ? -1 : as > bs ? 1 : 0; +} + +function getDefaultInstallShDir(): string | undefined { + if (process.platform == "win32") { + return process.env.USERPROFILE; + } else if (process.env.HOME) { + return path.join(process.env.HOME, "dlang"); + } else { + return undefined; + } +} + +function getLocalCompilersDir(): string { + return path.join(determineOutputFolder(), "compilers"); +} + let listCompilersCache: DetectedCompiler[] | undefined = undefined; export async function listCompilers(): Promise { if (listCompilersCache !== undefined) @@ -391,6 +493,8 @@ export async function listCompilersImpl(): Promise { const compilers = ["dmd", "ldc2", "ldc", "gdc", "gcc"]; let ret: DetectedCompiler[] = []; let fallbackPath: string | undefined = undefined; + + // test compilers in $PATH for (let i = 0; i < compilers.length; i++) { const check = compilers[i]; let result = await checkCompiler(check); @@ -402,11 +506,109 @@ export async function listCompilersImpl(): Promise { i++; // skip ldc / gcc } } + + async function testInstallShPath(dir: string, type: "dmd" | "ldc" | "gdc") { + let activateFile = process.platform == "win32" ? "activate.bat" : "activate"; + let activateContent: string | undefined = await new Promise((resolve) => { + fs.readFile(path.join(dir, activateFile), { encoding: "utf8" }, (err, data) => { + if (err) + return resolve(undefined); + resolve(data) + }); + }); + + if (!activateContent) + return; + + let foundPaths: string[] = []; + activatePathEnvironmentRegex.lastIndex = 0; + let m: RegExpMatchArray | null | undefined; + while (m = activatePathEnvironmentRegex.exec(activateContent)) { + // unshift because the scripts are prepending and we want 0 to be most specific + // at least on windows this will prefer the bin64 over bin folder + foundPaths.unshift.apply(foundPaths, m[1].split(process.platform == "win32" ? /;/g : /:/g)); + } + + for (var i = 0; i < foundPaths.length; i++) { + let exeName: string = type; + if (type == "ldc") + exeName += "2"; // ldc2.exe + if (process.platform == "win32") + exeName += ".exe"; + let exePath = path.join(foundPaths[i], exeName); + + if (!fs.existsSync(exePath)) + continue; + + let result = await checkCompiler(type, exePath); + fallbackPath = fallbackPath || result.path; + if (result && result.has) { + result.has = type; + ret.push(result); + break; + } + } + } + + // test global install.sh based D compilers + let defaultDir = getDefaultInstallShDir(); + if (defaultDir) { + await new Promise((resolve) => { + fs.readdir(defaultDir!, async (err, files) => { + try { + if (err) + return; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const type = getCompilerTypeFromPrefix(file); + if (type) + await testInstallShPath(path.join(defaultDir!, file), type); + } + } finally { + resolve(undefined); + } + }); + }); + } + + // test code-d install.sh based D compilers + await new Promise((resolve) => { + fs.readdir(defaultDir = getLocalCompilersDir(), async (err, files) => { + try { + if (err) + return; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const type = getCompilerTypeFromPrefix(file); + if (type) + await testInstallShPath(path.join(defaultDir!, file), type); + } + } finally { + resolve(undefined); + } + }); + }); + if (ret.length == 0 && fallbackPath) ret.push({ has: false, path: fallbackPath }); return ret; } +// compiler type by checking if the file/foldername starts with ldc/dmd/gdc +function getCompilerTypeFromPrefix(folderName: string): "ldc" | "dmd" | "gdc" | null { + if (folderName.startsWith("dmd")) + return "dmd"; + else if (folderName.startsWith("gdc") || folderName.startsWith("gcc")) + return "gdc"; + else if (folderName.startsWith("ldc")) + return "ldc"; + else + return null; +} + +const activatePathEnvironmentRegex = process.platform == "win32" + ? /^set\s+PATH="?([^%"]+)"?/gim + : /^(?:export\s+)?PATH="?([^$"]+)"?/gm; const gdcVersionRegex = /^gcc version\s+v?(\d+(?:\.\d+)+)/gm; const gdcFeVersionRegex = /^version\s+v?(\d+(?:\.\d+)+)/gm; const gdcImportPathRegex = /^import path\s*\[\d+\]\s*=\s*(.+)/gm; @@ -512,6 +714,8 @@ async function checkCompiler(compiler: "dmd" | "ldc" | "ldc2" | "gdc" | "gcc", c let binExistsCache: { [index: string]: string | false } = {}; async function testBinExists(binary: string): Promise { + // common bash install case for windows users + const win32GitBashPath = "C:\\Program Files\\Git\\usr\\bin\\bash.exe"; if (binExistsCache[binary] !== undefined) return binExistsCache[binary]; @@ -519,6 +723,10 @@ async function testBinExists(binary: string): Promise { let founds = await which(binary, { all: true }); + if (process.platform == "win32" && (binary.toUpperCase() == "BASH" || binary.toUpperCase() == "BASH.EXE")) { + if (fs.existsSync(win32GitBashPath)) + return binExistsCache[binary] = win32GitBashPath; + } for (let i = 0; i < founds.length; i++) { const found = founds[i]; diff --git a/src/util.ts b/src/util.ts index 4ed259c..d597775 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,7 +3,7 @@ import axiosLib = require("axios"); import { currentVersion } from "./extension"; export const axios = axiosLib.default; -export function reqType(type: axiosLib.ResponseType, baseURL?: string | undefined): axiosLib.AxiosInstance { +export function reqType(type: axiosLib.ResponseType, baseURL?: string | undefined, timeout: number = 10000): axiosLib.AxiosInstance { let proxy = vscode.workspace.getConfiguration("http").get("proxy", ""); if (proxy) process.env["http_proxy"] = proxy; @@ -11,19 +11,19 @@ export function reqType(type: axiosLib.ResponseType, baseURL?: string | undefine return axios.create({ baseURL, responseType: type, - timeout: 10000, + timeout: timeout, headers: { "User-Agent": "code-d/" + currentVersion + " (github:Pure-D/code-d)" } }); } -export function reqJson(baseURL?: string | undefined): axiosLib.AxiosInstance { - return reqType("json", baseURL); +export function reqJson(baseURL?: string | undefined, timeout: number = 10000): axiosLib.AxiosInstance { + return reqType("json", baseURL, timeout); } -export function reqText(baseURL?: string | undefined): axiosLib.AxiosInstance { - return reqType("text", baseURL); +export function reqText(baseURL?: string | undefined, timeout: number = 10000): axiosLib.AxiosInstance { + return reqType("text", baseURL, timeout); } // the shell quoting functions should only be used if really necessary! vscode From 43fd6e8e5ffb0fc5d3129d4b38b0816f5b98a29d Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Fri, 19 Nov 2021 21:28:25 +0100 Subject: [PATCH 7/8] disable enablement for rdmd in debug welcome view --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index afb71e8..930d52f 100644 --- a/package.json +++ b/package.json @@ -246,7 +246,6 @@ { "view": "debug", "contents": "You can run any D files (or D statements in an untitled file) through RDMD using this button, the command palette or by right-clicking a D file. To run or debug complex projects use DUB.\n[Run File with RDMD](command:code-d.rdmdCurrent)", - "enablement": "editorLangId == d", "when": "d.isActive" } ], From 2b8e59cce52f6883007c6e7cbf069cf41d80df8b Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Fri, 19 Nov 2021 21:28:53 +0100 Subject: [PATCH 8/8] add debug walkthrough page --- res/images/create-launch-json.png | Bin 0 -> 24346 bytes res/walkthroughs/debugProject.md | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 res/images/create-launch-json.png diff --git a/res/images/create-launch-json.png b/res/images/create-launch-json.png new file mode 100644 index 0000000000000000000000000000000000000000..faaec187a86771ffb9dc23ca9f3eb1f03b08c1c2 GIT binary patch literal 24346 zcmagG1yEd3lr4I3f@^ROZo%CN?(XjH?(PyCg1ZL@5ZqmZy99T4`wDVPj6XkpSAZTQbA4}5e^p)002Zu2~i~g05bsnAi{!!u5?;b<$~TIoP{M-U}0fb zx8=70fEbVz6;ko|ak}oUgRZ{8`7Y1E5*VnHEQ+WR3^%92&?-4^Fi@cq?T*z9&mXGR zlcHzRY~&x!zhK=Zn2Wh;SKdq`=+$acz(H^225(G|Dr}OL6cX$Mou!Kz_ zA%ukCZ|&QjT|Q5w&OGK6EV)0N&GzvJX=~fTVS|X9zUI0>qdFEP8+uS!UdWzc)n(gPw~IV(E0ruLKpHfK3{7YOJlsqhcuiO zr=)>lrzP@#t!rp-9va&8d!c7yx?=Qy8cU&7pUmdr_-QGd#ZJ%3x#TqpT8vTW_q4Ah zLLa-$(ogfLoYiLgwzjrt(Sm3!#(T1F(7{aB3suz0C9se|&(F_4e*AcSef8VlNR33N zJidEAS*(TVCgZkP*zmcLMd<5%KCXCP2T>7H;^x*cSfL z(9l@w8h58m8nIh%HT{GVf1c;c>Hq#ZnnFu}kFTYrMW;+5_XD)~fu^RWp#-mR%?2~s z9NwqlM6&t$dD6tg364z@<_sJh9EkM(quJ7UB7sSxx-V!WtM*;hpq;sc-_AVWozVNg zPJO&iZFG3sul)uC_`-OhgAFern-(r=OFqEK zbaIm4`@ZTB_BMmXTrQit(XbCXP;hH+Z}0s4eJ@Sl4WAVTCVU_o zOFsQ;esS@)auqWZlXMx179D<2%w}=ecfMYaVSEGm=k58V)|byEB_#zqxMy`$yIci4 zK#&-l#caHJ(+@totDa#qIX+$<6eggZAOq<XjgODtvnF7(QUU#GZEfxL@5Qd+ z%Lcj|6gcg_vXJsf@sHlcFH*Lr8!g?)IsG z*7VgyG3q2=?TrJ4Y$hwnfQ+4wr2PCF6BD+BF*p#7rW_p20&hq1o4yaaK0fV#EKZpp zPHMaUIsfitk;7(bI$qeu&~TgR{W4r#&vW3=;a{b=Tlt3=4vbfD3U;7Z@baXB_QArGQ96P7VtG|0*g`awU6lC;EB_)7cxca z?&YFOn{oCn3uK3t2vjl^zz$jI>w z<-!06eQMQO#Y*MxFI^uzY;3M0-^um#kmp?{O_)KLaB_5HnXTjA+1V)o%5)Ia+Z;|wO4pRcv+*@p{1k* z4lFM(3keCmEyM6gNlSy^?gfNEleq$26%{l*JZm;OynE!A`s6cgcyc+dc72b}Kb zKlVKI+T12uLpwjf3Vts*oY;W~F1*U^Q~q+>&agM;tIrk~&z zls$NJimSI#70Xq^kqCMneL(gCQRT;emVHHKr6!%4j7*Fv7<91X)3xczt4r?5>FKz^ zSMJW!s3m&8NRdOzM4DGi26T zX89_`3LQ7vJ&lLrOzSj2n@iO&{__E#(xAIr7U5sE;ptj5u7E;erG4iz!G$|pfeQa} zLW@qj=@gVFG$~O9W$;)Gzgt^D9B{R$?{8~u9UUFr=KWwd^qpX{Sg!PhA8W`_`df2n zCm|3bOah`D=L5M15HQ+mYp+h{i`Z{=+G^Gp7l#&x(7Zq_hhRHs5yT~kwH`Fy=MCY?%00^8fuvwgRy|8RMEsIjrH6ID>7 z)57e2u7f`W1>O^dN&5vYlH6W6AEoc>$viwfJQkhC*49?{&Q(xQ5GbiPvzpE~K(yoJ z?3~4H8dcmo^Dj7BTQmE%BL<98q~>R4oItskA+yj07&2%0fxgwZ=+BesXYnGYSag_H9>bHAao?ZM3>p*2-=YRIL&N zTOcX`LD>6w+XX2Z8Q9kGeC4`*+&_u}k@Hf!=M5;vU!Ij;L0mhT!9vN*{C(=+j`;P-OY46@+zFUzD66HqzP^jh&l!2cbz03ox< z@cDWhkD9!+wDvEHKcKY8kl8PmPV4-5f9WJB%$a7@RZ-d9+cRzHSOM8<)P`NZ&8>RJ zZ5~v*h9TlD#4q?ox3}{a{sDo(nqglUPVAudVhuStIT(n5Pda^HQW zOhCXd&>siBMk@*l1(lMP_Ho&VY~T5`4`MPEn)v|U{OQ6#?lYn-P{M(t5QKppAUJHa zUIYcl5Zz$#OA#IN(!y19Z??b7UjO!o?=yLwbDDUyf38VY5z@G&h3ybr5jTfR4ut`EtO|OJZ4ht!iV_qfzdOH;c0~bPRQwR`5>1V(o zMuDt@hx+fA-JG4Br=(f<3Xm-+5~xCAFQM7|pL0SZpI( z2|C(6OxqgncG?aN;}F?pI(qB-NLjJ~-P4QwuMYi@uGG4N8E(ky&pnsQM80l=%pQ2! z_j*QD_{*&?KL~*UzP%37<(BmIP(8yBjlIPR`;x$gOJb0dq1+r)%z4Sx26ZK6XSsDJN3AdSN-kmbF#t>`JmXq&ezi9 zWS03j7JeOp=l<5<)HnS1mDUIGBHv`E*-1aXIg85Syh{|-7#J26Obk4XS}(qHY@yEt zuZNXN!2R$d|D*OWEoQFzvNoEtVh96wGkIh0hHH1K93HY)Iw&#z|`pbl> zPLDAW(}~~O1Xgs{M+x6YE4r%F-6`KS8mq}K$eC_a9{3Tm6jANnQMh?tLU}I;RmsTD z|DK=4Dc@mift<#iCY~JQTRmUA=aq&PwxTbruBE`o?^4&+fjvSfFmQ4oX+lns<>t@o z^1R;?wH=-M%)O#l+OnVaOZfg5PR?7pb?DaB*N&WDEbhNIlknpXv^6n-xevx@{ z1R~lTMLjk)gsN)@be)D4T(;=sq9L|M? zuATAe36jb72SFPdd;>Zu{u@SN<&6dQsXMMC5n)xb>G$dwZj{>JE7utI5`ycJ&Wb+s$m}dg#R0YcTjt zdR`}+(bj3t10=VX#{DNb9L?;sIfUxa&Oc^-gsSL53VXUz4i?h5-cxeqklY}j<4^xt#_i-7H2h081r1i zzP0$~zByt2MjoCFIKyI&xMdb}=`!zeb@YF|3)B0lV{9|^QvKUThw0c}^ z-+#D0;a-1sxAU;o^6*P7EtNz51{Z0*iP`@}j?aEqf_=@F;nm8TBnCM;H+*?HfgCzk z=a=2Q!kFGH>*_HNW%siNug5<#yZY>m?x+zdFILN(J&L9kSNZbnp%$%-(Lhgb2j#zO zjrgj{-<^#uL>W__kG`|oQVIHgWAl)!Oa9@xQyLR~JmYop?IK5#fBu-#KYx~uU57uk zbnW4fIFaq^VPWja-dyB>^s zZ}#rC=I`e%?N(Es(R}m+VCM>YFPImc$<{m@>bcx*&bI~2c0gE#)tg1AO&K}I?BGoW zEjOFp$|~co=iTtR5wGR5v-s46M&r`MS`V$Iq^Dv2$xh$WbVXN2cJj4>YWFZ>eN>*aujCy`g*Wwj(!B6oF+{-XNXx%om#( znAa@M*sktt&g4leH@!m&K2|R^HD<)tX`e{XDoq}ON!gm}`M+=Rld|6Sd4OyEL%e(c zgL80g+~@7~`gTJRdRaHLHm2e;3X=Y=Q+J=umb&=f;3PFW#T;_DcWo!9(JF z@$$v$TLAqg1kEuA{r{`H{qJP=f79vyzq|X%h!Nbq$fQwR6T55##3LYppTa*j$DDq> zu76B0L^J#*BE`o$(Xxo^b8 z!z_gJosLI@-9@gF@IuI4f4`+oknuvAVj6l@G-EoEHT0mPS*>a14uXI_bnir!{~W>P zr{JbRS~zyQaltna5(xVQDA*r+{sBCCo3G9q#@T@+e$gJb%`CLz4o}BBW8l^ZS*4}U zoh)n$)0fyY^ShK66$*ui0la9zw1Pju34GQMl?}yGz<}|J-z5z@kIEmt40^Lt{g=HE zt2HN@MjX&XOJ3wP)BKJRK{fri-_Djj?gFBli6Lx8NEPJc* ziDMOL!`MEECeBVO)A|^S`7~La7ugc;hD($K%o9hXq4q~QHQY+tTuRuWK?rLtp6U(y z0dMDsfUXOjvXv%wRj4i0`zP@n940*fIba{}?uq9o2R9-RaRt8!Df{5sHypKA)L$e_ z09z_vm3UCIo}LdT-1?Pghv_96(j&(!aq^5c8i1@N%^lOrE7_49f(mO zN5f_?U>g2vJ@uHGycm-J^65^qr;iAQV-{4+D9&U$3Ls=UpZ)vl)|3K;x2f~2lf~W6 zMwR|#Bk?nQidtt#Yc(?YW_Mjv1da$8)I?pDp@fDYw~qeU)uoowEmaMvk2s6~CdYso*VS%YV8MKdO$+B z$vy=?{`E^5bf>+9%Wl;bsY2Rk6%??7wJy8yY0Xkq1zNc7Su%7ui!96K$$8&&Ye7+! zpE|um!&-Y+!IC&BUqBJJwxy`Oze{4v-D$Ue<$a~~o0#8`TN4sZpvquXkIoD+_4b^B z95H~}=plT;2S2;u_^F#;F}M;@m^E^Ormzx`0DZiI7hQbxFBkx1!B=RktJhj|NkVPX zhlIcr_v@|7qEO;Kj5S(r_Jnu{_d)>%(#vc99Or2q_!B0NReK0^auDXS@5v}wY=p@i zr}4G~^(&V3n{OE^Ry*$+g^IQ^xfL(uV``tzieLf2BvFa{d)xs;fav*9^DE5O@j+k; z&6owytxGNk#Z#azMotwOFr6ot;CtyS=pFPNsfQ ziPAyH-Jy&ZcGAnH=(jSrlw#j;(xUR>gw&@TIrS3ep*e=u0;EIsCX3R5>c_B-Sva_fVLlXze5*L zkj+HPL3p8)+4rPjt7NmgQY7Wgb-vxrulX-!JrKU8%c?1BA)0k((1Gy0F-&P460qQ$ zb0YX!D;WUeG>EG;dCsAjtm#~~%!~?XjcHBh+OR0@XI7~dCJZ##=@iKf5FN(baVTOa zc+T1KB^@k;5Bm`;1ZkDbe}`r;?uAuU$!e)^)T%OXka|(~$z(XiO0I zcM4U_@+U3ik1NQ14(UMZ*_kmHR^6(%fZ6TuNpeOLYb}7gkPmOiN&Or^)*`(VWabl@)j?E@ebzB5vg=RCM^zBCB4Zk&3xLy8ds}s&Zl(C95XZ z$oxO$7q%*l(r?YM`wYNb`eE7Az>=5eii;DhteL-x@;c!5nZ%R4pJ<7Xv+Ex$FVR&> z(~#O6f1Z>efd~+a*z(eMS`vxMEFjfQ$eo*)j7updtVQ|q`0~k8Ks}{C0+JXAXxd1U zt3Clo1#8u^eW6@WsX_n(U~+AI0@17I*DIYY;8^Mb0M^LI&G6m8;F}d10V*{IkY68KjSlwxYNF%dr;y@HbNIJfux;&Y$w#TW~1H1!}Xr zHeb?O|LBbw#Qf-IPGvixS67GmF@T;a$%kDzQEe(a5*-U&nX#4*Q$o^jcJ5#i%kt%; zZ6c(>%*2_?cO-uTqq;1)*yR_g#+Nc)rE+D=GGR<8a~yjfLBNL9?qEE}EPI4o9EwfN@`xH>9yD)tvA%mBIS#ts{GRz!K#yL=AlvZ(D- z(yLV2Tz>D)fz#z#OlHQqn{J!-2N_DO8jGJ9Je+ogICLbl^n=s}^^#AfmBoTRgu&iV zOaM070(UFk(KWoF8BXOoeo1(P1HMM#^0fyH2M_t;3#UvF1O?g8ka1Lh7i?;yTk664 z^a3f(sHc}`nrm81;%J5isnVCk;%}Kb%~i_#DpBCUn8FBO^XZHxGK&fMa$+mMO{>39 z^`{{RL_!o86$Y8`&)9IRZ&J#l*;uU1Jm5KUe)7P+wxnDaX_p*S}-P zsbTJ^sa256-TTW|E3m2V-lk_O)?jNUwz;dPA^~toXi8)SY8MEk0iXpbE{=ZHwpQ_Z zuNU^ABa0@0+EThNswLc=#*6}_CMO?a2EXJ~UFga?Wc90+;^UVH&*c(SM~ifi8UcmW z9G#yOJ|}?+TDn>)tDXFs>AzTL_p{l&u>xCvm8prNKFmffWnzdeCZ`<#x}Pnu-9P5P zmQoV767A2-4u>VSj*h#KpqU1X@Z#kMTE}-?x(+ehzN=eSCSx9#z*LiY^6?5Ms69*C zT^LOj_9cv%AlJ1vXzVkh@2L2CIy$QA7P>;=xFc}#1lYiO|PZlCXv)rc5B zlg5kKnuQ%_U!K8;Vpms=FE`SPKmab9VyXW9$Jr!4|KV*38bR_Ay(53jz5(y1-gON> z?$Y*B3g|kUCK($lUR8y~=m#`HRZIvW6m3F9%s$Q2py2$93M|9~*Ty@H-@jXLIjBKL zq69w}ScY(T72m}$nAKx8F~I!Xxa%a6c)0J#pNt#{FgTqrz)*Nl6GYj?nfU;O6a$=p z^D)8zllb@mLVT+^B*O7uCXTUcE_Xo7LEd%C^Zc#;w zwPnxrr7`m$n^T9l{F+N)LtW=u4lC9Ig&^Yi_>sBifR;D^xs?HJ3g;d~RVc1K;;Sn^ z#nbbhzZ;gHM0`~Z9Y>6P(m-dz!1*a)n@&;V7zv8}4#=Y!R}5^3UK+?4+Xe#uDCp~N zj+K^yW-YAlCg7=dcdkt+XtvYOSlp8BIuriDw?fq_FHmWn%C^2Thf->j2_l zZ6axuNwm=M3ahK+7WB&xz9Tx&ti@N}2Y8V8U6*@A zVZQeTdlw6zb$2r0(~M^ARd^5iI)-{!ECQK5umQlfUAdG$(bLiSeVvjo6MqJVN)Q3J z+I`aOxo8dAAlB>8!>sD;{ox4&OU~=Sl9D>c?>!~1{llF%CE?+<+Q^FobN(kCl8zAY z{;e4+=AiPwi_$Q)9k)mDK1i!iv+`z_%JHv@-_QZ;<;{(l`PbX)uDQUA&_R&_oQ?FY zI??D7?(MO0J`Nc*?Vi0ruw3ZaOHz$iZq-+HPyIKuzw)`-gc5|WvM9WNLglUHi_B_< z7s0xJrcn?f-1uZ5yW6R3)^q&us2o{ZDQoxe!x`+r=5^dyo~6tTya_rewU_(BF^cMP ze4*ql@Y|*(IIgSS;hYEk8(J0D#cd1b=Gt@i*?kwg+jdhTpUYEJfGfK0qlvHCr@Q** z?d+}tQ@$g#*{$FeVrU|tIcaqDb_<$Lw+FC7G>O6A$^H*-W%D0$zv-iNIS0LpJSQPP z`rxl_*Ef;$eO;V8IJYMp3jv^8h}UMWv7xq#pZ9{5jd98SGhP74{;X;aQktKq=R|Xl zfU=Jtau-LJegy+X7*w25VBu=%h3tKcqua_c*-gB1gBm1VGb^9LWZ~iWt*(!;g9bb# zWe9%XWxM#&>rfW7K~}psX`IHrQyhMOkJ@;o8OnbSJl$5-|9BG&ITzHpdNGc{$)I&2 zyj5Av{$9Yhh#NcME}wje@A0a*aAT*V_4#2e2M^=d@eJcS6u&{?1~_8hdw)8m9l7Y&8j%+CR*-L z^hUoQ3^;5HXR%Xd&9c*NyS0owN|7YvY~o;bc^5sn{t+GSUl`+c*E8$7vOz>y#HfI2 zJZ;h8%szWiiCZ}Kn}}U@Zhrl(!@aT*02VBx5%p3qg}Y>SoD~WF zlTEDQ2a9DH1hm`e zlL1|eBQ)Mqj|x9bfPmO_)b#uP@}!ghM;}Q+RLsW@3us-*0UVq0{ZPBYrEW)@tLD9T ze;fN?>{dF9nAq25h z;eKKL+_BNVP2J39pG+sLwH8%p#o5X_l@uH-Dt(I?%em%ZwQ##=&WN{3{7Z-x5Aj0&#P`|#A8lg5^rnY<^G>Nfw2luVOzYM7 zax~E#L=2GUin*2Dxx@Ae`&#b6&mj>!&*ohC>S|5HuzUZ`Fn4BU+P@9TI4khwzVe)^lcUe>*Z&KJ3%zg*QHDcWSj2@)AU#VL9Hc+h&1P-`fVt z>}VlmRi^)~_f;b_FY$CZh$WnGfKDUye+KU64``sz|L*OaG3@^Z6^!RvB)HNrLeRDR zsl+<%m~f0tLmK+(>gx8pQNDWF=XWUU*krKRDksXHz|%b1{&C&x8H5YBG)C>S$5X@@ z{B)i+EHn09Ipj9!E-<&~hOoH`8t#GFYRzMY3__6er4ho80Rnb71-&C2Kp!~B(C53u zU{n_kkA-m8SiwCf7)U(zlOHN#u!1JIEiJw`i-xYY(4j7EJQ_ai@5+l}w|;3iF;KHucnfX$ zR~@*BuAxwmzmy*fpniCKJj$kXAPHaqBqD_bDWBJze4K)}2SU^p8-ic~ z`E_+SjWNy+0Ga8$BaCSg;LsMF3zBMG@P;X=%b&&Iej6=%bFewowb3GldHqvjr^>*5 zH7h%R|DOD|e|Kz?*6Ic4?&i4tH>{%9$%PWa(}pJ1_o1j^dh+IyDA(mPba!{>gycMC z4#Va3)je7?7?p^wr=cSkjR0}{;;e%yKcC|<3F70)M)Ds39}t;8`hlk60xES@`=DUg zzMV3-Z98#`>DABCb}Q&b6hH*=zC05VJAef2h8Ep(1qEo(YFCY&Ym1^eo}%62-k;TM zbh0XrAE4P!H8q}4@B|4{!l{HDT7KtO{QTKob4Bgx1O^|$cN?d_SgtB=v_RmW%$RiL z8yxf_N(tWTrR$U7^n%B?p21ig-ziE8?GDPp&5`TCnFSS^cuMkvg*)Hj4KNCXm3rDW zbRfWLWiR9YYN6*0m#5J)DzU@CJx@m1b?^7m@-o2#3c4{U zU~B66@Fp)x_C!fFekTUw2?{Fm*AZ0Xo4ia?ZhNKh`UTa3i{n8X51a+Ujx;!F0hv*d zs{DXGlPajGGB~HT)%FY(DsB@?&Tcp6)ui{b_p_J;=@^|#@D}&{Z?(ej<}T}=J12b)Xg^oF)cK`1;UtU{9+3Rj;J0bbL0vlnDAxT7*myWaiO0OkamkI8g1O~2)l86-YnsgP*473I!R_JQ zK6K^IKS>VO&0#fVWi!>*RwF^`_O9!~gyqfkU)5pzh)Y;4)Vk@ZltWGoR@9${@(JvP z3$CsIq1H2lQ13gzaV@6HlglI1PbM!7JkPPDaBVJ-{<};Z zz-l#xOLw%cD}@Uv_n#wIKXGywqx~15xC|3&>nQOP)WRm^EbWG+6>-*9I^OlLa$WLZ zM2Fu(qv;%TTHPeNE(jV!S`T?4mJWQ?TDE_ZY{OAB-wlO$$AD81-d#iN6!39`-|Fc zt4X82icCa*Jp}_ATe1J`F8y8aO3xcX`oXT&Q$Wxn6OA%Xjo?> zTSHSh1+9oA1O;NeE(!HI9(s$DG47L-ib^RP_g?%36RicWqPEskKyOS^I3@n-v)%5o zFJ{4_NU8hE=Y71-=yX&Pk0;uR)=Bd_ePm{z=$G9eTXd|Ufu)_k6qSN(bQ$)%i z9w#PsyfH^}DQ$UryCuF^1%@x5`+ovJ07%UcH)qFxnH5zgn3or_|jkrQd7R=)gBKRq9QoVj=|c*v4!jL2JK@3j{kQ1*P?>lA!fm?Tfx$vYiC zF8SUmBR<oEb2{u!-#325sVtesjpft@GiGa)LQQ|tsRyy*vUp`Ktj4Om@LZYJ}-!fK76DnxZ z{_k|k(Wg3|IPGiwJa8fJ{j$uUTOCZvq>3PBHWDZD?!L`?R}u3uy;>XmL93Ey|lFr;7w{E>D2R^n5>$S!;~`Rqz9vki2YJ=eFC zKnofx4YKLB!qEYMvMSAd9$21%3GC#EHGi%-JI=~z7c0ALx42VJvbZ0*SgI~m-b#&m zrUt25+U=F#q=v}Y@*71Ivo#W%KFqlF_jwz{yBoac( z{5~@MNY`#nf^sEPsnqOWFyE*p!$1nG$kUHY3MR0%-b-hzVdATU8L-!A0a~74w+B|*72AR;WuQb98s9OLwQR%!cmcg%3H&7)R zcZ2>jUk5-69twonzr>koMlwD|+#+>L<6P9tB>2&>rVAV&=gzQ4xM?*a?beV2m;gZG zggMP!shqW7B`MrJv?A}<*{?pQQc@5IeW;Z{975VkMNV=;jPgC8tZPWLvas1yQ#a1a z7JYxD%l!_Gls|vg@@2(^8Yv1XNd-Spvxg&+)j!- zpqk(?@~g$2RSS*dY7H$nu5uL@ij}=ktE4!Z5BLfGDPh2+vV?_d7@S06>^cc}RT?Jj zC*sC0z%;8%EV|Zr$iPaVHGCZjp`dVMd(g$1WwXG<@BqE#1l;X-p;luur~NG+$CTJr%c(tLqFqeE_9O@(bIb66!7ZM{Ge|yHu%qDjAY~ zU}sDuc*q*wY4kZ+T?Vb5Tcc>Lp*e5owe-J>!)_-Bt{F#CrAIwrK0vjUthAn51XE1H zI2=CXq3X4q4lkDnDeM_ffAc5k3PV!hrJ=4)8x^_mkSdp{>X>u6sTy52;9N^bmsjORiW(Qs#KhO)#za3!x?|>dg}gxI_vw{gnX@q606NvyaN@ zhMn#eIizu7t35LyMGtC(BKHYR_z#vU! zxTBI#Dj`&?`XnoZWM4r|t7Pq+(n&+c)yG20%>=llKw(K&>M((UvD>H5m{Rl1s*4Z2 z+oL3`L}^u5v%{|W8vacTO+6q^9w8zcQ3V4Mn%u)H%1KLyPGg(~KwbBawt5#JZ%jt+ zAzH7F)-$C^N;BUM4I~)U-=Dh1$Ni^*Y;&4UQaVUY)t9*StDI$|(2+vmb`dQ?z#P2<&*jMT`P>9LPy}LL%cmFrwkwEa)phf{lzHK+ zS?xiM&KS@k=~{LaHNR-UEo3I|oRdik3sUPTwJR%o%GF_BkA^_1vphF)_Te4-m;>w# z!G7EGjfI4lxw@fvf33#-y7G1X!fZn9##dZz!mn7dRiV7v|E~<33b%a{{rXeMbh+U^ zV86jc0dXUm7v``&hVCO`o=4;nyPA4zEm37U+?!fL|KBmx*h2MuBen%u!(jS$;F1+v z1q?hJ=hDR`?HZErpiN*4S~Vcx;QdvQ1Pw={ED`NLr!{MOJ`?|&5y9XuNE4ypV(9wI zy3f?*8N|}@H@j~VwqWuyXt9CLodu&M{=WApznnga+0A;O)T}YKIIt-HpdWmeD#13 zJs|YUylxo(8mrM6oXFQG`3-Dq$%i~Yer`{1P)|U*yUVh)nsiO^VcB(&S*k$x2^*;cD;-k_OpO#^k%^%K)j8 zKwvsI=oz2ubva|};o3dK7cy-P>EA2|-~bR#UZ52>X|qJM%6&_3)Sh)VflAVEog}j? zgqSx^adRiF82^qrS`k?;L9;Mmw8LyhO_on%+Fb~&Py85|#cW8_FV znOH|ZUf@#lN5TwR;#k6^zBpRxLr!|glk<~Ve896`3^=0k-GL6~2>ays+eN05IgwpK z5L0p4ae@sR8VE>Kv$JQz4j3<*>&7>t6#S)R@b{~S9YX40GEuqgOD`nnm^nkkGkpCk zO+bD%U5zHatA`{0tzQj{22@(HN~Bf;HaPF#QK~*pPd5-M+Y^a?xgj;$x`}ZG7(x< zTx?j`C;>Xcj_-ACQ(nz}VeuCZmrW2%9vIL)R2LR>A$i4RTwspBPo>`+?0lY znf;nEq#qG5_o{YBNx`Yr^-AeEn1@b*_JIV@N&KIEt#c6Onp2v#oil$O9j^!h4vGAyxx%@fK$fr=!=R0|>Sa`+ z9NpkwQCCY$MKF*o#U(Qu&(t_0*h0{~2Jk5I^&%l*n}V-|blw+{rhpLiN*)|a*>fy0 z1bpK^?MnRvG*@jjp*}S0)+wO-njAk7Ni5-iCmO~OC zM}nSX!6+RnPUD=4Tu!^cD|ynJgh-+UwUyA2afr(*emts5Z^pfJF1YsW*UsEmXV42_ zB0=J?LKh^~*X_2^e>A$x*93`Om^y}oKCoDD0u7d#3Smh%=DAnII*mT|$^6r>+6A}I z<434hq1h)$&jrLoTe%8`gJ{*URC~EvryTT~soE~B@Jw=)-3<22DIwO>50oySqt!?O zwpJMX_v6b>wpqmn$ROTA!au=EvE<_K-plp#ZE!u_nt!+ZRyTzogiv>JZeeqaijLus zzA@@Hy*orU7~OULs$HZ~@P!b{p?^b03G)iBJ5bM}oUH_}draVU|*NRfIYjik=?R`x4(sd)nW3 zp`~(|3(lDKl;_b|XW+lngejWzaMQ;18W?RipP_0RH-|bpTs(QBkYKNEj%9O`zc+Vs zx36@(z8IxJF&{hJuJXX)fpu#(?dS5OZHEfXzl>j1tTfs@kzfK0YOkj2tqT6?W!!J3 zKCk(`&z)w6hR;Q!C`Gk|o$^xiiI4q0g;U_58@kl?P74*NFyKPto#z|99PV;=jK@Dn z-y`6%-`@u7}Zm zUcaBgO{U$z^ToQ zu4N)1?`JzPK>B?Pv?K zI~pDY-f%F6KbtRblNK} z-MrH~#|nESkP*-Cv01eO6dlpCbAs$WOdPw3rAE}TVBN9wn=&%#aFY_kuaxEu z24M4V!WLz$*gyQCJ?R4g!q$KP7a*&6M5M&wkb=X;LUCu~LF>iotF4JrCa+0B$E#_TB}b#!`7AWq(%*U z?0&%SAGfRJ5QzZ+7FCXzt#`M*k*l8% z%5`@BWFWR-u^mxzO(4{ zI*IKOFuE$&c~te`e&nskl`!6U`~DKpF_4Yv2?w^Me|)A3lule(cj}mln9_@{tLL_V z;xF-Oo?XJdw)X6MAO5_6c0S&+fMX;lV~lXti=D?bky2jR>`;)x&Ur}ZILqPiR&O3LdvyQ3&9SR}+k&FcoZfs`)!0ixxv}o< z6>p`_v1EgC1**rIhp1%z18luC*pe}cR6s3_B>|R1FUtf}TR!@frN!Bx?oV9FrNdvWaJdwL3jF)fBjGU)`vg}zB=|TuQkEYO z!Wsl=XX{n_^`s4w#;tmDgO0}A|7+ov;Q7l7f&DxDn&1!Df^U0a(jkH}Mm)MDmDRNB zxW@+^x670hKpvOSk*AEFeBWh-J5Hyb@fw&3@H*^+HR{THg3!*Wtc1u^aBupc(>djmNck(Agb9%M+yl4o1 z|5s89dctEx{C)dpyM%U=^9 zjYjrN?3J388zAJj_1rwy1}2QU7}hSNUAa+GH}A)p)W_3&&^B8G6DRO+{59Uiw4Cc2 ze(E=3r^@hwy5@vm1juB^(y^k-BmAH3wwwMZvzFaYAMfP7c&UTw$Hg<|g0aoX?F~ zg3PDiHC!z$-ob~6c@5y+q6Nj-4Nx*0lP1Q!n6(+}_yyJYcF^u;eiR#LVJKqVg56+K z<{a`KyG7uDqCF8@7Jbmm<1h^v_ugv1Z;}$d{B+ei4Dr%;=;lCAoA#8b(IG-{$lTiO z;vQ2tOf-E(zK4I~l=M4Ag#PDX$2Xp*aQ)OUp2t^H{w*rjUR0`9mjDi)FlOs){tn6& z4fyPTrNYqT$%~+T?(?X4AQV7Ik`?Z&sguM=iH^a6pTf7ht5dTtS+r)QM=SmEcQK+n zA#$MH4xLrQDKv4&T($S2WcWTyAe?7q8PPK1#DKmP19+(cHgr}^Tvc6MtA23~_NcQc zk$z`WKO57Oa&!Mbp$*MbdIr!>XH-Ct@^E-+t~S=J&0f#MPlQp6!|+S8CgXwISN-Y) zvnI)o_Rh`=D0%ASw{k;Co=;;8XxQkPRKq@;OS)D-r%3-H-GF+hZF7tmD!}NK@fjT( zpII9gz*Q`17u7wISgVEg3oq-nHF`{_zE^8tpOw2XfEw+z|I!b8&&@Bf5jE*00<&-} zcMB*%+}QuZO;d_ZdV|hEuR|FoVniyCqt@+8$O{NT6zPJaLR^`!0R`LK0{At-b6-88 zt)Lw1+!#`hzr;y%dp`KAUIxnPF5uquk_(2SI8aM^2pMoT8LzXM28Byp1d_f6IA_lZ zNAB!r*yOAj5T#B4y743N`M`@~C4~*t9sizhMx~k)UU*nz-jV8A-kFt3UyR8NHxk{G z!ZbMOc3T+4)K{zek6i^tkqn*|6Rqw%Q%#_@Ey@y7;9BABL7!;qdI4Ch+>B?GM z6aHXL0O2T;l1%vFezFHOlh%$S&#(btnT^#>Ju#b)GO23H!?dY3@3n6JWPMi|L5w0g zVILoN#e!QLk^*kmx*820EMi8mrK}FJN|SapBpp`XrF2g{|H%61XLF#UA{6|w<$gZR zJPl>wHo)VqGWfn_<-AQ>v_WAD{KyDEPdSwuuPz9FxR5VH`31|&bl&j^?r=zwMSg7i ztXqj1AVXDwNFdgBb6oRL`207AdVSV3==v4VEqmbm^@Vi+uN1bi1`dBr z1)kMx;4Vy&#Ywg5`aMheNcfzxJ|38=w2lRxb>v6S4D3k$`PaARAAJ@C;&?(T4^DW# zN@~Nv{M#zN^Fp+Lnged67{gZlUkq7WYFT?;yF8fOi_Z;SYFsj3kD*doCjqqX9bWRiD7X7J(QJLna%8S!AOA(@5#WA`j#ku@MWF`*wMiyG(b3Vma{o2;d0vv>KdMW(ZfO6j z(*LpkW$KazbXmrxupjN{@G{ayDr%z+uYJ|ec^^4OzQ3a)db>o{c7U$*=Y`>+Gki1Q zP6o}+FHusmqb7S38||>Z7&}QfvAX9q04zr@D!QTr*n6NZztIMSOailP%4$?ihQH0| zyYMcwy@wC4P8iXVz*iSM6L!AVi;6CM2zCz_J$3I9>1wiRg}>qydxIH~h7JGsEC7wC z%GK@mC0X}0hd>2kUFcb z8NwTyTB}2CnSe$~6O*KW#y$YPB#>TPI@&2WzW%!2chsr#R*7y~xw>yxROKFxb1c1p zDvU+>XSqxa2YR>D2sGBc!ze8Y&X@aTb%$L*PE>1JT0a)iQIG^aUVwfP9z@ctTc-C|5l7UrS&ur*` z#`{gWHwPaDv{n-^GbUD@G<8rpAzSoocL_j1rQZaEuYv@XvwjfsrS%8;eO!;3nxL`n z!P0ABkRZVonoIh%c!^uPg^sldOc_Qc#aA8Z6H zQ8*B0nRvv9*;M6XtwD8lZgIc>Zs4do^>v=;>=f-#`br^o~D7pJoJ{KHVmwcY*nmCH_gk4xyAi}2N>#uS3S}pXa#S|T#m*9JLGT-~` z9UF6H=R*PZ;b)Yl#>qYr?;U}PS$sn6%v|dIlfbk}GZV=^>lji!xeHy zVGTV}LV10A`%q4X?KT$kkDGnkOjyMImw<>t4sM&Dd@A|2X&|SBY^3^?@DppqLY;O1 zE8{SlDf73dEsw_1t`^3f;MU(a#Y$#Q0xiWmOm$y9HvWD7JSE-S2ay6}He3+ zr`|zxJuvU4t{lYV8R68FSLc&i2CfqafYLhSc7cWD!ziPCg@cXYuLd(JQPZAP?o%76 zQAi$xr?=4Fv*p5;Ja<$U8aZ1pF|DB#nJA%7e0KpFDZ~2;^UJgY$-*Fh+Hc`86WFF*LJlgvUCOnFR!7ZH z3jz zFEvp4)HEh6_C8f4P3}FDR^&en? zmGm9vey=fO>75I9{HfVlB@IxNPStLDE4*RYF;r@!X_2r0z9j7B#=!PaZY!z_2~g-o zTn)cEk-AGD^Xs4oiRD|jWF@e;_Dx#ScP(bQN61Zs2>~y(BCs@JpCytNGHT9w5Tyu> zE-wWD4-mqVL0Ct@eZ7z#U%r;M6M8IqbhKzU$UTkFB?m7pm7BMV?sbk?pr%NGoD7V}{$0X?-F zlln)(otZCwW$H$*p?la&5Q{gJg=$kSIu)&{;7sgVW*;uj6Bl# z@z6#`$i|m;O;u;S1(oz$+P`DiqF8UG)~Mv?4~=H3mTqXyB8BX6Jl# zl>kbLNWLIcB?lZ)V_@Y)6_z%$js?cTr6_>FT`#LsFY-$!A2X50MfIC85S!T(I}N=7 z#lr&*A%wR+MT0~RBKnp4idu^hptuT=ggU>)ck;x&fu*AY%vsKT)fQr@f3xrJyz|)1FKlQZ~b&Pp*hQR*o8&nXK=XY6I zd$@tEAa^hzBmf}>Z1QPCh9LWLlHkhuB9i=>la@?sDKI|8E!^$f?IdSgOn%UxO13Ce zmYL?RPlXiw{DM=X|NJzn!N4|m9KgF@>hENWtdHy}$tU2)hyqyf72m7x5L7LYlHi;+ zqvkYAD#iI7g{<3sZx7_rz3b3|6v^!TqM;LP(*4X=|#@HruAL_euP zBve{vFFv*D&BDy;s@a*4+9KKF?HFcIv7>!(Vc^;_*Cy=>Qb=HwSl0}WP%uGPt9Rea z8@hYHxVt12l=)5ULyWD5BzNOSxCW`H>2H*a3eeE01vAzUPM4(3=x?Pr3wiHF!r>l& zc9Ca-_TO_+6aMgj+#RWoX(y;e0@~$Q67j4${Sn=~nF z48Mq@x++9DdV-ZMc?h+@m_3-m`z9{)`;OB>t@%Y>_pmPp5Fz_P)nm7a$7sjQ>Ko~F z()py`m|t6A8or$czRMB#04|2VE|RC)+@(@-}xICTAlcNWq;rC$tLI$Sgvm!<6`Ej|u9acZeau@f)(2<33CcN)Md^fZ?vG{{)%;yZJ6+yHQ{>Dfd1*2DG#h5cv9Sg6MvsuLAka0prF^61x$56V$nfo zn|fXVM*uVWoV82-$^^Y;RqXs{ApN!BB_ahL{ly5RIcL!Fx?WO3I2c^%#e}sqGG9mxm>5!3C$tu)o zod~1=;}gs6YV$;zuGjNX)TGpW;xi}`Nq|1#gr5pemXgl9{=GA7KNW-21&rATE>P{v z2xdWzb^%=GQnN>|)!%j;9bYs*>al7aH0@|GogHguoPA&^{e8UFV5rd=$YbV2>ux9X zE?`hTywm1BSxu`2Z{3jrKRbY*&FB4qdakr1_(EyPXHZ-G3MhU9p9N6Ag8o_ioT=vdZF z#;vKT`I{6OhCHke9X=!7>}NxR)~|f~{-G4|#|;WQ2zv6%ou0MZH#|}sN5~e53e6IF z;k6Q3=O}|BwBCWzI;TQ4$ zxfc+KhW*kNYr%*wdW20969{!K)OPvd=Z9AIz29EDS_|FSq8dCQfm#}Cm)0};OYQ3R z3cjkUVx7@;PYOgSmpPa;Y3wSO;vmN|lKU5JYs=nL_yIEgku95TL?L*ZuaRSey^zJ% z=Ug{wYs9FJKxJx)Y&dM zX?*hLFUc*Y%Go{ne%|bjz$vWhmnJ~b#yh7Hu@807ct$P%!rpw0eHBWnrzLdj)HlkD zpQ{%@Si)bBr~3T`S6MSDuv8?xqkl0tr_nw(A(DtX>7yV|hS~D`mR_1vm`M%5y=vFJ zC_ORK_L6*$0`l5=dT6xAU)w;rLp?Jehmnl?+=PRCC;dm_wFTD;XSqTwz3+g1u3mh% zbzArF-?@Q?hSEd)(Me1Ut;Q#08HE_=*;fK}x_bdpg9STxEh|I)i7D|~qrA?{{+XO4 z^KGOQ+ySR`Lb|Xb#W36A)v42Q|SK~?LpK`Rr4EYU)$>n{OqJMmj5xwk6u zlP84K_^;C!%_TJLpMye{j&>Yq+4M+0(Wn;K^5L?u=rN=zyV18CVA|;2zeQ}>R2Gkf zS@VXC?dV5AB(r+z?a>tE@io`&UEIm0b;A0NR(0OuF!6JJJkd@hsfqFzHNXJig9ItJ zB60ICx4jai2mAW#Qxom+loc_%`yTYk__8F3c8fC`B$-_gwlhA!tG-VSh10ProkuJ~ z#EFlpEKyorP$+ru3j+89ty~oRJv=fV^^i-Q#~5A2H@Pbxu)xS4y78o^RWz$ADnAlv zF)Zc{R;Drm0Mc~b5xAoYLq8*^HIlJVhtq<4PVRm3o7S7GVhyy->cXacV3u5{Bl=EZLu z4eH-Ph(uWm#KmLF(Xd+g#&*x6&}Tn3UxWE~OWGS;x1I_lwo@Hn*3Mxot@}&TRj#S)ouS4g+;IWt^cVSQq3j?yl={;y zQ^{SvLn(uL?7YNy3K(%EPop8Mh655{zw>&_i6k&L>`aBB<%; z>4}8p_}WLJ*h!foHeG#xx8G)G`NO z7T3&VD7y|i)>@Zwji#rI(Fm3z2@=_LlwH)659S^9u#+!>OqJ-1QZ)nQt%G=wut0`M zV;f31{T%3NWKEdr2DQ!KpR-d9D47-WC|^Vk;NHM=D(IuC=Nz@wZY9B7v*h4jJZ{8A zYr6YXSavaUeNXBX!a$e*jynYjMyrR1P-{#!!R7~ytsloj=BP@rG= zyW4`2izejkqEilNVhvljE5h(f=W&B9lX=hQlgUIE*v$VDrIj_JC9iZ!dF8Rz%wNQE ztw=ypDmY}7;Rtq0oXf*Mu16AzDQ`jUBU%#qxk@J(N19}AXcv8v)A@}>4#mhk zN_YgaM~;GNkxT@mwq;?V=Yp7fK^4nV{K>Ykn2F(-$W4$+8(l05LXWSnzOX7FgEpV6hcEo)lTqFzE`06