Skip to content

Commit 9678f63

Browse files
authored
Consolidate version checking into CLI discovery file (#2552)
1 parent 214e431 commit 9678f63

File tree

3 files changed

+228
-229
lines changed

3 files changed

+228
-229
lines changed

extension/src/cli/dvc/discovery.ts

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import {
2+
LATEST_TESTED_CLI_VERSION,
3+
MAX_CLI_VERSION,
4+
MIN_CLI_VERSION
5+
} from './constants'
6+
import { CliCompatible, isVersionCompatible } from './version'
7+
import { IExtension } from '../../interfaces'
8+
import { Toast } from '../../vscode/toast'
9+
import { Response } from '../../vscode/response'
10+
import {
11+
ConfigKey,
12+
getConfigValue,
13+
setUserConfigValue
14+
} from '../../vscode/config'
15+
import {
16+
getPythonBinPath,
17+
isPythonExtensionInstalled,
18+
selectPythonInterpreter
19+
} from '../../extensions/python'
20+
21+
const getToastOptions = (isPythonExtensionInstalled: boolean): Response[] => {
22+
return isPythonExtensionInstalled
23+
? [Response.SETUP_WORKSPACE, Response.SELECT_INTERPRETER, Response.NEVER]
24+
: [Response.SETUP_WORKSPACE, Response.NEVER]
25+
}
26+
27+
export const warnUnableToVerifyVersion = () =>
28+
Toast.warnWithOptions(
29+
'The extension cannot initialize as we were unable to verify the DVC CLI version.'
30+
)
31+
32+
export const warnVersionIncompatible = (
33+
version: string,
34+
update: 'CLI' | 'extension'
35+
): void => {
36+
Toast.warnWithOptions(
37+
`The extension cannot initialize because you are using version ${version} of the DVC CLI. The expected version is ${MIN_CLI_VERSION} <= DVC < ${MAX_CLI_VERSION}. Please upgrade to the most recent version of the ${update} and reload this window.`
38+
)
39+
}
40+
41+
export const warnAheadOfLatestTested = (): void => {
42+
Toast.warnWithOptions(
43+
`The located DVC CLI is at least a minor version ahead of the latest version the extension was tested with (${LATEST_TESTED_CLI_VERSION}). This could lead to unexpected behaviour. Please upgrade to the most recent version of the extension and reload this window.`
44+
)
45+
}
46+
47+
const warnUserCLIInaccessible = async (
48+
extension: IExtension,
49+
isMsPythonInstalled: boolean,
50+
warningText: string
51+
): Promise<void> => {
52+
if (getConfigValue<boolean>(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE)) {
53+
return
54+
}
55+
56+
const response = await Toast.warnWithOptions(
57+
warningText,
58+
...getToastOptions(isMsPythonInstalled)
59+
)
60+
61+
switch (response) {
62+
case Response.SELECT_INTERPRETER:
63+
return selectPythonInterpreter()
64+
case Response.SETUP_WORKSPACE:
65+
return extension.setupWorkspace()
66+
case Response.NEVER:
67+
return setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
68+
}
69+
}
70+
71+
const warnUserCLIInaccessibleAnywhere = async (
72+
extension: IExtension,
73+
globalDvcVersion: string | undefined
74+
): Promise<void> => {
75+
const binPath = await getPythonBinPath()
76+
77+
return warnUserCLIInaccessible(
78+
extension,
79+
true,
80+
`The extension is unable to initialize. The CLI was not located using the interpreter provided by the Python extension. ${
81+
globalDvcVersion ? globalDvcVersion + ' is' : 'The CLI is also not'
82+
} installed globally. For auto Python environment activation, ensure the correct interpreter is set. Active Python interpreter: ${binPath}.`
83+
)
84+
}
85+
86+
const warnUser = (
87+
extension: IExtension,
88+
cliCompatible: CliCompatible,
89+
version: string | undefined
90+
): void => {
91+
if (!extension.hasRoots()) {
92+
return
93+
}
94+
switch (cliCompatible) {
95+
case CliCompatible.NO_BEHIND_MIN_VERSION:
96+
return warnVersionIncompatible(version as string, 'CLI')
97+
case CliCompatible.NO_CANNOT_VERIFY:
98+
warnUnableToVerifyVersion()
99+
return
100+
case CliCompatible.NO_MAJOR_VERSION_AHEAD:
101+
return warnVersionIncompatible(version as string, 'extension')
102+
case CliCompatible.NO_NOT_FOUND:
103+
warnUserCLIInaccessible(
104+
extension,
105+
isPythonExtensionInstalled(),
106+
'An error was thrown when trying to access the CLI.'
107+
)
108+
return
109+
case CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED:
110+
return warnAheadOfLatestTested()
111+
}
112+
}
113+
114+
type CanRunCli = {
115+
isAvailable: boolean
116+
isCompatible: boolean | undefined
117+
}
118+
119+
const isCliCompatible = (cliCompatible: CliCompatible): boolean | undefined => {
120+
if (cliCompatible === CliCompatible.NO_NOT_FOUND) {
121+
return
122+
}
123+
124+
return [
125+
CliCompatible.YES,
126+
CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED
127+
].includes(cliCompatible)
128+
}
129+
130+
const getVersionDetails = async (
131+
extension: IExtension,
132+
cwd: string,
133+
tryGlobalCli?: true
134+
): Promise<
135+
CanRunCli & {
136+
cliCompatible: CliCompatible
137+
version: string | undefined
138+
}
139+
> => {
140+
const version = await extension.getCliVersion(cwd, tryGlobalCli)
141+
const cliCompatible = isVersionCompatible(version)
142+
const isCompatible = isCliCompatible(cliCompatible)
143+
return { cliCompatible, isAvailable: !!isCompatible, isCompatible, version }
144+
}
145+
146+
const processVersionDetails = (
147+
extension: IExtension,
148+
cliCompatible: CliCompatible,
149+
version: string | undefined,
150+
isAvailable: boolean,
151+
isCompatible: boolean | undefined
152+
): CanRunCli => {
153+
warnUser(extension, cliCompatible, version)
154+
return {
155+
isAvailable,
156+
isCompatible
157+
}
158+
}
159+
160+
const tryGlobalFallbackVersion = async (
161+
extension: IExtension,
162+
cwd: string
163+
): Promise<CanRunCli> => {
164+
const tryGlobal = await getVersionDetails(extension, cwd, true)
165+
const { cliCompatible, isAvailable, isCompatible, version } = tryGlobal
166+
167+
if (extension.hasRoots() && !isCompatible) {
168+
warnUserCLIInaccessibleAnywhere(extension, version)
169+
}
170+
if (
171+
extension.hasRoots() &&
172+
cliCompatible === CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED
173+
) {
174+
warnAheadOfLatestTested()
175+
}
176+
177+
if (isCompatible) {
178+
extension.unsetPythonBinPath()
179+
}
180+
181+
return { isAvailable, isCompatible }
182+
}
183+
184+
const extensionCanAutoRunCli = async (
185+
extension: IExtension,
186+
cwd: string
187+
): Promise<CanRunCli> => {
188+
const {
189+
cliCompatible: pythonCliCompatible,
190+
isAvailable: pythonVersionIsAvailable,
191+
isCompatible: pythonVersionIsCompatible,
192+
version: pythonVersion
193+
} = await getVersionDetails(extension, cwd)
194+
195+
if (pythonCliCompatible === CliCompatible.NO_NOT_FOUND) {
196+
return tryGlobalFallbackVersion(extension, cwd)
197+
}
198+
return processVersionDetails(
199+
extension,
200+
pythonCliCompatible,
201+
pythonVersion,
202+
pythonVersionIsAvailable,
203+
pythonVersionIsCompatible
204+
)
205+
}
206+
207+
export const extensionCanRunCli = async (
208+
extension: IExtension,
209+
cwd: string
210+
): Promise<CanRunCli> => {
211+
if (await extension.isPythonExtensionUsed()) {
212+
return extensionCanAutoRunCli(extension, cwd)
213+
}
214+
215+
const { cliCompatible, isAvailable, isCompatible, version } =
216+
await getVersionDetails(extension, cwd)
217+
218+
return processVersionDetails(
219+
extension,
220+
cliCompatible,
221+
version,
222+
isAvailable,
223+
isCompatible
224+
)
225+
}

extension/src/cli/dvc/version.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
LATEST_TESTED_CLI_VERSION,
44
MIN_CLI_VERSION
55
} from './constants'
6-
import { Toast } from '../../vscode/toast'
76

