@@ -21,7 +21,6 @@ import {
2121} from '../../api' ;
2222import { computeCacheKey } from '../../common/inlineScriptCacheKey' ;
2323import {
24- InlineScriptEnvMeta ,
2524 META_SCHEMA_VERSION ,
2625 getBaseInterpreterStatus ,
2726 getScriptEnvCacheRoot ,
@@ -37,8 +36,9 @@ import {
3736 matchesPythonVersion ,
3837 readInlineScriptMetadataFromFile ,
3938} from '../../common/inlineScriptMetadata' ;
40- import { CONDA_MANAGER_ID , PYTHON_EXTENSION_ID , SYSTEM_MANAGER_ID } from '../../common/constants' ;
39+ import { CONDA_MANAGER_ID , PYENV_MANAGER_ID , SYSTEM_MANAGER_ID } from '../../common/constants' ;
4140import { acquireFileLock , AcquiredFileLock } from '../../common/lockfile.apis' ;
41+ import { isFileNotFoundError } from '../../common/utils/filesystem' ;
4242import { normalizePath } from '../../common/utils/pathUtils' ;
4343import { compareReleaseSegments , parseReleaseSegments } from '../../common/utils/pep440Release' ;
4444import { getVenvPythonPath } from '../../common/utils/virtualEnvironment' ;
@@ -48,12 +48,33 @@ import { createWithProgress, resolveVenvPythonEnvironmentPath } from './venvUtil
4848const BASE_INTERPRETER_MANAGER_IDS = new Set ( [
4949 SYSTEM_MANAGER_ID ,
5050 CONDA_MANAGER_ID ,
51- ` ${ PYTHON_EXTENSION_ID } :pyenv` ,
51+ PYENV_MANAGER_ID ,
5252] ) ;
5353
5454const CACHE_LOCK_TIMEOUT_MS = 5 * 60 * 1000 ;
5555const CACHE_LOCK_RETRY_MS = 500 ;
5656
57+ interface SelectedBaseInterpreter {
58+ readonly environment : PythonEnvironment ;
59+ readonly canonicalPath : string ;
60+ }
61+
62+ interface CreateOrReuseEnvironmentOptions {
63+ readonly cacheKey : string ;
64+ readonly packages : ReadonlyArray < string > ;
65+ readonly metadata : InlineScriptMetadata ;
66+ readonly selectedBase : SelectedBaseInterpreter ;
67+ }
68+
69+ interface BuildCacheEntryResult {
70+ readonly environment ?: PythonEnvironment ;
71+ readonly retainLock ?: boolean ;
72+ }
73+
74+ type CacheEntryInspection =
75+ | { readonly kind : 'absent' | 'stale' | 'uncertain' }
76+ | { readonly kind : 'reusable' ; readonly environment : PythonEnvironment } ;
77+
5778/** Manages extension-owned PEP 723 script environments. */
5879export class InlineScriptEnvManager implements EnvironmentManager , Disposable {
5980 private readonly pendingCreations = new Map < string , Promise < PythonEnvironment | undefined > > ( ) ;
@@ -100,9 +121,10 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
100121 return undefined ;
101122 }
102123
103- const packages = [ ...( metadata . dependencies ?? [ ] ) , ...( options ?. additionalPackages ?? [ ] ) ] . map ( ( value ) =>
104- value . trim ( ) ,
105- ) ;
124+ const packages = [
125+ ...( metadata . dependencies ?? [ ] ) ,
126+ ...( options ?. additionalPackages ?? [ ] ) ,
127+ ] . map ( ( value ) => value . trim ( ) ) ;
106128 if ( packages . some ( ( value ) => value . length === 0 ) ) {
107129 this . log . warn ( `Inline-script dependencies must not contain empty entries: ${ scriptUri . fsPath } .` ) ;
108130 return undefined ;
@@ -113,6 +135,7 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
113135 this . log . warn ( `No installed Python satisfies the inline-script requirements for ${ scriptUri . fsPath } .` ) ;
114136 return undefined ;
115137 }
138+
116139 const cacheKey = computeCacheKey ( {
117140 dependencies : packages ,
118141 interpreterPath : selectedBase . canonicalPath ,
@@ -122,7 +145,12 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
122145 return await pending ;
123146 }
124147
125- const creation = this . createOrReuseEnvironment ( cacheKey , packages , metadata , selectedBase ) ;
148+ const creation = this . createOrReuseEnvironment ( {
149+ cacheKey,
150+ packages,
151+ metadata,
152+ selectedBase,
153+ } ) ;
126154 this . pendingCreations . set ( cacheKey , creation ) ;
127155 try {
128156 return await creation ;
@@ -163,7 +191,8 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
163191 }
164192
165193 private async selectBaseInterpreter ( metadata : InlineScriptMetadata ) : Promise < SelectedBaseInterpreter | undefined > {
166- const reported = ( await this . api . getEnvironments ( 'global' ) ) . filter (
194+ const globalEnvironments = await this . api . getEnvironments ( 'global' ) ;
195+ const reported = globalEnvironments . filter (
167196 ( environment ) =>
168197 BASE_INTERPRETER_MANAGER_IDS . has ( environment . envId . managerId ) &&
169198 ( environment . envId . managerId !== CONDA_MANAGER_ID || environment . name === 'base' ) ,
@@ -209,12 +238,12 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
209238 return undefined ;
210239 }
211240
212- private async createOrReuseEnvironment (
213- cacheKey : string ,
214- packages : ReadonlyArray < string > ,
215- metadata : InlineScriptMetadata ,
216- selectedBase : SelectedBaseInterpreter ,
217- ) : Promise < PythonEnvironment | undefined > {
241+ private async createOrReuseEnvironment ( {
242+ cacheKey,
243+ packages,
244+ metadata,
245+ selectedBase,
246+ } : CreateOrReuseEnvironmentOptions ) : Promise < PythonEnvironment | undefined > {
218247 const cacheRoot = getScriptEnvCacheRoot ( this . globalStorageUri ) ;
219248 const envDir = getScriptEnvDir ( this . globalStorageUri , cacheKey ) ;
220249 await fs . ensureDir ( cacheRoot . fsPath ) ;
@@ -279,7 +308,7 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
279308 return { kind : 'uncertain' } ;
280309 }
281310 } catch ( error ) {
282- return this . isFileNotFoundError ( error ) ? { kind : 'absent' } : { kind : 'uncertain' } ;
311+ return isFileNotFoundError ( error ) ? { kind : 'absent' } : { kind : 'uncertain' } ;
283312 }
284313
285314 let resolvedEntry : string | undefined ;
@@ -329,8 +358,7 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
329358 if ( environmentStatus !== 'expected' ) {
330359 return { kind : environmentStatus } ;
331360 }
332- const releaseComparison = this . comparePythonReleases ( environment . version , selectedBase . environment . version ) ;
333- if ( releaseComparison !== 'same' ) {
361+ if ( ! this . areEqualPythonReleases ( environment . version , selectedBase . environment . version ) ) {
334362 return { kind : 'stale' } ;
335363 }
336364 const requiresPython = metadata . requiresPython ?. trim ( ) ;
@@ -385,22 +413,21 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
385413 return { } ;
386414 }
387415 if (
388- this . comparePythonReleases ( result . environment . version , selectedBase . environment . version ) !== 'same' ||
416+ ! this . areEqualPythonReleases ( result . environment . version , selectedBase . environment . version ) ||
389417 ( await inspectOwnedCacheEntry ( result . environment , cacheRoot , envDir ) ) !== 'expected'
390418 ) {
391419 this . log . error ( 'Created inline-script environment does not match the requested cache entry.' ) ;
392420 await this . removeCacheEntry ( envDir ) ;
393421 return { } ;
394422 }
395423
396- const sidecar : InlineScriptEnvMeta = {
397- schemaVersion : META_SCHEMA_VERSION ,
398- baseInterpreterPath : selectedBase . canonicalPath ,
399- baseInterpreterVersion : selectedBase . environment . version ,
400- lastUsedAt : new Date ( ) . toISOString ( ) ,
401- } ;
402424 try {
403- await writeMetaJson ( envDir , sidecar ) ;
425+ await writeMetaJson ( envDir , {
426+ schemaVersion : META_SCHEMA_VERSION ,
427+ baseInterpreterPath : selectedBase . canonicalPath ,
428+ baseInterpreterVersion : selectedBase . environment . version ,
429+ lastUsedAt : new Date ( ) . toISOString ( ) ,
430+ } ) ;
404431 } catch ( error ) {
405432 this . log . error ( `Failed to record inline-script cache metadata: ${ this . errorMessage ( error ) } ` ) ;
406433 await this . removeCacheEntry ( envDir ) ;
@@ -420,22 +447,13 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
420447 }
421448 }
422449
423- private isFileNotFoundError ( error : unknown ) : boolean {
424- return (
425- typeof error === 'object' &&
426- error !== null &&
427- 'code' in error &&
428- ( error as NodeJS . ErrnoException ) . code === 'ENOENT'
429- ) ;
430- }
431-
432- private comparePythonReleases ( actual : string , expected : string ) : PythonReleaseComparison {
450+ private areEqualPythonReleases ( actual : string , expected : string ) : boolean {
433451 const actualRelease = parseReleaseSegments ( actual ) ;
434452 const expectedRelease = parseReleaseSegments ( expected ) ;
435453 if ( actualRelease === undefined || expectedRelease === undefined ) {
436- return 'uncertain' ;
454+ return false ;
437455 }
438- return compareReleaseSegments ( actualRelease , expectedRelease ) === 0 ? 'same' : 'different' ;
456+ return compareReleaseSegments ( actualRelease , expectedRelease ) === 0 ;
439457 }
440458
441459 private errorMessage ( error : unknown ) : string {
@@ -447,19 +465,3 @@ export class InlineScriptEnvManager implements EnvironmentManager, Disposable {
447465 this . _onDidChangeEnvironment . dispose ( ) ;
448466 }
449467}
450-
451- interface SelectedBaseInterpreter {
452- readonly environment : PythonEnvironment ;
453- readonly canonicalPath : string ;
454- }
455-
456- interface BuildCacheEntryResult {
457- readonly environment ?: PythonEnvironment ;
458- readonly retainLock ?: boolean ;
459- }
460-
461- type CacheEntryInspection =
462- | { readonly kind : 'absent' | 'stale' | 'uncertain' }
463- | { readonly kind : 'reusable' ; readonly environment : PythonEnvironment } ;
464-
465- type PythonReleaseComparison = 'same' | 'different' | 'uncertain' ;
0 commit comments