/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts

https://github.com/DonJayamanne/pythonVSCode · TypeScript · 150 lines · 95 code · 19 blank · 36 comment · 29 complexity · 571c4a0a6a0ce189298a5dba165dc9ec MD5 · raw file

  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. 'use strict';
  4. import '../../extensions';
  5. import { inject, injectable } from 'inversify';
  6. import * as path from 'path';
  7. import { Uri } from 'vscode';
  8. import { ICondaService } from '../../../interpreter/contracts';
  9. import { IPlatformService } from '../../platform/types';
  10. import { IConfigurationService } from '../../types';
  11. import { ITerminalActivationCommandProvider, TerminalShellType } from '../types';
  12. // Version number of conda that requires we call activate with 'conda activate' instead of just 'activate'
  13. const CondaRequiredMajor = 4;
  14. const CondaRequiredMinor = 4;
  15. const CondaRequiredMinorForPowerShell = 6;
  16. /**
  17. * Support conda env activation (in the terminal).
  18. */
  19. @injectable()
  20. export class CondaActivationCommandProvider implements ITerminalActivationCommandProvider {
  21. constructor(
  22. @inject(ICondaService) private readonly condaService: ICondaService,
  23. @inject(IPlatformService) private platform: IPlatformService,
  24. @inject(IConfigurationService) private configService: IConfigurationService
  25. ) {}
  26. /**
  27. * Is the given shell supported for activating a conda env?
  28. */
  29. public isShellSupported(_targetShell: TerminalShellType): boolean {
  30. return true;
  31. }
  32. /**
  33. * Return the command needed to activate the conda env.
  34. */
  35. public getActivationCommands(
  36. resource: Uri | undefined,
  37. targetShell: TerminalShellType
  38. ): Promise<string[] | undefined> {
  39. const pythonPath = this.configService.getSettings(resource).pythonPath;
  40. return this.getActivationCommandsForInterpreter(pythonPath, targetShell);
  41. }
  42. /**
  43. * Return the command needed to activate the conda env.
  44. *
  45. */
  46. public async getActivationCommandsForInterpreter(
  47. pythonPath: string,
  48. targetShell: TerminalShellType
  49. ): Promise<string[] | undefined> {
  50. const envInfo = await this.condaService.getCondaEnvironment(pythonPath);
  51. if (!envInfo) {
  52. return;
  53. }
  54. const condaEnv = envInfo.name.length > 0 ? envInfo.name : envInfo.path;
  55. // Algorithm differs based on version
  56. // Old version, just call activate directly.
  57. // New version, call activate from the same path as our python path, then call it again to activate our environment.
  58. // -- note that the 'default' conda location won't allow activate to work for the environment sometimes.
  59. const versionInfo = await this.condaService.getCondaVersion();
  60. if (versionInfo && versionInfo.major >= CondaRequiredMajor) {
  61. // Conda added support for powershell in 4.6.
  62. if (
  63. versionInfo.minor >= CondaRequiredMinorForPowerShell &&
  64. (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore)
  65. ) {
  66. return this.getPowershellCommands(condaEnv);
  67. }
  68. if (versionInfo.minor >= CondaRequiredMinor) {
  69. // New version.
  70. const interpreterPath = await this.condaService.getCondaFileFromInterpreter(pythonPath, envInfo.name);
  71. if (interpreterPath) {
  72. const activatePath = path.join(path.dirname(interpreterPath), 'activate').fileToCommandArgument();
  73. const firstActivate = this.platform.isWindows ? activatePath : `source ${activatePath}`;
  74. return [firstActivate, `conda activate ${condaEnv.toCommandArgument()}`];
  75. }
  76. }
  77. }
  78. switch (targetShell) {
  79. case TerminalShellType.powershell:
  80. case TerminalShellType.powershellCore:
  81. return this.getPowershellCommands(condaEnv);
  82. // tslint:disable-next-line:no-suspicious-comment
  83. // TODO: Do we really special-case fish on Windows?
  84. case TerminalShellType.fish:
  85. return this.getFishCommands(condaEnv, await this.condaService.getCondaFile());
  86. default:
  87. if (this.platform.isWindows) {
  88. return this.getWindowsCommands(condaEnv);
  89. } else {
  90. return this.getUnixCommands(condaEnv, await this.condaService.getCondaFile());
  91. }
  92. }
  93. }
  94. public async getWindowsActivateCommand(): Promise<string> {
  95. let activateCmd: string = 'activate';
  96. const condaExePath = await this.condaService.getCondaFile();
  97. if (condaExePath && path.basename(condaExePath) !== condaExePath) {
  98. const condaScriptsPath: string = path.dirname(condaExePath);
  99. // prefix the cmd with the found path, and ensure it's quoted properly
  100. activateCmd = path.join(condaScriptsPath, activateCmd);
  101. activateCmd = activateCmd.toCommandArgument();
  102. }
  103. return activateCmd;
  104. }
  105. public async getWindowsCommands(condaEnv: string): Promise<string[] | undefined> {
  106. const activate = await this.getWindowsActivateCommand();
  107. return [`${activate} ${condaEnv.toCommandArgument()}`];
  108. }
  109. /**
  110. * The expectation is for the user to configure Powershell for Conda.
  111. * Hence we just send the command `conda activate ...`.
  112. * This configuration is documented on Conda.
  113. * Extension will not attempt to work around issues by trying to setup shell for user.
  114. *
  115. * @param {string} condaEnv
  116. * @returns {(Promise<string[] | undefined>)}
  117. * @memberof CondaActivationCommandProvider
  118. */
  119. public async getPowershellCommands(condaEnv: string): Promise<string[] | undefined> {
  120. return [`conda activate ${condaEnv.toCommandArgument()}`];
  121. }
  122. public async getFishCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
  123. // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28
  124. return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`];
  125. }
  126. public async getUnixCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
  127. const condaDir = path.dirname(condaFile);
  128. const activateFile = path.join(condaDir, 'activate');
  129. return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`];
  130. }
  131. }