Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions src/installRuntimeDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PackageInstallation, LogPlatformInfo, InstallationSuccess } from './sha
import { EventStream } from './eventStream';
import { getRuntimeDependenciesPackages } from './tools/runtimeDependencyPackageUtils';
import { getAbsolutePathPackagesToInstall } from './packageManager/getAbsolutePathPackagesToInstall';
import IInstallDependencies from './packageManager/IInstallDependencies';
import { DependencyInstallationStatus, IInstallDependencies } from './packageManager/IInstallDependencies';
import { AbsolutePathPackage } from './packageManager/absolutePathPackage';

export async function installRuntimeDependencies(
Expand All @@ -19,26 +19,39 @@ export async function installRuntimeDependencies(
platformInfo: PlatformInformation,
useFramework: boolean,
requiredPackageIds: string[]
): Promise<boolean> {
): Promise<DependencyInstallationStatus> {
const runTimeDependencies = getRuntimeDependenciesPackages(packageJSON);
const packagesToInstall = await getAbsolutePathPackagesToInstall(runTimeDependencies, platformInfo, extensionPath);

// PackagesToInstall will only return packages that are not already installed. However,
// we need to return the installation status of all required packages, so we need to
// track which required packages are already installed, so that we can return true for them.
const installedPackages = requiredPackageIds.filter(
(id) => packagesToInstall.find((pkg) => pkg.id === id) === undefined
);
const installedPackagesResults = installedPackages.reduce((acc, id) => ({ ...acc, [id]: true }), {});

const filteredPackages = filterOmniSharpPackage(packagesToInstall, useFramework);
const filteredRequiredPackages = filteredRequiredPackage(requiredPackageIds, filteredPackages);

if (filteredRequiredPackages.length > 0) {
eventStream.post(new PackageInstallation('C# dependencies'));
// Display platform information and RID
eventStream.post(new LogPlatformInfo(platformInfo));
if (filteredRequiredPackages.length === 0) {
return installedPackagesResults;
}

eventStream.post(new PackageInstallation('C# dependencies'));
// Display platform information and RID
eventStream.post(new LogPlatformInfo(platformInfo));

const installationResults = await installDependencies(filteredRequiredPackages);

if (await installDependencies(filteredRequiredPackages)) {
eventStream.post(new InstallationSuccess());
} else {
return false;
}
const failedPackages = Object.entries(installationResults)
.filter(([, installed]) => !installed)
.map(([name]) => name);
if (failedPackages.length === 0) {
eventStream.post(new InstallationSuccess());
}

//All the required packages are already downloaded and installed
return true;
return { ...installedPackagesResults, ...installationResults };
}

function filterOmniSharpPackage(packages: AbsolutePathPackage[], useFramework: boolean) {
Expand Down
16 changes: 15 additions & 1 deletion src/lsptoolshost/extensions/builtInComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { LanguageServerOptions } from '../../shared/options';
Expand All @@ -11,6 +12,7 @@ interface ComponentInfo {
defaultFolderName: string;
optionName: string;
componentDllPaths: string[];
isOptional?: boolean;
}

export const componentInfo: { [key: string]: ComponentInfo } = {
Expand Down Expand Up @@ -41,15 +43,27 @@ export const componentInfo: { [key: string]: ComponentInfo } = {
defaultFolderName: '.roslynCopilot',
optionName: 'roslynCopilot',
componentDllPaths: ['Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer.dll'],
isOptional: true,
},
};

export function getComponentPaths(componentName: string, options: LanguageServerOptions | undefined): string[] {
export function getComponentPaths(
componentName: string,
options: LanguageServerOptions | undefined,
channel?: vscode.LogOutputChannel
): string[] {
const component = componentInfo[componentName];
const baseFolder = getComponentFolderPath(component, options);
const paths = component.componentDllPaths.map((dllPath) => path.join(baseFolder, dllPath));
for (const dllPath of paths) {
if (!fs.existsSync(dllPath)) {
if (component.isOptional) {
// Component is optional and doesn't exist - log warning and return empty array
if (channel) {
channel.warn(`Optional component '${componentName}' could not be found at '${dllPath}'.`);
}
return [];
}
throw new Error(`Component DLL not found: ${dllPath}`);
}
}
Expand Down
20 changes: 12 additions & 8 deletions src/lsptoolshost/server/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ export class RoslynLanguageServer {
: razorOptions.razorServerPath;

let razorComponentPath = '';
getComponentPaths('razorExtension', languageServerOptions).forEach((extPath) => {
getComponentPaths('razorExtension', languageServerOptions, channel).forEach((extPath) => {
additionalExtensionPaths.push(extPath);
razorComponentPath = path.dirname(extPath);
});
Expand Down Expand Up @@ -695,10 +695,10 @@ export class RoslynLanguageServer {
// Set command enablement as soon as we know devkit is available.
await vscode.commands.executeCommand('setContext', 'dotnet.server.activationContext', 'RoslynDevKit');

const csharpDevKitArgs = this.getCSharpDevKitExportArgs(additionalExtensionPaths);
const csharpDevKitArgs = this.getCSharpDevKitExportArgs(additionalExtensionPaths, channel);
args = args.concat(csharpDevKitArgs);

await this.setupDevKitEnvironment(dotnetInfo.env, csharpDevkitExtension, additionalExtensionPaths);
await this.setupDevKitEnvironment(dotnetInfo.env, csharpDevkitExtension, additionalExtensionPaths, channel);
} else {
// C# Dev Kit is not installed - continue C#-only activation.
channel.info('Activating C# standalone...');
Expand Down Expand Up @@ -1012,10 +1012,13 @@ export class RoslynLanguageServer {
);
}

private static getCSharpDevKitExportArgs(additionalExtensionPaths: string[]): string[] {
private static getCSharpDevKitExportArgs(
additionalExtensionPaths: string[],
channel: vscode.LogOutputChannel
): string[] {
const args: string[] = [];

const devKitDepsPath = getComponentPaths('roslynDevKit', languageServerOptions);
const devKitDepsPath = getComponentPaths('roslynDevKit', languageServerOptions, channel);
if (devKitDepsPath.length > 1) {
throw new Error('Expected only one devkit deps path');
}
Expand All @@ -1026,7 +1029,7 @@ export class RoslynLanguageServer {

// Also include the Xaml Dev Kit extensions, if enabled.
if (languageServerOptions.enableXamlTools) {
getComponentPaths('xamlTools', languageServerOptions).forEach((path) =>
getComponentPaths('xamlTools', languageServerOptions, channel).forEach((path) =>
additionalExtensionPaths.push(path)
);
}
Expand Down Expand Up @@ -1086,7 +1089,8 @@ export class RoslynLanguageServer {
private static async setupDevKitEnvironment(
env: NodeJS.ProcessEnv,
csharpDevkitExtension: vscode.Extension<CSharpDevKitExports>,
additionalExtensionPaths: string[]
additionalExtensionPaths: string[],
channel: vscode.LogOutputChannel
): Promise<void> {
const exports: CSharpDevKitExports = await csharpDevkitExtension.activate();

Expand All @@ -1096,7 +1100,7 @@ export class RoslynLanguageServer {
await exports.setupTelemetryEnvironmentAsync(env);
}

getComponentPaths('roslynCopilot', languageServerOptions).forEach((extPath) => {
getComponentPaths('roslynCopilot', languageServerOptions, channel).forEach((extPath) => {
additionalExtensionPaths.push(extPath);
});
}
Expand Down
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { vscodeNetworkSettingsProvider } from './networkSettings';
import createOptionStream from './shared/observables/createOptionStream';
import { AbsolutePathPackage } from './packageManager/absolutePathPackage';
import { downloadAndInstallPackages } from './packageManager/downloadAndInstallPackages';
import IInstallDependencies from './packageManager/IInstallDependencies';
import { IInstallDependencies } from './packageManager/IInstallDependencies';
import { installRuntimeDependencies } from './installRuntimeDependencies';
import { isValidDownload } from './packageManager/isValidDownload';
import { MigrateOptions } from './shared/migrateOptions';
Expand Down Expand Up @@ -86,7 +86,7 @@ export async function activate(
const networkSettingsProvider = vscodeNetworkSettingsProvider(vscode);
const useFramework = useOmnisharpServer && omnisharpOptions.useModernNet !== true;
const installDependencies: IInstallDependencies = async (dependencies: AbsolutePathPackage[]) =>
downloadAndInstallPackages(dependencies, networkSettingsProvider, eventStream, isValidDownload);
downloadAndInstallPackages(dependencies, networkSettingsProvider, eventStream, isValidDownload, reporter);

const runtimeDependenciesExist = await installRuntimeDependencies(
context.extension.packageJSON,
Expand Down Expand Up @@ -119,7 +119,7 @@ export async function activate(
} else {
const getCoreClrDebugPromise = async (languageServerStartedPromise: Promise<void>) => {
let coreClrDebugPromise = Promise.resolve();
if (runtimeDependenciesExist) {
if (runtimeDependenciesExist['Debugger']) {
// activate coreclr-debug
coreClrDebugPromise = coreclrdebug.activate(
context.extension,
Expand Down
23 changes: 14 additions & 9 deletions src/omnisharp/omnisharpDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ import { getRuntimeDependenciesPackages } from '../tools/runtimeDependencyPackag
import { getAbsolutePathPackagesToInstall } from '../packageManager/getAbsolutePathPackagesToInstall';
import { isValidDownload } from '../packageManager/isValidDownload';
import { LatestBuildDownloadStart } from './omnisharpLoggingEvents';
import { ITelemetryReporter } from '../shared/telemetryReporter';

export class OmnisharpDownloader {
public constructor(
private networkSettingsProvider: NetworkSettingsProvider,
private eventStream: EventStream,
private packageJSON: any,
private platformInfo: PlatformInformation,
private extensionPath: string
private extensionPath: string,
private reporter?: ITelemetryReporter
) {}

public async DownloadAndInstallOmnisharp(
Expand All @@ -51,14 +53,17 @@ export class OmnisharpDownloader {
if (packagesToInstall.length > 0) {
this.eventStream.post(new PackageInstallation(`OmniSharp Version = ${version}`));
this.eventStream.post(new LogPlatformInfo(this.platformInfo));
if (
await downloadAndInstallPackages(
packagesToInstall,
this.networkSettingsProvider,
this.eventStream,
isValidDownload
)
) {
const installationResults = await downloadAndInstallPackages(
packagesToInstall,
this.networkSettingsProvider,
this.eventStream,
isValidDownload,
this.reporter
);
const failedPackages = Object.entries(installationResults)
.filter(([, installed]) => !installed)
.map(([name]) => name);
if (failedPackages.length === 0) {
this.eventStream.post(new InstallationSuccess());
return true;
}
Expand Down
12 changes: 8 additions & 4 deletions src/omnisharp/omnisharpLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ export async function activateOmniSharpLanguageServer(
eventStream,
context.extension.packageJSON,
platformInfo,
context.extension.extensionPath
context.extension.extensionPath,
reporter
);

await razorOmnisharpDownloader.DownloadAndInstallRazorOmnisharp(
Expand All @@ -178,7 +179,8 @@ export async function activateOmniSharpLanguageServer(
networkSettingsProvider,
eventStream,
context.extension.extensionPath,
omnisharpChannel
omnisharpChannel,
reporter
);
}

Expand All @@ -189,7 +191,8 @@ async function activate(
provider: NetworkSettingsProvider,
eventStream: EventStream,
extensionPath: string,
outputChannel: vscode.OutputChannel
outputChannel: vscode.OutputChannel,
reporter: ITelemetryReporter
) {
const disposables = new CompositeDisposable();

Expand All @@ -211,7 +214,8 @@ async function activate(
omnisharpDotnetResolver,
context,
outputChannel,
languageMiddlewareFeature
languageMiddlewareFeature,
reporter
);
const advisor = new Advisor(server); // create before server is started
const testManager = new TestManager(server, eventStream, languageMiddlewareFeature);
Expand Down
7 changes: 5 additions & 2 deletions src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import TestManager from './features/dotnetTest';
import { findLaunchTargets } from './launcher';
import { ProjectConfigurationMessage } from '../shared/projectConfiguration';
import { commonOptions, omnisharpOptions, razorOptions } from '../shared/options';
import { ITelemetryReporter } from '../shared/telemetryReporter';

enum ServerState {
Starting,
Expand Down Expand Up @@ -117,14 +118,16 @@ export class OmniSharpServer {
private dotnetResolver: IHostExecutableResolver,
private context: ExtensionContext,
private outputChannel: OutputChannel,
private languageMiddlewareFeature: LanguageMiddlewareFeature
private languageMiddlewareFeature: LanguageMiddlewareFeature,
reporter: ITelemetryReporter
) {
const downloader = new OmnisharpDownloader(
networkSettingsProvider,
this.eventStream,
this.packageJSON,
platformInfo,
extensionPath
extensionPath,
reporter
);
this._omnisharpManager = new OmnisharpManager(downloader, platformInfo);
this.updateProjectDebouncer.pipe(debounceTime(1500)).subscribe(async (_) => {
Expand Down
6 changes: 4 additions & 2 deletions src/packageManager/IInstallDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import { AbsolutePathPackage } from './absolutePathPackage';

export default interface IInstallDependencies {
(packages: AbsolutePathPackage[]): Promise<boolean>;
export type DependencyInstallationStatus = { [name: string]: boolean };

export interface IInstallDependencies {
(packages: AbsolutePathPackage[]): Promise<DependencyInstallationStatus>;
}
36 changes: 33 additions & 3 deletions src/packageManager/downloadAndInstallPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ import { mkdirpSync } from 'fs-extra';
import { PackageInstallStart } from '../shared/loggingEvents';
import { DownloadValidator } from './isValidDownload';
import { CancellationToken } from 'vscode';
import { ITelemetryReporter } from '../shared/telemetryReporter';
import { DependencyInstallationStatus } from './IInstallDependencies';

export async function downloadAndInstallPackages(
packages: AbsolutePathPackage[],
provider: NetworkSettingsProvider,
eventStream: EventStream,
downloadValidator: DownloadValidator,
telemetryReporter?: ITelemetryReporter,
token?: CancellationToken
): Promise<boolean> {
): Promise<DependencyInstallationStatus> {
eventStream.post(new PackageInstallStart());
const results: DependencyInstallationStatus = {};
for (const pkg of packages) {
let installationStage = 'touchBeginFile';
try {
Expand All @@ -48,20 +52,25 @@ export async function downloadAndInstallPackages(
await InstallZip(buffer, pkg.description, pkg.installPath, pkg.binaries, eventStream);
installationStage = 'touchLockFile';
await touchInstallFile(pkg.installPath, InstallFileType.Lock);
results[pkg.id] = true;
break;
} else {
eventStream.post(new IntegrityCheckFailure(pkg.description, pkg.url, willTryInstallingPackage()));
results[pkg.id] = false;
}
}
} catch (error) {
results[pkg.id] = false;

if (error instanceof NestedError) {
const packageError = new PackageError(error.message, pkg, error.err);
eventStream.post(new InstallationFailure(installationStage, packageError));
} else {
eventStream.post(new InstallationFailure(installationStage, error));
}

return false;
// Send telemetry for the failure
sendInstallationFailureTelemetry(pkg, installationStage, error);
} finally {
try {
if (await installFileExists(pkg.installPath, InstallFileType.Begin)) {
Expand All @@ -73,5 +82,26 @@ export async function downloadAndInstallPackages(
}
}

return true;
return results;

function sendInstallationFailureTelemetry(pkg: AbsolutePathPackage, installationStage: string, error: any): void {
if (!telemetryReporter) {
return;
}

const telemetryProperties: { [key: string]: string } = {
installStage: installationStage,
packageId: pkg.id,
};

if (error instanceof NestedError && error.err instanceof PackageError) {
telemetryProperties['error.message'] = error.err.message;
telemetryProperties['error.packageUrl'] = error.err.pkg.url;
} else if (error instanceof PackageError) {
telemetryProperties['error.message'] = error.message;
telemetryProperties['error.packageUrl'] = error.pkg.url;
}

telemetryReporter.sendTelemetryEvent('PackageInstallationFailed', telemetryProperties);
}
}
Loading