From c03cd447d4a26d9ebfcf8093c06923a4474f7eb2 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Fri, 15 Mar 2024 15:41:22 -0500 Subject: [PATCH] Add support for Hatch environments (microsoft/vscode-python#22779) Fixes microsoft/vscode-python#22810 TODO - [x] check if it actually works already or if more things need to be registered - [x] add config val - [x] add tests --- .../pythonEnvironments/base/info/envKind.ts | 5 +- .../pythonEnvironments/base/info/index.ts | 2 + .../base/locators/lowLevel/hatchLocator.ts | 57 ++++++++++ .../common/environmentManagers/hatch.ts | 93 ++++++++++++++++ .../src/client/pythonEnvironments/index.ts | 2 + .../base/info/envKind.unit.test.ts | 1 + .../lowLevel/hatchLocator.unit.test.ts | 76 +++++++++++++ .../environmentManagers/hatch.unit.test.ts | 102 ++++++++++++++++++ .../cK2g6fIm/project1/Scripts/python.exe | 1 + .../python/cK2g6fIm/project1/bin/python | 0 .../python/cK2g6fIm/project1/pyvenv.cfg | 3 + .../python/q4In3tK-/project2/bin/python | 0 .../python/q4In3tK-/project2/pyvenv.cfg | 3 + .../virtual/python/q4In3tK-/test/bin/python | 0 .../virtual/python/q4In3tK-/test/pyvenv.cfg | 3 + .../common/envlayouts/hatch/project1/.gitkeep | 0 .../envlayouts/hatch/project2/hatch.toml | 6 ++ 17 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts create mode 100644 extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts create mode 100644 extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/bin/python create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/bin/python create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/bin/python create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project1/.gitkeep create mode 100644 extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts b/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts index ff53a57d2f4..77ed7f22533 100644 --- a/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts +++ b/extensions/positron-python/src/client/pythonEnvironments/base/info/envKind.ts @@ -15,6 +15,7 @@ export function getKindDisplayName(kind: PythonEnvKind): string { [PythonEnvKind.MicrosoftStore, 'Microsoft Store'], [PythonEnvKind.Pyenv, 'pyenv'], [PythonEnvKind.Poetry, 'Poetry'], + [PythonEnvKind.Hatch, 'Hatch'], [PythonEnvKind.Custom, 'custom'], // For now we treat OtherGlobal like Unknown. [PythonEnvKind.Venv, 'venv'], @@ -39,12 +40,13 @@ export function getKindDisplayName(kind: PythonEnvKind): string { * Remarks: This is the order of detection based on how the various distributions and tools * configure the environment, and the fall back for identification. * Top level we have the following environment types, since they leave a unique signature - * in the environment or * use a unique path for the environments they create. + * in the environment or use a unique path for the environments they create. * 1. Conda * 2. Microsoft Store * 3. PipEnv * 4. Pyenv * 5. Poetry + * 6. Hatch * * Next level we have the following virtual environment tools. The are here because they * are consumed by the tools above, and can also be used independently. @@ -61,6 +63,7 @@ export function getPrioritizedEnvKinds(): PythonEnvKind[] { PythonEnvKind.MicrosoftStore, PythonEnvKind.Pipenv, PythonEnvKind.Poetry, + PythonEnvKind.Hatch, PythonEnvKind.Venv, PythonEnvKind.VirtualEnvWrapper, PythonEnvKind.VirtualEnv, diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts b/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts index e55031fe807..b6e9567b4a4 100644 --- a/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts +++ b/extensions/positron-python/src/client/pythonEnvironments/base/info/index.ts @@ -15,6 +15,7 @@ export enum PythonEnvKind { MicrosoftStore = 'global-microsoft-store', Pyenv = 'global-pyenv', Poetry = 'poetry', + Hatch = 'hatch', ActiveState = 'activestate', Custom = 'global-custom', OtherGlobal = 'global-other', @@ -44,6 +45,7 @@ export interface EnvPathType { export const virtualEnvKinds = [ PythonEnvKind.Poetry, + PythonEnvKind.Hatch, PythonEnvKind.Pipenv, PythonEnvKind.Venv, PythonEnvKind.VirtualEnvWrapper, diff --git a/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts new file mode 100644 index 00000000000..f7746a8c5a2 --- /dev/null +++ b/extensions/positron-python/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts @@ -0,0 +1,57 @@ +'use strict'; + +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; +import { Hatch } from '../../../common/environmentManagers/hatch'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { pathExists } from '../../../common/externalDependencies'; +import { traceError, traceVerbose } from '../../../../logging'; +import { chain, iterable } from '../../../../common/utils/async'; +import { getInterpreterPathFromDir } from '../../../common/commonUtils'; + +/** + * Gets all default virtual environment locations to look for in a workspace. + */ +async function getVirtualEnvDirs(root: string): Promise { + const hatch = await Hatch.getHatch(root); + const envDirs = (await hatch?.getEnvList()) ?? []; + return asyncFilter(envDirs, pathExists); +} + +/** + * Finds and resolves virtual environments created using Hatch. + */ +export class HatchLocator extends LazyResourceBasedLocator { + public readonly providerId: string = 'hatch'; + + public constructor(private readonly root: string) { + super(); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envDirs = await getVirtualEnvDirs(root); + const envGenerators = envDirs.map((envDir) => { + async function* generator() { + traceVerbose(`Searching for Hatch virtual envs in: ${envDir}`); + const filename = await getInterpreterPathFromDir(envDir); + if (filename !== undefined) { + try { + yield { executablePath: filename, kind: PythonEnvKind.Hatch }; + traceVerbose(`Hatch Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for Hatch envs`); + } + + return iterator(this.root); + } +} diff --git a/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts new file mode 100644 index 00000000000..a22ce38bbff --- /dev/null +++ b/extensions/positron-python/src/client/pythonEnvironments/common/environmentManagers/hatch.ts @@ -0,0 +1,93 @@ +import { isTestExecution } from '../../../common/constants'; +import { exec, pathExists } from '../externalDependencies'; +import { traceVerbose } from '../../../logging'; +import { cache } from '../../../common/utils/decorators'; + +/** Wraps the "Hatch" utility, and exposes its functionality. + */ +export class Hatch { + /** + * Locating Hatch binary can be expensive, since it potentially involves spawning or + * trying to spawn processes; so we only do it once per session. + */ + private static hatchPromise: Map> = new Map< + string, + Promise + >(); + + /** + * Creates a Hatch service corresponding to the corresponding "hatch" command. + * + * @param command - Command used to run hatch. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + * @param cwd - The working directory to use as cwd when running hatch. + */ + constructor(public readonly command: string, private cwd: string) {} + + /** + * Returns a Hatch instance corresponding to the binary which can be used to run commands for the cwd. + * + * Every directory is a valid Hatch project, so this should always return a Hatch instance. + */ + public static async getHatch(cwd: string): Promise { + if (Hatch.hatchPromise.get(cwd) === undefined || isTestExecution()) { + Hatch.hatchPromise.set(cwd, Hatch.locate(cwd)); + } + return Hatch.hatchPromise.get(cwd); + } + + private static async locate(cwd: string): Promise { + // First thing this method awaits on should be hatch command execution, + // hence perform all operations before that synchronously. + const hatchPath = 'hatch'; + traceVerbose(`Probing Hatch binary ${hatchPath}`); + const hatch = new Hatch(hatchPath, cwd); + const virtualenvs = await hatch.getEnvList(); + if (virtualenvs !== undefined) { + traceVerbose(`Found hatch binary ${hatchPath}`); + return hatch; + } + traceVerbose(`Failed to find Hatch binary ${hatchPath}`); + + // Didn't find anything. + traceVerbose(`No Hatch binary found`); + return undefined; + } + + /** + * Retrieves list of Python environments known to Hatch for this working directory. + * Returns `undefined` if we failed to spawn in some way. + * + * Corresponds to "hatch env show --json". Swallows errors if any. + */ + public async getEnvList(): Promise { + return this.getEnvListCached(this.cwd); + } + + /** + * Method created to facilitate caching. The caching decorator uses function arguments as cache key, + * so pass in cwd on which we need to cache. + */ + @cache(30_000, true, 10_000) + private async getEnvListCached(_cwd: string): Promise { + const envInfoOutput = await exec(this.command, ['env', 'show', '--json'], { + cwd: this.cwd, + throwOnStdErr: true, + }).catch(traceVerbose); + if (!envInfoOutput) { + return undefined; + } + const envPaths = await Promise.all( + Object.keys(JSON.parse(envInfoOutput.stdout)).map(async (name) => { + const envPathOutput = await exec(this.command, ['env', 'find', name], { + cwd: this.cwd, + throwOnStdErr: true, + }).catch(traceVerbose); + if (!envPathOutput) return undefined; + const dir = envPathOutput.stdout.trim(); + return (await pathExists(dir)) ? dir : undefined; + }), + ); + return envPaths.flatMap((r) => (r ? [r] : [])); + } +} diff --git a/extensions/positron-python/src/client/pythonEnvironments/index.ts b/extensions/positron-python/src/client/pythonEnvironments/index.ts index b690a0ddf0f..d3f6166295d 100644 --- a/extensions/positron-python/src/client/pythonEnvironments/index.ts +++ b/extensions/positron-python/src/client/pythonEnvironments/index.ts @@ -28,6 +28,7 @@ import { MicrosoftStoreLocator } from './base/locators/lowLevel/microsoftStoreLo import { getEnvironmentInfoService } from './base/info/environmentInfoService'; import { registerNewDiscoveryForIOC } from './legacyIOC'; import { PoetryLocator } from './base/locators/lowLevel/poetryLocator'; +import { HatchLocator } from './base/locators/lowLevel/hatchLocator'; import { createPythonEnvironments } from './api'; import { createCollectionCache as createCache, @@ -188,6 +189,7 @@ function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { (root: vscode.Uri) => [ new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath), + new HatchLocator(root.fsPath), new CustomWorkspaceLocator(root.fsPath), ], // Add an ILocator factory func here for each kind of workspace-rooted locator. diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts index fdf174b4c55..997c8a08c7f 100644 --- a/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts +++ b/extensions/positron-python/src/test/pythonEnvironments/base/info/envKind.unit.test.ts @@ -13,6 +13,7 @@ const KIND_NAMES: [PythonEnvKind, string][] = [ [PythonEnvKind.MicrosoftStore, 'winStore'], [PythonEnvKind.Pyenv, 'pyenv'], [PythonEnvKind.Poetry, 'poetry'], + [PythonEnvKind.Hatch, 'hatch'], [PythonEnvKind.Custom, 'customGlobal'], [PythonEnvKind.OtherGlobal, 'otherGlobal'], [PythonEnvKind.Venv, 'venv'], diff --git a/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts new file mode 100644 index 00000000000..9a2a69908f2 --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/base/locators/lowLevel/hatchLocator.unit.test.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as sinon from 'sinon'; +import * as path from 'path'; +import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import * as platformUtils from '../../../../../client/common/utils/platform'; +import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; +import { HatchLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/hatchLocator'; +import { assertBasicEnvsEqual } from '../envTestUtils'; +import { createBasicEnv } from '../../common'; +import { makeExecHandler, projectDirs, venvDirs } from '../../../common/environmentManagers/hatch.unit.test'; + +suite('Hatch Locator', () => { + let exec: sinon.SinonStub; + let getPythonSetting: sinon.SinonStub; + let getOSType: sinon.SinonStub; + let locator: HatchLocator; + + suiteSetup(() => { + getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting'); + getPythonSetting.returns('hatch'); + getOSType = sinon.stub(platformUtils, 'getOSType'); + exec = sinon.stub(externalDependencies, 'exec'); + }); + + suiteTeardown(() => sinon.restore()); + + suite('iterEnvs()', () => { + setup(() => { + getOSType.returns(platformUtils.OSType.Linux); + }); + + interface TestArgs { + osType?: platformUtils.OSType; + pythonBin?: string; + } + + const testProj1 = async ({ osType, pythonBin = 'bin/python' }: TestArgs = {}) => { + if (osType) { + getOSType.returns(osType); + } + + locator = new HatchLocator(projectDirs.project1); + exec.callsFake(makeExecHandler(venvDirs.project1, { path: true, cwd: projectDirs.project1 })); + + const iterator = locator.iterEnvs(); + const actualEnvs = await getEnvs(iterator); + + const expectedEnvs = [createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project1.default, pythonBin))]; + assertBasicEnvsEqual(actualEnvs, expectedEnvs); + }; + + test('project with only the default env', () => testProj1()); + test('project with only the default env on Windows', () => + testProj1({ + osType: platformUtils.OSType.Windows, + pythonBin: 'Scripts/python.exe', + })); + + test('project with multiple defined envs', async () => { + locator = new HatchLocator(projectDirs.project2); + exec.callsFake(makeExecHandler(venvDirs.project2, { path: true, cwd: projectDirs.project2 })); + + const iterator = locator.iterEnvs(); + const actualEnvs = await getEnvs(iterator); + + const expectedEnvs = [ + createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project2.default, 'bin/python')), + createBasicEnv(PythonEnvKind.Hatch, path.join(venvDirs.project2.test, 'bin/python')), + ]; + assertBasicEnvsEqual(actualEnvs, expectedEnvs); + }); + }); +}); diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts b/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts new file mode 100644 index 00000000000..5d348aa2b13 --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/environmentManagers/hatch.unit.test.ts @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { expect } from 'chai'; +import * as path from 'path'; +import * as sinon from 'sinon'; +import { ExecutionResult, ShellOptions } from '../../../../client/common/process/types'; +import * as externalDependencies from '../../../../client/pythonEnvironments/common/externalDependencies'; +import { Hatch } from '../../../../client/pythonEnvironments/common/environmentManagers/hatch'; +import { TEST_LAYOUT_ROOT } from '../commonTestConstants'; + +export type HatchCommand = { cmd: 'env show --json' } | { cmd: 'env find'; env: string } | { cmd: null }; + +export function hatchCommand(args: string[]): HatchCommand { + if (args.length < 2) { + return { cmd: null }; + } + if (args[0] === 'env' && args[1] === 'show' && args[2] === '--json') { + return { cmd: 'env show --json' }; + } + if (args[0] === 'env' && args[1] === 'find') { + return { cmd: 'env find', env: args[2] }; + } + return { cmd: null }; +} + +interface VerifyOptions { + path?: boolean; + cwd?: string; +} + +export function makeExecHandler(venvDirs: Record, verify: VerifyOptions = {}) { + return async (file: string, args: string[], options: ShellOptions): Promise> => { + if (verify.path && file !== 'hatch') { + throw new Error('Command failed'); + } + if (verify.cwd) { + const cwd = typeof options.cwd === 'string' ? options.cwd : options.cwd?.toString(); + if (!cwd || !externalDependencies.arePathsSame(cwd, verify.cwd)) { + throw new Error('Command failed'); + } + } + const cmd = hatchCommand(args); + if (cmd.cmd === 'env show --json') { + const envs = Object.fromEntries(Object.keys(venvDirs).map((name) => [name, { type: 'virtual' }])); + return { stdout: JSON.stringify(envs) }; + } + if (cmd.cmd === 'env find' && cmd.env in venvDirs) { + return { stdout: venvDirs[cmd.env] }; + } + throw new Error('Command failed'); + }; +} + +const testHatchDir = path.join(TEST_LAYOUT_ROOT, 'hatch'); +// This is usually in /hatch, e.g. `~/.local/share/hatch` +const hatchEnvsDir = path.join(testHatchDir, 'env/virtual/python'); +export const projectDirs = { + project1: path.join(testHatchDir, 'project1'), + project2: path.join(testHatchDir, 'project2'), +}; +export const venvDirs = { + project1: { default: path.join(hatchEnvsDir, 'cK2g6fIm/project1') }, + project2: { + default: path.join(hatchEnvsDir, 'q4In3tK-/project2'), + test: path.join(hatchEnvsDir, 'q4In3tK-/test'), + }, +}; + +suite('Hatch binary is located correctly', async () => { + let exec: sinon.SinonStub; + let getPythonSetting: sinon.SinonStub; + + setup(() => { + getPythonSetting = sinon.stub(externalDependencies, 'getPythonSetting'); + exec = sinon.stub(externalDependencies, 'exec'); + }); + + teardown(() => { + sinon.restore(); + }); + + const testPath = async (verify = true) => { + // If `verify` is false, don’t verify that the command has been called with that path + exec.callsFake( + makeExecHandler(venvDirs.project1, verify ? { path: true, cwd: projectDirs.project1 } : undefined), + ); + const hatch = await Hatch.getHatch(projectDirs.project1); + expect(hatch?.command).to.equal('hatch'); + }; + + test('Use Hatch on PATH if available', () => testPath()); + + test('Return undefined if Hatch cannot be found', async () => { + getPythonSetting.returns('hatch'); + exec.callsFake((_file: string, _args: string[], _options: ShellOptions) => + Promise.reject(new Error('Command failed')), + ); + const hatch = await Hatch.getHatch(projectDirs.project1); + expect(hatch?.command).to.equal(undefined); + }); +}); diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe new file mode 100644 index 00000000000..a37b666d049 --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/Scripts/python.exe @@ -0,0 +1 @@ +Not real python exe diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/bin/python new file mode 100644 index 00000000000..e69de29bb2d diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg new file mode 100644 index 00000000000..365d6f5eace --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/cK2g6fIm/project1/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin/python3.11 +include-system-site-packages = false +version = 3.11.1 diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/bin/python new file mode 100644 index 00000000000..e69de29bb2d diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg new file mode 100644 index 00000000000..a67a28be91b --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/project2/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin/python3.10 +include-system-site-packages = false +version = 3.10.3 diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/bin/python b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/bin/python new file mode 100644 index 00000000000..e69de29bb2d diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg new file mode 100644 index 00000000000..a67a28be91b --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/env/virtual/python/q4In3tK-/test/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin/python3.10 +include-system-site-packages = false +version = 3.10.3 diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project1/.gitkeep b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project1/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml new file mode 100644 index 00000000000..9848374b54f --- /dev/null +++ b/extensions/positron-python/src/test/pythonEnvironments/common/envlayouts/hatch/project2/hatch.toml @@ -0,0 +1,6 @@ +# this file is not actually used in tests, as all is mocked out + +# The default environment always exists +#[envs.default] + +[envs.test]