Skip to content

Commit e42fc1e

Browse files
Allow LS to be bundled, versioned (#12034)
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
1 parent b859a3d commit e42fc1e

14 files changed

+283
-11
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ ptvsd*.log
4242
pydevd*.log
4343
nodeLanguageServer/**
4444
nodeLanguageServer.*/**
45-
45+
bundledLanguageServer/**

.vscodeignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ webpack.datascience-*.config.js
5858
.vscode test/**
5959
languageServer/**
6060
languageServer.*/**
61+
nodeLanguageServer/**
62+
nodeLanguageServer.*/**
6163
bin/**
6264
build/**
6365
BuildOutput/**

src/client/activation/common/downloader.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { createDeferred } from '../../common/utils/async';
1414
import { Common, LanguageService } from '../../common/utils/localize';
1515
import { StopWatch } from '../../common/utils/stopWatch';
1616
import { IServiceContainer } from '../../ioc/types';
17+
import { traceError } from '../../logging';
1718
import { sendTelemetryEvent } from '../../telemetry';
1819
import { EventName } from '../../telemetry/constants';
1920
import {
@@ -58,6 +59,12 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
5859
}
5960

6061
public async downloadLanguageServer(destinationFolder: string, resource: Resource): Promise<void> {
62+
if (this.lsFolderService.isBundled()) {
63+
// Sanity check; a bundled LS should never be downloaded.
64+
traceError('Attempted to download bundled language server');
65+
return;
66+
}
67+
6168
const [downloadUri, lsVersion, lsName] = await this.getDownloadInfo(resource);
6269
const timer: StopWatch = new StopWatch();
6370
let success: boolean = true;

src/client/activation/common/languageServerFolderService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export abstract class LanguageServerFolderService implements ILanguageServerFold
2626
@unmanaged() protected readonly languageServerFolder: string
2727
) {}
2828

29+
public isBundled(): boolean {
30+
return false;
31+
}
32+
2933
@traceDecorators.verbose('Get language server folder name')
3034
public async getLanguageServerFolderName(resource: Resource): Promise<string> {
3135
const currentFolder = await this.getCurrentLanguageServerDirectory();

src/client/activation/languageServer/languageServerFolderService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { LanguageServerFolderService } from '../common/languageServerFolderServi
1010
import { DotNetLanguageServerFolder } from '../types';
1111

1212
// Must match languageServerVersion* keys in package.json
13-
const DotNetLanguageServerMinVersionKey = 'languageServerVersion';
13+
export const DotNetLanguageServerMinVersionKey = 'languageServerVersion';
1414

1515
@injectable()
1616
export class DotNetLanguageServerFolderService extends LanguageServerFolderService {

src/client/activation/node/languageServerFolderService.ts

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,89 @@
44
'use strict';
55

66
import { inject, injectable } from 'inversify';
7+
import * as semver from 'semver';
8+
import { IApplicationEnvironment, IWorkspaceService } from '../../common/application/types';
9+
import { NugetPackage } from '../../common/nuget/types';
10+
import { IConfigurationService, Resource } from '../../common/types';
711
import { IServiceContainer } from '../../ioc/types';
12+
import { traceWarning } from '../../logging';
813
import { LanguageServerFolderService } from '../common/languageServerFolderService';
9-
import { NodeLanguageServerFolder } from '../types';
14+
import {
15+
BundledLanguageServerFolder,
16+
FolderVersionPair,
17+
ILanguageServerFolderService,
18+
NodeLanguageServerFolder
19+
} from '../types';
1020

11-
@injectable()
12-
export class NodeLanguageServerFolderService extends LanguageServerFolderService {
13-
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
21+
// Must match languageServerVersion* keys in package.json
22+
export const NodeLanguageServerVersionKey = 'languageServerVersionV2';
23+
24+
class FallbackNodeLanguageServerFolderService extends LanguageServerFolderService {
25+
constructor(serviceContainer: IServiceContainer) {
1426
super(serviceContainer, NodeLanguageServerFolder);
1527
}
1628

1729
protected getMinimalLanguageServerVersion(): string {
1830
return '0.0.0';
1931
}
2032
}
33+
34+
@injectable()
35+
export class NodeLanguageServerFolderService implements ILanguageServerFolderService {
36+
private readonly _bundledVersion: semver.SemVer | undefined;
37+
private readonly fallback: FallbackNodeLanguageServerFolderService;
38+
39+
constructor(
40+
@inject(IServiceContainer) serviceContainer: IServiceContainer,
41+
@inject(IConfigurationService) configService: IConfigurationService,
42+
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
43+
@inject(IApplicationEnvironment) appEnv: IApplicationEnvironment
44+
) {
45+
this.fallback = new FallbackNodeLanguageServerFolderService(serviceContainer);
46+
47+
// downloadLanguageServer is a bit of a misnomer; if false then this indicates that a local
48+
// development copy should be run instead of a "real" build, telemetry discarded, etc.
49+
// So, we require it to be true, even though in the bundled case no real download happens.
50+
if (
51+
configService.getSettings().downloadLanguageServer &&
52+
!workspaceService.getConfiguration('python').get<string>('packageName')
53+
) {
54+
const ver = appEnv.packageJson[NodeLanguageServerVersionKey] as string;
55+
this._bundledVersion = semver.parse(ver) || undefined;
56+
if (this._bundledVersion === undefined) {
57+
traceWarning(
58+
`invalid language server version ${ver} in package.json (${NodeLanguageServerVersionKey})`
59+
);
60+
}
61+
}
62+
}
63+
64+
public get bundledVersion(): semver.SemVer | undefined {
65+
return this._bundledVersion;
66+
}
67+
68+
public isBundled(): boolean {
69+
return this._bundledVersion !== undefined;
70+
}
71+
72+
public async getLanguageServerFolderName(resource: Resource): Promise<string> {
73+
if (this._bundledVersion) {
74+
return BundledLanguageServerFolder;
75+
}
76+
return this.fallback.getLanguageServerFolderName(resource);
77+
}
78+
79+
public async getLatestLanguageServerVersion(resource: Resource): Promise<NugetPackage | undefined> {
80+
if (this._bundledVersion) {
81+
return undefined;
82+
}
83+
return this.fallback.getLatestLanguageServerVersion(resource);
84+
}
85+
86+
public async getCurrentLanguageServerDirectory(): Promise<FolderVersionPair | undefined> {
87+
if (this._bundledVersion) {
88+
return { path: BundledLanguageServerFolder, version: this._bundledVersion };
89+
}
90+
return this.fallback.getCurrentLanguageServerDirectory();
91+
}
92+
}

src/client/activation/node/languageServerProxy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
7979
options: LanguageClientOptions
8080
): Promise<void> {
8181
if (!this.languageClient) {
82-
const lsVersion = await this.folderService.getLatestLanguageServerVersion(resource);
83-
this.lsVersion = lsVersion?.version.format();
82+
const directory = await this.folderService.getCurrentLanguageServerDirectory();
83+
this.lsVersion = directory?.version.format();
8484

8585
this.cancellationStrategy = new FileBasedCancellationStrategy();
8686
options.connectionOptions = { cancellationStrategy: this.cancellationStrategy };

src/client/activation/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export enum LanguageServerType {
7272

7373
export const DotNetLanguageServerFolder = 'languageServer';
7474
export const NodeLanguageServerFolder = 'nodeLanguageServer';
75+
export const BundledLanguageServerFolder = 'bundledLanguageServer';
7576

7677
// tslint:disable-next-line: interface-name
7778
export interface DocumentHandler {
@@ -116,6 +117,7 @@ export interface ILanguageServerFolderService {
116117
getLanguageServerFolderName(resource: Resource): Promise<string>;
117118
getLatestLanguageServerVersion(resource: Resource): Promise<NugetPackage | undefined>;
118119
getCurrentLanguageServerDirectory(): Promise<FolderVersionPair | undefined>;
120+
isBundled(): boolean;
119121
}
120122

121123
export const ILanguageServerDownloader = Symbol('ILanguageServerDownloader');

src/test/activation/languageServer/downloader.unit.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,29 @@ suite('Language Server Activation - Downloader', () => {
260260
throw failure;
261261
}
262262
}
263+
class LanguageServeBundledTest extends LanguageServerDownloader {
264+
// tslint:disable-next-line:no-unnecessary-override
265+
public async downloadLanguageServer(destinationFolder: string, res?: Resource): Promise<void> {
266+
return super.downloadLanguageServer(destinationFolder, res);
267+
}
268+
// tslint:disable-next-line:no-unnecessary-override
269+
public async getDownloadInfo(_res?: Resource): Promise<string[]> {
270+
throw failure;
271+
}
272+
public async downloadFile(): Promise<string> {
273+
throw failure;
274+
}
275+
protected async unpackArchive(_extensionPath: string, _tempFilePath: string): Promise<void> {
276+
throw failure;
277+
}
278+
}
263279
let output: TypeMoq.IMock<IOutputChannel>;
264280
let appShell: TypeMoq.IMock<IApplicationShell>;
265281
let fs: TypeMoq.IMock<IFileSystem>;
266282
let platformData: TypeMoq.IMock<IPlatformData>;
267283
let languageServerDownloaderTest: LanguageServerDownloaderTest;
268284
let languageServerExtractorTest: LanguageServerExtractorTest;
285+
let languageServerBundledTest: LanguageServeBundledTest;
269286
setup(() => {
270287
appShell = TypeMoq.Mock.ofType<IApplicationShell>(undefined, TypeMoq.MockBehavior.Strict);
271288
folderService = TypeMoq.Mock.ofType<ILanguageServerFolderService>(undefined, TypeMoq.MockBehavior.Strict);
@@ -293,8 +310,18 @@ suite('Language Server Activation - Downloader', () => {
293310
workspaceService.object,
294311
undefined as any
295312
);
313+
languageServerBundledTest = new LanguageServeBundledTest(
314+
lsOutputChannel.object,
315+
undefined as any,
316+
folderService.object,
317+
appShell.object,
318+
fs.object,
319+
workspaceService.object,
320+
undefined as any
321+
);
296322
});
297323
test('Display error message if LS downloading fails', async () => {
324+
folderService.setup((f) => f.isBundled()).returns(() => false);
298325
const pkg = makePkgInfo('ls', 'xyz');
299326
folderService.setup((f) => f.getLatestLanguageServerVersion(resource)).returns(() => Promise.resolve(pkg));
300327
output.setup((o) => o.appendLine(LanguageService.downloadFailedOutputMessage()));
@@ -318,6 +345,7 @@ suite('Language Server Activation - Downloader', () => {
318345
platformData.verifyAll();
319346
});
320347
test('Display error message if LS extraction fails', async () => {
348+
folderService.setup((f) => f.isBundled()).returns(() => false);
321349
const pkg = makePkgInfo('ls', 'xyz');
322350
folderService.setup((f) => f.getLatestLanguageServerVersion(resource)).returns(() => Promise.resolve(pkg));
323351
output.setup((o) => o.appendLine(LanguageService.extractionFailedOutputMessage()));
@@ -340,6 +368,17 @@ suite('Language Server Activation - Downloader', () => {
340368
fs.verifyAll();
341369
platformData.verifyAll();
342370
});
371+
test('No download if bundled', async () => {
372+
folderService.setup((f) => f.isBundled()).returns(() => true);
373+
374+
await languageServerBundledTest.downloadLanguageServer('', resource);
375+
376+
folderService.verifyAll();
377+
output.verifyAll();
378+
appShell.verifyAll();
379+
fs.verifyAll();
380+
platformData.verifyAll();
381+
});
343382
});
344383
});
345384

src/test/activation/languageServer/languageServerFolderService.unit.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,15 @@ suite('Language Server Folder Service', () => {
267267
assert.deepEqual(result, expectedLSDirectory);
268268
});
269269
});
270+
271+
suite('Method isBundled()', () => {
272+
setup(() => {
273+
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
274+
languageServerFolderService = new DotNetLanguageServerFolderService(serviceContainer.object);
275+
});
276+
277+
test('isBundled is false', () => {
278+
expect(languageServerFolderService.isBundled()).to.be.equal(false, 'isBundled should be false');
279+
});
280+
});
270281
});

0 commit comments

Comments
 (0)