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.
This commit is contained in:
@@ -64,28 +64,47 @@ export class DistributionManager implements DistributionProvider {
|
||||
* Look up a CodeQL launcher binary.
|
||||
*/
|
||||
public async getDistribution(): Promise<FindDistributionResult> {
|
||||
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
|
||||
if (codeQlPath === undefined) {
|
||||
const distribution = await this.getDistributionWithoutVersionCheck();
|
||||
if (distribution === undefined) {
|
||||
return {
|
||||
kind: FindDistributionResultKind.NoDistribution,
|
||||
};
|
||||
}
|
||||
const version = await getCodeQlCliVersion(codeQlPath, logger);
|
||||
const version = await getCodeQlCliVersion(distribution.codeQlPath, logger);
|
||||
if (version === undefined) {
|
||||
return {
|
||||
codeQlPath,
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
|
||||
};
|
||||
}
|
||||
if (!semver.satisfies(version, this._versionRange)) {
|
||||
|
||||
/**
|
||||
* Specifies whether prerelease versions of the CodeQL CLI should be accepted.
|
||||
*
|
||||
* Suppose a user sets the includePrerelease config option, obtains a prerelease, then decides
|
||||
* they no longer want a prerelease, so unsets the includePrerelease config option.
|
||||
* Unsetting the includePrerelease config option should trigger an update check, and this
|
||||
* update check should present them an update that returns them back to a non-prerelease
|
||||
* version.
|
||||
*
|
||||
* Therefore, we adopt the following:
|
||||
*
|
||||
* - If the user is managing their own CLI, they can use a prerelease without specifying the
|
||||
* includePrerelease option.
|
||||
* - If the user is using an extension-managed CLI, then prereleases are only accepted when the
|
||||
* includePrerelease config option is set.
|
||||
*/
|
||||
const includePrerelease = distribution.kind !== DistributionKind.ExtensionManaged || this._config.includePrerelease;
|
||||
|
||||
if (!semver.satisfies(version, this._versionRange, { includePrerelease })) {
|
||||
return {
|
||||
codeQlPath,
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.IncompatibleDistribution,
|
||||
version,
|
||||
};
|
||||
}
|
||||
return {
|
||||
codeQlPath,
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.CompatibleDistribution,
|
||||
version
|
||||
};
|
||||
@@ -96,10 +115,18 @@ export class DistributionManager implements DistributionProvider {
|
||||
return result.kind !== FindDistributionResultKind.NoDistribution;
|
||||
}
|
||||
|
||||
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
|
||||
const distribution = await this.getDistributionWithoutVersionCheck();
|
||||
if (distribution === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return distribution.codeQlPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to a possibly-compatible CodeQL launcher binary, or undefined if a binary not be found.
|
||||
*/
|
||||
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
|
||||
async getDistributionWithoutVersionCheck(): Promise<Distribution | undefined> {
|
||||
// Check config setting, then extension specific distribution, then PATH.
|
||||
if (this._config.customCodeQlPath) {
|
||||
if (!await fs.pathExists(this._config.customCodeQlPath)) {
|
||||
@@ -117,19 +144,28 @@ export class DistributionManager implements DistributionProvider {
|
||||
) {
|
||||
warnDeprecatedLauncher();
|
||||
}
|
||||
return this._config.customCodeQlPath;
|
||||
return {
|
||||
codeQlPath: this._config.customCodeQlPath,
|
||||
kind: DistributionKind.CustomPathConfig
|
||||
};
|
||||
}
|
||||
|
||||
const extensionSpecificCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
|
||||
if (extensionSpecificCodeQlPath !== undefined) {
|
||||
return extensionSpecificCodeQlPath;
|
||||
return {
|
||||
codeQlPath: extensionSpecificCodeQlPath,
|
||||
kind: DistributionKind.ExtensionManaged
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.PATH) {
|
||||
for (const searchDirectory of process.env.PATH.split(path.delimiter)) {
|
||||
const expectedLauncherPath = await getExecutableFromDirectory(searchDirectory);
|
||||
if (expectedLauncherPath) {
|
||||
return expectedLauncherPath;
|
||||
return {
|
||||
codeQlPath: expectedLauncherPath,
|
||||
kind: DistributionKind.PathEnvironmentVariable
|
||||
};
|
||||
}
|
||||
}
|
||||
logger.log("INFO: Could not find CodeQL on path.");
|
||||
@@ -146,9 +182,9 @@ export class DistributionManager implements DistributionProvider {
|
||||
*/
|
||||
public async checkForUpdatesToExtensionManagedDistribution(
|
||||
minSecondsSinceLastUpdateCheck: number): Promise<DistributionUpdateCheckResult> {
|
||||
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
|
||||
const distribution = await this.getDistributionWithoutVersionCheck();
|
||||
const extensionManagedCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
|
||||
if (codeQlPath !== undefined && codeQlPath !== extensionManagedCodeQlPath) {
|
||||
if (distribution !== undefined && distribution.codeQlPath !== extensionManagedCodeQlPath) {
|
||||
// A distribution is present but it isn't managed by the extension.
|
||||
return createInvalidLocationResult();
|
||||
}
|
||||
@@ -406,7 +442,7 @@ export class ReleasesApiConsumer {
|
||||
}
|
||||
|
||||
const version = semver.parse(release.tag_name);
|
||||
if (version === null || !semver.satisfies(version, versionRange)) {
|
||||
if (version === null || !semver.satisfies(version, versionRange, { includePrerelease })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -534,6 +570,17 @@ function isRedirectStatusCode(statusCode: number): boolean {
|
||||
* Types and helper functions relating to those types.
|
||||
*/
|
||||
|
||||
export enum DistributionKind {
|
||||
CustomPathConfig,
|
||||
ExtensionManaged,
|
||||
PathEnvironmentVariable
|
||||
}
|
||||
|
||||
export interface Distribution {
|
||||
codeQlPath: string;
|
||||
kind: DistributionKind;
|
||||
}
|
||||
|
||||
export enum FindDistributionResultKind {
|
||||
CompatibleDistribution,
|
||||
UnknownCompatibilityDistribution,
|
||||
@@ -548,18 +595,18 @@ export type FindDistributionResult =
|
||||
| NoDistributionResult;
|
||||
|
||||
interface CompatibleDistributionResult {
|
||||
codeQlPath: string;
|
||||
distribution: Distribution;
|
||||
kind: FindDistributionResultKind.CompatibleDistribution;
|
||||
version: semver.SemVer;
|
||||
}
|
||||
|
||||
interface UnknownCompatibilityDistributionResult {
|
||||
codeQlPath: string;
|
||||
distribution: Distribution;
|
||||
kind: FindDistributionResultKind.UnknownCompatibilityDistribution;
|
||||
}
|
||||
|
||||
interface IncompatibleDistributionResult {
|
||||
codeQlPath: string;
|
||||
distribution: Distribution;
|
||||
kind: FindDistributionResultKind.IncompatibleDistribution;
|
||||
version: semver.SemVer;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,16 @@ import * as languageSupport from './languageSupport';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions';
|
||||
import { DEFAULT_DISTRIBUTION_VERSION_RANGE, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution';
|
||||
import {
|
||||
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
||||
DistributionKind,
|
||||
DistributionManager,
|
||||
DistributionUpdateCheckResultKind,
|
||||
FindDistributionResult,
|
||||
FindDistributionResultKind,
|
||||
GithubApiError,
|
||||
GithubRateLimitedError
|
||||
} from './distribution';
|
||||
import * as helpers from './helpers';
|
||||
import { assertNever } from './helpers-pure';
|
||||
import { spawnIdeServer } from './ide-server';
|
||||
@@ -83,7 +92,8 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
|
||||
|
||||
const distributionConfigListener = new DistributionConfigListener();
|
||||
ctx.subscriptions.push(distributionConfigListener);
|
||||
const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_RANGE);
|
||||
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
|
||||
const distributionManager = new DistributionManager(ctx, distributionConfigListener, codeQlVersionRange);
|
||||
|
||||
const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
|
||||
|
||||
@@ -184,9 +194,23 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
|
||||
case FindDistributionResultKind.CompatibleDistribution:
|
||||
logger.log(`Found compatible version of CodeQL CLI (version ${result.version.raw})`);
|
||||
break;
|
||||
case FindDistributionResultKind.IncompatibleDistribution:
|
||||
helpers.showAndLogWarningMessage("The current version of the CodeQL CLI is incompatible with this extension.");
|
||||
case FindDistributionResultKind.IncompatibleDistribution: {
|
||||
const fixGuidanceMessage = (() => {
|
||||
switch (result.distribution.kind) {
|
||||
case DistributionKind.ExtensionManaged:
|
||||
return "Please update the CodeQL CLI by running the \"CodeQL: Check for CLI Updates\" command.";
|
||||
case DistributionKind.CustomPathConfig:
|
||||
return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`;
|
||||
case DistributionKind.PathEnvironmentVariable:
|
||||
return `Please update the CodeQL CLI on your PATH to a version in the range ${codeQlVersionRange}, or ` +
|
||||
`set the \"CodeQL CLI Executable Path\" setting to the path of a CLI in the version range ${codeQlVersionRange}.`;
|
||||
}
|
||||
})();
|
||||
|
||||
helpers.showAndLogWarningMessage(`The current version of the CodeQL CLI (${result.version.raw}) ` +
|
||||
"is incompatible with this extension. " + fixGuidanceMessage);
|
||||
break;
|
||||
}
|
||||
case FindDistributionResultKind.UnknownCompatibilityDistribution:
|
||||
helpers.showAndLogWarningMessage("Compatibility with the configured CodeQL CLI could not be determined. " +
|
||||
"You may experience problems using the extension.");
|
||||
|
||||
Reference in New Issue
Block a user