-
Notifications
You must be signed in to change notification settings - Fork 675
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
Divide the package manager into separate components #2188
Changes from 43 commits
6205e1d
c632ab2
01f8cb1
56675b9
19ee685
400ba5e
5c4bfc0
3968900
cbf50af
8c09369
c74aecf
3e58f13
5565a2e
c8135b5
9afc874
9cc0016
98e7e74
e20a434
97e83ec
e7b520a
c6bee5a
2dac16c
85d867f
348e03a
57d4bff
9afa848
e5b0d65
7e77a6e
76e9780
47a18d1
441153a
df2f874
4c2c15c
7e44a06
e218cb4
b96fb22
10b6073
b9ac792
10c6811
053efe3
286fa7c
12dfc3f
24a5c03
9e26124
bccfa13
2b6ba72
7b2586f
df111f6
3ff44bb
ebe6a2a
3a97968
93ea65c
cc4a782
13ad87d
a6ed9a0
fdc9e0c
16e1c88
1fe14a5
bde8b49
7abd6a6
3e323af
41b7b87
28fab5a
45300ac
573ca83
87fff8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,68 +4,62 @@ | |
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import * as util from './common'; | ||
import { GetNetworkConfiguration, defaultPackageManagerFactory, IPackageManagerFactory } from './downloader.helper'; | ||
import { PackageManager } from './packages'; | ||
import { PlatformInformation } from './platform'; | ||
import { PackageInstallation, LogPlatformInfo, InstallationSuccess, InstallationFailure } from './omnisharp/loggingEvents'; | ||
import { EventStream } from './EventStream'; | ||
import { vscode } from './vscodeAdapter'; | ||
import { DownloadAndInstallPackages } from './packageManager/PackageManager'; | ||
import { Package } from './packageManager/Package'; | ||
import { NetworkSettingsProvider } from './NetworkSettings'; | ||
import { ResolveFilePaths } from './packageManager/PackageFilePathResolver'; | ||
|
||
/* | ||
* Class used to download the runtime dependencies of the C# Extension | ||
*/ | ||
export class CSharpExtDownloader { | ||
private proxy: string; | ||
private strictSSL: boolean; | ||
private packageManager: PackageManager; | ||
|
||
public constructor( | ||
vscode: vscode, | ||
private provider: NetworkSettingsProvider, | ||
private eventStream: EventStream, | ||
private packageJSON: any, | ||
private platformInfo: PlatformInformation, | ||
packageManagerFactory: IPackageManagerFactory = defaultPackageManagerFactory) { | ||
|
||
let networkConfiguration = GetNetworkConfiguration(vscode); | ||
this.proxy = networkConfiguration.Proxy; | ||
this.strictSSL = networkConfiguration.StrictSSL; | ||
this.packageManager = packageManagerFactory(this.platformInfo, this.packageJSON); | ||
private platformInfo: PlatformInformation) { | ||
} | ||
|
||
public async installRuntimeDependencies(): Promise<boolean> { | ||
this.eventStream.post(new PackageInstallation("C# dependencies")); | ||
let installationStage = 'touchBeginFile'; | ||
let success = false; | ||
let installationStage = ''; | ||
|
||
try { | ||
await util.touchInstallFile(util.InstallFileType.Begin); | ||
|
||
await util.touchInstallFile(util.InstallFileType.Begin); | ||
// Display platform information and RID | ||
this.eventStream.post(new LogPlatformInfo(this.platformInfo)); | ||
|
||
installationStage = 'downloadPackages'; | ||
await this.packageManager.DownloadPackages(this.eventStream, this.proxy, this.strictSSL); | ||
|
||
installationStage = 'installPackages'; | ||
await this.packageManager.InstallPackages(this.eventStream); | ||
|
||
let runTimeDependencies = this.GetRunTimeDependenciesPackages(); | ||
runTimeDependencies.forEach(pkg => ResolveFilePaths(pkg)); | ||
installationStage = 'downloadAndInstallPackages'; | ||
await DownloadAndInstallPackages(runTimeDependencies, this.provider, this.platformInfo, this.eventStream); | ||
installationStage = 'touchLockFile'; | ||
await util.touchInstallFile(util.InstallFileType.Lock); | ||
|
||
success = true; | ||
this.eventStream.post(new InstallationSuccess()); | ||
} | ||
catch (error) { | ||
this.eventStream.post(new InstallationFailure(installationStage, error)); | ||
} | ||
finally { | ||
// We do this step at the end so that we clean up the begin file in the case that we hit above catch block | ||
// Attach a an empty catch to this so that errors here do not propogate | ||
try { | ||
util.deleteInstallFile(util.InstallFileType.Begin); | ||
} | ||
catch (error) { } | ||
return success; | ||
} | ||
} | ||
} | ||
|
||
private GetRunTimeDependenciesPackages(): Package[] { | ||
if (this.packageJSON.runtimeDependencies) { | ||
return JSON.parse(JSON.stringify(<Package[]>this.packageJSON.runtimeDependencies)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By this we are copying these packages by 'value' using this method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the common practice to "deep copy" json object in js... it is awkward but it is what it is |
||
} | ||
|
||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does the .foreach on line 38 do if we return null here? How would that even happen? User deleted/edited package.json? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes right. Changed it to throw an error instead. However I am not sure what the scenario would be. Do you think we should remove the check ? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
import * as tmp from 'tmp'; | ||
import { NestedError } from './packageManager/Package'; | ||
import { rimraf } from 'async-file'; | ||
|
||
export async function createTmpFile(): Promise<TmpAsset> { | ||
const tmpFile = await new Promise<tmp.SynchrounousResult>((resolve, reject) => { | ||
tmp.file({ prefix: 'package-' }, (err, path, fd, cleanupCallback) => { | ||
if (err) { | ||
return reject(new NestedError('Error from tmp.file', err)); | ||
} | ||
if (fd == 0) { | ||
return reject(new NestedError("Temporary package file unavailable")); | ||
} | ||
|
||
resolve(<tmp.SynchrounousResult>{ name: path, fd: fd, removeCallback: cleanupCallback }); | ||
}); | ||
}); | ||
|
||
return { | ||
fd: tmpFile.fd, | ||
name: tmpFile.name, | ||
dispose: () => tmpFile.removeCallback() | ||
}; | ||
} | ||
|
||
export async function createTmpDir(unsafeCleanup: boolean): Promise<TmpAsset> { | ||
const tmpDir = await new Promise<tmp.SynchrounousResult>((resolve, reject) => { | ||
tmp.dir({ unsafeCleanup }, (err, path, cleanupCallback) => { | ||
if (err) { | ||
return reject(new NestedError('Error from tmp.dir', err)); | ||
} | ||
|
||
resolve(<tmp.SynchrounousResult>{ name: path, removeCallback: cleanupCallback }); | ||
}); | ||
}); | ||
|
||
return { | ||
fd: tmpDir.fd, | ||
name: tmpDir.name, | ||
dispose: () => { | ||
if (unsafeCleanup) { | ||
rimraf(tmpDir.name);//to delete directories that have folders inside them | ||
} | ||
else { | ||
tmpDir.removeCallback(); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
export interface TmpAsset { | ||
fd: number; | ||
name: string; | ||
dispose: () => void; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { vscode } from "./vscodeAdapter"; | ||
|
||
export default class NetworkSettings { | ||
constructor(public readonly proxy: string, public readonly strictSSL: boolean) { | ||
} | ||
} | ||
|
||
export interface NetworkSettingsProvider { | ||
//to do: make this async | ||
(): NetworkSettings; | ||
} | ||
|
||
export function vscodeNetworkSettingsProvider(vscode: vscode): NetworkSettingsProvider { | ||
return () => { | ||
const config = vscode.workspace.getConfiguration(); | ||
const proxy = config.get<string>('http.proxy'); | ||
const strictSSL = config.get('http.proxyStrictSSL', true); | ||
return new NetworkSettings(proxy, strictSSL); | ||
}; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,31 +3,24 @@ | |
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { GetNetworkConfiguration, IPackageManagerFactory, defaultPackageManagerFactory } from '../downloader.helper'; | ||
import { GetPackagesFromVersion, GetVersionFilePackage } from './OmnisharpPackageCreator'; | ||
import { Package, PackageManager } from '../packages'; | ||
import * as fs from 'fs'; | ||
import { GetPackagesFromVersion } from './OmnisharpPackageCreator'; | ||
import { PlatformInformation } from '../platform'; | ||
import { PackageInstallation, LogPlatformInfo, InstallationSuccess, InstallationFailure, InstallationProgress } from './loggingEvents'; | ||
import { EventStream } from '../EventStream'; | ||
import { vscode } from '../vscodeAdapter'; | ||
|
||
import { NetworkSettingsProvider } from '../NetworkSettings'; | ||
import { DownloadAndInstallPackages } from '../packageManager/PackageManager'; | ||
import { createTmpFile, TmpAsset } from '../CreateTmpAsset'; | ||
import { DownloadFile } from '../packageManager/FileDownloader'; | ||
import { ResolveFilePaths } from '../packageManager/PackageFilePathResolver'; | ||
|
||
export class OmnisharpDownloader { | ||
private proxy: string; | ||
private strictSSL: boolean; | ||
private packageManager: PackageManager; | ||
|
||
public constructor( | ||
vscode: vscode, | ||
private provider: NetworkSettingsProvider, | ||
private eventStream: EventStream, | ||
private packageJSON: any, | ||
private platformInfo: PlatformInformation, | ||
packageManagerFactory: IPackageManagerFactory = defaultPackageManagerFactory) { | ||
|
||
let networkConfiguration = GetNetworkConfiguration(vscode); | ||
this.proxy = networkConfiguration.Proxy; | ||
this.strictSSL = networkConfiguration.StrictSSL; | ||
this.packageManager = packageManagerFactory(this.platformInfo, this.packageJSON); | ||
private platformInfo: PlatformInformation) { | ||
} | ||
|
||
public async DownloadAndInstallOmnisharp(version: string, serverUrl: string, installPath: string) { | ||
|
@@ -36,18 +29,11 @@ export class OmnisharpDownloader { | |
|
||
try { | ||
this.eventStream.post(new LogPlatformInfo(this.platformInfo)); | ||
|
||
installationStage = 'getPackageInfo'; | ||
let packages: Package[] = GetPackagesFromVersion(version, this.packageJSON.runtimeDependencies, serverUrl, installPath); | ||
|
||
installationStage = 'downloadPackages'; | ||
// Specify the packages that the package manager needs to download | ||
this.packageManager.SetVersionPackagesForDownload(packages); | ||
await this.packageManager.DownloadPackages(this.eventStream, this.proxy, this.strictSSL); | ||
|
||
installationStage = 'installPackages'; | ||
await this.packageManager.InstallPackages(this.eventStream); | ||
|
||
let packages = GetPackagesFromVersion(version, this.packageJSON.runtimeDependencies, serverUrl, installPath); | ||
packages.forEach(pkg => ResolveFilePaths(pkg)); | ||
installationStage = 'downloadAndInstallPackages'; | ||
await DownloadAndInstallPackages(packages, this.provider, this.platformInfo, this.eventStream); | ||
this.eventStream.post(new InstallationSuccess()); | ||
} | ||
catch (error) { | ||
|
@@ -56,18 +42,25 @@ export class OmnisharpDownloader { | |
} | ||
} | ||
|
||
public async GetLatestVersion(serverUrl: string, latestVersionFileServerPath: string): Promise<string> { | ||
public async GetLatestVersion(serverUrl: string, latestVersionFileServerPath: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious, why did we remove the return type annotation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed by mistake. Thanks for pointing it out. |
||
let installationStage = 'getLatestVersionInfoFile'; | ||
let description = "Latest Omnisharp Version Information"; | ||
let url = `${serverUrl}/${latestVersionFileServerPath}`; | ||
let tmpFile: TmpAsset; | ||
try { | ||
this.eventStream.post(new InstallationProgress(installationStage, 'Getting latest build information...')); | ||
//The package manager needs a package format to download, hence we form a package for the latest version file | ||
let filePackage = GetVersionFilePackage(serverUrl, latestVersionFileServerPath); | ||
//Fetch the latest version information from the file | ||
return await this.packageManager.GetLatestVersionFromFile(this.eventStream, this.proxy, this.strictSSL, filePackage); | ||
tmpFile = await createTmpFile(); | ||
await DownloadFile(tmpFile.fd, description, url, "", this.eventStream, this.provider); | ||
return fs.readFileSync(tmpFile.name, 'utf8'); | ||
} | ||
catch (error) { | ||
this.eventStream.post(new InstallationFailure(installationStage, error)); | ||
throw error; | ||
} | ||
finally { | ||
if (tmpFile) { | ||
tmpFile.dispose(); | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since c#extdl'er assumes that DownloadAndInstallPacakges returning means the lockfile can be changed, maybe we should just move lockfile manipulation into the downloader component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are planning to remove the Lockfile concept(in a subsequent PR) entirely and using the InstallTestPath to check whether the packages are installed or not.