Skip to content

Commit 18a9e27

Browse files
committed
Update handling of prerelease versions of the CodeQL CLI.
Suppose a user has the includePrereleases config option set, installs an extension-managed prerelease, then decides they no longer want prereleases and disables includePrereleases. In this case, we should prompt the user to downgrade the CLI to a non-prerelease version. However, if the user is managing their own CLI, we will allow them to use prereleases without incompatibility prompts.
1 parent 8208940 commit 18a9e27

File tree

2 files changed

+92
-21
lines changed

2 files changed

+92
-21
lines changed

extensions/ql-vscode/src/distribution.ts

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,28 +64,47 @@ export class DistributionManager implements DistributionProvider {
6464
* Look up a CodeQL launcher binary.
6565
*/
6666
public async getDistribution(): Promise<FindDistributionResult> {
67-
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
68-
if (codeQlPath === undefined) {
67+
const distribution = await this.getDistributionWithoutVersionCheck();
68+
if (distribution === undefined) {
6969
return {
7070
kind: FindDistributionResultKind.NoDistribution,
7171
};
7272
}
73-
const version = await getCodeQlCliVersion(codeQlPath, logger);
73+
const version = await getCodeQlCliVersion(distribution.codeQlPath, logger);
7474
if (version === undefined) {
7575
return {
76-
codeQlPath,
76+
distribution,
7777
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
7878
};
7979
}
80-
if (!semver.satisfies(version, this._versionRange)) {
80+
81+
/**
82+
* Specifies whether prerelease versions of the CodeQL CLI should be accepted.
83+
*
84+
* Suppose a user sets the includePrerelease config option, obtains a prerelease, then decides
85+
* they no longer want a prerelease, so unsets the includePrerelease config option.
86+
* Unsetting the includePrerelease config option should trigger an update check, and this
87+
* update check should present them an update that returns them back to a non-prerelease
88+
* version.
89+
*
90+
* Therefore, we adopt the following:
91+
*
92+
* - If the user is managing their own CLI, they can use a prerelease without specifying the
93+
* includePrerelease option.
94+
* - If the user is using an extension-managed CLI, then prereleases are only accepted when the
95+
* includePrerelease config option is set.
96+
*/
97+
const includePrerelease = distribution.kind !== DistributionKind.ExtensionManaged || this._config.includePrerelease;
98+
99+
if (!semver.satisfies(version, this._versionRange, { includePrerelease })) {
81100
return {
82-
codeQlPath,
101+
distribution,
83102
kind: FindDistributionResultKind.IncompatibleDistribution,
84103
version,
85104
};
86105
}
87106
return {
88-
codeQlPath,
107+
distribution,
89108
kind: FindDistributionResultKind.CompatibleDistribution,
90109
version
91110
};
@@ -96,10 +115,18 @@ export class DistributionManager implements DistributionProvider {
96115
return result.kind !== FindDistributionResultKind.NoDistribution;
97116
}
98117

118+
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
119+
const distribution = await this.getDistributionWithoutVersionCheck();
120+
if (distribution === undefined) {
121+
return undefined;
122+
}
123+
return distribution.codeQlPath;
124+
}
125+
99126
/**
100127
* Returns the path to a possibly-compatible CodeQL launcher binary, or undefined if a binary not be found.
101128
*/
102-
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
129+
async getDistributionWithoutVersionCheck(): Promise<Distribution | undefined> {
103130
// Check config setting, then extension specific distribution, then PATH.
104131
if (this._config.customCodeQlPath) {
105132
if (!await fs.pathExists(this._config.customCodeQlPath)) {
@@ -117,19 +144,28 @@ export class DistributionManager implements DistributionProvider {
117144
) {
118145
warnDeprecatedLauncher();
119146
}
120-
return this._config.customCodeQlPath;
147+
return {
148+
codeQlPath: this._config.customCodeQlPath,
149+
kind: DistributionKind.CustomPathConfig
150+
};
121151
}
122152

123153
const extensionSpecificCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
124154
if (extensionSpecificCodeQlPath !== undefined) {
125-
return extensionSpecificCodeQlPath;
155+
return {
156+
codeQlPath: extensionSpecificCodeQlPath,
157+
kind: DistributionKind.ExtensionManaged
158+
};
126159
}
127160

128161
if (process.env.PATH) {
129162
for (const searchDirectory of process.env.PATH.split(path.delimiter)) {
130163
const expectedLauncherPath = await getExecutableFromDirectory(searchDirectory);
131164
if (expectedLauncherPath) {
132-
return expectedLauncherPath;
165+
return {
166+
codeQlPath: expectedLauncherPath,
167+
kind: DistributionKind.PathEnvironmentVariable
168+
};
133169
}
134170
}
135171
logger.log("INFO: Could not find CodeQL on path.");
@@ -146,9 +182,9 @@ export class DistributionManager implements DistributionProvider {
146182
*/
147183
public async checkForUpdatesToExtensionManagedDistribution(
148184
minSecondsSinceLastUpdateCheck: number): Promise<DistributionUpdateCheckResult> {
149-
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
185+
const distribution = await this.getDistributionWithoutVersionCheck();
150186
const extensionManagedCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
151-
if (codeQlPath !== undefined && codeQlPath !== extensionManagedCodeQlPath) {
187+
if (distribution !== undefined && distribution.codeQlPath !== extensionManagedCodeQlPath) {
152188
// A distribution is present but it isn't managed by the extension.
153189
return createInvalidLocationResult();
154190
}
@@ -406,7 +442,7 @@ export class ReleasesApiConsumer {
406442
}
407443

408444
const version = semver.parse(release.tag_name);
409-
if (version === null || !semver.satisfies(version, versionRange)) {
445+
if (version === null || !semver.satisfies(version, versionRange, { includePrerelease })) {
410446
return false;
411447
}
412448

@@ -534,6 +570,17 @@ function isRedirectStatusCode(statusCode: number): boolean {
534570
* Types and helper functions relating to those types.
535571
*/
536572

573+
export enum DistributionKind {
574+
CustomPathConfig,
575+
ExtensionManaged,
576+
PathEnvironmentVariable
577+
}
578+
579+
export interface Distribution {
580+
codeQlPath: string;
581+
kind: DistributionKind;
582+
}
583+
537584
export enum FindDistributionResultKind {
538585
CompatibleDistribution,
539586
UnknownCompatibilityDistribution,
@@ -548,18 +595,18 @@ export type FindDistributionResult =
548595
| NoDistributionResult;
549596

550597
interface CompatibleDistributionResult {
551-
codeQlPath: string;
598+
distribution: Distribution;
552599
kind: FindDistributionResultKind.CompatibleDistribution;
553600
version: semver.SemVer;
554601
}
555602

556603
interface UnknownCompatibilityDistributionResult {
557-
codeQlPath: string;
604+
distribution: Distribution;
558605
kind: FindDistributionResultKind.UnknownCompatibilityDistribution;
559606
}
560607

561608
interface IncompatibleDistributionResult {
562-
codeQlPath: string;
609+
distribution: Distribution;
563610
kind: FindDistributionResultKind.IncompatibleDistribution;
564611
version: semver.SemVer;
565612
}

extensions/ql-vscode/src/extension.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ import * as languageSupport from './languageSupport';
88
import { DatabaseManager } from './databases';
99
import { DatabaseUI } from './databases-ui';
1010
import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions';
11-
import { DEFAULT_DISTRIBUTION_VERSION_RANGE, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution';
11+
import {
12+
DEFAULT_DISTRIBUTION_VERSION_RANGE,
13+
DistributionKind,
14+
DistributionManager,
15+
DistributionUpdateCheckResultKind,
16+
FindDistributionResult,
17+
FindDistributionResultKind,
18+
GithubApiError,
19+
GithubRateLimitedError
20+
} from './distribution';
1221
import * as helpers from './helpers';
1322
import { assertNever } from './helpers-pure';
1423
import { spawnIdeServer } from './ide-server';
@@ -83,7 +92,8 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
8392

8493
const distributionConfigListener = new DistributionConfigListener();
8594
ctx.subscriptions.push(distributionConfigListener);
86-
const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_RANGE);
95+
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
96+
const distributionManager = new DistributionManager(ctx, distributionConfigListener, codeQlVersionRange);
8797

8898
const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
8999

@@ -184,9 +194,23 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
184194
case FindDistributionResultKind.CompatibleDistribution:
185195
logger.log(`Found compatible version of CodeQL CLI (version ${result.version.raw})`);
186196
break;
187-
case FindDistributionResultKind.IncompatibleDistribution:
188-
helpers.showAndLogWarningMessage("The current version of the CodeQL CLI is incompatible with this extension.");
197+
case FindDistributionResultKind.IncompatibleDistribution: {
198+
const fixGuidanceMessage = (() => {
199+
switch (result.distribution.kind) {
200+
case DistributionKind.ExtensionManaged:
201+
return "Please update the CodeQL CLI by running the \"CodeQL: Check for CLI Updates\" command.";
202+
case DistributionKind.CustomPathConfig:
203+
return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`;
204+
case DistributionKind.PathEnvironmentVariable:
205+
return `Please update the CodeQL CLI on your PATH to a version in the range ${codeQlVersionRange}, or ` +
206+
`set the \"CodeQL CLI Executable Path\" setting to the path of a CLI in the version range ${codeQlVersionRange}.`;
207+
}
208+
})();
209+
210+
helpers.showAndLogWarningMessage(`The current version of the CodeQL CLI (${result.version.raw}) ` +
211+
"is incompatible with this extension. " + fixGuidanceMessage);
189212
break;
213+
}
190214
case FindDistributionResultKind.UnknownCompatibilityDistribution:
191215
helpers.showAndLogWarningMessage("Compatibility with the configured CodeQL CLI could not be determined. " +
192216
"You may experience problems using the extension.");

0 commit comments

Comments
 (0)