Skip to content

Add PEP 723 inline script environment creation (PEP 723 PR 5c/16)#1656

Draft
StellaHuang95 wants to merge 3 commits into
microsoft:mainfrom
StellaHuang95:pep723-pr5c-manager
Draft

Add PEP 723 inline script environment creation (PEP 723 PR 5c/16)#1656
StellaHuang95 wants to merge 3 commits into
microsoft:mainfrom
StellaHuang95:pep723-pr5c-manager

Conversation

@StellaHuang95

@StellaHuang95 StellaHuang95 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Part of #1602 (PEP 723 inline script env support). Design doc: #1601.

Split for review (3 PRs). Reviewers flagged the original PR 5 as too large, so it is split into three stacked PRs grouped by dependency layer:

Applied together the three PRs are byte-for-byte identical to the original single change. Merge order: 5a → 5b → 5c.

This PR is stacked on #1655 (which is stacked on #1651), so the diff below currently also includes the 5a and 5b commits. To review just this PR, open the Add PEP 723 inline script environment creation commit (0433271, 5 files). Once #1651 and #1655 merge to main, this PR reduces to its own changes with no rebase needed.

Roadmap context

This is the final slice of PR 5 of 16 — the actual create() happy path. See #1651 for the full roadmap table.

Phase 2: Manager PR Status
PR 4: InlineScriptEnvManager skeleton merged (#1610)
PR 5a: generic env-creation utilities #1651
PR 5b: inline-script cache + interpreter utilities #1655
PR 5c: create() happy path (manager + wiring) this PR (#1656)
PR 6: create() uv-install fallback not started (needs 3, 5)

Why this PR

InlineScriptEnvManager.create() was a deliberately empty no-op after PR 4. This PR implements its happy path: the case where the machine already has a base interpreter that satisfies the script's requires-python, so no uv Python install is required. Given a PEP 723 script, it builds — or reuses — a dependency-keyed virtual environment under the extension's global storage, following the pipx-style cache design from Q4 of #1601. The uv-install fallback (no compatible interpreter present) is deferred to PR 6.

It composes the primitives from 5a (#1651) and the inline-script utilities from 5b (#1655); this PR adds only the manager and its wiring.

What this PR does

Wires the manager's collaborators (extension.ts, inlineScriptMain.ts): registerInlineScriptFeatures and the InlineScriptEnvManager constructor now receive the NativePythonFinder, the PythonEnvironmentApi, the base (system) environment manager, and globalStorageUri.

Implements create(scope) (inlineScriptEnvManager.ts):

  • Accepts exactly one local file: URI (a bare Uri or single-element array). Anything else — 'global', a folder, or multiple URIs — logs a warning and returns undefined.
  • Reads PEP 723 metadata from the script; missing or invalid metadata returns undefined.
  • Merges metadata.dependencies with options.additionalPackages, trims each, and rejects empty entries.
  • Selects a base interpreter, computes the dependency + interpreter cache key, and de-duplicates concurrent in-process create() calls for the same key via a pendingCreations map.

Base-interpreter selection (selectBaseInterpreter): starts from getEnvironments('global'), keeps only true base managers (system, pyenv, conda base), and excludes derived environments by rejecting a non-absolute sysPrefix or the presence of pyvenv.cfg. It then picks the newest compatible interpreter with pickCompatibleInterpreter and resolves the executable through fs.realpath so the cache key is canonical. If a candidate cannot be resolved it falls through to the next.

Create-or-reuse under a cross-process lock (createOrReuseEnvironment): acquires a directory lock (5a), inspects the existing cache entry, and reuses / rebuilds / preserves accordingly, always releasing the lock in finally.

Fail-closed cache inspection (inspectCacheEntry) returns absent | stale | uncertain | reusable:

  • Rejects symlinks and non-directories; verifies the entry is contained under the cache root (resolveCacheEntryPath).
  • Reads and validates the .meta.json sidecar and confirms the recorded base-interpreter path and version still match the selected base.
  • Confirms the base interpreter is still present on disk (getBaseInterpreterStatus).
  • Resolves the cached venv to a real PythonEnvironment, confirms it is genuinely ours via realpath containment (inspectOwnedCacheEntry), compares Python release segments, and re-checks requires-python with the existing matchesPythonVersion.
  • Only conclusive evidence marks an entry stale (rebuild); any doubt yields uncertain, and an uncertain entry is preserved, never deleted. A reused entry has its lastUsedAt refreshed.

Environment build (buildCacheEntry): delegates to the existing createWithProgress venv flow with { trackUvEnvironment: false } (5a) so cached script environments are not registered as workspace venvs. On success it writes the sidecar and re-validates that the built environment matches the requested release and is owned by this entry. On failure it removes the directory and returns empty. On cancellation it retains the lock (5a) so a half-built environment is not silently reused later.

Tests

  • inlineScriptEnvManager.unit.test.ts — 40 tests across scope/metadata validation, base-interpreter selection, cache creation, cache reuse, transaction rollback, and events/disposal.
  • inlineScriptMain.unit.test.ts — updated for the new registerInlineScriptFeatures signature.

On this branch (5a + 5b + 5c, i.e. the full original change) npm run lint and npm run compile-tests are clean and npm run unittest reports 1487 passing, 0 failing, 5 pending.

User impact

None on the default path. The manager is still registered only when the undeclared python-envs.inlineScripts.enabled flag is on, so default users see nothing.

create() is now a declared method (PR 4 omitted it), so with the flag on the inline manager can appear as a create target. But it acts only on a single local script URI and no-ops on every other scope, and nothing in the extension routes a script URI to it yet. Wiring the trigger is later work: routing in PR 9, and the "Set up env for this script" picker item and bulk command in PR 11/12.

Merge order

Merge #1651 first, then #1655, then this PR.

StellaHuang95 and others added 3 commits July 23, 2026 17:04
Cross-process file lock, venv Python-path helper, cancellation-safe process runner, and createWithProgress tracking options that inline-script environment creation builds on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39dcc6a3-0fbd-4f36-9d0f-68677de49c27
Cache-key tail normalization, cache-layout ownership/status checks with typed sidecar reads, and interpreter-constraint trimming that inline-script environment creation builds on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39dcc6a3-0fbd-4f36-9d0f-68677de49c27
Implement InlineScriptEnvManager.create(): select a compatible base interpreter and build or reuse a dependency-keyed virtual environment, with cache-ownership validation, cross-process locking, and cancellation-safe creation. Wire the manager's collaborators.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39dcc6a3-0fbd-4f36-9d0f-68677de49c27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature-request Request for new features or functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant