Skip to content
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@
"omnisharp.dotnetPath": {
"type": "string",
"scope": "window",
"description": "Specified the path to a dotnet installation to use when \"useModernNet\" is set to true, instead of the default system one. Example: \"/home/username/mycustomdotnetdirectory\"."
"description": "Specified the path to a dotnet installation to use when \"useModernNet\" is set to true, instead of the default system one. This only influences the dotnet installation to use for hosting Omnisharp itself. Example: \"/home/username/mycustomdotnetdirectory\"."
},
"omnisharp.waitForDebugger": {
"type": "boolean",
Expand Down Expand Up @@ -1092,6 +1092,14 @@
"type": "string",
"description": "Path to the .runsettings file which should be used when running unit tests."
},
"omnisharp.dotNetCliPaths": {
"type": "array",
"items": {
"type": "string"
},
"description": "Paths to a local download of the .NET CLI to use for running any user code.",
"uniqueItems": true
},
"razor.plugin.path": {
"type": "string",
"scope": "machine",
Expand Down
4 changes: 2 additions & 2 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export function safeLength<T>(arr: T[] | undefined) {
return arr ? arr.length : 0;
}

export async function execChildProcess(command: string, workingDirectory: string = getExtensionPath()): Promise<string> {
export async function execChildProcess(command: string, workingDirectory: string = getExtensionPath(), env: NodeJS.ProcessEnv = {}): Promise<string> {
return new Promise<string>((resolve, reject) => {
cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024 }, (error, stdout, stderr) => {
cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024, env: env }, (error, stdout, stderr) => {
if (error) {
reject(new Error(`${error}
${stdout}
Expand Down
2 changes: 1 addition & 1 deletion src/constants/IGetDotnetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
import { DotnetInfo } from "../utils/getDotnetInfo";

export interface IGetDotnetInfo {
(): Promise<DotnetInfo>;
(dotNetCliPaths: string[]): Promise<DotnetInfo>;
}
20 changes: 11 additions & 9 deletions src/coreclr-debug/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import CSharpExtensionExports from '../CSharpExtensionExports';
import { getRuntimeDependencyPackageWithId } from '../tools/RuntimeDependencyPackageUtils';
import { getDotnetInfo, DotnetInfo } from '../utils/getDotnetInfo';
import { DotnetDebugConfigurationProvider } from './debugConfigurationProvider';
import { Options } from '../omnisharp/options';

let _debugUtil: CoreClrDebugUtil = null;

export async function activate(thisExtension: vscode.Extension<CSharpExtensionExports>, context: vscode.ExtensionContext, platformInformation: PlatformInformation, eventStream: EventStream) {
export async function activate(thisExtension: vscode.Extension<CSharpExtensionExports>, context: vscode.ExtensionContext, platformInformation: PlatformInformation, eventStream: EventStream, options: Options) {
_debugUtil = new CoreClrDebugUtil(context.extensionPath);

if (!CoreClrDebugUtil.existsSync(_debugUtil.debugAdapterDir())) {
Expand All @@ -29,10 +30,10 @@ export async function activate(thisExtension: vscode.Extension<CSharpExtensionEx
showInstallErrorMessage(eventStream);
}
} else if (!CoreClrDebugUtil.existsSync(_debugUtil.installCompleteFilePath())) {
completeDebuggerInstall(platformInformation, eventStream);
completeDebuggerInstall(platformInformation, eventStream, options);
}

const factory = new DebugAdapterExecutableFactory(platformInformation, eventStream, thisExtension.packageJSON, thisExtension.extensionPath);
const factory = new DebugAdapterExecutableFactory(platformInformation, eventStream, thisExtension.packageJSON, thisExtension.extensionPath, options);
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('coreclr', new DotnetDebugConfigurationProvider(platformInformation)));
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('clr', new DotnetDebugConfigurationProvider(platformInformation)));
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('coreclr', factory));
Expand Down Expand Up @@ -73,8 +74,8 @@ async function checkIsValidArchitecture(platformInformation: PlatformInformation
return false;
}

async function completeDebuggerInstall(platformInformation: PlatformInformation, eventStream: EventStream): Promise<boolean> {
return _debugUtil.checkDotNetCli()
async function completeDebuggerInstall(platformInformation: PlatformInformation, eventStream: EventStream, options: Options): Promise<boolean> {
return _debugUtil.checkDotNetCli(options.dotNetCliPaths)
.then(async (dotnetInfo: DotnetInfo) => {

let isValidArchitecture: boolean = await checkIsValidArchitecture(platformInformation, eventStream);
Expand Down Expand Up @@ -132,7 +133,7 @@ function showDotnetToolsWarning(message: string): void {
// Else it will launch the debug adapter
export class DebugAdapterExecutableFactory implements vscode.DebugAdapterDescriptorFactory {

constructor(private readonly platformInfo: PlatformInformation, private readonly eventStream: EventStream, private readonly packageJSON: any, private readonly extensionPath: string) {
constructor(private readonly platformInfo: PlatformInformation, private readonly eventStream: EventStream, private readonly packageJSON: any, private readonly extensionPath: string, private readonly options: Options) {
}

async createDebugAdapterDescriptor(_session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): Promise<vscode.DebugAdapterDescriptor> {
Expand Down Expand Up @@ -160,7 +161,7 @@ export class DebugAdapterExecutableFactory implements vscode.DebugAdapterDescrip
}
// install.complete does not exist, check dotnetCLI to see if we can complete.
else if (!CoreClrDebugUtil.existsSync(util.installCompleteFilePath())) {
let success: boolean = await completeDebuggerInstall(this.platformInfo, this.eventStream);
let success: boolean = await completeDebuggerInstall(this.platformInfo, this.eventStream, this.options);

if (!success) {
this.eventStream.post(new DebuggerNotInstalledFailure());
Expand All @@ -172,12 +173,13 @@ export class DebugAdapterExecutableFactory implements vscode.DebugAdapterDescrip
// debugger has finished installation, kick off our debugger process

// Check for targetArchitecture
const targetArchitecture: string = getTargetArchitecture(this.platformInfo, _session.configuration.targetArchitecture, await getDotnetInfo());
let dotNetInfo = await getDotnetInfo(this.options.dotNetCliPaths);
const targetArchitecture: string = getTargetArchitecture(this.platformInfo, _session.configuration.targetArchitecture, dotNetInfo);

// use the executable specified in the package.json if it exists or determine it based on some other information (e.g. the session)
if (!executable) {
const command = path.join(common.getExtensionPath(), ".debugger", targetArchitecture, "vsdbg-ui" + CoreClrDebugUtil.getPlatformExeExtension());
executable = new vscode.DebugAdapterExecutable(command);
executable = new vscode.DebugAdapterExecutable(command, [], { env: { 'DOTNET_ROOT' : dotNetInfo.CliPath ? path.dirname(dotNetInfo.CliPath) : '' } });
}

// make VS Code launch the DA executable
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr-debug/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export class CoreClrDebugUtil {
// is new enough for us.
// Returns: a promise that returns a DotnetInfo class
// Throws: An DotNetCliError() from the return promise if either dotnet does not exist or is too old.
public async checkDotNetCli(): Promise<DotnetInfo> {
let dotnetInfo = await getDotnetInfo();
public async checkDotNetCli(dotNetCliPaths: string[]): Promise<DotnetInfo> {
let dotnetInfo = await getDotnetInfo(dotNetCliPaths);

if (dotnetInfo.FullInfo === DOTNET_MISSING_MESSAGE) {
// something went wrong with spawning 'dotnet --info'
Expand Down
4 changes: 2 additions & 2 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { IHostExecutableResolver } from '../constants/IHostExecutableResolver';
import { getDotnetInfo } from '../utils/getDotnetInfo';
import { getDecompilationAuthorization, resetDecompilationAuthorization } from '../omnisharp/decompilationPrompt';

export default function registerCommands(context: vscode.ExtensionContext, server: OmniSharpServer, platformInfo: PlatformInformation, eventStream: EventStream, optionProvider: OptionProvider, monoResolver: IHostExecutableResolver, packageJSON: any, extensionPath: string): CompositeDisposable {
export default function registerCommands(context: vscode.ExtensionContext, server: OmniSharpServer, platformInfo: PlatformInformation, eventStream: EventStream, optionProvider: OptionProvider, monoResolver: IHostExecutableResolver, dotnetResolver: IHostExecutableResolver, packageJSON: any, extensionPath: string): CompositeDisposable {
let disposable = new CompositeDisposable();
disposable.add(vscode.commands.registerCommand('o.restart', async () => restartOmniSharp(context, server, optionProvider)));
disposable.add(vscode.commands.registerCommand('o.pickProjectAndStart', async () => pickProjectAndStart(server, optionProvider)));
Expand Down Expand Up @@ -53,7 +53,7 @@ export default function registerCommands(context: vscode.ExtensionContext, serve
// Register command for generating tasks.json and launch.json assets.
disposable.add(vscode.commands.registerCommand('dotnet.generateAssets', async (selectedIndex) => generateAssets(server, selectedIndex)));

disposable.add(vscode.commands.registerCommand('csharp.reportIssue', async () => reportIssue(vscode, eventStream, getDotnetInfo, platformInfo.isValidPlatformForMono(), optionProvider.GetLatestOptions(), monoResolver)));
disposable.add(vscode.commands.registerCommand('csharp.reportIssue', async () => reportIssue(vscode, eventStream, getDotnetInfo, platformInfo.isValidPlatformForMono(), optionProvider.GetLatestOptions(), monoResolver, dotnetResolver)));

disposable.add(vscode.commands.registerCommand('csharp.showDecompilationTerms', async () => showDecompilationTerms(context, server, optionProvider)));

Expand Down
6 changes: 4 additions & 2 deletions src/features/reportIssue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import { OpenURL } from "../omnisharp/loggingEvents";
import { Options } from "../omnisharp/options";
import { IHostExecutableResolver } from "../constants/IHostExecutableResolver";
import { IGetDotnetInfo } from "../constants/IGetDotnetInfo";
import { dirname } from "path";

const issuesUrl = "https://github.com/OmniSharp/omnisharp-vscode/issues/new";

export default async function reportIssue(vscode: vscode, eventStream: EventStream, getDotnetInfo: IGetDotnetInfo, isValidPlatformForMono: boolean, options: Options, monoResolver: IHostExecutableResolver) {
const dotnetInfo = await getDotnetInfo();
export default async function reportIssue(vscode: vscode, eventStream: EventStream, getDotnetInfo: IGetDotnetInfo, isValidPlatformForMono: boolean, options: Options, monoResolver: IHostExecutableResolver, dotnetResolver: IHostExecutableResolver) {
// Get info for the dotnet that the Omnisharp executable is run on, not the dotnet Omnisharp will execute user code on.
const dotnetInfo = await getDotnetInfo([ dirname((await dotnetResolver.getHostExecutableInfo(options)).path) ]);
const monoInfo = await getMonoIfPlatformValid(isValidPlatformForMono, options, monoResolver);
let extensions = getInstalledExtensions(vscode);
let csharpExtVersion = getCsharpExtensionVersion(vscode);
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<CSharp
let coreClrDebugPromise = Promise.resolve();
if (runtimeDependenciesExist) {
// activate coreclr-debug
coreClrDebugPromise = coreclrdebug.activate(context.extension, context, platformInfo, eventStream);
coreClrDebugPromise = coreclrdebug.activate(context.extension, context, platformInfo, eventStream, optionProvider.GetLatestOptions());
}

let razorPromise = Promise.resolve();
Expand Down
2 changes: 1 addition & 1 deletion src/observers/TelemetryObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class TelemetryObserver {
}

private async handleOmnisharpInitialisation(event: OmnisharpInitialisation) {
this.dotnetInfo = await getDotnetInfo();
this.dotnetInfo = await getDotnetInfo(event.dotNetCliPaths);
this.solutionId = this.createSolutionId(event.solutionPath);
}

Expand Down
2 changes: 1 addition & 1 deletion src/omnisharp/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
localDisposables = undefined;
}));

disposables.add(registerCommands(context, server, platformInfo, eventStream, optionProvider, omnisharpMonoResolver, packageJSON, extensionPath));
disposables.add(registerCommands(context, server, platformInfo, eventStream, optionProvider, omnisharpMonoResolver, omnisharpDotnetResolver, packageJSON, extensionPath));

if (!context.workspaceState.get<boolean>('assetPromptDisabled')) {
disposables.add(server.onServerStart(async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/omnisharp/loggingEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class OmnisharpStart extends TelemetryEventWithMeasures {

export class OmnisharpInitialisation implements BaseEvent {
type = EventType.OmnisharpInitialisation;
constructor(public timeStamp: Date, public solutionPath: string) { }
constructor(public dotNetCliPaths: string[], public timeStamp: Date, public solutionPath: string) { }
}

export class OmnisharpLaunch implements BaseEvent {
Expand Down
8 changes: 6 additions & 2 deletions src/omnisharp/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export class Options {
public dotnetPath: string,
public excludePaths: string[],
public maxProjectFileCountForDiagnosticAnalysis: number,
public testRunSettings: string) {
public testRunSettings: string,
public dotNetCliPaths: string[]) {
}

public static Read(vscode: vscode): Options {
Expand Down Expand Up @@ -148,6 +149,8 @@ export class Options {

const excludePaths = this.getExcludedPaths(vscode);

const dotNetCliPaths = omnisharpConfig.get<string[]>('dotNetCliPaths', []);

return new Options(
path,
useModernNet,
Expand Down Expand Up @@ -198,7 +201,8 @@ export class Options {
dotnetPath,
excludePaths,
maxProjectFileCountForDiagnosticAnalysis,
testRunSettings
testRunSettings,
dotNetCliPaths
);
}

Expand Down
6 changes: 5 additions & 1 deletion src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ export class OmniSharpServer {
args.push('RoslynExtensionsOptions:AnalyzeOpenDocumentsOnly=true');
}

for (let i = 0; i < options.dotNetCliPaths.length; i++) {
args.push(`DotNetCliOptions:LocationPaths:${i}=${options.dotNetCliPaths[i]}`);
}

let launchInfo: LaunchInfo;
try {
launchInfo = await this._omnisharpManager.GetOmniSharpLaunchInfo(this.packageJSON.defaults.omniSharp, options.path, /* useFramework */ !options.useModernNet, serverUrl, latestVersionFileServerPath, installPath, this.extensionPath);
Expand All @@ -443,7 +447,7 @@ export class OmniSharpServer {
return;
}

this.eventStream.post(new ObservableEvents.OmnisharpInitialisation(new Date(), solutionPath));
this.eventStream.post(new ObservableEvents.OmnisharpInitialisation(options.dotNetCliPaths, new Date(), solutionPath));
this._fireEvent(Events.BeforeServerStart, solutionPath);

try {
Expand Down
20 changes: 18 additions & 2 deletions src/utils/getDotnetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { join } from "path";
import { execChildProcess } from "../common";
import { CoreClrDebugUtil } from "../coreclr-debug/util";

export const DOTNET_MISSING_MESSAGE = "A valid dotnet installation could not be found.";

Expand All @@ -13,15 +15,28 @@ let _dotnetInfo: DotnetInfo;
// is new enough for us.
// Returns: a promise that returns a DotnetInfo class
// Throws: An DotNetCliError() from the return promise if either dotnet does not exist or is too old.
export async function getDotnetInfo(): Promise<DotnetInfo> {
export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInfo> {
if (_dotnetInfo !== undefined) {
return _dotnetInfo;
}

let dotnetExeName = CoreClrDebugUtil.getPlatformExeExtension();
let dotnetExecutablePath = undefined;

for (const dotnetPath of dotNetCliPaths) {
let dotnetFullPath = join(dotnetPath, dotnetExeName);
if (CoreClrDebugUtil.existsSync(dotnetFullPath)) {
dotnetExecutablePath = dotnetFullPath;
break;
}
}

let dotnetInfo = new DotnetInfo();

try {
let data = await execChildProcess('dotnet --info', process.cwd());
let data = await execChildProcess(`${dotnetExecutablePath || 'dotnet'} --info`, process.cwd());

dotnetInfo.CliPath = dotnetExecutablePath;

dotnetInfo.FullInfo = data;

Expand Down Expand Up @@ -49,6 +64,7 @@ export async function getDotnetInfo(): Promise<DotnetInfo> {
}

export class DotnetInfo {
public CliPath?: string;
public FullInfo: string;
public Version: string;
public OsVersion: string;
Expand Down
30 changes: 30 additions & 0 deletions test/unitTests/Fakes/FakeDotnetResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IHostExecutableResolver } from "../../../src/constants/IHostExecutableResolver";
import { HostExecutableInformation } from "../../../src/constants/HostExecutableInformation";

export const fakeMonoInfo: HostExecutableInformation = {
version: "someDotNetVersion",
path: "someDotNetPath",
env: undefined
};

export class FakeDotnetResolver implements IHostExecutableResolver {
public getDotnetCalled: boolean;

constructor(public willReturnDotnetInfo = true) {
this.getDotnetCalled = false;
}

async getHostExecutableInfo(): Promise<HostExecutableInformation> {
this.getDotnetCalled = true;
if (this.willReturnDotnetInfo) {
return Promise.resolve(fakeMonoInfo);
}

return Promise.resolve(undefined);
}
}
3 changes: 2 additions & 1 deletion test/unitTests/Fakes/FakeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ export function getEmptyOptions(): Options {
/* dotnetPath */"",
/* excludePaths */null,
/* maxProjectFileCountForDiagnosticAnalysis */null,
/* testRunSettings */"");
/* testRunSettings */"",
/* dotNetCliPaths */[]);
}
Loading