87
export enum CliCompatible {
98
NO_BEHIND_MIN_VERSION = 'no-behind-min-version',
@@ -14,18 +13,6 @@ export enum CliCompatible {
1413
YES = 'yes'
1514
}
1615

17-
export const isCliCompatible = (
18-
cliCompatible: CliCompatible
19-
): boolean | undefined => {
20-
if (cliCompatible === CliCompatible.NO_NOT_FOUND) {
21-
return
22-
}
23-
24-
return [
25-
CliCompatible.YES,
26-
CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED
27-
].includes(cliCompatible)
28-
}
2916
export type ParsedSemver = { major: number; minor: number; patch: number }
3017

3118
export const extractSemver = (stdout: string): ParsedSemver | undefined => {
@@ -37,26 +24,6 @@ export const extractSemver = (stdout: string): ParsedSemver | undefined => {
3724
return { major: Number(major), minor: Number(minor), patch: Number(patch) }
3825
}
3926

40-
export const warnUnableToVerifyVersion = () =>
41-
Toast.warnWithOptions(
42-
'The extension cannot initialize as we were unable to verify the DVC CLI version.'
43-
)
44-
45-
export const warnVersionIncompatible = (
46-
version: string,
47-
update: 'CLI' | 'extension'
48-
): void => {
49-
Toast.warnWithOptions(
50-
`The extension cannot initialize because you are using version ${version} of the DVC CLI. The expected version is ${MIN_CLI_VERSION} <= DVC < ${MAX_CLI_VERSION}. Please upgrade to the most recent version of the ${update} and reload this window.`
51-
)
52-
}
53-
54-
export const warnAheadOfLatestTested = (): void => {
55-
Toast.warnWithOptions(
56-
`The located DVC CLI is at least a minor version ahead of the latest version the extension was tested with (${LATEST_TESTED_CLI_VERSION}). This could lead to unexpected behaviour. Please upgrade to the most recent version of the extension and reload this window.`
57-
)
58-
}
59-
6027
const cliIsCompatible = (
6128
currentMajor: number,
6229
currentMinor: number

0 commit comments

Comments
 (0)