Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions de_shell/js/main/pythonEnv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import { dirname, join } from 'path'
import { tmpdir } from 'os'
import { createHash } from 'crypto'
import { configureShell } from './config.ts'
import { parseUvLine } from './envProgress.ts'
import {
findUv, resolvePythonEnv, hasNvidiaGpu, readWheelhouse, findWheelhouse,
wheelhouseCandidates, lockStamp, buildSyncArgs, offlinePlan, effectiveExtras,
gpuEnvOverride, platformMatches, fileUrl, checkWheelhouseExtras, venvPython, clearBrokenEnv,
withoutPythonDownloadLines,
} from './pythonEnv.ts'

configureShell({
Expand Down Expand Up @@ -284,6 +286,44 @@ test('offlinePlan builds the python, venv, export, pip-install sequence', () =>
assert.equal(plan.install.at(-1), venvPython(join('/ud', 'python-env')))
})

/** uv 0.13.4's output for the wheelhouse's interpreter step, piped (de-shell #31). */
const UV_PYTHON_FROM_MIRROR =
'Downloading cpython-3.13.12-windows-x86_64-none (download) (20.7MiB)\n' +
' Downloaded cpython-3.13.12-windows-x86_64-none (download)\n' +
'Installed Python 3.13.12 in 1.22s\n' +
' + cpython-3.13.12-windows-x86_64-none\n'

function relayed(chunks: string[]): string {
const out: string[] = []
const python = withoutPythonDownloadLines((s) => out.push(s))
for (const chunk of chunks) python.relay(chunk)
python.flush()
return out.join('')
}

test('the wheelhouse interpreter step drops uv\'s download lines and keeps the rest', () => {
assert.equal(relayed([UV_PYTHON_FROM_MIRROR]),
'Installed Python 3.13.12 in 1.22s\n + cpython-3.13.12-windows-x86_64-none\n')
})

test('a download line split across chunks is still dropped whole', () => {
const cut = UV_PYTHON_FROM_MIRROR.indexOf('(download) (20.7MiB)')
assert.equal(relayed([UV_PYTHON_FROM_MIRROR.slice(0, cut), UV_PYTHON_FROM_MIRROR.slice(cut)]),
'Installed Python 3.13.12 in 1.22s\n + cpython-3.13.12-windows-x86_64-none\n')
})

test('an unfinished last line reaches the progress stream at flush', () => {
assert.equal(relayed(['error: no Python in the mirror']), 'error: no Python in the mirror')
})

test('the setup window reads the offline interpreter step as an install, never a download', () => {
const lines = ('[env-setup] installing Python 3.13.12 from the wheelhouse\n' +
relayed([UV_PYTHON_FROM_MIRROR])).split('\n')
const events = lines.map(parseUvLine).filter((e) => e !== null)
assert.equal(events[0]?.step, 'Installing Python 3.13.12 from the wheelhouse')
assert.deepEqual(events.filter((e) => e.phase === 'downloading'), [])
})

test('checkWheelhouseExtras names the missing extra', () => {
const m = { ...goodManifest, extras: [] }
assert.throws(() => checkWheelhouseExtras(['gpu'], m), /built without the 'gpu' extra/)
Expand Down
42 changes: 41 additions & 1 deletion de_shell/js/main/pythonEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,39 @@ function runUv(
})
}

/** uv's words for copying an interpreter from its install mirror. */
const PYTHON_DOWNLOAD_LINE = /^ *Download(?:ing|ed) cpython-/

/**
* A progress relay for the wheelhouse's interpreter step. Its mirror is the
* wheelhouse's `python/` folder on disk, but uv words the copy as a download
* ("Downloading cpython-…", "Downloaded cpython-…"), and the setup window would
* show it as one. Those lines are dropped and the rest passed on. Whole lines
* only: a chunk's unfinished last line is held until its end arrives, or `flush`.
*/
export function withoutPythonDownloadLines(onProgress?: (line: string) => void): {
relay: (chunk: string) => void
flush: () => void
} {
let held = ''
const pass = (text: string) => {
const kept = text.split(/(?<=\n)/).filter((line) => !PYTHON_DOWNLOAD_LINE.test(line)).join('')
if (kept) onProgress?.(kept)
}
return {
relay(chunk) {
const text = held + chunk
const end = text.lastIndexOf('\n') + 1
held = text.slice(end)
if (end) pass(text.slice(0, end))
},
flush() {
if (held) pass(held)
held = ''
},
}
}

/**
* Install the locked torch release into the managed env with the backend
* resolved for THIS machine (`--torch-backend=auto`: uv probes the NVIDIA
Expand Down Expand Up @@ -772,7 +805,14 @@ async function setupEnv(o: {
onProgress?.(`[env-setup] offline install from wheelhouse ${dir}\n`)
const plan = offlinePlan({ wheelhouse: dir, manifest, envDir, userData: join(envDir, '..'), extras })
mkdirSync(join(envDir, '..'), { recursive: true })
await runUv(projectDir, envDir, plan.python, appVersion, onProgress, plan.env)
onProgress?.(`[env-setup] installing Python ${manifest.python} from the wheelhouse
`)
const python = withoutPythonDownloadLines(onProgress)
try {
await runUv(projectDir, envDir, plan.python, appVersion, python.relay, plan.env)
} finally {
python.flush()
}
await runUv(projectDir, envDir, plan.venv, appVersion, onProgress, plan.env)
await runUv(projectDir, envDir, plan.export, appVersion, onProgress, plan.env)
await runUv(projectDir, envDir, plan.install, appVersion, onProgress, plan.env)
Expand Down
1 change: 1 addition & 0 deletions upcoming_changes/32.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An offline install's setup window said it was downloading Python, although the interpreter is copied from the wheelhouse; it now says it is installing Python from the wheelhouse.
Loading