Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable download and usage of latest version of omnisharp #2039

Merged
merged 30 commits into from
Feb 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a1454d3
Enable usage of multiple versions
akshita31 Feb 5, 2018
26da0a6
Either load the server from a path or download the version packages
akshita31 Feb 6, 2018
e3c702b
Tests for the package creator
akshita31 Feb 6, 2018
58a78ab
Added null check and removed semver check in package creator
akshita31 Feb 6, 2018
453006b
Test for the experiment omnisharp downloader
akshita31 Feb 6, 2018
a6e8f98
Added test for package manager
akshita31 Feb 7, 2018
d77bdd0
Code clean up
akshita31 Feb 7, 2018
2026d6e
Added null or empty check for version
akshita31 Feb 7, 2018
f160fc2
Changes
akshita31 Feb 7, 2018
7357cbc
Modified the description
akshita31 Feb 8, 2018
80e8959
Remove comment
akshita31 Feb 9, 2018
6f7e243
Merge branch 'master' into useVersion
akshita31 Feb 9, 2018
9a568c6
Remove unnecessary usage
akshita31 Feb 9, 2018
19b04c6
CR comments
akshita31 Feb 13, 2018
71ab1e2
Removed experimental
akshita31 Feb 13, 2018
3c2e1c8
Modified launcher
akshita31 Feb 13, 2018
3202960
Removed experimental
akshita31 Feb 13, 2018
b743558
Modified tests
akshita31 Feb 13, 2018
29347f9
Modified package description to include version information
akshita31 Feb 13, 2018
7d96e60
Renamed launch path
akshita31 Feb 13, 2018
fcfd5dc
Add more tests
akshita31 Feb 13, 2018
8a3fdc2
Changed the description in package.json
akshita31 Feb 13, 2018
b475534
Getting latest version info
akshita31 Feb 14, 2018
abed667
Refactored code and added tests
akshita31 Feb 14, 2018
fd6c2c3
Resolve merge conflicts
akshita31 Feb 14, 2018
36512ee
Remove unnecessary using
akshita31 Feb 15, 2018
9f55285
CR comments
akshita31 Feb 15, 2018
a8602ca
Use common function for latest download
akshita31 Feb 15, 2018
71ffd3d
Add new line
akshita31 Feb 23, 2018
97b9b03
Resolve binaries on linux
akshita31 Feb 23, 2018
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@
"null"
],
"default": null,
"description": "Specifies how to acquire the OmniSharp to use. Can be one of \"latest\", a specific version number, or the absolute path to a local OmniSharp folder."
"description": "Specifies the path to OmniSharp. This can be the absolute path to an OmniSharp executable, a specific version number, or \"latest\". If a version number or \"latest\" is specified, the appropriate version of OmniSharp will be downloaded on your behalf."
},
"omnisharp.useMono": {
"type": "boolean",
Expand Down
13 changes: 5 additions & 8 deletions src/OmnisharpDownload.Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { PackageManager, Status, PackageError, Package } from './packages';
import { Status, PackageError } from './packages';
import { PlatformInformation } from './platform';
import { Logger } from './logger';
import TelemetryReporter from 'vscode-extension-telemetry';

export async function GetDependenciesAndDownloadPackages(packages: Package[], status: Status, platformInfo: PlatformInformation, packageManager: PackageManager, logger: Logger) {
export function GetNetworkDependencies() {
const config = vscode.workspace.getConfiguration();
const proxy = config.get<string>('http.proxy');
const strictSSL = config.get('http.proxyStrictSSL', true);
await packageManager.DownloadPackages(logger, status, proxy, strictSSL);
return { Proxy: proxy, StrictSSL: strictSSL };
}

export function SetStatus() {
Expand All @@ -31,13 +31,9 @@ export function SetStatus() {
return { StatusItem: statusItem, Status: status };
}

export async function GetAndLogPlatformInformation(logger: Logger): Promise<PlatformInformation> {
let platformInfo = await PlatformInformation.GetCurrent();

export function LogPlatformInformation(logger: Logger, platformInfo: PlatformInformation) {
logger.appendLine(`Platform: ${platformInfo.toString()}`);
logger.appendLine();

return platformInfo;
}

export function ReportInstallationError(logger: Logger, error, telemetryProps: any, installationStage: string) {
Expand All @@ -60,6 +56,7 @@ export function ReportInstallationError(logger: Logger, error, telemetryProps: a
errorMessage = error.toString();
}

logger.appendLine();
logger.appendLine(`Failed at stage: ${installationStage}`);
logger.appendLine(errorMessage);
}
Expand Down
65 changes: 46 additions & 19 deletions src/omnisharp/OmnisharpDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,96 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { PackageManager, Package } from '../packages';
import { PackageManager, Package, Status } from '../packages';
import { PlatformInformation } from '../platform';
import { Logger } from '../logger';
import TelemetryReporter from 'vscode-extension-telemetry';
import { GetPackagesFromVersion } from './OmnisharpPackageCreator';
import { GetDependenciesAndDownloadPackages, SetStatus, GetAndLogPlatformInformation, ReportInstallationError, SendInstallationTelemetry } from '../OmnisharpDownload.Helper';
import { GetPackagesFromVersion, GetVersionFilePackage } from './OmnisharpPackageCreator';
import { SetStatus, LogPlatformInformation, ReportInstallationError, SendInstallationTelemetry, GetNetworkDependencies } from '../OmnisharpDownload.Helper';

export class OmnisharpDownloader {
private status: Status;
private statusItem: vscode.StatusBarItem;
private proxy: string;
private strictSSL: boolean;
private packageManager: PackageManager;
private telemetryProps: any;

public constructor(
private channel: vscode.OutputChannel,
private logger: Logger,
private packageJSON: any,
private platformInfo: PlatformInformation,
private reporter?: TelemetryReporter) {

let statusObject = SetStatus();
this.status = statusObject.Status;
this.statusItem = statusObject.StatusItem;

let networkObject = GetNetworkDependencies();
this.proxy = networkObject.Proxy;
this.strictSSL = networkObject.StrictSSL;

this.telemetryProps = {};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this empty object for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.packageManager = new PackageManager(this.platformInfo, this.packageJSON);
}

public async DownloadAndInstallOmnisharp(version: string, serverUrl: string, installPath: string) {
if (!version) {
throw new Error('Invalid version');
}

this.logger.append('Installing Omnisharp Packages...');
this.logger.appendLine('Installing Omnisharp Packages...');
this.logger.appendLine();
this.channel.show();

let statusObject = SetStatus();
let status = statusObject.Status;
let statusItem = statusObject.StatusItem;

let telemetryProps: any = {};
let installationStage = '';
let platformInfo: PlatformInformation;

if (this.reporter) {
this.reporter.sendTelemetryEvent("AcquisitionStart");
}

try {
installationStage = 'getPlatformInfo';
platformInfo = await GetAndLogPlatformInformation(this.logger);
LogPlatformInformation(this.logger, this.platformInfo);

installationStage = 'getPackageInfo';
let packages: Package[] = GetPackagesFromVersion(version, this.packageJSON.runtimeDependencies, serverUrl, installPath);

installationStage = 'downloadPackages';
let packageManager = new PackageManager(platformInfo, this.packageJSON);

// Specify the packages that the package manager needs to download
packageManager.SetVersionPackagesForDownload(packages);
await GetDependenciesAndDownloadPackages(packages,status, platformInfo, packageManager, this.logger);
this.packageManager.SetVersionPackagesForDownload(packages);
await this.packageManager.DownloadPackages(this.logger, this.status, this.proxy, this.strictSSL);

this.logger.appendLine();

installationStage = 'installPackages';
await packageManager.InstallPackages(this.logger, status);
await this.packageManager.InstallPackages(this.logger, this.status);

installationStage = 'completeSuccess';
}

catch (error) {
ReportInstallationError(this.logger, error, telemetryProps, installationStage);
ReportInstallationError(this.logger, error, this.telemetryProps, installationStage);
throw error;// throw the error up to the server
}
finally {
SendInstallationTelemetry(this.logger, this.reporter, telemetryProps, installationStage, platformInfo, statusItem);
SendInstallationTelemetry(this.logger, this.reporter, this.telemetryProps, installationStage, this.platformInfo, this.statusItem);
}
}

public async GetLatestVersion(serverUrl: string, versionFilePathInServer): Promise<string> {
let installationStage = 'getLatestVersionInfoFile';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our current pattern, anyone who wants to add an installation stage and print errors on failure has to set up an identical try/catch/finally block. I wonder if we could add a RunInstllationStage helper that takes an async func that does the work, the stage name, and the logger and does all that for you.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you see this as a regression in the source? There is a lot of refactoring to be done in this area of the code and I'd like to limit the changes in this PR lest it grows too big...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically it's not a pattern this PR introduced, but this PR does create more instances of it. @akshita31 I'd be OK with getting this refactoring in a followup PR.

try {
this.logger.appendLine('Getting latest build information...');
this.logger.appendLine();
//The package manager needs a package format to download, hence we form a package for the latest version file
let filePackage = GetVersionFilePackage(serverUrl, versionFilePathInServer);
//Fetch the latest version information from the file
return await this.packageManager.GetLatestVersionFromFile(this.logger, this.status, this.proxy, this.strictSSL, filePackage);
}
catch (error) {
ReportInstallationError(this.logger, error, this.telemetryProps, installationStage);
throw error;
}
}
}
28 changes: 17 additions & 11 deletions src/omnisharp/OmnisharpManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,42 @@ export class OmnisharpManager {
private reporter?: TelemetryReporter) {
}

public async GetOmnisharpPath(omnisharpPath: string, useMono: boolean, serverUrl: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation): Promise<string> {
public async GetOmnisharpPath(omnisharpPath: string, useMono: boolean, serverUrl: string, versionFilePathInServer: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation): Promise<string> {
// Looks at the options path, installs the dependencies and returns the path to be loaded by the omnisharp server
// To Do : Add the functionality for the latest option

let downloader = new OmnisharpDownloader(this.channel, this.logger, this.packageJSON, platformInfo, this.reporter);
if (path.isAbsolute(omnisharpPath)) {
if (await util.fileExists(omnisharpPath)) {
return omnisharpPath;
}
else {
throw new Error('Invalid path specified');
throw new Error('The system could not find the specified path');
}
}
//If the path is not a valid path on disk, treat it as a version
return await this.InstallVersionAndReturnLaunchPath(omnisharpPath, useMono, serverUrl, installPath, extensionPath, platformInfo);
else if (omnisharpPath == "latest") {
return await this.LatestInstallAndReturnLaunchPath(downloader, useMono, serverUrl, versionFilePathInServer, installPath, extensionPath, platformInfo);
}

//If the path is neither a valid path on disk not the string "latest", treat it as a version
return await this.InstallVersionAndReturnLaunchPath(downloader, omnisharpPath, useMono, serverUrl, installPath, extensionPath, platformInfo);
}

public async InstallVersionAndReturnLaunchPath(version: string, useMono: boolean, serverUrl: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation) {
public async LatestInstallAndReturnLaunchPath(downloader: OmnisharpDownloader, useMono: boolean, serverUrl: string, versionFilePathInServer: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation) {
let version = await downloader.GetLatestVersion(serverUrl, versionFilePathInServer);
return await this.InstallVersionAndReturnLaunchPath(downloader, version, useMono, serverUrl, installPath, extensionPath, platformInfo);
}

public async InstallVersionAndReturnLaunchPath(downloader: OmnisharpDownloader, version: string, useMono: boolean, serverUrl: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation) {
if (semver.valid(version)) {
let downloader = new OmnisharpDownloader(this.channel, this.logger, this.packageJSON, this.reporter);
await downloader.DownloadAndInstallOmnisharp(version, serverUrl, installPath);

return await GetLaunchPathForVersion(platformInfo, version, installPath, extensionPath, useMono);
return GetLaunchPathForVersion(platformInfo, version, installPath, extensionPath, useMono);
}
else {
throw new Error('Invalid omnisharp version specified');
}
}
}

export async function GetLaunchPathForVersion(platformInfo: PlatformInformation, version: string, installPath: string, extensionPath: string, useMono: boolean) {
export function GetLaunchPathForVersion(platformInfo: PlatformInformation, version: string, installPath: string, extensionPath: string, useMono: boolean) {
if (!version) {
throw new Error('Invalid Version');
}
Expand Down
9 changes: 8 additions & 1 deletion src/omnisharp/OmnisharpPackageCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ function GetPackageFromArchitecture(inputPackage: Package, serverUrl: string, ve
};

return versionPackage;
}
}

export function GetVersionFilePackage(serverUrl: string, pathInServer: string): Package {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing. Passing around packages to represent the actual packages we download, sure. Creating a package to represent downloading a text file...strange. Can we at least add a comment explaining why?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add the comment for now, then refactor more in a future PR? all of the great work that @DustinCampbell did around proxy negotiation, etc. is currently best accessed via the Package construct.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

return <Package>{
"description": "Latest version information file",
"url": `${serverUrl}/${pathInServer}`
};
}
3 changes: 2 additions & 1 deletion src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ export class OmniSharpServer {
let serverUrl = "https://roslynomnisharp.blob.core.windows.net";
let installPath = ".omnisharp/experimental";
let extensionPath = utils.getExtensionPath();
let versionFilePathInServer = 'releases/versioninfo.txt';
let manager = new OmnisharpManager(this._csharpChannel, this._csharpLogger, this._packageJSON, this._reporter);
let platformInfo = await PlatformInformation.GetCurrent();
launchPath = await manager.GetOmnisharpPath(this._options.path, this._options.useMono, serverUrl, installPath, extensionPath, platformInfo);
launchPath = await manager.GetOmnisharpPath(this._options.path, this._options.useMono, serverUrl, versionFilePathInServer,installPath, extensionPath, platformInfo);
}
catch (error) {
this._logger.appendLine('Error occured in loading omnisharp from omnisharp.path');
Expand Down
33 changes: 28 additions & 5 deletions src/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,7 @@ export class PackageManager {
this.allPackages = <Package[]>this.packageJSON.runtimeDependencies;

// Convert relative binary paths to absolute
for (let pkg of this.allPackages) {
if (pkg.binaries) {
pkg.binaries = pkg.binaries.map(value => path.resolve(getBaseInstallPath(pkg), value));
}
}
resolvePackageBinaries(this.allPackages);

resolve(this.allPackages);
}
Expand Down Expand Up @@ -111,6 +107,33 @@ export class PackageManager {

public SetVersionPackagesForDownload(packages: Package[]) {
this.allPackages = packages;
resolvePackageBinaries(this.allPackages);
}

public async GetLatestVersionFromFile(logger: Logger, status: Status, proxy: string, strictSSL: boolean, filePackage: Package): Promise<string> {
try {
let latestVersion: string;
await maybeDownloadPackage(filePackage, logger, status, proxy, strictSSL);
if (filePackage.tmpFile) {
latestVersion = fs.readFileSync(filePackage.tmpFile.name, 'utf8');
//Delete the temporary file created
filePackage.tmpFile.removeCallback();
}

return latestVersion;
}
catch (error) {
throw new Error(`Could not download the latest version file due to ${error.toString()}`);
}
}
}

function resolvePackageBinaries(packages: Package[]) {
// Convert relative binary paths to absolute
for (let pkg of packages) {
if (pkg.binaries) {
pkg.binaries = pkg.binaries.map(value => path.resolve(getBaseInstallPath(pkg), value));
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions test/unitTests/OmnisharpDownloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { should } from 'chai';
import { Logger } from '../../src/logger';
import { OmnisharpDownloader } from '../../src/omnisharp/OmnisharpDownloader';
import { rimraf } from 'async-file';
import { PlatformInformation } from '../../src/platform';

const tmp = require('tmp');
const chai = require("chai");
Expand All @@ -19,7 +20,7 @@ let expect = chai.expect;
suite("DownloadAndInstallExperimentalVersion : Gets the version packages, downloads and installs them", () => {
let tmpDir = null;
const version = "1.2.3";
const downloader = GetOmnisharpDownloader();
const downloader = GetTestOmnisharpDownloader();
const serverUrl = "https://roslynomnisharp.blob.core.windows.net";
const installPath = ".omnisharp/experimental/";

Expand Down Expand Up @@ -57,10 +58,10 @@ suite("DownloadAndInstallExperimentalVersion : Gets the version packages, downlo
});
});

function GetOmnisharpDownloader() {
function GetTestOmnisharpDownloader() {
let channel = vscode.window.createOutputChannel('Experiment Channel');
let logger = new Logger(text => channel.append(text));
return new OmnisharpDownloader(channel, logger, GetTestPackageJSON(), null);
return new OmnisharpDownloader(channel, logger, GetTestPackageJSON(), new PlatformInformation("win32", "x86"), null);
}

//Since we need only the runtime dependencies of packageJSON for the downloader create a testPackageJSON
Expand Down
Loading