diff --git a/package.json b/package.json index a1e69404f..f04ec3b54 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/OmnisharpDownload.Helper.ts b/src/OmnisharpDownload.Helper.ts index 1fcb7326e..3281d38d5 100644 --- a/src/OmnisharpDownload.Helper.ts +++ b/src/OmnisharpDownload.Helper.ts @@ -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('http.proxy'); const strictSSL = config.get('http.proxyStrictSSL', true); - await packageManager.DownloadPackages(logger, status, proxy, strictSSL); + return { Proxy: proxy, StrictSSL: strictSSL }; } export function SetStatus() { @@ -31,13 +31,9 @@ export function SetStatus() { return { StatusItem: statusItem, Status: status }; } -export async function GetAndLogPlatformInformation(logger: Logger): Promise { - 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) { @@ -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); } diff --git a/src/omnisharp/OmnisharpDownloader.ts b/src/omnisharp/OmnisharpDownloader.ts index 356aeb617..abb223833 100644 --- a/src/omnisharp/OmnisharpDownloader.ts +++ b/src/omnisharp/OmnisharpDownloader.ts @@ -4,19 +4,38 @@ *--------------------------------------------------------------------------------------------*/ 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 = {}; + this.packageManager = new PackageManager(this.platformInfo, this.packageJSON); } public async DownloadAndInstallOmnisharp(version: string, serverUrl: string, installPath: string) { @@ -24,49 +43,57 @@ export class OmnisharpDownloader { 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 { + let installationStage = 'getLatestVersionInfoFile'; + 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; } } } diff --git a/src/omnisharp/OmnisharpManager.ts b/src/omnisharp/OmnisharpManager.ts index 61dff1ec0..3ef632564 100644 --- a/src/omnisharp/OmnisharpManager.ts +++ b/src/omnisharp/OmnisharpManager.ts @@ -20,28 +20,34 @@ export class OmnisharpManager { private reporter?: TelemetryReporter) { } - public async GetOmnisharpPath(omnisharpPath: string, useMono: boolean, serverUrl: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation): Promise { + public async GetOmnisharpPath(omnisharpPath: string, useMono: boolean, serverUrl: string, versionFilePathInServer: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation): Promise { // 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'); @@ -49,7 +55,7 @@ export class OmnisharpManager { } } -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'); } diff --git a/src/omnisharp/OmnisharpPackageCreator.ts b/src/omnisharp/OmnisharpPackageCreator.ts index 66fe6abeb..a645311da 100644 --- a/src/omnisharp/OmnisharpPackageCreator.ts +++ b/src/omnisharp/OmnisharpPackageCreator.ts @@ -55,4 +55,11 @@ function GetPackageFromArchitecture(inputPackage: Package, serverUrl: string, ve }; return versionPackage; -} \ No newline at end of file +} + +export function GetVersionFilePackage(serverUrl: string, pathInServer: string): Package { + return { + "description": "Latest version information file", + "url": `${serverUrl}/${pathInServer}` + }; +} diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 5c62d14e7..bd1a269e6 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -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'); diff --git a/src/packages.ts b/src/packages.ts index a5e15d197..64a2ebbd2 100644 --- a/src/packages.ts +++ b/src/packages.ts @@ -78,11 +78,7 @@ export class PackageManager { this.allPackages = 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); } @@ -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 { + 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)); + } } } diff --git a/test/unitTests/OmnisharpDownloader.test.ts b/test/unitTests/OmnisharpDownloader.test.ts index 84678cd49..1709be753 100644 --- a/test/unitTests/OmnisharpDownloader.test.ts +++ b/test/unitTests/OmnisharpDownloader.test.ts @@ -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"); @@ -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/"; @@ -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 diff --git a/test/unitTests/OmnisharpManager.test.ts b/test/unitTests/OmnisharpManager.test.ts index 1593baec7..e9338311e 100644 --- a/test/unitTests/OmnisharpManager.test.ts +++ b/test/unitTests/OmnisharpManager.test.ts @@ -23,6 +23,7 @@ suite('GetExperimentalOmnisharpPath : Returns Omnisharp experiment path dependin const platformInfo = new PlatformInformation("win32", "x86"); const serverUrl = "https://roslynomnisharp.blob.core.windows.net"; const installPath = ".omnisharp/experimental"; + const versionFilepathInServer = "releases/testVersionInfo.txt"; const useMono = false; const manager = GetTestOmnisharpManager(); let extensionPath: string; @@ -38,50 +39,55 @@ suite('GetExperimentalOmnisharpPath : Returns Omnisharp experiment path dependin }); test('Throws error if the path is neither an absolute path nor a valid semver', async () => { - expect(manager.GetOmnisharpPath("Some incorrect path", useMono, serverUrl, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + expect(manager.GetOmnisharpPath("Some incorrect path", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); }); test('Throws error when the specified path is null', async () => { - expect(manager.GetOmnisharpPath(null, useMono, serverUrl, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + expect(manager.GetOmnisharpPath(null, useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); }); test('Throws error when the specified path is empty', async () => { - expect(manager.GetOmnisharpPath("", useMono, serverUrl, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + expect(manager.GetOmnisharpPath("", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); }); test('Throws error when the specified path is an invalid semver', async () => { - expect(manager.GetOmnisharpPath("a.b.c", useMono, serverUrl, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + expect(manager.GetOmnisharpPath("a.b.c", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); }); test('Returns the same path if absolute path to an existing file is passed', async () => { tmpFile = tmp.fileSync(); - let omnisharpPath = await manager.GetOmnisharpPath(tmpFile.name, useMono, serverUrl, installPath, extensionPath, platformInfo); + let omnisharpPath = await manager.GetOmnisharpPath(tmpFile.name, useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); omnisharpPath.should.equal(tmpFile.name); }); + test('Installs the latest version and returns the launch path based on the version and platform', async () => { + let omnisharpPath = await manager.GetOmnisharpPath("latest", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); + omnisharpPath.should.equal(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/OmniSharp.exe`)); + }); + test('Installs the test version and returns the launch path based on the version and platform', async () => { - let omnisharpPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, installPath, extensionPath, platformInfo); + let omnisharpPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); omnisharpPath.should.equal(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/OmniSharp.exe`)); }); test('Downloads package from given url and installs them at the specified path', async () => { - let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, installPath, extensionPath, platformInfo); + let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); let exists = await util.fileExists(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/install_check_1.2.3.txt`)); exists.should.equal(true); }); test('Downloads package and returns launch path based on platform - Not using mono on Linux ', async () => { - let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, installPath, extensionPath, new PlatformInformation("linux", "x64")); + let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, new PlatformInformation("linux", "x64")); launchPath.should.equal(path.resolve(extensionPath, '.omnisharp/experimental/1.2.3/run')); }); test('Downloads package and returns launch path based on platform - Using mono on Linux ', async () => { - let launchPath = await manager.InstallVersionAndReturnLaunchPath("1.2.3", true, serverUrl, installPath, extensionPath, new PlatformInformation("linux", "x64")); + let launchPath = await manager.GetOmnisharpPath("1.2.3", true, serverUrl, versionFilepathInServer, installPath, extensionPath, new PlatformInformation("linux", "x64")); launchPath.should.equal(path.resolve(extensionPath, '.omnisharp/experimental/1.2.3/omnisharp/OmniSharp.exe')); }); test('Downloads package and returns launch path based on install path ', async () => { - let launchPath = await manager.InstallVersionAndReturnLaunchPath("1.2.3", true, serverUrl, "installHere", extensionPath, platformInfo); + let launchPath = await manager.GetOmnisharpPath("1.2.3", true, serverUrl, versionFilepathInServer, "installHere", extensionPath, platformInfo); launchPath.should.equal(path.resolve(extensionPath, 'installHere/1.2.3/OmniSharp.exe')); }); diff --git a/test/unitTests/OmnisharpPackageCreator.test.ts b/test/unitTests/OmnisharpPackageCreator.test.ts index 34c97403d..b06f56990 100644 --- a/test/unitTests/OmnisharpPackageCreator.test.ts +++ b/test/unitTests/OmnisharpPackageCreator.test.ts @@ -6,7 +6,7 @@ import { assert, should, expect } from "chai"; import { Package } from "../../src/packages"; import { GetTestPackageJSON } from "./OmnisharpDownloader.test"; -import { GetOmnisharpPackage, GetPackagesFromVersion } from "../../src/omnisharp/OmnisharpPackageCreator"; +import { GetOmnisharpPackage, GetPackagesFromVersion, GetVersionFilePackage } from "../../src/omnisharp/OmnisharpPackageCreator"; suite("GetOmnisharpPackage : Output package depends on the input package and other input parameters like serverUrl", () => { @@ -194,4 +194,16 @@ suite('GetPackagesFromVersion : Gets the experimental omnisharp packages from a outPackages.length.should.equal(1); outPackages[0].experimentalPackageId.should.equal("win-x64"); }); +}); + +suite('GetVersionFilePackage : Gives the package for the latest file download', () => { + test('Contains the expected description', () => { + let testPackage = GetVersionFilePackage("someUrl", "somePath"); + expect(testPackage.description).to.equal('Latest version information file'); + }); + + test('Contains the url based on serverUrl and the pathInServer', () => { + let testPackage = GetVersionFilePackage("someUrl", "somePath"); + expect(testPackage.url).to.equal('someUrl/somePath'); + }); }); \ No newline at end of file