Commit df05e69
Fix five PEP 723 inline-script defects: cancellation, rename, shared builds, terminal scope, and package drift (#1772)
## Summary
Five fixes to the PEP 723 inline-script feature, found while working
through reported issues against the testbed. Each is independently
reviewable and has unit coverage that was checked to **fail when the
corresponding guard is removed**, not merely to pass.
The feature remains behind the undeclared
`python-envs.inlineScripts.enabled` flag (default `false`), so nothing
here is user-visible until that is enabled.
## Commits and the issue each addresses
| Commit | Issue addressed |
|---|---|
| `0a21a2c1` | Cancelling setup retained the cache lock with no cleanup,
so the retry failed with `Lock was retained after an interrupted
operation` |
| `887548d8` | Renaming a script dropped its environment association
even though nothing about the environment changed |
| `eaa71423` | Cancelling a build shared by two scripts reported
cancellation to only one of them |
| `22628e1f` | Selecting a script environment could leak into general
workspace terminals |
| `669cb8ed` | Editing packages on a shared environment silently broke
other scripts |
Two earlier commits on this branch (`9acf5a21`, `cd7d214a`) predate this
work and are unrelated to the above.
---
### `0a21a2c1` — Discard cancelled environments instead of quarantining
them
Cancelling package installation retained the cache-entry lock and made
no cleanup attempt. The cancellation surfaced as a generic *"Failed to
set up the environment for this script"*, and the next attempt failed
with `ELOCKRETAINED`. Recovery required clearing the entire cache.
Cancellation now cleans up automatically. `discardCacheEntry` deletes
`.meta.json` and any `.meta.json.backup-*` **first**, then removes the
directory with bounded retry.
**The key invariant for reviewers:** the sidecar is the correctness
guarantee, not the directory. `inspectCacheEntry` treats a missing
sidecar as `stale`, and `writeMetaJson` is the only writer of that file
— four call sites, all under the cache-entry lock (see the comment above
`withCacheEntryLock`). A surviving installer writes into `site-packages`
and cannot recreate a sidecar. So an entry whose directory survives
deletion is inert, and gets rebuilt or TTL-swept rather than reused.
Retry exists because a just-stopped installer can hold file handles
briefly, most visibly on Windows.
Also fixes two bulk-setup bugs:
`setUpInlineScriptEnvironmentsInWorkspace` only counted successes and
never surfaced outcomes, so a mid-run cancellation was invisible and the
next install started immediately. It now stops the run on cancellation
and reports failures distinctly.
### `887548d8` — Follow renames instead of dropping associations
A cache entry is keyed by normalized dependencies and base interpreter —
**the script path is not an input** — so a rename cannot invalidate it.
Neither the metadata block nor the environment changes.
This also left two subsystems disagreeing. `PythonProjectManagerImpl`
already follows renames via `updatePythonProjectSettingPath`, which
rewrites the `pythonProjects` entry and preserves its
`_inlineScriptRegistration` marker. Clearing the association therefore
produced a managed inline-script project entry pointing at a file with
no environment behind it.
The record is transferred in one persistence transaction and then
**re-validated rather than trusted**: its metadata binding is
content-derived, so if the file at the new path no longer matches,
ordinary validation clears it and the CodeLens returns.
Guards: destination must still be a routable local `.py`; renaming onto
an already-associated script replaces it; deletes are unchanged.
**Known gap:** directory renames are not covered — VS Code reports one
event per folder rather than one per contained file. Moving an
individual *file* already works, since VS Code reports a move as a
rename.
### `eaa71423` — Cancellation reaches every script sharing a build
Scripts whose dependencies normalize to the same list resolve to the
same cache key, so a second request joins the in-flight build via
`pendingCreations` instead of starting its own. Cancelling recorded an
outcome only against the initiating URI. The joined script fell through
to a generic failure, and in a bulk run its outcome was not a
cancellation — so the run kept installing.
The failure is now recorded on the shared `PendingCreationContext` and
translated per caller after awaiting the shared promise.
**Note for reviewers:** same-file coalescing (`pendingSetups`) and
different-file shared builds (`pendingCreations`) are separate
mechanisms. Only the second was broken. Bulk setup is sequential, so
selecting both files in one bulk run does not reproduce it — the second
request must overlap the first build.
### `22628e1f` — Shell startup variables resolve at folder scope
Shell-startup activation keeps a single activation command per workspace
folder (for example `VSCODE_PYTHON_PWSH_ACTIVATE`), injected into every
terminal opened there. `handleEnvironmentChange` wrote whichever
environment the event carried into that slot after collapsing the
event's URI to its containing folder — so a per-file selection became
the folder default.
The handler now resolves the folder's own environment via
`getEnvironment(workspaceFolder.uri)`. This matches what
`initializeInternal` already did, so the two paths no longer disagree,
and it fixes the whole class rather than special-casing the
inline-script manager: any file-scoped environment is excluded. A folder
already polluted by an earlier session self-repairs on the next
environment change.
Only reachable with `python-envs.terminal.autoActivationType:
"shellStartup"`; the default is `"command"`. Adds the first unit
coverage for this manager.
### `669cb8ed` — Invalidate an environment when its packages are edited
Cache entries are shared by design. Editing packages there through the
package UI silently affected every bound script: nothing validated
installed versions, so the entry stayed valid, the CodeLens stayed
hidden, and a script the user never opened would run the wrong version
while its own header still declared the original pin. Re-running setup
reused the modified entry rather than repairing it.
A package change on an owned entry now records `manuallyModified` in the
sidecar and un-routes every associated script, so the CodeLens returns
for each. `inspectCacheEntry` treats a marked entry as stale, so the
next setup rebuilds from declared metadata.
**Please look closely at the in-flight guard.** Setup installs through
`managePackages`, which fires this same event. Without the guard, every
setup would mark the environment it had just built and rebuild
endlessly. The guard relies on VS Code's `EventEmitter` being
synchronous, so a build still registered in `pendingCreations` is
recognised before any `await`; the check is repeated once the lock is
held.
**Known gaps, deliberately not addressed:** sharing is still not
surfaced before an edit; a deliberate ad-hoc install is discarded by the
next setup without explanation; package changes made outside VS Code
fire no event and remain undetected.
---
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 66a6bc6 commit df05e69
9 files changed
Lines changed: 894 additions & 67 deletions
File tree
- src
- common/inlineScript
- features
- inlineScript
- terminal
- managers/builtin/inlineScript
- test
- features
- inlineScript
- terminal
- managers/builtin/inlineScript
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
| 46 | + | |
45 | 47 | | |
46 | 48 | | |
47 | 49 | | |
| |||
447 | 449 | | |
448 | 450 | | |
449 | 451 | | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
| 452 | + | |
455 | 453 | | |
456 | 454 | | |
457 | 455 | | |
| |||
469 | 467 | | |
470 | 468 | | |
471 | 469 | | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
472 | 473 | | |
473 | 474 | | |
474 | 475 | | |
| |||
479 | 480 | | |
480 | 481 | | |
481 | 482 | | |
| 483 | + | |
482 | 484 | | |
483 | 485 | | |
484 | 486 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | | - | |
9 | | - | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
150 | 151 | | |
151 | 152 | | |
152 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
153 | 159 | | |
154 | 160 | | |
155 | 161 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
78 | 81 | | |
79 | 82 | | |
80 | 83 | | |
| |||
124 | 127 | | |
125 | 128 | | |
126 | 129 | | |
127 | | - | |
128 | | - | |
| 130 | + | |
| 131 | + | |
129 | 132 | | |
130 | 133 | | |
131 | 134 | | |
132 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
133 | 140 | | |
134 | 141 | | |
135 | 142 | | |
| |||
223 | 230 | | |
224 | 231 | | |
225 | 232 | | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
226 | 236 | | |
| 237 | + | |
227 | 238 | | |
228 | 239 | | |
229 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
230 | 252 | | |
231 | 253 | | |
| 254 | + | |
232 | 255 | | |
233 | 256 | | |
234 | 257 | | |
235 | | - | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
236 | 284 | | |
237 | 285 | | |
238 | 286 | | |
| |||
Lines changed: 18 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
| 51 | + | |
| 52 | + | |
65 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
66 | 70 | | |
67 | 71 | | |
68 | 72 | | |
| |||
0 commit comments