|
| 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 | +} |
0 commit comments