:: run_setup.bat -- Python vs Windows :: Project Home: https://github.com/mixmansoundude/Python_vs_Windows :: Description: Automated, zero-config Python environment management for Windows. Standalone and portable. :: [VERSION_METADATA] :: Last Verified Date: 2026-06-19 :: Verified Windows: Windows 10/11 (CI runner: Windows Server 2025) :: Verified PowerShell: 5.1+ :: Verified Python: 3.14.6 (CI Latest) :: NOTE: Update 'Last Verified Date' only when 'Verified Python' version changes :: or this entry is more than 6 months stale. Do NOT update the date on every commit. :: RECOVERY: If a future Python version breaks auto-detection, install a Python version matching the :: 'Last Verified' metadata above, then run 'set PVW_PYTHON_EXE=C:\path\to\python.exe' in your :: terminal before running this script to bypass the broken detection. :: ============================================================ :: FIRST-TIME WINDOWS USERS: SmartScreen / Security Warning :: ============================================================ :: When you double-click this file, Windows may show: :: "Windows protected your PC" :: This is normal for any .bat file downloaded from the internet. :: :: TO RUN: Click "More info" then "Run anyway" :: :: IF "Run anyway" is not shown: :: 1. Close this dialog :: 2. Right-click run_setup.bat in File Explorer :: 3. Click "Properties" :: 4. At the bottom, check the "Unblock" checkbox :: 5. Click "OK" :: 6. Double-click run_setup.bat again :: ============================================================ :: FOR CONTRIBUTORS/EDITORS OF THIS FILE (not needed to just run it): :: Do NOT edit this file using the GitHub web editor, "Edit in place", or :: any Mac/Linux text tool -- these silently strip the CR half of this :: file's required Windows (CRLF) line endings, corrupting it for every :: Windows user who later downloads it. This has broken things before :: (see docs/agent-closed-backlog.md's Item 44). Edit only from a real :: Windows/CRLF checkout, and run :: `python tools/check_crlf.py --fix run_setup.bat` before committing if :: unsure -- also enforced by a gating CI check on every PR. :: ============================================================ @echo off setlocal DisableDelayedExpansion rem ============================================================ rem LINE-ENDING SELF-CHECK -- keep this first, before any goto/call in rem this file. Defense-in-depth, not a fix for a known-broken distribution rem channel: this file's line endings are Windows ^(CRLF^) by construction rem and enforced in CI ^(.gitattributes' "-text" for *.bat/*.cmd plus rem tools/check_crlf.py -- a raw/blob download via GitHub's "Raw" button or rem a raw.githubusercontent.com link is expected to serve genuine CRLF^), rem but an old cached download from before that fix, or a copy re-saved by rem an editor that does not preserve CRLF, can still corrupt a user's copy. rem cmd.exe's goto/call label lookup can silently misbehave on an LF-only rem copy of a file this size -- wrong-label errors, skipped blocks, rem corrupted commands -- producing a confusing partial run instead of a rem clear failure. This check must therefore be self-reliant ^(no goto/call rem anywhere in this file, since that is exactly what an LF-only copy rem breaks^) and must run before anything else. rem ============================================================ rem HP_PREFLIGHT_STATUS is script-rooted via %~dp0 (not CWD-relative, and not the rem later %STATUS_FILE%/:write_status machinery -- neither exists yet at this point rem in the file, and :write_status itself is call-based, unsafe to invoke before the rem line-ending check above has passed). Written directly, only on a preflight rem failure below, so a stale "ok" status from an earlier successful run in this rem same folder can never be misread as this run's result if this run's copy of rem the file cannot even get past its own preflight. set "HP_PREFLIGHT_STATUS=%~dp0~bootstrap.status.json" where powershell >nul 2>&1 if defined HP_TEST_FORCE_NO_POWERSHELL ( rem derived requirement: force a nonzero errorlevel here without touching PATH, rem so CI can deterministically exercise the branch below on a shared runner rem with no risk of actually breaking PowerShell resolution for it. cmd /c "exit /b 1" ) if errorlevel 1 ( echo *** echo *** [ERROR] PowerShell was not found on this machine. echo *** This script requires PowerShell to run at all; please echo *** repair or reinstall it, then run this script again. echo *** echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%" if not defined HP_CI_LANE ( pause ) exit /b 1 ) if defined HP_TEST_FORCE_LF_ONLY ( rem derived requirement: point the check at a synthetic LF-only file instead rem of this running copy, so CI can exercise the invalid-line-endings branch rem below without corrupting the file that is actually executing right now. set "HP_SELF_PATH=%~dp0~test_lf_only.bat" ) else if defined HP_TEST_FORCE_PS_CHECK_FAIL ( rem derived requirement: point at a path that does not exist, so the real rem PowerShell command below genuinely throws and hits its own catch branch rem -- exercises the real failure path instead of faking an exit code rem externally. set "HP_SELF_PATH=%~dp0~test_missing_for_ps_check.bat" ) else ( set "HP_SELF_PATH=%~f0" ) powershell -NoProfile -Command "try{$c=[System.IO.File]::ReadAllText($env:HP_SELF_PATH);$crlf=-join @([char]13,[char]10);$lf=[string][char]10;$cr=[string][char]13;$norm=$c.Replace($crlf,'');if($c.Contains($crlf) -and -not $norm.Contains($lf) -and -not $norm.Contains($cr)){exit 0}else{exit 1}}catch{exit 2}" >nul 2>&1 if errorlevel 2 ( echo *** echo *** [ERROR] PowerShell could not check the line endings of this file. echo *** This may mean PowerShell is restricted on this machine -- for echo *** example, by a Constrained Language Mode policy -- please repair echo *** or unblock PowerShell, then run this script again. echo *** echo {"state":"error","exitCode":2,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%" if not defined HP_CI_LANE ( pause ) exit /b 2 ) if errorlevel 1 ( echo *** echo *** [ERROR] This copy of run_setup.bat has invalid line endings. echo *** Every line ending in this file must be Windows-style ^(CRLF^); at echo *** least one is not, and the file will fail with confusing, partial, echo *** hard-to-diagnose errors if run as-is. echo *** echo *** Easiest fix: delete this copy and download run_setup.bat again -- echo *** the GitHub "Raw" link and the download page are both fine now. echo *** This is usually an old cached copy, or one re-saved by an editor echo *** that does not preserve Windows line endings. echo *** echo *** Or open this file in an editor that shows line endings echo *** ^(e.g. Notepad++, VS Code^) and convert it to Windows ^(CRLF^) echo *** line endings, then save and run this script again. echo *** echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%" if not defined HP_CI_LANE ( pause ) exit /b 1 ) set "DEP_SOURCE=unknown" rem [REQ-026] Argv passthrough escape hatch (docs/plan-cli-interactive-verification.md P1): rem capture trailing arguments (%2-%9) here, before anything else touches %1-%9, and forward rem them verbatim to the target program at every real launch site (EXE smoke, EXE fast-path rem reuse, interpreter run, checkpoint's elective second run). Deliberately does NOT use `shift` rem -- %~1 (the entry file) is read directly via "%~1" at multiple later points in this file rem (the UNC check below, both :determine_entry call sites), and shifting here would silently rem change what those later "%~1" reads see. Practical limit of 8 extra args (%2-%9): CMD has rem no way to read beyond %9 without `shift`, and `shift` is off-limits for the reason above. rem Each present token is individually re-quoted (not just %~2 raw) so a token containing spaces rem survives as ONE argv element to the target program, matching ordinary Windows command-line rem quoting; a token containing a literal " is not supported (documented limitation, not silently rem mishandled -- see README [REQ-026]). set "HP_APP_ARGS=" if not "%~2"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~2"" if not "%~3"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~3"" if not "%~4"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~4"" if not "%~5"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~5"" if not "%~6"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~6"" if not "%~7"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~7"" if not "%~8"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~8"" if not "%~9"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~9"" rem Boot strap renamed to run_setup.bat set "HP_SCRIPT_LAUNCH_DIR=%~dp0" if "%HP_SCRIPT_LAUNCH_DIR:~0,2%"=="\\" ( rem derived requirement: parentheses must be escaped inside IF blocks in CMD or parsing breaks. echo *** WARNING: UNC/network paths detected ^(\\server\share^). echo *** This script may fail in this environment. echo *** Recommended: Map the network path to a drive letter and re-run. echo *** ) if not exist "%~dp0" ( echo [ERROR] Workspace path invalid: %~dp0 exit /b 1 ) cd /d "%~dp0" if errorlevel 1 ( echo [ERROR] Workspace path invalid: %~dp0 exit /b 1 ) rem derived requirement: writable-CWD preflight (CLAUDE.md Item 48) -- this is the first rem point in the file safe to attempt a real write (CWD is now confirmed to be the app rem folder itself, via the cd /d above), and it runs before every other write in this file rem (the CI marker, ~setup.log, and :merge_git_config's own .gitignore/.gitattributes rem writes) so an unwritable folder fails with one clear, named message instead of a rem confusing cascade of silently-swallowed write failures later. rem derived requirement: clear any pre-existing ~wtest.tmp before probing -- a crash between rem a past successful probe and its own cleanup (or, unlikely given the tilde convention, a rem coincidentally-named leftover) must not let a stale entry at this exact path masquerade as rem this run's own successful write. del only removes a FILE at this path; if a directory of rem the same name exists instead (however unlikely), del is a silent no-op against it, so rd rem /s /q runs too whenever the entry is still present afterward -- between the two, either rem shape of stale leftover is cleared before the real probe below ever runs. del /f /q "~wtest.tmp" >nul 2>&1 if exist "~wtest.tmp" rd /s /q "~wtest.tmp" >nul 2>&1 if defined HP_TEST_FORCE_CWD_NOT_WRITABLE ( rem derived requirement: force the not-writable branch deterministically for CI, without rem actually revoking filesystem permissions on a shared runner -- skip the real write rem attempt entirely so ~wtest.tmp is never created, the same signal a genuine write rem failure would produce. Intentionally empty otherwise: doing nothing IS the forcing rem mechanism here. ) else ( type nul > "~wtest.tmp" 2>nul ) if not exist "~wtest.tmp" ( echo *** echo *** [ERROR] This folder does not appear to be writable: "%CD%" echo *** This script needs to create files here -- logs, a dependency cache, and echo *** eventually a standalone program. Move this script and your .py files to echo *** a folder you have write access to, then run it again. echo *** echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%" if not defined HP_CI_LANE ( pause ) exit /b 1 ) del /f /q "~wtest.tmp" >nul 2>&1 rem derived requirement: PowerShell capability preflight (CLAUDE.md Item 47) -- placed after rem the CWD-writable check above so a real folder-permission failure is diagnosed by that rem check first, not misattributed to this one. Bare PowerShell presence, checked earlier in rem the line-ending self-check, only proves powershell.exe exists on PATH, not that it can do rem what this bootstrapper actually needs: :emit_from_base64, used to write every embedded rem ~*.py/~*.ps1 helper, needs Convert.FromBase64String plus IO.File.WriteAllBytes, and the rem failfast-probe/exe-smokerun helpers need System.Diagnostics.ProcessStartInfo -- all of rem which a locked-down corporate image, such as AppLocker, WDAC, or Constrained Language rem Mode, can block even with PowerShell itself present. Probing all three together here turns rem five-plus later opaque "Could not write ~x" failures into one clear, named diagnostic. rem derived requirement: %RANDOM% suffix (two draws) makes the probe path unique per process -- rem this preflight runs before :acquire_lock below, so two genuinely concurrent instances in the rem same folder must not be able to race on (delete/overwrite) each other's probe file and rem misreport a capability failure that isn't real. Not a full mutex, just enough entropy that a rem same-folder collision within one preflight's brief lifetime is negligible. set "HP_PS_PROBE_NAME=~ps_capability_probe.%RANDOM%%RANDOM%.tmp" set "HP_PS_PROBE_FILE=%~dp0%HP_PS_PROBE_NAME%" if defined HP_TEST_FORCE_PS_CAPABILITY_FAIL ( rem derived requirement: point the probe at a nonexistent directory so the real rem WriteAllBytes call genuinely throws and hits its own catch branch -- exercises the rem real failure path instead of faking an exit code externally, matching rem HP_TEST_FORCE_PS_CHECK_FAIL's established technique above. set "HP_PS_PROBE_FILE=%~dp0~nonexistent_dir_xyz\%HP_PS_PROBE_NAME%" ) del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1 if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1 rem derived requirement: [IO.File]::Exists/Delete, not Test-Path/Remove-Item -- the latter two rem PowerShell cmdlets treat "[" and "]" in a path as wildcard syntax by default, so a folder rem name containing literal brackets could make Test-Path report a genuinely-written file as rem missing, misreporting a capability failure that isn't real. The .NET IO.File methods used rem here never glob-expand, so the app folder's own path can contain any legal Windows filename rem character without affecting this probe's outcome. powershell -NoProfile -Command "try{$b=[Convert]::FromBase64String('cHZ3');[IO.File]::WriteAllBytes($env:HP_PS_PROBE_FILE,$b);if(-not [IO.File]::Exists($env:HP_PS_PROBE_FILE)){exit 1};[IO.File]::Delete($env:HP_PS_PROBE_FILE);$psi=New-Object System.Diagnostics.ProcessStartInfo;exit 0}catch{exit 1}" >nul 2>&1 if errorlevel 1 ( echo *** echo *** [ERROR] PowerShell on this machine cannot perform an operation this echo *** script needs -- decoding embedded data, writing files, or preparing echo *** to launch a process. This is usually caused by a restrictive policy echo *** such as Constrained Language Mode, AppLocker, or WDAC on a managed echo *** or corporate machine. Ask an administrator to allow PowerShell Full echo *** Language Mode for this script, or run it on an unrestricted machine. echo *** echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%" del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1 if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1 if not defined HP_CI_LANE ( pause ) exit /b 1 ) del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1 if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1 set "HP_SCRIPT_ROOT=%~dp0" for %%R in ("%HP_SCRIPT_ROOT%") do set "HP_SCRIPT_ROOT=%%~fR" if not "%HP_SCRIPT_ROOT:~-1%"=="\" set "HP_SCRIPT_ROOT=%HP_SCRIPT_ROOT%\" set "HP_CI_MARKER=.ci_bootstrap_marker" type nul > "%HP_CI_MARKER%" 2>nul set "LOG=~setup.log" set "LOGPREV=~setup.prev.log" set "STATUS_FILE=~bootstrap.status.json" if not exist "%LOG%" (type nul > "%LOG%") rem derived requirement (real CI failure, PR #455): :merge_git_config now needs an HP_* rem payload (HP_MIGRATE_GITATTRIBUTES, Item 60) at its own :emit_from_base64 call site, so rem :define_helper_payloads must run BEFORE this call, not at its old position ~300 lines rem later -- see the paired removal below, near the old ":rotate_log"/"CI fast path" site. call :define_helper_payloads call :merge_git_config if "%HP_TEST_FORCE_CONNECTIVITY_CHECK%"=="1" call :check_net_after_dl_fail if "%HP_TEST_FORCE_CONSENT_CHECK%"=="1" ( call :system_python_consent_gate if errorlevel 1 ( call :log "[INFO] REQ-014: Consent gate test: user declined." exit /b 1 ) call :log "[INFO] REQ-014: Consent gate test: user accepted." ) rem --- PVW_ super-user overrides (inherit from calling terminal; logged before detection runs) --- rem derived requirement: PVW_ variables let a super-user pre-set values to bypass auto-detection. rem Single-line if form avoids parse-time expansion issues in block-form if-statements when a rem variable value contains parentheses, such as a path under "C:\Program Files (x86)\...". if defined PVW_PYTHON_EXE call :log "[DEBUG] Using super-user override for PVW_PYTHON_EXE: %PVW_PYTHON_EXE%" if defined PVW_UV_EXE call :log "[DEBUG] Using super-user override for PVW_UV_EXE: %PVW_UV_EXE%" if defined PVW_CONDA_EXE call :log "[DEBUG] Using super-user override for PVW_CONDA_EXE: %PVW_CONDA_EXE%" if defined PVW_TARGET_PY call :log "[DEBUG] Using super-user override for PVW_TARGET_PY: %PVW_TARGET_PY%" if defined PVW_WORKSPACE call :log "[DEBUG] Using super-user override for PVW_WORKSPACE: %PVW_WORKSPACE%" rem --- Path-length guard: warn if script root path approaches the 260-char cmd.exe limit --- for /f "usebackq delims=" %%L in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:HP_SCRIPT_ROOT.Length" 2^>nul`) do set "HP_PATH_LEN=%%L" if defined HP_PATH_LEN if %HP_PATH_LEN% GEQ 200 ( echo *** WARNING: Script path is %HP_PATH_LEN% chars. Paths near 260 chars may cause cmd.exe failures. call :log "[WARN] Script path is %HP_PATH_LEN% chars; paths near 260 chars may cause failures." ) set "HP_PATH_LEN=" rem --- Synced-folder guard: warn when running from a OneDrive or similar sync folder --- if /I not "%HP_SCRIPT_ROOT:OneDrive=%"=="%HP_SCRIPT_ROOT%" ( echo *** WARNING: Script appears to be in a OneDrive folder. File locking may cause failures. call :log "[WARN] OneDrive path detected; file locking may cause failures." ) rem --- System-directory guard: check-and-abort (not warn-only) if the script root is under a rem Windows system folder. Unlike the OneDrive/path-length/disk-space guards above (marginal rem conditions that MIGHT cause a failure), a user occasionally drops this script into rem %WINDIR%/%PROGRAMFILES% thinking it "installs" there -- without elevation this will rem essentially always fail on write-permission errors several steps in (creating ~setup.log, rem ~uv_bin, .uv_env, etc. right next to itself), so a clear early abort is kinder than a rem cryptic failure deep in the bootstrap. findstr substring match mirrors the UNC-path check rem at the top of this file; HP_SCRIPT_ROOT always has a trailing backslash (see above), so a rem trailing backslash on the search pattern prevents a same-prefix false match (e.g. rem "C:\WindowsFooBar\" does not contain the substring "C:\Windows\"). set "HP_SYSDIR_HIT=" set "HP_PF86=%ProgramFiles(x86)%" rem derived requirement: the search pattern ends in "\\" (two backslashes), not "\" -- a rem SINGLE backslash immediately before the closing quote is a classic Windows argv-parsing rem trap: findstr.exe (like most native console apps) follows the standard C-runtime rule rem that an ODD number of backslashes right before a closing quote escapes the quote instead rem of closing the string, silently corrupting the whole /C: argument (and swallowing the rem trailing ">nul" into the search pattern) so the match can never succeed. An EVEN count rem (here, two) collapses to a single literal backslash and the quote closes normally. rem derived requirement: a CodeRabbit review finding (PR #417) -- HP_SCRIPT_ROOT is echoed rem UNQUOTED into a pipe here; if it contains '&', cmd.exe's own parser (which does not rem distinguish "this & came from a variable" from "this & was typed") treats it as a command rem separator, splitting this single line into two commands and feeding findstr only a rem truncated prefix -- silently defeating the guard for a script dropped under a path like rem "C:\Users\Sales & Marketing\run_setup.bat". Quoting protects it: cmd.exe tracks quote state rem left-to-right as it scans (including through %VAR% expansion), so a '&' landing inside the rem quoted region is never treated as an operator. The literal quote characters `echo` leaves in rem its own output (echo does not strip them, unlike most commands) don't affect the match -- rem findstr's /C: pattern is a substring search, so it still finds "%WINDIR%\\" etc. regardless rem of the extra leading/trailing quote characters surrounding it. if defined WINDIR ( echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%WINDIR%\\" >nul if not errorlevel 1 set "HP_SYSDIR_HIT=1" ) if defined ProgramFiles ( echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%ProgramFiles%\\" >nul if not errorlevel 1 set "HP_SYSDIR_HIT=1" ) if defined HP_PF86 ( echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%HP_PF86%\\" >nul if not errorlevel 1 set "HP_SYSDIR_HIT=1" ) set "HP_PF86=" if defined HP_SYSDIR_HIT ( echo *** ERROR: This script is located inside a Windows system folder. echo *** Placing it here does not "install" it. Windows restricts writes to this location echo *** without administrator rights, and this bootstrapper needs to create files right echo *** next to itself to work. echo *** Please move this script ^(and your .py files^) to a normal folder -- your Desktop echo *** or Documents folder both work well -- then run it again from there. call :log "[ERROR] System-directory guard: script root is under a Windows system folder; aborting." exit /b 1 ) set "HP_SYSDIR_HIT=" rem --- Free-disk-space guard: warn (never abort) if the script's drive looks low on space --- rem derived requirement: Miniconda download/install plus conda env creation can together need rem several hundred MB to a few GB; a low-disk-space beginner machine would otherwise see whatever rem low-level error curl/conda/pip happens to surface for "no space left on device" deep inside the rem bootstrap, instead of a clear, early, plain-language message. Threshold is deliberately generous rem (2 GB) and this only warns -- per REQ-001 (env-var flags are scaffolding, never a Prime-Directive rem gate), a low reading must never hard-block the run: the user may still have just enough, or may rem free space and retry after seeing the warning. set "HP_FREE_GB=" if defined HP_TEST_FORCE_LOW_DISK ( set "HP_FREE_GB=0" ) else ( for /f "usebackq delims=" %%D in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $letter = ([System.IO.Path]::GetPathRoot($env:HP_SCRIPT_ROOT)).Substring(0,1); [math]::Floor((Get-PSDrive -Name $letter).Free / 1GB) } catch { '' }" 2^>nul`) do set "HP_FREE_GB=%%D" ) if defined HP_FREE_GB if %HP_FREE_GB% LSS 2 ( echo *** WARNING: Only ~%HP_FREE_GB% GB free disk space detected on this drive. echo *** Downloading Python/Miniconda and building your app can need several GB. echo *** If setup fails partway through, freeing up disk space is a likely fix. call :log "[WARN] REQ-025: low disk space detected (~%HP_FREE_GB% GB free); continuing (warn-only)." ) set "HP_FREE_GB=" if exist "%STATUS_FILE%" del "%STATUS_FILE%" rem REQ-024: concurrent-instance protection -- must run before any real bootstrap work rem (env creation, downloads, EXE fast path) so two double-clicks in this folder cannot race. call :acquire_lock if errorlevel 1 exit /b 1 set "HP_BOOTSTRAP_STATE=ok" rem REQ-010: nullify host-system Python path variables to prevent library interference set "PYTHONPATH=" set "PYTHONHOME=" set "HP_ENV_MODE=conda" set "HP_ENV_READY=" set "HP_SKIP_PIPREQS=%HP_SKIP_PIPREQS%" set "HP_PY=" set "HP_UV_PROVIDING_PYTHON=" set "HP_FIND_ENTRY_SYNTAX_OK=" set "HP_HELPER_SYNTAX_EMITTED=" set "HP_HELPER_CMD_LOGGED=" set "HP_FIND_ENTRY_NAME=~find_entry.py" set "HP_FIND_ENTRY_ABS=" set "HP_PIPREQS_VERSION=%HP_PIPREQS_VERSION%" rem derived requirement: pin pipreqs to 0.4.13, NOT 0.5.0. pipreqs 0.5.0 added Jupyter rem notebook scanning, which hard-pins ipython==8.12.3 (the last ipython supporting Python rem 3.8). ipython 8.12.3 does not support Python 3.13+, so 0.5.0's metadata declares rem Requires-Python >=3.8.1,<3.13. Because the bootstrapper always targets the latest rem conda-forge Python (3.14+), 0.5.0 refuses to install there and pipreqs is lost entirely. rem 0.4.13 has Requires-Python >=3.7 (no upper cap), deps only docopt+yarg, supports the same rem --mode compat / --force / --savepath flags, uses only stable stdlib (ast-based scan), and rem runs on Python 3.14. Do NOT "upgrade" back to 0.5.0 -- it reintroduces the <3.13 cap. rem The only feature lost is .ipynb scanning, which was already non-functional on latest Python. if not defined HP_PIPREQS_VERSION set "HP_PIPREQS_VERSION=0.4.13" set "HP_MINICONDA_MIN_BYTES=%HP_MINICONDA_MIN_BYTES%" if not defined HP_MINICONDA_MIN_BYTES set "HP_MINICONDA_MIN_BYTES=5000000" set "HP_CONDA_DL_INJECTED=" if defined HP_MINICONDA_URL set "HP_CONDA_DL_INJECTED=1" if not defined HP_MINICONDA_URL set "HP_MINICONDA_URL=https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe" set "HP_MINICONDA_FALLBACK_URL=https://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86_64.exe" set "HP_UV_DL_INJECTED=" if defined HP_UV_URL set "HP_UV_DL_INJECTED=1" if not defined HP_UV_URL set "HP_UV_URL=https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip" rem HP_UV_FALLBACK_URL: pinned release used when primary GitHub releases/latest CDN fails set "HP_UV_FALLBACK_URL=https://github.com/astral-sh/uv/releases/download/0.7.3/uv-x86_64-pc-windows-msvc.zip" set "HP_UV_MIN_BYTES=%HP_UV_MIN_BYTES%" if not defined HP_UV_MIN_BYTES set "HP_UV_MIN_BYTES=1000000" rem REQ-023b: get-pip.py -- used only by the venv fallback tier's --without-pip retry (see rem :download_get_pip / :try_venv_fallback). HP_GETPIP_FALLBACK_URL is the get-pip project's own rem GitHub source (the file bootstrap.pypa.io serves is generated from this repo), mirroring the rem primary-CDN + GitHub-source fallback pattern already used for Miniconda/uv above. if not defined HP_GETPIP_URL set "HP_GETPIP_URL=https://bootstrap.pypa.io/get-pip.py" set "HP_GETPIP_FALLBACK_URL=https://raw.githubusercontent.com/pypa/get-pip/main/public/get-pip.py" rem REQ-009 Tier 5 (by naming/history; executed 3rd as of the provider-chain reorder): the rem embeddable-Python fallback, reached right after conda fails (before venv/system) so a rem pinned runtime.txt/pyproject.toml version is still honored via a fresh checksummed download rem instead of silently falling back to whatever ambient Python happens to be on the machine. rem Only a single "latest" version is ever downloaded here -- HP_EMBED_LATEST_SHA256 MUST match rem the "3.14" entry embedded in the Python-side rem ~embed_pyver_check.py payload below (a unit test asserts this so a refresh that updates one rem but not the other is caught at CI time, not discovered live). See docs/agent-interconnect.md rem "Standalone Python-download tier" for the two-stage PowerShell/Python design and why a rem per-request version table deliberately does NOT live here in batch. if not defined HP_EMBED_LATEST_PATCH set "HP_EMBED_LATEST_PATCH=3.14.6" set "HP_EMBED_LATEST_SHA256=df901e84a896ff1ee720ad03377e0c8d8c2244fda79808aeeaff6316df1cb75c" rem HP_TEST_OFFLINE=1: simulates ping failure for REQ-013 branch coverage (CI test flag) set "HP_TEST_OFFLINE=%HP_TEST_OFFLINE%" rem HP_OFFLINE_MODE is set by :check_net_after_dl_fail when user declines or no internet set "HP_OFFLINE_MODE=%HP_OFFLINE_MODE%" rem HP_TEST_FORCE_CONNECTIVITY_CHECK=1: triggers connectivity gate at startup for CI coverage set "HP_TEST_FORCE_CONNECTIVITY_CHECK=%HP_TEST_FORCE_CONNECTIVITY_CHECK%" rem HP_TEST_FORCE_VENV_FAIL=1: simulates venv creation failure for REQ-009/REQ-014 branch coverage set "HP_TEST_FORCE_VENV_FAIL=%HP_TEST_FORCE_VENV_FAIL%" rem HP_TEST_FORCE_VENV_CANARY_FAIL=1: simulates the post-creation canary probe (REQ-023) failing rem after a real, successful venv creation (distinct from HP_TEST_FORCE_VENV_FAIL, which skips rem creation entirely) set "HP_TEST_FORCE_VENV_CANARY_FAIL=%HP_TEST_FORCE_VENV_CANARY_FAIL%" rem HP_TEST_FORCE_VENV_CREATE_FAIL=1: simulates the FIRST plain "python -m venv" attempt failing rem outright (e.g. a stripped-down host Python missing ensurepip) so the REQ-023b --without-pip rem retry path is exercised for real; distinct from HP_TEST_FORCE_VENV_FAIL, which skips venv rem creation entirely and never reaches either attempt. set "HP_TEST_FORCE_VENV_CREATE_FAIL=%HP_TEST_FORCE_VENV_CREATE_FAIL%" rem HP_TEST_FORCE_EMBED_FAIL=1: simulates the REQ-009 Tier 5 embedded-Python fallback failing rem outright, for CI coverage of the "every tier exhausted" clean-:die path. set "HP_TEST_FORCE_EMBED_FAIL=%HP_TEST_FORCE_EMBED_FAIL%" rem HP_TEST_FORCE_EMBED_REAL=1: narrow test-only hole through HP_OFFLINE_MODE=1 for the embed rem tier's own download AND the get-pip.py download it triggers (mirrors rem HP_TEST_FORCE_VENV_CREATE_FAIL's existing exception for :download_get_pip) so CI can exercise rem the real embed download/extract/patch/pip-bootstrap path while HP_OFFLINE_MODE=1 still blocks rem unrelated Miniconda/uv downloads earlier in the same test run. set "HP_TEST_FORCE_EMBED_REAL=%HP_TEST_FORCE_EMBED_REAL%" rem HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=1: former CLAUDE.md Active Backlog item 12. Deterministically rem fails ONLY the first embed-tier download attempt (no network touched, no real failure needed) rem so :embed_dl_retry's genuine mid-download-failure-then-retry-once path gets CI coverage -- rem mirrors HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL's established one-shot-then-succeed pattern. set "HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=%HP_TEST_FORCE_EMBED_DL_FAIL_ONCE%" rem HP_TEST_FORCE_CONDA_FAIL=1: simulates conda env creation failure for REQ-009/REQ-014 branch coverage set "HP_TEST_FORCE_CONDA_FAIL=%HP_TEST_FORCE_CONDA_FAIL%" rem HP_TEST_FORCE_WARNFIX_UNRESOLVED=1: forces the warnfix cascade-candidate detection (REQ-009/REQ-005.10) for branch coverage set "HP_TEST_FORCE_WARNFIX_UNRESOLVED=%HP_TEST_FORCE_WARNFIX_UNRESOLVED%" rem HP_TEST_CASCADE_ANSWER=Y|N: bypasses the cascade consent prompt (REQ-009/REQ-005.10) for CI testing set "HP_TEST_CASCADE_ANSWER=%HP_TEST_CASCADE_ANSWER%" rem HP_ALLOW_VENV_FALLBACK (deprecated): venv fallback is now unconditional when conda fails; accepted but ignored. set "HP_ALLOW_VENV_FALLBACK=%HP_ALLOW_VENV_FALLBACK%" rem HP_ALLOW_SYSTEM_FALLBACK (deprecated as a gate): system Python fallback (REQ-009 Tier 4) is now rem reached in any run and gated solely by the REQ-014 consent prompt; this flag is accepted but rem ignored. Conda-only mode still suppresses all non-conda fallbacks via HP_FORCE_CONDA_ONLY. set "HP_ALLOW_SYSTEM_FALLBACK=%HP_ALLOW_SYSTEM_FALLBACK%" rem HP_TEST_FORCE_CONSENT_CHECK=1: directly triggers consent gate at startup for REQ-014 branch coverage set "HP_TEST_FORCE_CONSENT_CHECK=%HP_TEST_FORCE_CONSENT_CHECK%" rem HP_TEST_SYSCON_ANSWER=Y|N: bypasses the REQ-014 system Python consent prompt for CI testing set "HP_TEST_SYSCON_ANSWER=%HP_TEST_SYSCON_ANSWER%" rem HP_TEST_CORRUPT_CONDA=1: simulates a corrupt conda binary for REQ-020 branch coverage (corruption hardening) set "HP_TEST_CORRUPT_CONDA=%HP_TEST_CORRUPT_CONDA%" rem HP_TEST_HEAL_ANSWER=Y|N: bypasses the interactive Y/N prompt in :conda_binary_corrupt for CI testing set "HP_TEST_HEAL_ANSWER=%HP_TEST_HEAL_ANSWER%" rem HP_TEST_CORRUPT_UV=1: simulates a corrupt uv binary; clears cache and re-downloads for REQ-020 branch coverage set "HP_TEST_CORRUPT_UV=%HP_TEST_CORRUPT_UV%" rem HP_TEST_FORCE_UV_FAIL=1: CI-only; forces uv acquisition to fail entirely (before any download attempt). rem Use in justme-test lane to exercise Miniconda/JustMe paths that are bypassed when uv succeeds. rem Mirrors HP_TEST_FORCE_CONDA_FAIL=1 pattern. Does not affect HP_TEST_UV_DL_FALLBACK behavior. set "HP_TEST_FORCE_UV_FAIL=%HP_TEST_FORCE_UV_FAIL%" rem HP_TEST_SKIP_EVICT=1: CI-only; skips the rmdir and Miniconda re-download in :evict_and_rebuild. rem Use with HP_TEST_CORRUPT_CONDA=1 + HP_TEST_HEAL_ANSWER=Y to test the accept branch without rem deleting the real CI Miniconda installation. The eviction log line is still emitted. set "HP_TEST_SKIP_EVICT=%HP_TEST_SKIP_EVICT%" rem HP_TEST_FORCE_PYINSTALLER_FAIL=1: CI-only; forces the PyInstaller build command itself to rem fail (errorlevel 1) without touching the real invocation. Branch coverage for the rem PyInstaller-build-failure -> HP_BOOTSTRAP_STATE=error path (see the "derived requirement" rem comment right before the PyInstaller build call, inside :run_entry_smoke's else-branch, rem for the bug this guards against). set "HP_TEST_FORCE_PYINSTALLER_FAIL=%HP_TEST_FORCE_PYINSTALLER_FAIL%" rem HP_TEST_FORCE_OUTPUT_VANISH=1: CI-only; deletes the freshly-built dist\.exe immediately rem after a genuinely successful PyInstaller build, before the exist-check that follows -- rem simulates AV-style post-creation removal (research Finding 2 in docs/prd-av-safe-build-path.md) rem as a distinct scenario from the build command itself failing. set "HP_TEST_FORCE_OUTPUT_VANISH=%HP_TEST_FORCE_OUTPUT_VANISH%" rem HP_TEST_FORCE_NUITKA_FAIL=1: CI-only; forces the AV-Safe Build Path Tier A fallback rem (:try_nuitka_tier_a) to fail without attempting a real Nuitka build. Used to test rem tier-exhaustion (both PyInstaller and the fallback fail) independently of a real, rem environment-dependent Nuitka build outcome. set "HP_TEST_FORCE_NUITKA_FAIL=%HP_TEST_FORCE_NUITKA_FAIL%" rem HP_SKIP_NIVISA=1: REQ-008 opt-out -- skip the NI-VISA driver install even when pyvisa/visa is detected (debugging) set "HP_SKIP_NIVISA=%HP_SKIP_NIVISA%" rem HP_NIVISA_WAIT_SECS=: REQ-008 diagnostic -- post-install registry poll budget in seconds. rem Default (unset) keeps the fast ~15s / 3-retry behavior for gating lanes. A dedicated non-gating rem lane sets a large value (e.g. 2700 = 45 min) to wait out a genuinely slow NI-VISA install. set "HP_NIVISA_WAIT_SECS=%HP_NIVISA_WAIT_SECS%" rem HP_TEST_FORCE_CONDA_BULK_FAIL=1: simulate a non-transient conda bulk-install failure so the rem REQ-005.3 per-package fallback fires (CI branch coverage). Consumed once in :conda_bulk_install. set "HP_TEST_FORCE_CONDA_BULK_FAIL=%HP_TEST_FORCE_CONDA_BULK_FAIL%" rem HP_TEST_FORCE_INTERACTIVE_PROBE=1: CI-only; forces the fail-fast probe's interactive branch rem (:try_fast_exe / :verify_no_exe_interpreter) even under HP_CI_LANE, for deterministic branch rem coverage of the ALIVE_AT_PROBE state machine. Mirrors HP_TEST_FORCE_PICKER. set "HP_TEST_FORCE_INTERACTIVE_PROBE=%HP_TEST_FORCE_INTERACTIVE_PROBE%" rem HP_FAILFAST_PROBE_MS: the fail-fast probe's classification window (default 10000ms). This is rem NOT the same concept as the unrelated ~30s hard-kill cap used by :run_exe_smokerun / rem :hidden_import_recover -- that is a force-kill ceiling for the fresh-build verification run rem (the only run this bootstrapper ever kills). This probe window only decides how long to wait rem before treating a launched process as "still alive / healthy" rather than "failed fast"; once rem classified alive, the wait becomes unbounded and the process is never killed. rem Widened from the original 5000ms after a real CI flake (self.failfast.probe.fastfail): rem what races this window is not the failure itself (a raised exception unwinds and exits in rem microseconds) but PyInstaller onefile COLD START -- extracting the bundled runtime to a temp rem dir and booting an embedded interpreter before any user code (or its failure) can even run. rem That step is commonly 1-3+ seconds on a healthy machine and can be pushed well past 5s under rem CI-runner CPU/disk contention or a Defender on-access scan of the freshly-extracted EXE/DLLs. rem A real user's own machine sees this same cold-start cost but rarely the added contention, so rem widening this is a low-risk, low-cost change: it never introduces a kill (this window only rem ever governs classification, never termination -- see above), and the only cost of widening rem it is a few extra seconds before a genuinely broken cached EXE is recognized as such. set "HP_FAILFAST_PROBE_MS=%HP_FAILFAST_PROBE_MS%" if not defined HP_FAILFAST_PROBE_MS set "HP_FAILFAST_PROBE_MS=10000" rem HP_TEST_CHECKPOINT_ANSWER=Y|N: bypasses the REQ-018 post-execution checkpoint prompt for CI rem testing (mirrors HP_TEST_SYSBUILD_ANSWER/HP_TEST_SYSCON_ANSWER). Checked before HP_CI_LANE so rem an explicit Y reaches the accept branch even in CI. set "HP_TEST_CHECKPOINT_ANSWER=%HP_TEST_CHECKPOINT_ANSWER%" rem HP_TEST_OPTBUILD_ANSWER=Y|N: bypasses the AV-Safe Build Path requirement 9 "want an rem optimized build too?" prompt for CI testing (mirrors HP_TEST_CHECKPOINT_ANSWER). Checked rem before HP_CI_LANE so an explicit Y reaches the accept branch even in CI. set "HP_TEST_OPTBUILD_ANSWER=%HP_TEST_OPTBUILD_ANSWER%" rem HP_TEST_FORCE_OPTBUILD_FAIL=1: CI-only; forces the requirement-9 optimized build to fail rem deterministically (without attempting a real Nuitka build), so the "original EXE left rem completely untouched on failure" guarantee can be tested without depending on a real, rem environment-dependent Nuitka build outcome. Mirrors HP_TEST_FORCE_NUITKA_FAIL. set "HP_TEST_FORCE_OPTBUILD_FAIL=%HP_TEST_FORCE_OPTBUILD_FAIL%" rem HP_TEST_FORCE_OPTBUILD_SWAP_FAIL=1: CI-only; forces the requirement-9 post-verification rem swap (move /y the verified temp EXE into place) to fail deterministically, by skipping the rem real move and leaving the temp file in place -- reproducing the exact "source still exists rem after move" signature a genuine failed swap (e.g. an AV/indexer lock on the destination) rem would leave, without depending on an artificial OS-level file lock. Proves the original EXE rem is left untouched and HP_NUITKA_FALLBACK_USED is never set when the swap itself fails, even rem though the build and verification both succeeded. set "HP_TEST_FORCE_OPTBUILD_SWAP_FAIL=%HP_TEST_FORCE_OPTBUILD_SWAP_FAIL%" rem HP_SKIP_PEP723_WRITEBACK=1: REQ-005.11/[REQ-019] opt-out -- skip the PEP 723 header rem write-back (uv add --script) that otherwise runs after a fresh dependency install or a rem successful warnfix repair in uv mode. Suppression-only, per [REQ-019]: absence never rem blocks a Prime-Directive-needed behavior; setting this only disables an optional step. set "HP_SKIP_PEP723_WRITEBACK=%HP_SKIP_PEP723_WRITEBACK%" rem derived requirement: CI's conda-only lane must surface conda regressions instead of masking them with opt-in fallbacks. if "%HP_FORCE_CONDA_ONLY%"=="1" ( rem derived requirement: conda-full diagnostics must avoid venv/system fallbacks so iterate can flag real conda regressions. set "HP_ALLOW_SYSTEM_FALLBACK=" call :log "[INFO] Conda-only flag active: fallbacks disabled." ) if not defined HP_NDJSON if exist "%CD%\tests" set "HP_NDJSON=%CD%\tests\~test-results.ndjson" if defined HP_NDJSON ( for %%F in ("%HP_NDJSON%") do ( if not "%%~dpF"=="" if not exist "%%~dpF" mkdir "%%~dpF" >nul 2>&1 ) if not exist "%HP_NDJSON%" ( type nul > "%HP_NDJSON%" ) ) rem --- CI fast path (entry tests only) --- call :rotate_log rem HP_* variables represent "Helper Payload" assets emitted on demand -- now decoded rem earlier, right before :merge_git_config, not here; see that call site's own comment. for %%I in ("%CD%") do set "ENVNAME=%%~nI" rem derived requirement: conda env names reject characters like '~'; self env smoke rem scenarios run from tests\~envsmoke so normalize to ASCII word chars/_/-. rem CLAUDE.md Item 26: '&' is special-cased to the bare word 'and' BEFORE the blanket rem substitution below -- the blanket rule alone already avoids the real hazard (a raw '&' rem confuses URL query-string parsing and renders oddly in Outlook), but collapses it to '_' rem like any other stripped character, losing readability ("Sales & Marketing" -> a folder a rem user might rename and email becoming "Sales___Marketing.exe" instead of the more legible rem "Sales_and_Marketing.exe"). Deliberately a bare word (no surrounding underscores): the rem existing spaces on either side of '&' are still converted to '_' by the blanket rule right rem after, so "Sales & Marketing" -> "Sales and Marketing" -> "Sales_and_Marketing" without rem this substitution needing to supply its own separators. rem derived requirement: a CodeRabbit review finding on this same PR -- '&' -> 'and' is a 1-to-3 rem character expansion, so a folder name unusually heavy in '&' could make the sanitized name rem LONGER than the original (every other stripped character before this change was a 1-to-1 rem substitution, never lengthening the result). Bounded to 64 chars post-substitution -- ample rem for a real project folder name, well clear of Windows/conda env-name length limits for rem everything this value later becomes (ENV_PATH, dist\.exe). set "ENVNAME_ORIG=%ENVNAME%" set "ENVNAME_SANITIZED=" for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$name = $env:ENVNAME; if (-not $name) { $name = 'env'; } $name = ($name -replace '&', 'and'); $san = ($name -replace '[^A-Za-z0-9_-]', '_'); $san = ($san -replace '^-+', '_'); if ($san.Length -gt 64) { $san = $san.Substring(0, 64).TrimEnd('_', '-') }; if ([string]::IsNullOrWhiteSpace($san) -or ($san.Trim('_', '-').Length -eq 0)) { $san = 'env'; } [Console]::Write($san)"` ) do set "ENVNAME_SANITIZED=%%I" rem derived requirement: fail CLOSED, not open -- if the PowerShell sanitization command itself rem errored or emitted nothing (missing powershell.exe, execution-policy lockdown, etc.), silently rem falling through to the raw, UNSANITIZED folder name would defeat this whole guard (a leading rem hyphen or an embedded '&' would flow straight to `conda create -n` / the exported filename). if defined ENVNAME_SANITIZED ( set "ENVNAME=%ENVNAME_SANITIZED%" ) else ( call :log "[WARN] REQ-004: env-name sanitization command produced no output; falling back to 'env' for safety." set "ENVNAME=env" ) set "ENVNAME_SANITIZED=" rem derived requirement: a leading hyphen is replaced above because `conda create -n -foo` rem parses the name as a command-line flag (malformed); internal hyphens (my-app) are kept. rem G1 guardrail: warn when folder name contained only non-word chars and defaulted to 'env' if "%ENVNAME%"=="env" if not "%ENVNAME_ORIG%"=="env" ( call :log "[WARN] Env name could not be derived from '%ENVNAME_ORIG%'; defaulting to 'env'." ) set "ENVNAME_ORIG=" call :log "[INFO] Environment name: %ENVNAME%" rem --- Host environment diagnostics (confirm runner OS/PS version for every CI run) --- for /f "tokens=*" %%V in ('ver') do call :log "[INFO] Host OS: %%V" for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$PSVersionTable.PSVersion.ToString()" 2^>nul`) do call :log "[INFO] Host PowerShell: %%P" set "PYCOUNT=0" for /f "delims=" %%F in ('dir /b /a-d *.py 2^>nul') do call :count_python "%%F" if "%PYCOUNT%"=="" set "PYCOUNT=0" call :log "[INFO] Python file count: %PYCOUNT%" rem derived requirement: REQ-011 cross-dir check moved to pre-flight so users get instant rem feedback rather than waiting through env creation to see the rejection error. if not "%~1"=="" if /i not "%~dp1"=="%~dp0" ( echo [ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness. call :log "[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder." call :write_status "error" 1 %PYCOUNT% exit /b 1 ) set "HP_CONDA_PROBE_STATUS=skipped" set "HP_CONDA_PROBE_REASON=not-requested" rem Slice 2b-C: compute the shared interactivity determination once, before the very first rem :try_fast_exe call below, so both untimed user-code launch points dispatch consistently. call :compute_interactive_run rem --- Very top EXE fast path: reuse dist\%ENVNAME%.exe when sources are unchanged --- set "HP_FASTPATH_USED=" rem CLAUDE.md Active Backlog Item 39 (CodeRabbit review, PR #460): defensive reset. Every real rem write site for HP_FRESH_BUILD_OK lives inside :run_entry_smoke, whose own per-build-attempt rem reset sits past a preflight-failure early-return (HP_PREFLIGHT_FAILED) -- a provider-cascade rem re-entry that hits that early return would skip the in-subroutine reset. Clearing it here too, rem before the very first :try_fast_exe call of the whole run, closes that gap unconditionally so rem :write_fast_hash below can never fire off a value this run never genuinely earned. set "HP_FRESH_BUILD_OK=" rem REQ-016: start clean so an inherited env var can never trigger a false "EXE rem unverified" caveat; :run_exe_smokerun sets this only on a real non-zero EXE exit. set "HP_EXE_VERIFY_FAILED=" rem [REQ-027] P2 honest messaging: mirrors HP_EXE_VERIFY_FAILED above, but for the NO-EXE rem interpreter path -- :verify_no_exe_interpreter sets this only on a real non-zero rem interpreter exit, so :print_no_exe_briefing can stop unconditionally claiming success. set "HP_NOEXE_VERIFY_FAILED=" rem REQ-012: HP_EXE_SKIPPED records that EXE verification was skipped by request rem (HP_SKIP_EXE_SMOKERUN) -- distinct from "failed" -- for the post-flight note. set "HP_EXE_SKIPPED=" rem Slice 2b-C: start clean so an inherited HP_SMOKE_RC (e.g. from a parent shell/CI wrapper) rem can never be misread as "the just-attempted run failed" below -- :try_fast_exe's own rem REQ-012 HP_SKIP_EXE_SMOKERUN early-return leaves HP_SMOKE_RC untouched by design (no run rem happened), and the HP_FASTPATH_RUN_FAILED check right after this call relies on that rem meaning "empty", not "whatever happened to be inherited." set "HP_SMOKE_RC=" if not "%PYCOUNT%"=="0" ( call :try_fast_exe ) rem Slice 2b-C: HP_FASTPATH_USED alone is no longer proof of a clean run -- the interactive rem fail-fast probe can leave it set even when the reused EXE later exited non-zero (it is rem classified alive/healthy at the probe and is never discarded/rebuilt for a later failure; rem see :try_fast_exe). Decouple "keep the cached EXE, skip the rebuild" from "declare full rem success" so that outcome is never silently swallowed: HP_SMOKE_RC is empty when the run rem never happened or was skipped by request (REQ-012, HP_EXE_SKIPPED) -- still the rem zero-friction path -- and is a real non-zero value only for a genuine post-probe failure. set "HP_FASTPATH_RUN_FAILED=" if defined HP_SMOKE_RC if not "%HP_SMOKE_RC%"=="0" set "HP_FASTPATH_RUN_FAILED=1" if defined HP_FASTPATH_USED ( if defined HP_FASTPATH_RUN_FAILED ( rem :run_failfast_probe already logged "[STATUS] Run Status: FAILED, Exit Code: ..." rem for this exact run -- it always fires before returning here -- add only the extra rem context, not a duplicate STATUS line. call :log "[WARN] dist\%ENVNAME%.exe (standalone EXE, PyInstaller build) ran to completion and exited non-zero after passing the fail-fast probe; treated as your program's own result, not a rebuild trigger." ) else ( rem derived requirement: if the EXE fast path succeeds, treat bootstrap as complete without touching Conda/venv. call :log "[INFO] Fast path: skipping PyInstaller rebuild for existing dist\%ENVNAME%.exe" ) if /I "%HP_BOOTSTRAP_STATE%"=="ok" ( call :write_status ok 0 %PYCOUNT% ) else ( call :write_status "%HP_BOOTSTRAP_STATE%" 0 %PYCOUNT% ) goto :success ) set "HP_FASTPATH_RUN_FAILED=" if "%PYCOUNT%"=="0" ( rem derived requirement: CI observed the Miniconda probe firing before the rem empty-repo fast path, so keep this guard ahead of any network/bootstrap rem calls to avoid flaky failures when no Python sources exist. echo Python file count: %PYCOUNT% >> "%LOG%" echo Python file count: %PYCOUNT% echo No Python files detected; skipping environment bootstrap. >> "%LOG%" echo No Python files detected; skipping environment bootstrap. call :log "[INFO] No Python files detected; skipping environment bootstrap." call :check_hidden_ext_hint call :check_subfolder_hint call :write_status no_python_files 0 %PYCOUNT% goto :success ) if defined HP_CI_SKIP_ENV goto :ci_skip_entry rem === uv acquisition (preferred env+dep installer; falls back to conda) ======= rem derived requirement: uv is gated by HP_FORCE_CONDA_ONLY (same gate used for rem venv/system fallbacks) so the conda-full CI lane exercises the pure conda path. rem The binary is cached under ~uv_bin\ (tilde-prefix keeps it gitignored). rem Orchestration layer: force uv to use only its own managed CPython toolchain and rem ignore any ambient/legacy system or conda interpreter on PATH/registry (e.g. the rem GitHub runner's hostedtoolcache Python). With no user version constraint uv then rem selects the latest managed CPython; a user runtime.txt/pyproject.toml is still rem honored via the --python forwarding applied downstream where the uv venv is created rem (HP_UV_PY_REQ; loose constraints forward the range so uv picks the latest satisfying rem version, exact pins stay fixed). Set before the PVW_UV_EXE branch and before the first uv rem invocation so every uv command (run, rem venv, pip) in this process inherits it. See docs/agent-lessons-learned.md. set "UV_PYTHON_PREFERENCE=only-managed" call :log "[INFO] uv: UV_PYTHON_PREFERENCE=only-managed (orchestration uses managed Python)." if not defined PVW_UV_EXE goto :pvw_uv_exe_skip set "HP_UV_EXE=%PVW_UV_EXE%" call :log "[INFO] uv: using super-user override PVW_UV_EXE." goto :uv_acquire_done :pvw_uv_exe_skip set "HP_UV_EXE=" set "HP_UV_BIN=%HP_SCRIPT_ROOT%~uv_bin" set "HP_UV_ZIP=%TEMP%\~uv_setup.zip" if "%HP_FORCE_CONDA_ONLY%"=="1" ( call :log "[INFO] uv: skipped (HP_FORCE_CONDA_ONLY=1)." goto :uv_acquire_done ) if "%HP_TEST_FORCE_UV_FAIL%"=="1" ( call :log "[WARN] uv: HP_TEST_FORCE_UV_FAIL: simulating uv acquisition failure." set "UV_FALLBACK_REASON=test_forced_fail" call :log "[WARN] UV_FALLBACK reason=test_forced_fail" goto :uv_acquire_done ) if exist "%HP_UV_BIN%\uv.exe" ( if defined HP_TEST_CORRUPT_UV ( call :log "[WARN] HP_TEST_CORRUPT_UV: simulating corrupt uv binary; clearing cache." del /f /q "%HP_UV_BIN%\uv.exe" >nul 2>&1 goto :uv_acquire_download ) "%HP_UV_BIN%\uv.exe" --version >nul 2>&1 if errorlevel 1 ( call :log "[WARN] Cached uv.exe failed health check; clearing and re-downloading." del /f /q "%HP_UV_BIN%\uv.exe" >nul 2>&1 goto :uv_acquire_download ) set "HP_UV_EXE=%HP_UV_BIN%\uv.exe" call :log "[INFO] uv: cached binary found at ~uv_bin\uv.exe" goto :uv_acquire_done ) :uv_acquire_download if "%HP_OFFLINE_MODE%"=="1" ( call :log "[INFO] REQ-013: Offline mode: skipping uv download." set "UV_FALLBACK_REASON=offline" call :log "[WARN] UV_FALLBACK reason=offline" goto :uv_acquire_done ) call :log "[INFO] uv: downloading to ~uv_bin..." if not exist "%HP_UV_BIN%" mkdir "%HP_UV_BIN%" >nul 2>&1 set "HP_UV_ACTIVE_URL=%HP_UV_URL%" if "%HP_TEST_UV_DL_FALLBACK%"=="1" if not defined HP_UV_DL_INJECTED set "HP_UV_ACTIVE_URL=https://uv-test-fail.invalid/uv-x86_64-pc-windows-msvc.zip" call :log "[INFO] Downloading uv from %HP_UV_ACTIVE_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_UV_ACTIVE_URL%" -o "%HP_UV_ZIP%" >> "%LOG%" 2>&1 if errorlevel 1 if exist "%HP_UV_ZIP%" del "%HP_UV_ZIP%" >nul 2>&1 if not exist "%HP_UV_ZIP%" ( if not "%HP_TEST_UV_DL_FALLBACK%"=="1" call :check_net_after_dl_fail ) if not exist "%HP_UV_ZIP%" if not "%HP_OFFLINE_MODE%"=="1" ( if defined HP_UV_DL_INJECTED ( call :log "[ERROR] Injected HP_UV_URL failed; not trying fallback." ) else ( call :log "[INFO] Trying fallback uv URL: %HP_UV_FALLBACK_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_UV_FALLBACK_URL%" -o "%HP_UV_ZIP%" >> "%LOG%" 2>&1 if errorlevel 1 if exist "%HP_UV_ZIP%" del "%HP_UV_ZIP%" >nul 2>&1 if exist "%HP_UV_ZIP%" ( call :log "[INFO] uv download succeeded from fallback URL." ) else ( call :log "[WARN] uv: all download URLs failed." ) ) ) if exist "%HP_UV_ZIP%" ( for %%S in ("%HP_UV_ZIP%") do ( if %%~zS GEQ %HP_UV_MIN_BYTES% ( powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Expand-Archive -LiteralPath '%HP_UV_ZIP%' -DestinationPath '%HP_UV_BIN%' -Force } catch { exit 1 }" >> "%LOG%" 2>&1 ) else ( call :log "[WARN] uv: zip too small (%%~zS bytes); skipping extract." ) ) del "%HP_UV_ZIP%" >nul 2>&1 ) if exist "%HP_UV_BIN%\uv.exe" ( set "HP_UV_EXE=%HP_UV_BIN%\uv.exe" call :log "[INFO] uv: acquired at ~uv_bin\uv.exe" ) else ( call :log "[WARN] uv: acquisition failed; will use conda for env creation." set "UV_FALLBACK_REASON=acquire_failed" call :log "[WARN] UV_FALLBACK reason=acquire_failed" ) :uv_acquire_done rem === uv-first: skip Miniconda when uv can provide Python ==================== rem derived requirement: when uv is available, use it to run ~detect_python.py rem instead of CONDA_BASE_PY so the Miniconda download can be skipped entirely. rem HP_FORCE_CONDA_ONLY already cleared HP_UV_EXE above, so no extra check needed. if not defined HP_UV_EXE goto :uv_first_skip set "HP_RUNTIME_TXT_PREEXIST=" if exist "runtime.txt" set "HP_RUNTIME_TXT_PREEXIST=1" call :emit_from_base64 "~detect_python.py" HP_DETECT_PY if errorlevel 1 goto :uv_first_skip "%HP_UV_EXE%" run --no-project python "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%" if errorlevel 1 ( call :log "[WARN] uv-first: detect_python via uv run failed; will download Miniconda." goto :uv_first_skip ) set "PYSPEC=" for /f "usebackq delims=" %%A in ("~py_spec.txt") do set "PYSPEC=%%A" set "HP_UV_PROVIDING_PYTHON=1" call :log "[INFO] uv-first: Miniconda download skipped." :uv_first_skip rem === Miniconda URL probe (CI only, deferred after uv detection) ============== rem derived requirement: probe deferred to after uv detection so that uv-first rem runs skip the ~99 MB download when Miniconda will not be used. The probe rem verifies the Miniconda URL is reachable only when conda is actually needed. if "%HP_CI_TEST_CONDA_DL%"=="1" ( if not defined HP_CI_SKIP_ENV ( if not defined HP_UV_PROVIDING_PYTHON ( set "HP_CONDA_PROBE_STATUS=ran" call :probe_conda_url if errorlevel 1 ( rem derived requirement: CI observed the Miniconda probe erroring before real bootstrap. rem Emit a warning and continue so the actual install path can still run. set "HP_CONDA_PROBE_STATUS=failed" set "HP_CONDA_PROBE_REASON=probe-failed" call :log "[WARN] Miniconda download probe failed; continuing to bootstrap." ) ) else ( set "HP_CONDA_PROBE_REASON=uv-first" ) ) else ( set "HP_CONDA_PROBE_REASON=skip-env" ) ) if "%HP_CONDA_PROBE_STATUS%"=="skipped" ( call :emit_conda_probe_skip ) rem === Miniconda location (non-admin) ========================================= rem G2 guardrail: warn if PUBLIC is absent so path failures are observable if not defined PUBLIC call :log "[WARN] PUBLIC env var not defined; Miniconda path may be invalid." set "MC=%PUBLIC%\Documents\Miniconda3" set "CONDA_MAIN=%MC%\condabin\conda.bat" set "CONDA_ALT=%MC%\Scripts\conda.bat" set "MINICONDA_ROOT=%MC%" set "CONDA_BASE_PY=%MINICONDA_ROOT%\python.exe" call :select_conda_bat rem PVW_CONDA_EXE: super-user override for the conda batch file path. When set, Miniconda rem installation is skipped. Requires conda to already be on PATH (the 'where conda' probe rem below will fail gracefully if not). Typical usage: pointing at a system-wide conda install. if defined PVW_CONDA_EXE set "CONDA_BAT=%PVW_CONDA_EXE%" rem Install Miniconda if conda.bat is missing (skipped when uv is providing Python) set "HP_CONDA_JUST_INSTALLED=" if not defined HP_UV_PROVIDING_PYTHON if not defined CONDA_BAT ( set "HP_CONDA_JUST_INSTALLED=1" echo [INFO] Installing Miniconda into "%MINICONDA_ROOT%"... call :download_miniconda_exe if exist "%TEMP%\miniconda.exe" ( REM Attempt AllUsers install with JustMe fallback; see :try_conda_install. call :try_conda_install ) if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1 call :select_conda_bat ) set "PATH=%MINICONDA_ROOT%\condabin;%MINICONDA_ROOT%\Scripts;%MINICONDA_ROOT%\Library\bin;%MINICONDA_ROOT%;%PATH%" :after_conda_bat_validation if not defined HP_UV_PROVIDING_PYTHON if not defined CONDA_BAT ( set "HP_ENV_READY=" call :handle_conda_failure "conda.bat not found after bootstrap." if defined HP_ENV_READY goto :after_env_mode_selection call :die "[ERROR] conda.bat not found after bootstrap." goto :after_env_mode_selection ) rem === Fresh install: warm up conda to initialize base-env state (REQ-020) === rem derived requirement: Silent Miniconda install (/S /AddToPath=0) leaves the base rem environment in a state where conda info may return non-zero until conda is run rem once with its own directories in PATH. Calling conda info here (silently, after rem the PATH update) ensures subsequent bootstrap runs that find the pre-installed rem Miniconda pass the corruption health check instead of falsely flagging it corrupt. if defined HP_CONDA_JUST_INSTALLED if defined CONDA_BAT ( call "%CONDA_BAT%" info >nul 2>&1 ) rem === Validate existing conda binary health (REQ-020: corruption hardening) === rem Only fires for pre-existing installs (HP_CONDA_JUST_INSTALLED guards fresh downloads). rem Skipped when HP_TEST_FORCE_CONDA_FAIL=1 (test flag already simulates conda failure). rem Placed after PATH update so conda.bat internal calls can resolve their dependencies. if defined CONDA_BAT if not defined HP_CONDA_JUST_INSTALLED if not defined HP_TEST_FORCE_CONDA_FAIL ( if defined HP_TEST_CORRUPT_CONDA ( call :log "[ERROR] HP_TEST_CORRUPT_CONDA: simulating corrupt conda binary." goto :conda_binary_corrupt ) call "%CONDA_BAT%" info >nul 2>&1 if errorlevel 1 goto :conda_binary_corrupt ) if defined HP_UV_PROVIDING_PYTHON goto :after_conda_probes where conda >> "%LOG%" 2>&1 || ( set "HP_ENV_READY=" call :handle_conda_failure "[ERROR] 'conda' not found on PATH after bootstrap." if defined HP_ENV_READY goto :after_env_mode_selection call :die "[ERROR] 'conda' not found on PATH after bootstrap." goto :after_env_mode_selection ) where python >> "%LOG%" 2>&1 || ( set "HP_ENV_READY=" call :handle_conda_failure "[ERROR] 'python' not found on PATH after bootstrap." if defined HP_ENV_READY goto :after_env_mode_selection call :die "[ERROR] 'python' not found on PATH after bootstrap." goto :after_env_mode_selection ) python -V >> "%LOG%" 2>&1 || ( set "HP_ENV_READY=" call :handle_conda_failure "[ERROR] 'python -V' failed after bootstrap." if defined HP_ENV_READY goto :after_env_mode_selection call :die "[ERROR] 'python -V' failed after bootstrap." goto :after_env_mode_selection ) :after_conda_probes rem === Channel policy (determinism & legal) =================================== if not defined HP_UV_PROVIDING_PYTHON if not exist "%CONDA_BAT%" ( call :die "[ERROR] Conda not found at: %CONDA_BAT%" goto :after_env_mode_selection ) if not defined HP_UV_PROVIDING_PYTHON call "%CONDA_BAT%" config --name base --add channels conda-forge >> "%LOG%" 2>&1 rem NOTE: every 'conda create' or 'conda install' call below MUST include: rem --override-channels -c conda-forge set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%" call :log "[INFO] Workspace: %CD%" call :log "[INFO] Env name: %ENVNAME%" call :log "[INFO] Log: %LOG%" if not defined HP_UV_PROVIDING_PYTHON ( set "HP_RUNTIME_TXT_PREEXIST=" if exist "runtime.txt" set "HP_RUNTIME_TXT_PREEXIST=1" ) rem --- Detect required Python version (must run before env-state check) --- rem derived requirement: PYSPEC must be known before the env-state skip decision rem so a runtime.txt / pyproject.toml change triggers a full env rebuild. rem uv-first path: already ran detect_python via uv run and set PYSPEC above. if defined HP_UV_PROVIDING_PYTHON goto :detect_python_done call :emit_from_base64 "~detect_python.py" HP_DETECT_PY if errorlevel 1 call :die "[ERROR] Could not write ~detect_python.py" if exist "%CONDA_BASE_PY%" ( "%CONDA_BASE_PY%" "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%" ) else ( call "%CONDA_BAT%" run -n base python "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%" ) set "PYSPEC=" for /f "usebackq delims=" %%A in ("~py_spec.txt") do set "PYSPEC=%%A" :detect_python_done if defined PVW_TARGET_PY set "PYSPEC=%PVW_TARGET_PY%" if defined PVW_TARGET_PY call :log "[INFO] Python version: using super-user override PVW_TARGET_PY." rem --- Env-state fast path: skip conda create+install if env is still valid --- rem derived requirement: ~env.state.json records envMode/envName/envPath/pySpec/lockSize rem from the last successful run. Fast path fires only when PYSPEC, envName, lock size, rem and python.exe all match the stored snapshot. rem uv-first path: env reuse is handled by the .uv_env existence check below; skip here. if defined HP_UV_PROVIDING_PYTHON goto :env_state_check_done set "HP_ENV_STATE_RESULT=" call :emit_from_base64 "~env_state.py" HP_ENV_STATE if errorlevel 1 goto :env_state_check_done if exist "%CONDA_BASE_PY%" ( "%CONDA_BASE_PY%" "~env_state.py" --check > "~env_state.txt" 2>> "%LOG%" ) else ( call "%CONDA_BAT%" run -n base python "~env_state.py" --check > "~env_state.txt" 2>> "%LOG%" ) for /f "usebackq delims=" %%E in ("~env_state.txt") do set "HP_ENV_STATE_RESULT=%%E" if exist "~env_state.txt" del "~env_state.txt" >nul 2>&1 if exist "~env_state.py" del "~env_state.py" >nul 2>&1 if /I "%HP_ENV_STATE_RESULT%"=="skip" ( call :log "[INFO] Env-state fast path: reusing conda env %ENVNAME%." goto :env_state_fast_path ) :env_state_check_done rem --- ENVNAME guard: default to 'env' if sanitization yielded an empty name --- if "%ENVNAME%"=="" ( call :log "[WARN] Conda env name resolved to empty; defaulting to 'env'." set "ENVNAME=env" ) rem Recalculate ENV_PATH so it is always consistent with the guarded ENVNAME value set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%" rem === uv venv creation (primary path when uv was acquired) ==================== rem derived requirement: uv creates a pip-native venv at .uv_env in the project rem folder, short-circuiting conda create. On failure, :try_conda_create runs the rem existing conda path unchanged. REQ-004 Tier 1-2 Python version (PYSPEC) is rem forwarded to uv via --python X.Y when a lower-bound version can be extracted. if not defined HP_UV_EXE goto :try_conda_create set "HP_UV_ENV_PATH=%HP_SCRIPT_ROOT%.uv_env" if defined PVW_WORKSPACE set "HP_UV_ENV_PATH=%PVW_WORKSPACE%" if exist "%HP_UV_ENV_PATH%\Scripts\python.exe" ( "%HP_UV_ENV_PATH%\Scripts\python.exe" -c "import pip;exit(0)" >nul 2>&1 if not errorlevel 1 ( set "HP_ENV_MODE=uv" set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe" set "ENV_PATH=%HP_UV_ENV_PATH%" call :log "[INFO] uv: reusing existing .uv_env" goto :uv_venv_ready ) ) rem Translate PYSPEC into a uv --python request (REQ-004 Tiers 1-2, floor-vs-pin). rem Two outputs, pipe-delimited: HP_UV_PY_REQ (forwarded to uv) and HP_UV_PY_DISP (log only). rem - Exact pins (python=X.Y runtime.txt, python==X.Y) -> bare "X.Y" (uv pins to X.Y). rem - Loose/range forms (python>=X.Y, python>X.Y, python>=X.Y, the RANGE itself, so rem uv resolves the LATEST satisfying managed CPython instead of the floor (matches the rem conda path, which hands the full range to its solver). rem CRITICAL: the range may contain < and > . Forward it ONLY through the double-quoted rem --python "%HP_UV_PY_REQ%" argument (quotes shield it from cmd's redirection parser). rem The :log line uses HP_UV_PY_DISP, which is operator-free ("X.Y" or "X.Y or newer"), rem because :log echoes its message UNQUOTED -- a raw < or > there would be a redirection. rem Single quotes only inside -Command (a literal " would close the cmd-level quote). rem [Console]::Write avoids the trailing CR that for /f does not strip. set "HP_UV_PY_REQ=" set "HP_UV_PY_DISP=" if defined PYSPEC ( for /f "usebackq tokens=1,2 delims=|" %%V in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$s = $env:PYSPEC; $req = ($s -replace '^python','').Trim(); if ($req -match '^==?([0-9].*)$') { $req = $Matches[1] }; $floor = ''; if ($s -match '([0-9]+\.[0-9]+)') { $floor = $Matches[1] }; $disp = $floor; if ($req -ne $floor) { $disp = $floor + ' or newer' }; [Console]::Write($req + '|' + $disp)"`) do ( set "HP_UV_PY_REQ=%%V" set "HP_UV_PY_DISP=%%W" ) ) if defined HP_UV_PY_REQ ( call :log "[INFO] uv: creating venv at .uv_env with Python %HP_UV_PY_DISP%..." "%HP_UV_EXE%" venv --seed --python "%HP_UV_PY_REQ%" "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1 ) else ( call :log "[INFO] uv: creating venv at .uv_env..." "%HP_UV_EXE%" venv --seed "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1 ) if errorlevel 1 goto :uv_venv_fail if not exist "%HP_UV_ENV_PATH%\Scripts\python.exe" goto :uv_venv_fail set "HP_ENV_MODE=uv" set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe" set "ENV_PATH=%HP_UV_ENV_PATH%" call :log "[INFO] uv: venv created at .uv_env" :uv_venv_ready call :log "[INFO] HP_ENV_MODE=uv" call :log "[BOOT] REQ-009: Selected Python provider: UV." call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER if not errorlevel 1 ( "%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%" for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A" if not defined HP_RUNTIME_TXT_PREEXIST call :write_runtime_txt ) if "%HP_TEST_UV_FAIL%"=="1" ( call :log "[TEST] Injecting uv dep install failure" "%HP_UV_EXE%" pip install --python "%HP_PY%" __hp_test_nonexistent_pkg_0xdeadbeef__ >> "%LOG%" 2>&1 call :log "[WARN] uv pip install -r requirements.txt failed; some packages may be missing." set "UV_FALLBACK_REASON=dep_install_failed" call :log "[WARN] UV_FALLBACK reason=dep_install_failed" ) goto :after_env_mode_selection :uv_venv_fail rem derived requirement: uv venv reads pyproject.toml for requires-python even when rem --python is not passed, so a malformed pyproject.toml causes venv creation to fail. rem When HP_UV_PROVIDING_PYTHON=1 (conda not installed), retry via "uv run --no-project rem python -m venv" which bypasses project discovery and ignores pyproject.toml. rem The HP_PYPROJ_DEPS path (line ~712) later detects the malformed TOML and emits the rem [WARN] pyproject.toml TOML parse error message as normal. if defined HP_UV_PROVIDING_PYTHON ( call :log "[WARN] uv: venv creation failed; retrying via uv run --no-project (malformed pyproject.toml guard)." if defined HP_UV_PY_REQ ( "%HP_UV_EXE%" run --no-project --python "%HP_UV_PY_REQ%" python -m venv "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1 ) else ( "%HP_UV_EXE%" run --no-project python -m venv "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1 ) if not errorlevel 1 ( if exist "%HP_UV_ENV_PATH%\Scripts\python.exe" ( set "HP_ENV_MODE=uv" set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe" set "ENV_PATH=%HP_UV_ENV_PATH%" call :log "[INFO] uv: venv created at .uv_env via uv run --no-project fallback" goto :uv_venv_ready ) ) call :log "[WARN] uv: uv run --no-project venv also failed; falling back to conda create." ) call :log "[WARN] uv: venv creation failed; falling back to conda create." set "UV_FALLBACK_REASON=venv_create_failed" call :log "[WARN] UV_FALLBACK reason=venv_create_failed" set "HP_UV_EXE=" :try_conda_create call :log "[INFO] HP_ENV_MODE=conda" if "%HP_TEST_FORCE_CONDA_FAIL%"=="1" goto :hp_test_conda_fail rem derived requirement: conda env create can take several minutes; emit a user-facing message rem so the script never appears to hang silently during the longest single step. call :log "[INFO] Creating Python environment '%ENVNAME%' -- this may take several minutes..." rem REQ-022: transient-retry for conda create, mirroring the proven :conda_bulk_install pattern rem (findstr-detect a transient network error, wait 15s, retry once). Goto-based dispatch on rem purpose (see docs/agent-lessons-learned.md "Provider-cascade dispatch is goto-based on rem purpose"): the create call + %ERRORLEVEL% capture is never nested inside a parenthesized rem if/else block, so cmd's parse-time %VAR% expansion cannot freeze it to a stale value. if exist "~conda_create.tmp" del "~conda_create.tmp" >nul 2>&1 if "%HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL%"=="1" goto :conda_create_test_network_fail rem [TEST] HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL: like HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL, rem but genuinely fails BOTH the initial attempt and the retry (not cleared after the first rem simulated failure -- see the retry call site below), so :conda_create_failed is reached rem through the real create/retry code path instead of the :hp_test_conda_fail bypass. Exists rem to test CLAUDE.md Active Backlog Item 23's cascade-restore fix, which specifically needs a rem genuine (not HP_TEST_FORCE_CONDA_FAIL-style) failure reaching :conda_create_failed during a rem cascade re-entry. if "%HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL%"=="1" goto :conda_create_test_network_fail rem derived requirement: HP_PYSPEC_WRITEBACK marks a PYSPEC value that was rem self-written moments ago by a DIFFERENT provider's own resolved interpreter rem (see :write_runtime_txt and its two inline duplicates) -- an artifact of rem whichever provider happened to run first, not a genuine user requirement. rem Forcing that exact patch-level pin onto conda's own solver is unsafe: conda- rem forge's python package availability is a wholly separate release cadence rem from CPython's/uv's, so an exact pin like python=3.14.7 can be genuinely rem unresolvable there even though it worked fine under uv moments earlier rem (confirmed via real CI: self.layered_e2e.chain's uv-to-conda cascade failed rem with PackagesNotFoundInChannelsError for exactly this reason). A REAL rem pre-existing user pin (HP_RUNTIME_TXT_PREEXIST defined, or PYSPEC sourced rem from pyproject.toml/PEP 723 without ever going through write-back) is left rem untouched -- only the self-authored, write-back-derived exact pin is dropped. rem HP_PYSPEC_ORIGINAL (snapshotted at each write-back site immediately before rem PYSPEC was overwritten) preserves whatever constraint existed BEFORE rem write-back fired -- a genuine pyproject.toml/PEP 723 requires-python range rem (Tier 2) or nothing at all (Tier 3, both PYSPEC and PYSPEC_ORIGINAL empty) -- rem so a real user-authored range constraint still reaches conda's solver rem instead of being discarded along with the bad exact pin. set "HP_CONDA_PYSPEC_USE=%PYSPEC%" if defined HP_PYSPEC_WRITEBACK set "HP_CONDA_PYSPEC_USE=%HP_PYSPEC_ORIGINAL%" set "HP_CONDA_PYSPEC_SKIP=" if "%HP_CONDA_PYSPEC_USE%"=="" set "HP_CONDA_PYSPEC_SKIP=1" if defined HP_PYSPEC_WRITEBACK if not "%HP_PYSPEC_ORIGINAL%"=="" call :log "[INFO] conda create: dropping the write-back-derived exact Python pin; using the original pre-write-back Python constraint instead." if defined HP_PYSPEC_WRITEBACK if "%HP_PYSPEC_ORIGINAL%"=="" call :log "[INFO] conda create: dropping the write-back-derived exact Python pin; conda will resolve its own latest compatible Python instead." rem derived requirement: PYSPEC/HP_CONDA_PYSPEC_USE can legitimately contain PEP rem 440 range operators (>=, <, <=) once sourced from pyproject.toml's rem requires-python -- pep440_to_conda's own output -- unquoted on this command line, rem cmd.exe would parse a bare < or > as a real redirection operator and corrupt rem the create command (a genuine pre-existing bug found while adding this rem HP_PYSPEC_ORIGINAL fallback, which specifically increases how often a rem range value reaches this exact line). Quoting protects it the same way rem HP_UV_PY_REQ is already quoted at its own uv venv --python call site. if defined HP_CONDA_PYSPEC_SKIP ( call "%CONDA_BAT%" create -y -n "%ENVNAME%" python pip --override-channels -c conda-forge > "~conda_create.tmp" 2>&1 ) else ( call "%CONDA_BAT%" create -y -n "%ENVNAME%" "%HP_CONDA_PYSPEC_USE%" pip --override-channels -c conda-forge > "~conda_create.tmp" 2>&1 ) set "HP_CCREATE_RC=%ERRORLEVEL%" goto :conda_create_have_rc :conda_create_test_network_fail rem [TEST] HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL: simulate a transient CondaHTTPError on the rem first conda-create attempt only (mirrors HP_TEST_FORCE_CONDA_NETWORK_FAIL for bulk-install). echo CondaHTTPError: HTTP 000 CONNECTION FAILED (simulated) > "~conda_create.tmp" set "HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL=" set "HP_CCREATE_RC=1" :conda_create_have_rc type "~conda_create.tmp" >> "%LOG%" if not "%HP_CCREATE_RC%"=="0" goto :conda_create_check_transient del "~conda_create.tmp" >nul 2>&1 goto :conda_create_done :conda_create_check_transient findstr /i /c:"CondaHTTPError" /c:"Failed to fetch" /c:"timed out" /c:"ConnectionError" "~conda_create.tmp" >nul 2>&1 set "HP_CCREATE_TRANSIENT_RC=%ERRORLEVEL%" del "~conda_create.tmp" >nul 2>&1 if not "%HP_CCREATE_TRANSIENT_RC%"=="0" goto :conda_create_failed echo Conda environment creation failed -- possible network or repository issue. Retrying once... call :log "[INFO] conda create: transient failure detected; retrying after 15s." timeout /t 15 /nobreak >nul 2>&1 echo Retrying environment creation... if "%HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL%"=="1" goto :conda_create_retry_forced_fail rem Same HP_CONDA_PYSPEC_USE/HP_CONDA_PYSPEC_SKIP decision as the initial attempt rem above -- PYSPEC/HP_PYSPEC_ORIGINAL/HP_PYSPEC_WRITEBACK are unchanged since rem then, so both are still valid. if defined HP_CONDA_PYSPEC_SKIP ( call "%CONDA_BAT%" create -y -n "%ENVNAME%" python pip --override-channels -c conda-forge >> "%LOG%" 2>&1 ) else ( call "%CONDA_BAT%" create -y -n "%ENVNAME%" "%HP_CONDA_PYSPEC_USE%" pip --override-channels -c conda-forge >> "%LOG%" 2>&1 ) if not errorlevel 1 goto :conda_create_done :conda_create_retry_forced_fail echo *** Conda environment creation could not complete. This may be a temporary network issue. echo *** See log file for details: ~setup.log call :log "[WARN] conda create: retry after transient failure also failed." :conda_create_failed set "HP_ENV_READY=" call :handle_conda_failure "[ERROR] conda env create failed." if defined HP_ENV_READY goto :after_env_mode_selection rem derived requirement: a genuine conda-create failure reached via a REQ-009 cascade re-entry rem (HP_CASCADE_SAVED_PY defined, see :provider_cascade) must gracefully keep the previous rem working build instead of hard-failing the whole bootstrap, matching every other rem cascade-target failure (:cascade_conda_unavailable etc.) -- see CLAUDE.md Active Backlog rem Item 23 for the full trace of why this was previously missing. On a genuine first attempt rem (no earlier build to fall back to), HP_CASCADE_SAVED_PY is never defined, so this check is a rem no-op and the existing hard-failure behavior below is unchanged. if defined HP_CASCADE_SAVED_PY goto :cascade_conda_create_failed rem CLAUDE.md Active Backlog Item 46 (Bucket A, slice 1): :die returns via exit /b, not a rem process halt (see docs/agent-lessons-learned.md's ":die" entry), so without this goto, rem falling through here means EVERY tier (uv already failed earlier, conda create just rem failed, embed/venv/system all exhausted by :handle_conda_failure above) has been tried rem and none worked -- yet execution would still fall into :conda_create_done, which rem unconditionally sets HP_PY to a path that provably does not exist, immediately re-detects rem that via its own "if not exist" guard, and calls :handle_conda_failure A SECOND TIME. rem This is not just wasted CPU: :handle_conda_failure re-attempts embed download, venv rem creation, AND the REQ-014 system-Python consent prompt -- for a real user, this means rem watching the same failing embed/venv attempts a second time and being asked the same rem system-Python "keep going?" question again right after already answering it once. Skipping rem straight to :after_env_mode_selection (the same target the already-shipped :hp_test_conda_ rem fail sibling a few thousand lines below has used since before this fix) avoids that replay rem -- note this does not, by itself, reduce the total pause count to one: :after_env_mode_ rem selection's own "if not defined HP_PY" check (a few dozen lines below this label) has the rem identical non-halting :die shape and is reached here since HP_PY was never set this run; rem that is a separate, not-yet-addressed call site, tracked as a follow-up Bucket A slice. call :die "[ERROR] conda env create failed." goto :after_env_mode_selection :conda_create_done set "CONDA_PREFIX=%ENV_PATH%" set "HP_PY=%CONDA_PREFIX%\python.exe" rem [TEST] HP_TEST_FORCE_CONDA_MISSING_PYTHON: forces the "conda create genuinely succeeded but rem python.exe is missing afterward" branch below through a REAL successful create (not a rem simulated create-command failure) -- deletes the real python.exe a genuine create just rem produced. Exists to test the OTHER call site of the CLAUDE.md Item 23 cascade-restore fix rem (see :conda_create_failed's own comment above); selfapps_cascade_conda_create_fail.ps1's rem missing_python scenario is the only caller. if "%HP_TEST_FORCE_CONDA_MISSING_PYTHON%"=="1" if exist "%HP_PY%" del /f /q "%HP_PY%" >nul 2>&1 if not exist "%HP_PY%" ( set "HP_ENV_READY=" call :handle_conda_failure "[ERROR] python.exe missing from conda environment." if defined HP_ENV_READY goto :after_env_mode_selection if defined HP_CASCADE_SAVED_PY goto :cascade_conda_create_failed call :die "[ERROR] python.exe missing from conda environment." goto :after_env_mode_selection ) call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER if errorlevel 1 call :die "[ERROR] Could not write ~print_pyver.py" "%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%" for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A" if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" ( >"runtime.txt" echo %PYVER% if errorlevel 1 ( call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active." ) else ( call :log "[INFO] runtime.txt written: %PYVER%" set "HP_PYSPEC_ORIGINAL=%PYSPEC%" set "PYSPEC=%PYVER:python-=python=%" set "HP_PYSPEC_WRITEBACK=1" ) ) rem README.md documents the conda-forge policy for this project and why .condarc is required. rem Emit the .condarc payload from base64 so quoting stays robust on Windows CMD. call :emit_from_base64 "~condarc" HP_CONDARC if errorlevel 1 ( call :die "[ERROR] Could not stage ~condarc" goto :after_env_mode_selection ) if not exist "%ENV_PATH%" mkdir "%ENV_PATH%" copy /y "~condarc" "%ENV_PATH%\.condarc" >> "%LOG%" 2>&1 if errorlevel 1 call :die "[ERROR] Could not write %ENV_PATH%\.condarc" call :log "[BOOT] REQ-009: Selected Python provider: Conda (Portable)." goto :after_env_mode_selection :env_state_fast_path rem derived requirement: env exists from prior run; set interpreter, skip create+install. set "CONDA_PREFIX=%ENV_PATH%" set "HP_PY=%CONDA_PREFIX%\python.exe" if not exist "%HP_PY%" ( call :log "[WARN] Env-state fast path: %HP_PY% not found; falling back to full env rebuild." set "HP_ENV_STATE_RESULT=stale" goto :env_state_check_done ) call :log "[BOOT] REQ-009: Selected Python provider: Conda (Portable) [fast path]." call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER if not errorlevel 1 ( "%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%" for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A" if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" ( >"runtime.txt" echo %PYVER% if errorlevel 1 ( call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active." ) else ( call :log "[INFO] runtime.txt written: %PYVER%" set "HP_PYSPEC_ORIGINAL=%PYSPEC%" set "PYSPEC=%PYVER:python-=python=%" set "HP_PYSPEC_WRITEBACK=1" ) ) ) :after_env_mode_selection rem derived requirement: this label is the shared re-entry convergence point for every REQ-009 rem provider-selection success (first-time AND cascade-driven, see docs/agent-interconnect.md rem "Provider cascade execution re-enters env-create") -- clear the cascade save/restore slot rem here so a LATER pass's cascade decline (falling through to :after_cascade_decision without rem ever re-entering :provider_cascade) does not restore a now-stale HP_PY from an earlier, rem already-successful cascade. set "HP_CASCADE_SAVED_PY=" if defined PVW_PYTHON_EXE set "HP_PY=%PVW_PYTHON_EXE%" if defined PVW_PYTHON_EXE call :log "[INFO] Python host: using super-user override PVW_PYTHON_EXE." rem === Conda base periodic update (~30 days) ==================================== rem derived requirement: README.md requires periodic conda base update; skip on rem first install (timestamp seeded) and when uv env is in use. call :conda_base_update rem === end conda base update ==================================================== call :emit_from_base64 "~prep_requirements.py" HP_PREP_REQUIREMENTS if errorlevel 1 call :die "[ERROR] Could not write ~prep_requirements.py" set "REQ=requirements.txt" if exist "%REQ%" ( for %%S in ("%REQ%") do if %%~zS EQU 0 del "%REQ%" ) if exist "%REQ%" ( echo *** [INFO] Using requirements.txt for dependencies echo *** [INFO] Dependency accuracy depends on file correctness set "DEP_SOURCE=requirements.txt" set "DEP_LAYER_REQUIREMENTS=1" call :log "[INFO] DEP_LAYER_REQUIREMENTS=1" call :log "[TRACE] dep source selected: requirements.txt" ) set "HP_JOB_SUMMARY=~pipreqs.summary.txt" if exist "%HP_JOB_SUMMARY%" del "%HP_JOB_SUMMARY%" if not defined HP_PY ( set "HP_NO_INTERPRETER=1" call :die "[ERROR] Active Python interpreter not resolved." ) echo Interpreter: %HP_PY% >> "%LOG%" echo Interpreter: %HP_PY% call :append_env_mode_row "%HP_PY%" -c "print('py_ok')" 1>nul 2>nul || (call :log "[WARN] Interpreter smoke test failed (continuing)." & set "HP_NO_INTERPRETER=1") "%HP_PY%" -c "import sys;print(sys.version.split()[0])" > "~pyver_host.tmp" 2>nul if exist "~pyver_host.tmp" for /f "usebackq delims=" %%Y in ("~pyver_host.tmp") do call :log "[INFO] Host Python: %%Y" if exist "~pyver_host.tmp" del "~pyver_host.tmp" >nul 2>&1 set "PEP723_ACTIVE=" set "PEP723_BLOCK_FOUND=" set "PEP723_REQ=~requirements.pep723.txt" if exist "%PEP723_REQ%" del "%PEP723_REQ%" >nul 2>&1 call :determine_entry "%~1" if errorlevel 11 ( call :write_status "error" 1 %PYCOUNT% exit /b 1 ) if errorlevel 1 ( call :die "[ERROR] Could not determine entry point" goto :after_env_bootstrap ) rem derived requirement: do NOT interpolate %HP_APP_ARGS% into this message -- :log echoes rem UNQUOTED, and a user-supplied argument could contain < > | & (docs/agent-lessons-learned.md rem ":log echoes UNQUOTED"). This confirms forwarding is active without echoing arg content. if defined HP_APP_ARGS call :log "[INFO] REQ-026: extra launch argument(s) detected; they will be forwarded to your program at every launch." rem === REQ-005.13 (Tier 2, docs/plan-autopep723-two-tier.md): HP_PVW_KNOWN_IDEMPOTENT === rem Opt-in execute-mode dependency discovery: the user has explicitly declared their entry rem script safe to run more than once, so actually run it via uvx autopep723 (which persists rem what it needed into the entry file's own PEP 723 header on success). Never gates the rem Prime Directive: absence of the flag leaves this whole block a no-op. uv lane only. set "HP_UVX_EXE=%HP_UV_EXE:uv.exe=uvx.exe%" if defined HP_PVW_KNOWN_IDEMPOTENT if /I "%HP_ENV_MODE%"=="uv" if defined HP_ENTRY if exist "%HP_UVX_EXE%" ( call :pvw_known_idempotent_run ) set "HP_PYPROJ_REQ=~requirements.pyproject.txt" if exist "%HP_PYPROJ_REQ%" del "%HP_PYPROJ_REQ%" >nul 2>&1 set "HP_PYPROJ_ACTIVE=" if exist "pyproject.toml" ( call :emit_from_base64 "~pyproj_deps.py" HP_PYPROJ_DEPS if not errorlevel 1 ( "%HP_PY%" "~pyproj_deps.py" "%HP_PYPROJ_REQ%" >nul 2>&1 if errorlevel 1 ( rem derived requirement: check the highest threshold first -- "if errorlevel N" is a rem GEQ N test, so errorlevel 3 -- pyproj_deps.py's own unexpected-internal-error exit, rem distinct from its deliberate exit 1 "nothing to do here" -- would also satisfy rem "if errorlevel 2" and get mislabeled as a TOML parse error if checked second. if errorlevel 3 ( >> "%LOG%" echo pyproj_deps.py: unexpected internal error, exit 3; falling back to requirements.txt or pipreqs. ) else if errorlevel 2 ( echo *** [WARN] pyproject.toml could not be parsed as valid TOML; falling back to requirements.txt or pipreqs. call :log "[WARN] pyproject.toml TOML parse error; falling back." ) ) else ( if exist "%HP_PYPROJ_REQ%" for %%S in ("%HP_PYPROJ_REQ%") do if %%~zS GTR 0 set "HP_PYPROJ_ACTIVE=1" ) if exist "~pyproj_deps.py" del "~pyproj_deps.py" >nul 2>&1 ) ) if defined HP_PYPROJ_ACTIVE ( echo *** [INFO] pyproject.toml [project].dependencies found; overrides requirements.txt call :log "[INFO] pyproject.toml [project].dependencies detected" call :log "[INFO] DEP_SOURCE=pyproject" call :log "[INFO] DEP_LAYER_PYPROJECT=1" call :log "[TRACE] dep source selected: pyproject" set "DEP_SOURCE=pyproject" set "DEP_LAYER_PYPROJECT=1" copy /y "%HP_PYPROJ_REQ%" "requirements.txt" >nul 2>&1 ) if exist "%HP_PYPROJ_REQ%" del "%HP_PYPROJ_REQ%" >nul 2>&1 set "HP_PYPROJ_REQ=" set "HP_PYPROJ_ACTIVE=" if defined HP_ENTRY if exist "%HP_ENTRY%" ( findstr /c:"# /// script" "%HP_ENTRY%" >nul 2>&1 if not errorlevel 1 ( set "PEP723_BLOCK_FOUND=1" echo *** PEP 723 metadata detected call :extract_pep723_requirements "%HP_ENTRY%" "%PEP723_REQ%" if exist "%PEP723_REQ%" for %%S in ("%PEP723_REQ%") do if %%~zS GTR 0 set "PEP723_ACTIVE=1" ) ) if defined PEP723_BLOCK_FOUND if not defined PEP723_ACTIVE ( echo *** [WARN] PEP 723 block found but dependency list is empty or malformed; falling back call :log "[WARN] PEP 723 block found but no valid dependencies extracted; pipreqs fallback." ) set "PEP723_BLOCK_FOUND=" set "HP_PIPREQS_INSTALL_PASS=0" set "HP_PIPREQS_INSTALL_ATTEMPTED=0" if not defined HP_SKIP_PIPREQS if not defined PEP723_ACTIVE ( set "HP_PIPREQS_INSTALL_ATTEMPTED=1" if "%HP_ENV_MODE%"=="uv" ( rem derived requirement: uv pip install bypasses python -m pip so pip need not rem be a module inside the uv venv; uv's own resolver handles the installation. "%HP_UV_EXE%" pip install --python "%HP_PY%" -q pipreqs==%HP_PIPREQS_VERSION% >> "%LOG%" 2>&1 ) else ( "%HP_PY%" -m pip install -q --disable-pip-version-check pipreqs==%HP_PIPREQS_VERSION% >> "%LOG%" 2>&1 ) if errorlevel 1 ( call :log "[WARN] pipreqs install failed (Python version incompatible with pipreqs). Fallback: warnfix will detect and install missing imports at build time. Consider adding requirements.txt or pyproject.toml [project].dependencies for explicit dependency specification." set "HP_SKIP_PIPREQS=1" set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs unavailable for this Python version)" ) else ( set "HP_PIPREQS_INSTALL_PASS=1" call :log "[INFO] pipreqs %HP_PIPREQS_VERSION% installed successfully; using it for dependency discovery." ) ) if defined HP_NDJSON ( rem Emit pass=true for intentional skips -- PEP 723 or pre-existing HP_SKIP_PIPREQS -- rem pass=true for successful installs, pass=false for install failures. rem Use HP_PIPREQS_INSTALL_ATTEMPTED to distinguish failed install, attempted=1 pass=0, rem from intentional skip, attempted=0. if defined PEP723_ACTIVE ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$row = @{ id='pipreqs.install'; pass=$true; reason='pep723_active' } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) else ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$attempted = [Environment]::GetEnvironmentVariable('HP_PIPREQS_INSTALL_ATTEMPTED') -eq '1';" ^ "$pass = [Environment]::GetEnvironmentVariable('HP_PIPREQS_INSTALL_PASS') -eq '1';" ^ "if ($attempted) { $reason = if ($pass) { 'success' } else { 'install_failed' } } else { $reason = 'skip_preexisting' };" ^ "$pass = if ($attempted) { $pass } else { $true };" ^ "$row = @{ id='pipreqs.install'; pass=$pass; reason=$reason } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) ) set "HP_PIPREQS_TARGET_WORK=%CD%\requirements.auto.txt" set "HP_PIPREQS_TARGET=%HP_PIPREQS_TARGET_WORK%" set "HP_PIPREQS_IGNORE=.git,.github,.venv,venv,env,.uv_env,build,dist,__pycache__,tests" set "HP_PIPREQS_IGNORE_DISPLAY=" if defined HP_PIPREQS_IGNORE set "HP_PIPREQS_IGNORE_DISPLAY= --ignore \"%HP_PIPREQS_IGNORE%\"" set "HP_PIPREQS_SUMMARY_PHASE=" set "HP_PIPREQS_SUMMARY_NOTE=" set "HP_PIPREQS_SUMMARY_CMD_PATH=%HP_PIPREQS_TARGET_WORK%" set "HP_PIPREQS_SUMMARY_IGNORE=%HP_PIPREQS_IGNORE_DISPLAY%" set "HP_PIPREQS_PHASE_RESULT=" set "HP_PIPREQS_LAST_LOG=" set "HP_PIPREQS_DIRECT_LOG=~pipreqs_direct.log" if exist "%HP_PIPREQS_DIRECT_LOG%" del "%HP_PIPREQS_DIRECT_LOG%" set "HP_PIPREQS_STAGE_LOG=~pipreqs_stage.log" if exist "%HP_PIPREQS_STAGE_LOG%" del "%HP_PIPREQS_STAGE_LOG%" set "HP_PIPREQS_STAGE_COPY_LOG=~pipreqs_stage_copy.log" if exist "%HP_PIPREQS_STAGE_COPY_LOG%" del "%HP_PIPREQS_STAGE_COPY_LOG%" set "HP_PIPREQS_CANON=pipreqs . --force --mode compat --savepath requirements.auto.txt" set "HP_PIPREQS_CMD_LOG=pipreqs . --force --mode compat --savepath \"%HP_PIPREQS_TARGET%\"%HP_PIPREQS_IGNORE_DISPLAY%" call :log "[INFO] pipreqs (direct) command: %HP_PIPREQS_CMD_LOG%" echo Pipreqs command (direct): %HP_PIPREQS_CMD_LOG% if defined HP_SKIP_PIPREQS ( set "HP_PIPREQS_PHASE_RESULT=skipped" set "HP_PIPREQS_SUMMARY_PHASE=skipped" set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped for %HP_ENV_MODE% mode)" set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%" goto :after_pipreqs_run ) if defined PEP723_ACTIVE ( echo *** Using dependencies from PEP 723 metadata echo *** [INFO] Using PEP 723 inline dependency metadata echo *** [INFO] Dependency accuracy depends on script metadata correctness call :log "[INFO] Using PEP 723 inline dependency metadata" call :log "[INFO] DEP_SOURCE=pep723" call :log "[INFO] PEP723_USED=1" call :log "[INFO] DEP_LAYER_PEP723=1" call :log "[TRACE] dep source selected: pep723" set "DEP_SOURCE=pep723" set "DEP_LAYER_PEP723=1" copy /y "%PEP723_REQ%" "requirements.txt" >nul 2>&1 if errorlevel 1 call :die "[ERROR] Could not stage PEP 723 requirements." copy /y "%PEP723_REQ%" "requirements.auto.txt" >nul 2>&1 set "HP_PIPREQS_PHASE_RESULT=skipped" set "HP_PIPREQS_SUMMARY_PHASE=skipped" set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped: PEP 723 metadata)" set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%" goto :after_pipreqs_run ) rem REQ-005: Only warn when no user-provided dep source was detected (no requirements.txt / rem pyproject / PEP 723). If DEP_SOURCE is already set, pipreqs runs as an augmentation rem pass but the user has explicit deps -- the WARN is misleading and must be suppressed. if not defined DEP_SOURCE ( echo *** [WARN] Dependencies were auto-detected via pipreqs echo *** [WARN] Auto-detection may be incomplete or incorrect echo *** [INFO] Consider adding requirements.txt or PEP 723 metadata for reliability set "DEP_SOURCE=pipreqs" ) else ( call :log "[TRACE] pipreqs augmenting %DEP_SOURCE% dep source; auto-detect WARN suppressed." ) rem pipreqs invocation: uses "python -m pipreqs.pipreqs" NOT the console script (pipreqs command). rem derived requirement: bootstrap determinism. The console script (pipreqs) relies on PATH being set rem correctly after conda env activation, which is not guaranteed in the same shell session immediately rem after environment creation. Using explicit Python interpreter + module bypasses PATH resolution and rem works reliably in bootstrap contexts where shell state / PATH propagation is not fully initialized. rem This is NOT a pipreqs API issue (the console script is the official API); it is a Windows batch rem bootstrap sequencing issue. pipreqs is pinned to 0.4.13 permanently, so internal coupling is a rem low-risk controlled assumption due to the pinned dependency version. rem pipreqs flags are locked by CI (pipreqs.flags gate). rem Rationale: compat mode for deterministic output; force overwrite; write to requirements.auto.txt (separate from committed requirements). "%HP_PY%" -m pipreqs.pipreqs . --force --mode compat --savepath "%HP_PIPREQS_TARGET%" --ignore "%HP_PIPREQS_IGNORE%" > "%HP_PIPREQS_DIRECT_LOG%" 2>&1 :pipreqs_direct_done rem CLAUDE.md Item 51: capture %errorlevel% on the line immediately after the pipreqs rem invocation, before any intervening "set", matching the staging path's own already-safe rem pattern a few dozen lines below (which never had an intervening command). A successful rem plain "set VAR=literal" does not itself modify ERRORLEVEL in real cmd.exe, so this was rem very likely never a live bug -- but the two call sites disagreeing on ordering, for no rem reason, is what made this worth a live-cmd.exe check in the first place. Zero-risk either rem way; closes the inconsistency for good. set "HP_PIPREQS_RC=%errorlevel%" set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%" if "%HP_PIPREQS_RC%"=="0" if exist "%HP_PIPREQS_TARGET_WORK%" ( rem Zero imports are valid: pipreqs exits 0 and may intentionally leave requirements.auto.txt empty. set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=direct" goto :after_pipreqs_run ) rem ---- pipreqs result handling ------------------------------------------- rem RC=0 + file exists -> OK (deps found, or zero deps via exit 0) rem RC!=0 + no file -> OK (no imports: zero requirements) rem RC!=0 + file empty (size=0) -> OK (no imports: zero requirements) rem RC!=0 + file populated -> FAIL (unexpected; fall through to staging) rem rem Notes: rem - pipreqs returns non-zero when no imports are found: that is NOT an error rem - We inspect file state, not just exit code, to distinguish zero-deps from crash rem - If pipreqs crashed on a file WITH imports, output is absent/empty and this rem guard treats it as zero-requirements; downstream pip/tokenFound will expose it rem - Staging path below acts as fallback for direct path quoting/path issues rem ------------------------------------------------------------------------- rem Debug: log rc + file size so future diagnosis is trivial set "HP_PIPREQS_SIZE=missing" if exist "%HP_PIPREQS_TARGET_WORK%" for %%A in ("%HP_PIPREQS_TARGET_WORK%") do set "HP_PIPREQS_SIZE=%%~zA" call :log "[DEBUG] pipreqs (direct) rc=%HP_PIPREQS_RC% size=%HP_PIPREQS_SIZE%" rem Zero-requirements guard: accept non-zero exit only when output is absent or empty if not "%HP_PIPREQS_RC%"=="0" ( if not exist "%HP_PIPREQS_TARGET_WORK%" ( set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=direct" set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)" goto :after_pipreqs_run ) else ( for %%A in ("%HP_PIPREQS_TARGET_WORK%") do if %%~zA EQU 0 ( set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=direct" set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)" goto :after_pipreqs_run ) ) ) set "HP_TEMP_ROOT=%RUNNER_TEMP%" if not defined HP_TEMP_ROOT set "HP_TEMP_ROOT=%TEMP%" set "HP_PIPREQS_STAGE_ROOT=%HP_TEMP_ROOT%\pipreqs_stage" set "HP_PIPREQS_STAGE_TARGET=%HP_PIPREQS_STAGE_ROOT%\requirements.auto.txt" if exist "%HP_PIPREQS_STAGE_ROOT%" rd /s /q "%HP_PIPREQS_STAGE_ROOT%" mkdir "%HP_PIPREQS_STAGE_ROOT%" >nul 2>&1 robocopy . "%HP_PIPREQS_STAGE_ROOT%" /E /NFL /NDL /NJH /NJS /NP ^ /XD .git .github .venv venv env build dist __pycache__ tests ^ /XF ~*.py > "%HP_PIPREQS_STAGE_COPY_LOG%" 2>&1 set "HP_PIPREQS_STAGE_COPY_RC=%errorlevel%" if %HP_PIPREQS_STAGE_COPY_RC% GEQ 8 ( set "HP_PIPREQS_PHASE_RESULT=fail" set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_STAGE_COPY_LOG%" set "HP_PIPREQS_SUMMARY_NOTE=(robocopy staging failed)" goto :after_pipreqs_run ) set "HP_PIPREQS_SUMMARY_CMD_PATH=%HP_PIPREQS_STAGE_TARGET%" set "HP_PIPREQS_SUMMARY_IGNORE=" if not exist "%HP_PIPREQS_STAGE_ROOT%\" ( echo [WARN] pushd skipped, stage root missing: %HP_PIPREQS_STAGE_ROOT% set "HP_PIPREQS_PHASE_RESULT=fail" set "HP_PIPREQS_SUMMARY_NOTE=(stage root missing)" goto :after_pipreqs_run ) pushd "%HP_PIPREQS_STAGE_ROOT%" >nul 2>&1 if errorlevel 1 ( set "HP_PIPREQS_PHASE_RESULT=fail" set "HP_PIPREQS_SUMMARY_NOTE=(pushd to staging root failed)" goto :after_pipreqs_run ) call :log "[INFO] pipreqs (staging) command: pipreqs . --force --mode compat --savepath ""%HP_PIPREQS_STAGE_TARGET%""" echo Pipreqs command (staging): pipreqs . --force --mode compat --savepath "%HP_PIPREQS_STAGE_TARGET%" :: pipreqs flags are locked by CI (pipreqs.flags gate). :: Rationale: compat mode for deterministic output; force overwrite; write to requirements.auto.txt (separate from committed requirements). "%HP_PY%" -m pipreqs.pipreqs . --force --mode compat --savepath "%HP_PIPREQS_STAGE_TARGET%" > "%HP_PIPREQS_STAGE_LOG%" 2>&1 set "HP_PIPREQS_RC=%errorlevel%" popd >nul 2>&1 if errorlevel 1 call :log "[WARN] pipreqs staging: popd failed; CWD may not be restored." set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_STAGE_LOG%" if "%HP_PIPREQS_RC%"=="0" if exist "%HP_PIPREQS_STAGE_TARGET%" ( rem Zero imports are valid: copy the staging file even when pipreqs produced an empty requirements list. copy /y "%HP_PIPREQS_STAGE_TARGET%" "%HP_PIPREQS_TARGET_WORK%" >nul 2>&1 if errorlevel 1 ( set "HP_PIPREQS_PHASE_RESULT=fail" set "HP_PIPREQS_SUMMARY_NOTE=(failed to copy staging output)" goto :after_pipreqs_run ) set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=staging" set "HP_PIPREQS_SUMMARY_NOTE=(fallback after direct failure)" goto :after_pipreqs_run ) rem Debug: log rc + file size for staging path set "HP_PIPREQS_SIZE=missing" if exist "%HP_PIPREQS_STAGE_TARGET%" for %%A in ("%HP_PIPREQS_STAGE_TARGET%") do set "HP_PIPREQS_SIZE=%%~zA" call :log "[DEBUG] pipreqs (staging) rc=%HP_PIPREQS_RC% size=%HP_PIPREQS_SIZE%" rem Zero-requirements guard for staging path if not "%HP_PIPREQS_RC%"=="0" ( if not exist "%HP_PIPREQS_STAGE_TARGET%" ( set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=staging" set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)" goto :after_pipreqs_run ) else ( for %%A in ("%HP_PIPREQS_STAGE_TARGET%") do if %%~zA EQU 0 ( set "HP_PIPREQS_PHASE_RESULT=ok" set "HP_PIPREQS_SUMMARY_PHASE=staging" set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)" goto :after_pipreqs_run ) ) ) if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(staging pipreqs failed)" set "HP_PIPREQS_PHASE_RESULT=fail" :after_pipreqs_run set "DEP_FINAL_COUNT=0" if exist "requirements.txt" for /f "usebackq eol=# tokens=*" %%L in ("requirements.txt") do if not "%%L"=="" set /a DEP_FINAL_COUNT+=1 call :log "[INFO] DEP_RESOLUTION_STRATEGY=layered" call :log "[INFO] DEP_FINAL_COUNT=%DEP_FINAL_COUNT%" set "DEP_FINAL_COUNT=" if exist "%HP_PIPREQS_STAGE_ROOT%" rd /s /q "%HP_PIPREQS_STAGE_ROOT%" set "HP_PIPREQS_TARGET=%HP_PIPREQS_TARGET_WORK%" if "%HP_PIPREQS_PHASE_RESULT%"=="ok" ( call :write_pipreqs_summary ) else ( if /I "%HP_PIPREQS_PHASE_RESULT%"=="skipped" ( if not defined HP_PIPREQS_SUMMARY_PHASE set "HP_PIPREQS_SUMMARY_PHASE=skipped" if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped)" call :write_pipreqs_summary ) else ( set "HP_PIPREQS_SUMMARY_PHASE=failed" if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs run failed)" set "HP_PIPREQS_FAILURE_LOG=%HP_PIPREQS_LAST_LOG%" call :write_pipreqs_summary rem G3 guardrail: pipreqs is discovery only; a failed scan must not block bootstrap. call :log "[WARN] pipreqs generation failed; continuing without auto-detected requirements." ) ) if not exist "%REQ%" if exist "requirements.auto.txt" ( copy /y "requirements.auto.txt" "requirements.txt" >> "%LOG%" 2>&1 if errorlevel 1 ( echo *** Could not generate requirements.txt. Continuing without dependencies... call :log "[WARN] Failed to copy requirements.auto.txt to requirements.txt; continuing without dependency installation." ) ) echo (no diff: requirements files not both present) > "~pipreqs.diff.txt" if exist "requirements.txt" if exist "requirements.auto.txt" ( fc "requirements.txt" "requirements.auto.txt" > "~pipreqs.diff.txt" 2>&1 rem derived requirement: use fc.exe's own exit code -- 0=identical, 1=differ, 2=comparison rem error -- not a findstr match on its English "FC: no differences encountered" message -- rem that text is localized on non-English Windows, so a findstr match would never fire there rem and the diff would incorrectly show even for two identical files. Check errorlevel 2 rem before errorlevel 1 -- "if errorlevel N" matches ERRORLEVEL GEQ N, so the higher value rem must be checked first or it would never be reached. if errorlevel 2 ( call :log "[WARN] fc comparison of requirements.txt vs requirements.auto.txt reported an error; skipping diff display." ) else if errorlevel 1 ( echo [INFO] requirements.txt differs from the auto-detected dependency scan; details below: type "~pipreqs.diff.txt" ) ) call :log "[INFO] REQ-005.5: dependency source diff computed -- ~pipreqs.diff.txt" rem === REQ-005.12 (Tier 1, docs/plan-autopep723-two-tier.md): autopep723 discovery === rem Non-gating, additive-only augmentation of pipreqs's own discovery, uv lane only for v1. rem autopep723 runs via uvx (isolated tool venv) rather than a direct interpreter -- see rem docs/agent-lessons-learned.md's autopep723 environment-leak section for why a direct rem invocation would silently under-report already-installed packages, and uvx does not. rem A failed/missing/skipped autopep723 result is a silent no-op; pipreqs's own results rem and the existing dependency-install path below are entirely unaffected either way. set "HP_UVX_EXE=%HP_UV_EXE:uv.exe=uvx.exe%" if /I "%HP_ENV_MODE%"=="uv" if defined HP_ENTRY if exist "%HP_UVX_EXE%" if not defined HP_SKIP_AUTOPEP_DISCOVERY ( "%HP_UVX_EXE%" autopep723 check "%HP_ENTRY%" > "requirements.autopep.txt" 2>>"%LOG%" call :emit_from_base64 "~autopep_merge.py" HP_AUTOPEP_MERGE if not errorlevel 1 ( "%HP_PY%" "~autopep_merge.py" "requirements.autopep.txt" "requirements.txt" >> "%LOG%" 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-005.12: autopep723 discovery merge complete." ) else ( call :log "[WARN] REQ-005.12: autopep723 merge helper failed; continuing with pipreqs-only results." ) if exist "~autopep_merge.py" del "~autopep_merge.py" >nul 2>&1 ) ) rem --- Dep-check fast path: skip conda install when all pipreqs packages are in the lock --- rem derived requirement: skip the slow conda solver on repeat runs when the rem environment lock file already contains every package pipreqs detected. rem goto is used to avoid %errorlevel% parse-time expansion inside parenthesized blocks. set "HP_DEP_SKIP=" set "HP_DEP_RESULT=" rem REQ-005.11: reset on every entry to this label (including REQ-009 provider-cascade rem re-entry) so a stale HP_UV_INSTALL_OK from a previous, now-abandoned uv attempt can rem never satisfy :pep723_writeback's confirmed-installed gate for an unrelated trigger. set "HP_UV_INSTALL_OK=" if not "%HP_ENV_MODE%"=="conda" if not "%HP_ENV_MODE%"=="uv" goto :dep_check_done call :emit_from_base64 "~dep_check.py" HP_DEP_CHECK if errorlevel 1 goto :dep_check_done "%HP_PY%" "~dep_check.py" > "~dep_check.txt" 2>> "%LOG%" set "HP_DEP_RC=%errorlevel%" for /f "usebackq delims=" %%D in ("~dep_check.txt") do set "HP_DEP_RESULT=%%D" if exist "~dep_check.txt" del "~dep_check.txt" >nul 2>&1 if exist "~dep_check.py" del "~dep_check.py" >nul 2>&1 if not "%HP_DEP_RC%"=="0" goto :dep_check_done if /I "%HP_DEP_RESULT%"=="skip" set "HP_DEP_SKIP=1" if defined HP_DEP_SKIP call :log "[INFO] Dep-check: all pipreqs packages satisfied in lock; skipping conda install." :dep_check_done rem IMPORTANT: requirements.txt must always reflect the final dependency set passed to installer. rem All source layers (PEP 723, pyproject, pipreqs) overwrite requirements.txt in-place above. rem Do not bypass requirements.txt without updating the snapshot and installed-state logic below. rem --- Snapshot resolved dependency input before install --- if exist "~dependency_resolved.txt" del "~dependency_resolved.txt" >nul 2>&1 if exist "requirements.txt" ( copy /y "requirements.txt" "~dependency_resolved.txt" >nul 2>&1 if not errorlevel 1 call :log "[INFO] DEP_RESOLVED_FILE written: ~dependency_resolved.txt" ) call :log "[INFO] DEP_RESOLVED_FROM=requirements.txt" call :log "[INFO] DEP_INSTALL_SOURCE=requirements.txt" call :log "[TRACE] dep install phase: start" if exist "requirements.txt" ( rem derived requirement, CLAUDE.md Item 42 precondition: the dependency-install phase is rem plausibly the single longest silent stretch in a fresh build, but previously had no rem [INFO]-tier progress line -- only [TRACE]/[INSTALL] lines, which a future console-tiering rem change, lever 1, could suppress by default, removing the only "something is happening" rem signal for this step. Mirrors the existing conda-create/PyInstaller-build precedents. call :log "[INFO] Installing dependencies -- this may take a few minutes..." if exist "~reqs_conda.txt" del "~reqs_conda.txt" call :log "[TRACE] heuristic augmentation: ~prep_requirements.py" if "%HP_ENV_MODE%"=="conda" ( "%CONDA_BASE_PY%" "~prep_requirements.py" "requirements.txt" >nul 2>> "%LOG%" ) else ( "%HP_PY%" "~prep_requirements.py" "requirements.txt" >nul 2>> "%LOG%" ) call :log "[TRACE] heuristic augmentation: complete" if "%HP_ENV_MODE%"=="conda" ( if not defined HP_DEP_SKIP ( call :log "[INSTALL] conda bulk from ~reqs_conda.txt" call :conda_bulk_install if errorlevel 1 ( call :log "[INSTALL] conda per-pkg fallback" for /f "usebackq delims=" %%P in ("~reqs_conda.txt") do ( call "%CONDA_BAT%" install -y -n "%ENVNAME%" --override-channels -c conda-forge %%P >> "%LOG%" 2>&1 ) ) ) call :log "[INSTALL] pip gap fill from requirements.txt" "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1 if errorlevel 1 ( echo *** Warning: Some requirements may have failed to install. call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing." ) ) else if "%HP_ENV_MODE%"=="venv" ( "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1 if errorlevel 1 ( echo *** Warning: Some requirements may have failed to install. call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing." ) ) else if "%HP_ENV_MODE%"=="uv" ( rem derived requirement: uv pip install targets the uv venv explicitly via --python. rem HP_DEP_SKIP is set by dep_check before this block; 'if not defined' evaluates at rem runtime so there is no block-parse-time expansion issue. if not defined HP_DEP_SKIP ( "%HP_UV_EXE%" pip install --python "%HP_PY%" -r requirements.txt >> "%LOG%" 2>&1 if errorlevel 1 ( echo *** Warning: Some requirements may have failed to install. call :log "[WARN] uv pip install -r requirements.txt failed; some packages may be missing." set "UV_FALLBACK_REASON=dep_install_failed" call :log "[WARN] UV_FALLBACK reason=dep_install_failed" ) else ( rem REQ-005.11: install fully succeeded; PEP 723 write-back may run for this round. set "HP_UV_INSTALL_OK=1" ) ) else ( rem REQ-005.11: HP_DEP_SKIP means the lock already satisfied every package -- still a rem confirmed-installed state, not a failure to distinguish from a genuine install. set "HP_UV_INSTALL_OK=1" ) call :log "[INFO] UV_USED=1" ) else if "%HP_ENV_MODE%"=="embed" ( rem REQ-009 Tier 5: embed is a private, bootstrapper-owned interpreter like venv, not a rem shared/uncontrolled one like system -- installing into it is exactly the point, so it rem must NOT fall into the system catch-all below -- that branch deliberately skips install. "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1 if errorlevel 1 ( echo *** Warning: Some requirements may have failed to install. call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing." ) ) else ( call :log "[WARN] System fallback: skipping requirement installation." ) ) rem --- Capture installed package state via pip freeze --- if exist "~dependency_installed.txt" del "~dependency_installed.txt" >nul 2>&1 if "%HP_ENV_MODE%"=="uv" ( "%HP_UV_EXE%" pip freeze --python "%HP_PY%" > "~dependency_installed.txt" 2>nul ) else ( "%HP_PY%" -m pip freeze > "~dependency_installed.txt" 2>nul ) set "HP_DEP_INST_RC=%errorlevel%" if "%HP_DEP_INST_RC%"=="0" call :log "[INFO] DEP_INSTALLED_CAPTURED=1" if not "%HP_DEP_INST_RC%"=="0" ( if exist "~dependency_installed.txt" del "~dependency_installed.txt" >nul 2>&1 call :log "[WARN] DEP_INSTALLED_CAPTURE_FAILED=1" ) set "HP_DEP_INST_RC=" rem --- Capture resolved environment snapshot --- rem derived requirement: goto avoids %errorlevel% parse-time expansion that rem would occur inside a parenthesized if-block -- cmd.exe expands %var% for rem the whole block at parse time, so set HP_LOCK_RC=%errorlevel% inside an rem if block always captures the pre-block errorlevel, not conda list's exit code. if "%HP_ENV_MODE%"=="uv" ( rem derived requirement: dep_check.py and selfapps_depcheck.ps1 expect rem ~environment.lock.txt regardless of env mode; reuse the pip freeze output rem already captured in ~dependency_installed.txt to avoid a second freeze call. if exist "~dependency_installed.txt" copy /y "~dependency_installed.txt" "~environment.lock.txt" >nul 2>&1 if exist "~environment.lock.txt" call :log "[INFO] Environment snapshot written: ~environment.lock.txt" if not exist "~environment.lock.txt" ( set "UV_FALLBACK_REASON=lock_failed" call :log "[WARN] UV_FALLBACK reason=lock_failed" ) goto :lock_done ) if not "%HP_ENV_MODE%"=="conda" goto :lock_done call :log "[INFO] Capturing environment snapshot..." call "%CONDA_BAT%" list -n "%ENVNAME%" --export > "~environment.lock.txt" 2>> "%LOG%" set "HP_LOCK_RC=%errorlevel%" if "%HP_LOCK_RC%"=="0" ( for %%Z in ("~environment.lock.txt") do if %%~zZ GTR 0 ( call :log "[INFO] Environment snapshot written: ~environment.lock.txt" goto :lock_done ) call :log "[WARN] Environment snapshot: conda list succeeded but output is empty." if exist "~environment.lock.txt" del "~environment.lock.txt" >nul 2>&1 ) else ( call :log "[WARN] Environment snapshot failed (conda list rc=%HP_LOCK_RC%)." if exist "~environment.lock.txt" del "~environment.lock.txt" >nul 2>&1 ) :lock_done rem REQ-005.11: fresh-install PEP 723 write-back trigger. Must run after the lock rem snapshot above (HP_UV_INSTALL_OK is set during dependency install, further up rem this same :after_env_mode_selection scope) and before pyvisa detection below. call :pep723_writeback fresh rem Detect pyvisa/visa usage so harness sees NI-VISA requirements call :emit_from_base64 "~detect_visa.py" HP_DETECT_VISA if errorlevel 1 call :die "[ERROR] Could not write ~detect_visa.py" set "NEED_VISA=0" if exist "~visa.flag" del "~visa.flag" "%HP_PY%" "~detect_visa.py" > "~visa.flag" 2>> "%LOG%" for /f "usebackq delims=" %%V in ("~visa.flag") do set "NEED_VISA=%%V" if "%NEED_VISA%"=="1" ( call :log "[INFO] Detected pyvisa/visa import; NI-VISA install may be required." ) else ( call :log "[INFO] No pyvisa/visa imports detected." ) if exist "~visa.flag" del "~visa.flag" rem --- NI-VISA presence check and install (REQ-008) --- rem derived requirement: NEED_VISA must be exactly "1"; any other value (0, empty, error) rem skips install to avoid hanging CI on non-visa projects. if not "%NEED_VISA%"=="1" ( call :log "[VISA] skipped (not_required)" goto visa_done ) rem REQ-008: allow disabling the NI-VISA install for debugging, even when pyvisa/visa is detected. if "%HP_SKIP_NIVISA%"=="1" ( call :log "[VISA] skipped (disabled)" goto visa_done ) reg query "HKLM\SOFTWARE\National Instruments\NI-VISA" /v "CurrentVersion" >nul 2>&1 if not errorlevel 1 ( call :log "[VISA] present" goto visa_done ) set "NIVISA_INSTALLER=~ni-visa-runtime.exe" set "HP_VISA_DLVIA=curl" rem derived requirement: --retry/--max-time match every other curl download in this file rem (uv/Miniconda/get-pip/embed) -- this call was the one exception, so a stalled connection rem (e.g. a captive-portal network that accepts the TCP handshake but never completes the HTTP rem response) could hang the whole bootstrap indefinitely. See "no-timeout audit" in rem docs/agent-lessons-learned.md. curl -L --silent --fail --retry 3 --retry-delay 5 --max-time 120 -o "%NIVISA_INSTALLER%" "https://download.ni.com/support/nipkg/products/ni-v/ni-visa/21.5/online/ni-visa_21.5_online.exe" 2>> "%LOG%" if errorlevel 1 ( set "HP_VISA_DLVIA=powershell" rem derived requirement: curl can leave a partial file on failure; delete before fallback rem so PowerShell does not find a corrupt file and skip its own download attempt. if exist "%NIVISA_INSTALLER%" del "%NIVISA_INSTALLER%" >nul 2>&1 powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://download.ni.com/support/nipkg/products/ni-v/ni-visa/24.0/runtime/ni-visa-runtime_24.0.0_windows.exe' -OutFile '%NIVISA_INSTALLER%' -UseBasicParsing -TimeoutSec 120 -ErrorAction Stop } catch { exit 1 }" 2>> "%LOG%" ) if not exist "%NIVISA_INSTALLER%" ( call :log "[VISA] install_failed (download)" goto visa_done ) rem REQ-008 diagnostic: record the downloaded installer's provenance, size, and PE validity so a rem non-zero installer exit code can be classified (blocked/redirected payload vs a real installer rem that refused unattended install). These are read-only probes -- they never touch the install. call :log "[VISA] download method: %HP_VISA_DLVIA%" set "HP_VISA_DLSIZE=0" for %%S in ("%NIVISA_INSTALLER%") do set "HP_VISA_DLSIZE=%%~zS" call :log "[VISA] installer file size: %HP_VISA_DLSIZE% bytes" powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $fs=[System.IO.File]::OpenRead('%NIVISA_INSTALLER%'); $a=$fs.ReadByte(); $b=$fs.ReadByte(); $fs.Close(); if ($a -eq 77 -and $b -eq 90) { 'PE_OK' } else { 'NOT_PE' } } catch { 'PROBE_ERR' }" > "~visa_pe.txt" 2>nul set "HP_VISA_PE=" if exist "~visa_pe.txt" for /f "usebackq delims=" %%P in ("~visa_pe.txt") do set "HP_VISA_PE=%%P" if exist "~visa_pe.txt" del "~visa_pe.txt" >nul 2>&1 call :log "[VISA] installer PE check: %HP_VISA_PE%" rem derived requirement: [Active Backlog item 14] bound the installer launch with a generous rem timeout ceiling instead of an unbounded wait. Real-world NI installer-family reports (NI rem Community forum threads on the shared NI Package Manager stack, which NI-VISA's own rem installer is built on) document multi-hour installs when antivirus scans every file the rem installer writes; this repo's own CI evidence separately shows NI-VISA failing FAST rem (~10s, installer_rc=-125083) in this environment -- see CLAUDE.md's "NI-VISA real install rem fails fast in CI" Known Finding. 90 minutes is a generous ceiling against a genuinely hung rem install (the failure mode a timeout exists to catch) without being the multi-hour extreme rem outlier from the (heavier, different-product) NI Package Manager reports. call :run_installer_timeout "%NIVISA_INSTALLER%" "--quiet --accept-eulas --prevent-reboot --prevent-activation" 5400000 "NI-VISA" set "HP_VISA_INSTALLER_RC=%ERRORLEVEL%" rem derived requirement: capture the installer exit code so diagnostics can tell a hard failure rem (non-zero rc) apart from a slow-but-progressing install (rc=0, registry not yet populated). call :log "[VISA] installer exit code: %HP_VISA_INSTALLER_RC%" rem derived requirement: NI installers may spawn child processes; poll the registry before declaring rem post-install failure. Budget is configurable via HP_NIVISA_WAIT_SECS so a dedicated diagnostic rem lane can wait out a slow (~30-45 min) NI-VISA install without slowing gating lanes. Default keeps rem the original ~15s behavior (3 retries x ~5s); single-line ifs avoid parenthesized parse-time issues. set "HP_VISA_PINGN=6" set "HP_VISA_MAXRETRY=3" if defined HP_NIVISA_WAIT_SECS set "HP_VISA_PINGN=31" if defined HP_NIVISA_WAIT_SECS set /a HP_VISA_MAXRETRY=%HP_NIVISA_WAIT_SECS%/30 if %HP_VISA_MAXRETRY% lss 3 set "HP_VISA_MAXRETRY=3" set "HP_VISA_RETRY=0" :visa_post_check reg query "HKLM\SOFTWARE\National Instruments\NI-VISA" /v "CurrentVersion" >nul 2>&1 if not errorlevel 1 ( call :log "[VISA] install_success" goto visa_cleanup ) set /a HP_VISA_RETRY+=1 if %HP_VISA_RETRY% lss %HP_VISA_MAXRETRY% ( call :log "[VISA] post-check waiting; retry %HP_VISA_RETRY%/%HP_VISA_MAXRETRY% (installer_rc=%HP_VISA_INSTALLER_RC%)" ping -n %HP_VISA_PINGN% 127.0.0.1 >nul 2>&1 goto visa_post_check ) call :log "[VISA] install_failed (post_check_timeout) installer_rc=%HP_VISA_INSTALLER_RC%" :visa_cleanup if exist "%NIVISA_INSTALLER%" del "%NIVISA_INSTALLER%" >nul 2>&1 :visa_done rem --- Write env state for fast path on next run --- rem derived requirement: goto avoids %errorlevel% parse-time expansion inside rem parenthesized if-blocks; same pattern as dep-check and lock-capture blocks. call :emit_from_base64 "~env_state.py" HP_ENV_STATE if errorlevel 1 goto :env_state_write_done "%HP_PY%" "~env_state.py" --write >> "%LOG%" 2>&1 set "HP_ENV_STATE_WRITE_RC=%errorlevel%" if exist "~env_state.py" del "~env_state.py" >nul 2>&1 if "%HP_ENV_STATE_WRITE_RC%"=="0" call :log "[INFO] Env state written: ~env.state.json" if not "%HP_ENV_STATE_WRITE_RC%"=="0" call :log "[WARN] Env state write failed (rc=%HP_ENV_STATE_WRITE_RC%)." set "HP_ENV_STATE_WRITE_RC=" :env_state_write_done goto :after_env_bootstrap :ci_skip_entry call :log "[INFO] CI self-test: skipping environment bootstrap" set "HP_ENTRY=" set "HP_CRUMB=" if exist "~entry.abs" del "~entry.abs" rem --- stage helper --- call :emit_from_base64 "~find_entry.py" HP_FIND_ENTRY if errorlevel 1 ( call :die "[ERROR] CI skip: entry helper staging failed" goto :after_env_bootstrap ) call :update_find_entry_abs call :verify_find_entry_helper if errorlevel 1 call :die "[ERROR] find_entry helper syntax error" rem --- locate a Python --- set "HP_SYS_PY=" & set "HP_SYS_PY_ARGS=" set "HP_SYS_PY_LOGGED=" where python >nul 2>&1 && set "HP_SYS_PY=python" if not defined HP_SYS_PY ( where py >nul 2>&1 && (set "HP_SYS_PY=py" & set "HP_SYS_PY_ARGS=-3") ) if defined HP_SYS_PY for %%C in ("%HP_SYS_PY%") do set "HP_SYS_PY=%%~C" if defined HP_SYS_PY_ARGS for %%A in ("%HP_SYS_PY_ARGS%") do set "HP_SYS_PY_ARGS=%%~A" rem --- run helper and capture RELATIVE crumb --- set "HP_CRUMB=" if not defined HP_SYS_PY goto :ci_skip_helper_done rem derived requirement: use goto instead of if-block; DisableDelayedExpansion coalesces rem the entire block at parse time, so %HP_CRUMB_FILE% (set mid-block) was empty when rem used as a redirect target, causing "syntax of the command is incorrect" / exit 255. rem derived requirement: CI observed `'python" "~find_entry.py' is not recognized` when rem helper args were empty. Keep the helper invocation split so CMD never appends a stray rem quote to the interpreter token, and route stdout through a file to avoid shell quoting drift. if not defined HP_SYS_PY_LOGGED ( if defined HP_SYS_PY_ARGS ( >> "%LOG%" echo Helper command: "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_FIND_ENTRY_ABS%" ) else ( >> "%LOG%" echo Helper command: "%HP_SYS_PY%" "%HP_FIND_ENTRY_ABS%" ) set "HP_SYS_PY_LOGGED=1" ) rem derived requirement: CI skip jobs sometimes run from a borrowed working directory. rem Normalize the helper root so helper discovery always executes from the bootstrapper tree. set "HP_HELPER_ROOT=%HP_SCRIPT_ROOT%" for %%R in ("%HP_HELPER_ROOT%") do set "HP_HELPER_ROOT=%%~fR" if not defined HP_HELPER_ROOT set "HP_HELPER_ROOT=%CD%" if not exist "%HP_HELPER_ROOT%" mkdir "%HP_HELPER_ROOT%" >nul 2>&1 set "HP_CHOOSER_ROOT=%HP_HELPER_ROOT%" for %%R in ("%HP_HELPER_ROOT%tests") do if exist "%%~fR" set "HP_CHOOSER_ROOT=%%~fR" rem derived requirement: the helper enumerates cwd *.py files; pivot into tests/ when present rem so crumbs reference the self-test entry scripts without emitting "The system cannot find the path specified.". for %%R in ("%HP_CHOOSER_ROOT%") do set "HP_CHOOSER_ROOT=%%~fR" if not exist "%HP_CHOOSER_ROOT%" set "HP_CHOOSER_ROOT=%HP_HELPER_ROOT%" set "HP_CRUMB_FILE=%HP_CHOOSER_ROOT%" if not "%HP_CRUMB_FILE:~-1%"=="\" set "HP_CRUMB_FILE=%HP_CRUMB_FILE%\" set "HP_CRUMB_FILE=%HP_CRUMB_FILE%~crumb.txt" if exist "%HP_CRUMB_FILE%" del "%HP_CRUMB_FILE%" >nul 2>&1 set "HP_CHOOSER_PUSHD=" if exist "%HP_CHOOSER_ROOT%" ( pushd "%HP_CHOOSER_ROOT%" >nul 2>&1 set "HP_CHOOSER_PUSHD=1" ) else ( echo [WARN] pushd skipped, chooser root missing: %HP_CHOOSER_ROOT% ) if defined HP_SYS_PY_ARGS ( "%HP_SYS_PY%" %HP_SYS_PY_ARGS% -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul ) else ( "%HP_SYS_PY%" -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul ) if defined HP_SYS_PY_ARGS ( "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_FIND_ENTRY_ABS%" > "%HP_CRUMB_FILE%" 2>> "%LOG%" ) else ( "%HP_SYS_PY%" "%HP_FIND_ENTRY_ABS%" > "%HP_CRUMB_FILE%" 2>> "%LOG%" ) if defined HP_CHOOSER_PUSHD ( popd >nul 2>&1 set "HP_CHOOSER_PUSHD=" ) if exist "%HP_CRUMB_FILE%" ( for /f "usebackq delims=" %%L in ("%HP_CRUMB_FILE%") do if not defined HP_CRUMB set "HP_CRUMB=%%L" del "%HP_CRUMB_FILE%" >nul 2>&1 ) if /i not "%HP_CHOOSER_ROOT%"=="%HP_HELPER_ROOT%" ( if defined HP_CRUMB set "HP_CRUMB=tests\%HP_CRUMB%" ) :ci_skip_helper_done if not defined HP_CRUMB ( echo [INFO] CI skip: no entry script detected. goto :after_env_bootstrap ) rem --- write breadcrumb EXACTLY (no trailing punctuation) --- echo Chosen entry: %HP_CRUMB% >> "%LOG%" echo Chosen entry: %HP_CRUMB% rem optional: set HP_ENTRY if helper emitted an abs path file if exist "~entry.abs" set /p HP_ENTRY=<"~entry.abs" rem Helper now only prints the crumb; reuse it for optional smoke runs. if not defined HP_ENTRY if defined HP_CRUMB set "HP_ENTRY=%HP_CRUMB%" rem optional best-effort run; never install in skip mode if defined HP_ENTRY if defined HP_SYS_PY ( if defined HP_SYS_PY_ARGS ( "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_ENTRY%" > "~run.out.txt" 2>&1 || echo [WARN] CI skip: system Python non-zero ) else ( "%HP_SYS_PY%" "%HP_ENTRY%" > "~run.out.txt" 2>&1 || echo [WARN] CI skip: system Python non-zero ) ) call :append_env_mode_row goto :after_env_bootstrap :after_env_bootstrap if defined HP_CI_SKIP_ENV goto :after_env_skip call :determine_entry "%~1" if errorlevel 11 ( call :write_status "error" 1 %PYCOUNT% exit /b 1 ) if errorlevel 1 call :die "[ERROR] Could not determine entry point" if "%HP_ENTRY%"=="" ( call :log "[INFO] No entry script detected; skipping PyInstaller packaging." ) else ( call :run_entry_smoke ) rem REQ-009/REQ-005.10 slice 3: if warnfix left dependencies unresolved under the current rem provider AND the user approved (HP_CASCADE_APPROVED, set in :warnfix_cascade_detect), rem re-attempt the dependency phase under the next REQ-009 provider tier. The per-tier rem HP_CASCADE_TRIED_* guards inside :provider_cascade ensure a tier is never used as a rem cascade source twice, so an unresolvable dependency exhausts the tiers and stops -- rem it never loops. Each re-attempt re-enters at :try_conda_create / :after_env_mode_selection. if defined HP_CASCADE_APPROVED goto :provider_cascade :after_cascade_decision rem derived requirement: restore HP_PY if a cascade attempt clobbered it. A DECLINED/failed rem fallback tier (:try_venv_fallback / :try_system_fallback) clears its own HP_PY on failure rem exit as ITS OWN invariant (must leave no trace of a speculative attempt -- see rem docs/agent-lessons-learned.md "A declined/failed fallback tier must clear HP_PY, not just rem return failure"), which is correct for the INITIAL fallback chain but destructive here: on rem cascade re-entry, HP_PY may already hold a working interpreter from an earlier successful rem build (the one :print_postflight_briefing is about to describe as "your standalone rem application is ready"). Every path that reaches this label via :provider_cascade's own rem exhaustion/decline branches (not the direct fall-through when no cascade was ever approved) rem is exactly "keeping current build" -- restore what :provider_cascade saved on entry. if defined HP_CASCADE_SAVED_PY set "HP_PY=%HP_CASCADE_SAVED_PY%" set "HP_CASCADE_SAVED_PY=" if /i "%HP_BOOTSTRAP_STATE%"=="ok" ( call :write_status ok 0 %PYCOUNT% ) else ( call :write_status "%HP_BOOTSTRAP_STATE%" 0 %PYCOUNT% ) goto :success :after_env_skip call :write_status ok 0 %PYCOUNT% goto :success :success rem derived requirement: skip write when DEP_SOURCE=unknown (EXE fast path / no-python-files paths rem fire before dep resolution; preserve any existing dependency_source.txt from previous run). if not "%DEP_SOURCE%"=="unknown" ( echo dependency_source=%DEP_SOURCE%> "dependency_source.txt" echo *** [INFO] Dependency source logged to dependency_source.txt ) rem REQ-016: show post-flight briefing when a full EXE build completed. HP_BUILD_OK is only rem set once :run_entry_smoke actually attempts a build (undefined if no entry file was ever rem found), so "HP_BUILD_OK defined AND no EXE exists" precisely means packaging was attempted rem and failed outright (both PyInstaller and the Nuitka fallback) -- distinct from "no entry rem file" or a declined system-Python build, where no briefing of either kind applies. if not defined HP_FASTPATH_USED ( if exist "dist\%ENVNAME%.exe" ( call :print_postflight_briefing ) else if defined HP_BUILD_OK ( call :print_no_exe_briefing ) ) else if defined HP_FASTPATH_RUN_FAILED ( rem [REQ-027] P2 honest messaging: the fast path stays zero-friction for its own PROMPTS -- rem no consent gate added here -- but a print-only informational note costs nothing and rem closes a genuine gap -- before this, a cached EXE kept despite a later nonzero exit, rem classified alive/healthy at the fail-fast probe so never discarded/rebuilt, see rem :try_fast_exe, had NO postflight signal at all beyond one WARN log line buried among rem other console output. call :print_fastpath_ambiguous_note ) rem CLAUDE.md Active Backlog Item 39: gated on HP_FRESH_BUILD_OK, not merely "HP_FASTPATH_USED rem unset" (CodeRabbit review, PR #460, caught a real bug in the original gate: a skipped or rem failed rebuild also leaves HP_FASTPATH_USED unset, but a stale dist\%ENVNAME%.exe left over rem from an earlier successful run would still be sitting there -- writing the CURRENT sources' rem hash against that OLD binary would make the next run wrongly trust it as "fresh"). Set ONLY rem in :run_entry_smoke's own genuine build-success branches -- see the reset/set sites there. rem HP_FRESH_BUILD_OK being set already implies the fast path was not taken (a build can only be rem attempted after the fast path declined to fire), so this alone is a strictly more precise rem replacement for the old HP_FASTPATH_USED check, not an additional condition alongside it. if defined HP_FRESH_BUILD_OK call :write_fast_hash call :release_lock rem REQ-016: retain terminal window on success so user can read the output. if not defined HP_CI_LANE ( pause ) exit /b 0 :provider_cascade rem REQ-009/REQ-005.10 slice 3: re-attempt the dependency phase under the next provider tier. rem Dispatch is goto-based (no parenthesized interdependent sets) to avoid CMD parse-time rem expansion traps. Each tier is marked HP_CASCADE_TRIED_ the first time it is used as a rem cascade source; a tier is never used twice, so tiers exhaust and the run stops (no loop). rem HP_ENV_MODE only advances (uv -> conda -> embed -> venv -> system), so re-entry cannot rem revisit a tier. conda and embed both front-load acquisition of a FRESH/pinned interpreter rem before falling back to venv/system, which merely wrap whatever Python is already ambient on rem the machine -- see docs/agent-interconnect.md "Standalone Python-download tier" for the rem ordering rationale. NOTE: the :log messages below say "uv to conda" (not "uv -> conda") on rem purpose -- :log echoes UNQUOTED, so a ">" in the message would be parsed as redirection and rem eat the line (see docs/agent-lessons-learned.md). Do not "fix" these to arrows. rem derived requirement: save HP_PY before any cascade tier attempt can clobber it (a failed/ rem declined tier clears its own HP_PY on the way out); :after_cascade_decision restores this rem on every exhaustion/decline exit from this label -- see the comment there for why. set "HP_CASCADE_SAVED_PY=%HP_PY%" set "HP_CASCADE_APPROVED=" if /i "%HP_ENV_MODE%"=="uv" goto :cascade_from_uv if /i "%HP_ENV_MODE%"=="conda" goto :cascade_from_conda if /i "%HP_ENV_MODE%"=="embed" goto :cascade_from_embed if /i "%HP_ENV_MODE%"=="venv" goto :cascade_from_venv rem NOTE: no "system" case here, by design -- system is the absolute last resort (Tier 4 by rem naming/history, REQ-014-gated), no cascade target beyond it. A re-entry with rem HP_ENV_MODE=system falls straight through to the catch-all below, exactly as a re-entry rem with HP_ENV_MODE=embed used to before this reorder (when embed was terminal). call :log "[INFO] REQ-009: provider tiers exhausted after %HP_ENV_MODE%; keeping current build." goto :after_cascade_decision :cascade_from_uv if defined HP_CASCADE_TRIED_UV goto :after_cascade_decision set "HP_CASCADE_TRIED_UV=1" call :log "[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies." echo *** [INFO] Trying the next Python provider (conda) to resolve dependencies... call :cascade_acquire_conda if not defined CONDA_BAT goto :cascade_conda_unavailable set "HP_UV_PROVIDING_PYTHON=" set "HP_ENV_MODE=conda" set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%" goto :try_conda_create :cascade_conda_unavailable call :log "[WARN] REQ-009: cascade to conda unavailable (Miniconda not installed); keeping current build." goto :after_cascade_decision :cascade_conda_create_failed rem derived requirement: reached from :conda_create_failed / :conda_create_done (via goto, not rem call) when a genuine conda-create failure occurs during THIS cascade re-entry specifically -- rem see the comment at :conda_create_failed for the full rationale. Deliberately does NOT call rem :die: :after_cascade_decision's own "keeping current build" restore only works correctly when rem HP_BOOTSTRAP_STATE is left as whatever it already was (the prior successful build's "ok"), rem not overwritten to "error" the way :die would. call :log "[WARN] REQ-009: cascade target conda create failed; keeping current build." goto :after_cascade_decision :cascade_from_conda if defined HP_CASCADE_TRIED_CONDA goto :after_cascade_decision set "HP_CASCADE_TRIED_CONDA=1" if "%HP_FORCE_CONDA_ONLY%"=="1" goto :cascade_condaonly_stop call :log "[INFO] REQ-009: cascading provider conda to embed; re-attempting dependencies." echo *** [INFO] Trying the next Python provider (embed) to resolve dependencies... call :try_embed_fallback if errorlevel 1 goto :cascade_embed_unavailable goto :after_env_mode_selection :cascade_condaonly_stop call :log "[INFO] REQ-009: conda-only mode; cascade beyond conda suppressed; keeping current build." goto :after_cascade_decision :cascade_embed_unavailable call :log "[WARN] REQ-009: cascade target embedded Python unavailable; keeping current build." goto :after_cascade_decision :cascade_from_embed if defined HP_CASCADE_TRIED_EMBED goto :after_cascade_decision set "HP_CASCADE_TRIED_EMBED=1" call :log "[INFO] REQ-009: cascading provider embed to venv; re-attempting dependencies." echo *** [INFO] Trying the next Python provider (venv) to resolve dependencies... call :try_venv_fallback if errorlevel 1 goto :cascade_venv_unavailable goto :after_env_mode_selection :cascade_venv_unavailable call :log "[WARN] REQ-009: cascade target venv unavailable; keeping current build." goto :after_cascade_decision :cascade_from_venv if defined HP_CASCADE_TRIED_VENV goto :after_cascade_decision set "HP_CASCADE_TRIED_VENV=1" rem REQ-009/REQ-014: system Python is Tier 4 (by naming/history; executed last in chain order); rem reached in any run and gated only by the REQ-014 consent prompt inside :try_system_fallback rem (no env flag). A decline keeps the current build. call :log "[INFO] REQ-009: cascading provider venv to system; re-attempting dependencies." call :try_system_fallback if errorlevel 1 goto :cascade_system_unavailable goto :after_env_mode_selection :cascade_system_unavailable call :log "[WARN] REQ-009: cascade target system Python unavailable; keeping current build." goto :after_cascade_decision :cascade_acquire_conda rem REQ-009 slice 3: a uv-first run skipped Miniconda; acquire it on demand for a uv->conda rem cascade. MINICONDA_ROOT / CONDA_MAIN / CONDA_ALT are already set (near line 410) even in rem uv-first runs, so :select_conda_bat and :try_conda_install work without further setup. call :select_conda_bat if defined CONDA_BAT goto :eof echo [INFO] Installing Miniconda into "%MINICONDA_ROOT%"... set "HP_CONDA_JUST_INSTALLED=1" call :download_miniconda_exe if exist "%TEMP%\miniconda.exe" call :try_conda_install if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1 call :select_conda_bat goto :eof :count_python set "NAME=%~1" if "%NAME%"=="" exit /b 0 if "%NAME:~0,1%"=="~" exit /b 0 set /a PYCOUNT+=1 >nul exit /b 0 :check_hidden_ext_hint rem CLAUDE.md Item 32: Windows hides known file extensions by default, so a beginner rem who saves script.py from a text editor (or downloads it as an email attachment) rem can end up with script.py.txt without realizing it. Purely additive diagnostic rem hint -- never changes the exit code or any other behavior. Deliberately does not rem echo the matched filename: a legal Windows filename can contain "&", a live rem cmd.exe operator once substituted into an unquoted echo/:log line (see rem docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry) -- a plain rem existence check via errorlevel sidesteps that hazard entirely. dir /b /a-d *.py.txt >nul 2>&1 if errorlevel 1 exit /b 0 echo Hint: found a file ending in .py.txt -- Windows may be hiding known file extensions. >> "%LOG%" echo Hint: found a file ending in .py.txt -- Windows may be hiding known file extensions. echo In File Explorer's View tab, turn on File name extensions, then rename the file so it ends in .py instead of .py.txt. >> "%LOG%" echo In File Explorer's View tab, turn on File name extensions, then rename the file so it ends in .py instead of .py.txt. call :log "[INFO] REQ-002: found a *.py.txt file -- Windows may be hiding known file extensions." exit /b 0 :check_subfolder_hint rem CLAUDE.md Item 43: entry detection and the top-level Python-file count are both rem depth-1-only by design (matches the documented "drop run_setup.bat alongside your rem .py files" contract), but a user who unzips a src/-style project layout and drops rem run_setup.bat at the root sees "No Python files detected," which reads as false rem when .py files are visibly one folder down. Purely additive diagnostic hint -- rem never changes the exit code or any other behavior. Genuinely depth-1-only: a rem `for /d` loop over immediate subdirectories, NOT `dir /s` (unbounded-depth, would rem falsely match a .py file buried many folders down). Skips bootstrapper-owned rem folders (dist, build, and any ~/.-prefixed dir such as .uv_env, .git, ~uv_bin, rem ~embed_python) so a leftover build/venv artifact from an earlier run can never rem produce a false positive. Deliberately does not echo the matched subfolder name, rem for the same "&" cmd.exe operator hazard :check_hidden_ext_hint's own header rem comment documents. `/a-d` on the inner scan excludes directories -- a plain rem `dir /b "X\*.py"` also matches a SUBDIRECTORY literally named *.py (e.g. a rem package folder named "helpers.py"), the same distinction :count_python's own rem top-level scan already makes via its own `dir /b /a-d *.py`. Per-candidate rem exclusion logic is a `call`ed subroutine with single-condition `if` statements, rem NOT a chained `if X if Y if Z (...)` line -- a real CI failure confirmed the rem hint firing when it should not have (dist/build/tilde/dot exclusions in one rem 4-deep chained-if line inside the for-loop body), and this repo's own rem established pattern is call/goto-based dispatch over deep if-chaining for rem exactly this reliability reason (see docs/agent-lessons-learned.md's rem "Provider-cascade dispatch is goto-based on purpose"). The subdirectory name rem is staged into HP_SFC via a plain `set` BEFORE the `call`, never as a `call` rem argument -- `call` re-scans its OWN line a second time before dispatching rem (see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry, "call rem triggers cmd.exe's own second expansion pass"), so a subfolder literally rem named e.g. "has%PATH%in-it" passed as a `call` argument would have that rem text re-expanded into the REAL PATH variable's value during dispatch, rem corrupting the scan target -- the same hazard class already found and rem fixed once before for the conda native-DLL bundling loop (CodeRabbit rem review, CLAUDE.md Item 24). for /d %%D in (*) do ( set "HP_SFC=%%D" call :subfolder_hint_check_one if not errorlevel 1 goto :subfolder_hint_found ) exit /b 0 :subfolder_hint_check_one if /i "%HP_SFC%"=="dist" exit /b 1 if /i "%HP_SFC%"=="build" exit /b 1 if "%HP_SFC:~0,1%"=="~" exit /b 1 if "%HP_SFC:~0,1%"=="." exit /b 1 dir /b /a-d "%HP_SFC%\*.py" >nul 2>&1 exit /b %errorlevel% :subfolder_hint_found echo Hint: found .py file(s) in a subfolder, but this script only looks in the exact folder it is in. >> "%LOG%" echo Hint: found .py file(s) in a subfolder, but this script only looks in the exact folder it is in. echo Move run_setup.bat next to your scripts, or move your scripts up into this folder, then run it again. >> "%LOG%" echo Move run_setup.bat next to your scripts, or move your scripts up into this folder, then run it again. call :log "[INFO] REQ-002: found .py file(s) in an immediate subfolder; this script only scans its own folder." exit /b 0 :select_conda_bat set "CONDA_BAT=" if exist "%CONDA_MAIN%" set "CONDA_BAT=%CONDA_MAIN%" if not defined CONDA_BAT if exist "%CONDA_ALT%" set "CONDA_BAT=%CONDA_ALT%" if defined CONDA_BAT if not exist "%CONDA_BAT%" set "CONDA_BAT=" exit /b 0 :download_miniconda_exe set "HP_MINICONDA_ACTIVE_URL=%HP_MINICONDA_URL%" if "%HP_TEST_CONDA_DL_FALLBACK%"=="1" if not defined HP_CONDA_DL_INJECTED set "HP_MINICONDA_ACTIVE_URL=https://miniconda-test-fail.invalid/Miniconda3-latest-Windows-x86_64.exe" if "%HP_OFFLINE_MODE%"=="1" ( call :log "[INFO] REQ-013: Offline mode: skipping Miniconda download." goto :eof ) call :log "[INFO] Downloading Miniconda from %HP_MINICONDA_ACTIVE_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_ACTIVE_URL%" -o "%TEMP%\miniconda.exe" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\miniconda.exe" goto :eof echo *** curl download failed, trying PowerShell... powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_MINICONDA_ACTIVE_URL%' -OutFile '%TEMP%\miniconda.exe' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\miniconda.exe" goto :eof if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1 rem REQ-013: primary download failed; check connectivity before trying fallback. rem Skip connectivity check when HP_TEST_CONDA_DL_FALLBACK=1 (failure was intentional). if not "%HP_TEST_CONDA_DL_FALLBACK%"=="1" call :check_net_after_dl_fail if "%HP_OFFLINE_MODE%"=="1" goto :eof if defined HP_CONDA_DL_INJECTED ( call :log "[ERROR] Injected HP_MINICONDA_URL failed; not trying fallback." goto :eof ) call :log "[INFO] Trying fallback Miniconda URL: %HP_MINICONDA_FALLBACK_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_FALLBACK_URL%" -o "%TEMP%\miniconda.exe" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\miniconda.exe" ( call :log "[INFO] Miniconda download succeeded from fallback URL." goto :eof ) powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_MINICONDA_FALLBACK_URL%' -OutFile '%TEMP%\miniconda.exe' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\miniconda.exe" ( call :log "[INFO] Miniconda download succeeded from fallback URL." goto :eof ) call :log "[WARN] Miniconda: all download URLs failed." goto :eof :download_get_pip rem REQ-023b: fetches get-pip.py so the venv fallback tier can bootstrap pip after a rem --without-pip venv creation (see :try_venv_fallback). Sets HP_GETPIP_PY to the downloaded rem file path on success; leaves it undefined/absent on failure so the caller can detect it via rem "if not exist". No interactive connectivity gate here (unlike :download_miniconda_exe's use rem of :check_net_after_dl_fail) -- this runs deep inside a fallback tier where the zero-friction rem design intent reserves the one interactive prompt for the REQ-014 system-Python consent gate; rem a plain download failure here should silently decline the tier, not stop to ask the user. set "HP_GETPIP_PY=" rem The HP_TEST_FORCE_VENV_CREATE_FAIL / HP_TEST_FORCE_EMBED_REAL exceptions below let CI rem exercise this real download path while still using HP_OFFLINE_MODE=1 to cheaply skip rem unrelated downloads (Miniconda, the embed zip's own earlier PowerShell-stage download) rem elsewhere in the same test run -- neither flag weakens real-user offline protection, since rem they are never set outside CI coverage. set "HP_GETPIP_SKIP_OFFLINE=1" if "%HP_TEST_FORCE_VENV_CREATE_FAIL%"=="1" set "HP_GETPIP_SKIP_OFFLINE=" if "%HP_TEST_FORCE_EMBED_REAL%"=="1" set "HP_GETPIP_SKIP_OFFLINE=" if "%HP_OFFLINE_MODE%"=="1" if defined HP_GETPIP_SKIP_OFFLINE ( call :log "[INFO] REQ-013: Offline mode: skipping get-pip.py download." goto :eof ) call :log "[INFO] Downloading get-pip.py from %HP_GETPIP_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_GETPIP_URL%" -o "%TEMP%\get-pip.py" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\get-pip.py" ( set "HP_GETPIP_PY=%TEMP%\get-pip.py" goto :eof ) powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_GETPIP_URL%' -OutFile '%TEMP%\get-pip.py' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\get-pip.py" ( set "HP_GETPIP_PY=%TEMP%\get-pip.py" goto :eof ) if exist "%TEMP%\get-pip.py" del "%TEMP%\get-pip.py" >nul 2>&1 call :log "[INFO] Trying fallback get-pip.py URL: %HP_GETPIP_FALLBACK_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_GETPIP_FALLBACK_URL%" -o "%TEMP%\get-pip.py" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\get-pip.py" ( call :log "[INFO] get-pip.py download succeeded from fallback URL." set "HP_GETPIP_PY=%TEMP%\get-pip.py" goto :eof ) powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_GETPIP_FALLBACK_URL%' -OutFile '%TEMP%\get-pip.py' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%TEMP%\get-pip.py" ( call :log "[INFO] get-pip.py download succeeded from fallback URL." set "HP_GETPIP_PY=%TEMP%\get-pip.py" goto :eof ) call :log "[WARN] get-pip.py: all download URLs failed." goto :eof :handle_conda_failure set "HP_FAIL_MSG=%~1" if not "%HP_FAIL_MSG%"=="" call :log "%HP_FAIL_MSG%" if "%HP_FORCE_CONDA_ONLY%"=="1" ( rem derived requirement: the dedicated conda CI slice must surface conda failures instead of hiding behind venv/system fallbacks. call :log "[INFO] Conda-only mode: skipping fallback attempts." exit /b 0 ) rem REQ-009 (reordered): embedded Python (Tier 5 by naming/history, executed 3rd since this rem reorder) is attempted immediately after conda fails, ahead of venv/system -- so a user who rem pinned a newer-than-ambient Python version via runtime.txt/pyproject.toml still gets it via rem a fresh checksummed python.org download, before falling back to a tier that just wraps rem whatever's already on the machine. No consent gate (see :try_embed_fallback's header rem comment for why). See docs/agent-interconnect.md "Standalone Python-download tier" for the rem full ordering rationale. call :try_embed_fallback if not errorlevel 1 ( set "HP_ENV_READY=1" exit /b 0 ) rem REQ-009: venv fallback is attempted when conda and embed both fail; HP_ALLOW_VENV_FALLBACK rem is deprecated/ignored. call :try_venv_fallback if not errorlevel 1 ( set "HP_ENV_READY=1" exit /b 0 ) rem REQ-009/REQ-014: system Python is Tier 4 (by naming/history) and the absolute last resort -- rem the only tier gated by the REQ-014 consent prompt (touches the user's real environment), so rem it stays final regardless of embed's move. HP_ALLOW_SYSTEM_FALLBACK is deprecated/ignored. call :try_system_fallback if not errorlevel 1 ( set "HP_ENV_READY=1" exit /b 0 ) exit /b 0 :try_venv_fallback call :log "[WARN] Attempting venv fallback..." if "%HP_TEST_FORCE_VENV_FAIL%"=="1" ( call :log "[TEST] HP_TEST_FORCE_VENV_FAIL: simulating venv creation failure." exit /b 1 ) call :resolve_system_python if errorlevel 1 ( call :log "[WARN] venv fallback: system Python not found." exit /b 1 ) if exist ".\.venv" rd /s /q ".\.venv" >nul 2>&1 if "%HP_TEST_FORCE_VENV_CREATE_FAIL%"=="1" goto :venv_create_retry if defined HP_SYS_ARGS ( "%HP_SYS_CMD%" %HP_SYS_ARGS% -m venv .\.venv >> "%LOG%" 2>&1 ) else ( "%HP_SYS_CMD%" -m venv .\.venv >> "%LOG%" 2>&1 ) if not errorlevel 1 goto :venv_create_ok rem REQ-023b: "python -m venv" (which requires ensurepip) can fail outright on a stripped-down rem host Python that is missing ensurepip -- a commonly cited real-world failure mode. Retry rem once with --without-pip (which does not need ensurepip) and manually bootstrap pip via rem get-pip.py, mirroring the existing Miniconda/uv download-with-fallback pattern rem (:download_get_pip). Goto-based per "Provider-cascade dispatch is goto-based on purpose" in rem docs/agent-lessons-learned.md. :venv_create_retry call :log "[WARN] venv fallback: python -m venv failed; retrying once with --without-pip." if exist ".\.venv" rd /s /q ".\.venv" >nul 2>&1 if defined HP_SYS_ARGS ( "%HP_SYS_CMD%" %HP_SYS_ARGS% -m venv .\.venv --without-pip >> "%LOG%" 2>&1 ) else ( "%HP_SYS_CMD%" -m venv .\.venv --without-pip >> "%LOG%" 2>&1 ) if errorlevel 1 ( call :log "[WARN] venv fallback: python -m venv --without-pip also failed." exit /b 1 ) set "HP_PY=%CD%\.venv\Scripts\python.exe" if not exist "%HP_PY%" ( call :log "[WARN] venv fallback: interpreter missing after --without-pip creation." exit /b 1 ) call :download_get_pip if not exist "%HP_GETPIP_PY%" ( call :log "[WARN] venv fallback: get-pip.py download failed; venv has no pip." exit /b 1 ) "%HP_PY%" "%HP_GETPIP_PY%" >> "%LOG%" 2>&1 if errorlevel 1 ( del "%HP_GETPIP_PY%" >nul 2>&1 call :log "[WARN] venv fallback: get-pip.py bootstrap failed." exit /b 1 ) del "%HP_GETPIP_PY%" >nul 2>&1 call :log "[INFO] venv fallback: pip bootstrapped successfully via get-pip.py." goto :venv_create_pip_ready :venv_create_ok set "HP_PY=%CD%\.venv\Scripts\python.exe" if not exist "%HP_PY%" ( call :log "[WARN] venv fallback: interpreter missing after creation." exit /b 1 ) :venv_create_pip_ready rem REQ-023: canary probe -- a venv can be "created" (directory + exe present) yet still be rem non-functional (missing DLLs, broken symlinks, execution-policy blocks). Verify the fresh rem interpreter actually runs before declaring success, so a silently broken venv doesn't reach rem PyInstaller only to fail later with a more confusing error. Goto-based (not nested inside rem an if/else block) per "Provider-cascade dispatch is goto-based on purpose" in rem docs/agent-lessons-learned.md, so the probe call + %ERRORLEVEL% read is never frozen by rem cmd's parse-time %VAR% expansion. if "%HP_TEST_FORCE_VENV_CANARY_FAIL%"=="1" goto :venv_canary_fail "%HP_PY%" -c "import sys" >nul 2>&1 if errorlevel 1 goto :venv_canary_fail goto :venv_canary_ok :venv_canary_fail call :log "[WARN] venv fallback: interpreter created but failed canary probe (import sys)." rem derived requirement: HP_PY was set above to the venv interpreter path in preparation for rem success, but a failed canary probe must not leak it forward -- a later gate rem (:after_env_mode_selection's "if not defined HP_PY") would otherwise treat this failed tier rem as if a real provider had been selected, silently proceeding with a broken interpreter rem instead of reaching :die. Exact mirror of the :try_system_fallback fix. See rem docs/agent-lessons-learned.md "A declined/failed fallback tier must clear HP_PY". set "HP_PY=" exit /b 1 :venv_canary_ok set "HP_ENV_MODE=venv" set "HP_BOOTSTRAP_STATE=venv_env" set "HP_SKIP_PIPREQS=" call :log "[INFO] venv fallback ready: %HP_PY%" call :log "[BOOT] REQ-009: Selected Python provider: Local venv (fallback)." exit /b 0 :try_system_fallback call :log "[WARN] Attempting system Python fallback (degraded)..." call :resolve_system_python if errorlevel 1 ( call :log "[WARN] system fallback: interpreter not available." exit /b 1 ) set "HP_PY=%HP_SYS_EXE%" if not exist "%HP_PY%" ( call :log "[WARN] system fallback: resolved interpreter path missing." set "HP_PY=" exit /b 1 ) rem REQ-014: consent gate before using global system Python. call :system_python_consent_gate if errorlevel 1 ( call :log "[INFO] REQ-014: System Python fallback aborted: consent not granted." rem derived requirement: HP_PY was set above to prepare for a possible accept, but rem a decline must not leak it forward -- a later gate, :after_env_mode_selection's rem own "if not defined HP_PY" check, would otherwise treat this exhausted tier as if a real rem provider had been selected, silently proceeding with a stale interpreter path rem instead of reaching :die. See docs/agent-lessons-learned.md. set "HP_PY=" exit /b 1 ) set "HP_ENV_MODE=system" set "HP_BOOTSTRAP_STATE=degraded_env" set "HP_SKIP_PIPREQS=1" call :log "[INFO] System fallback using %HP_PY%" call :log "[BOOT] REQ-009: Selected Python provider: System Python (degraded)." exit /b 0 :resolve_system_python set "HP_SYS_CMD=" set "HP_SYS_ARGS=" set "HP_SYS_EXE=" where python >nul 2>&1 && set "HP_SYS_CMD=python" if not defined HP_SYS_CMD ( where py >nul 2>&1 && (set "HP_SYS_CMD=py" & set "HP_SYS_ARGS=-3") ) if not defined HP_SYS_CMD exit /b 1 set "HP_SYS_TMP=~sys_exe.txt" if exist "%HP_SYS_TMP%" del "%HP_SYS_TMP%" >nul 2>&1 if defined HP_SYS_ARGS ( "%HP_SYS_CMD%" %HP_SYS_ARGS% -c "import sys;print(sys.executable)" > "%HP_SYS_TMP%" 2>nul ) else ( "%HP_SYS_CMD%" -c "import sys;print(sys.executable)" > "%HP_SYS_TMP%" 2>nul ) if exist "%HP_SYS_TMP%" ( set /p HP_SYS_EXE=<"%HP_SYS_TMP%" del "%HP_SYS_TMP%" >nul 2>&1 ) if not defined HP_SYS_EXE exit /b 1 exit /b 0 :try_embed_fallback rem REQ-009 Tier 5: last-resort fallback when uv, conda, venv, and system all failed (or no rem ambient interpreter exists at all). Unlike system Python, embed is a private, checksummed, rem bootstrapper-controlled extraction -- no REQ-014-style consent gate, matching venv's rem zero-friction treatment, not system's. Two stages: PowerShell (~embed_extract.ps1) always rem fetches ONE hardcoded "latest" version with no per-request branching; Python rem (~embed_pyver_check.py), running under that fresh interpreter, checks PYSPEC (already rem computed earlier by ~detect_python.py -- the same value uv/conda already honor) and re-fetches rem a different version via its own urllib/hashlib/zipfile if requested. See rem docs/agent-interconnect.md "Standalone Python-download tier" for the full design rationale, rem including why this is deliberately NOT all-PowerShell. call :log "[WARN] Attempting embedded Python download (REQ-009 Tier 5)..." if "%HP_TEST_FORCE_EMBED_FAIL%"=="1" ( call :log "[TEST] HP_TEST_FORCE_EMBED_FAIL: simulating embed tier failure." exit /b 1 ) rem derived requirement: HP_TEST_FORCE_EMBED_REAL punches a narrow hole through the offline rem gate for this tier only, mirroring HP_TEST_FORCE_VENV_CREATE_FAIL's exception for rem :download_get_pip -- lets CI exercise the real embed download while HP_OFFLINE_MODE=1 still rem blocks the earlier uv/conda/venv tiers' unrelated downloads in the same test run. if "%HP_OFFLINE_MODE%"=="1" if not "%HP_TEST_FORCE_EMBED_REAL%"=="1" ( call :log "[WARN] embed fallback: offline mode; cannot download embedded Python." exit /b 1 ) set "HP_EMBED_URL=https://www.python.org/ftp/python/%HP_EMBED_LATEST_PATCH%/python-%HP_EMBED_LATEST_PATCH%-embed-amd64.zip" set "HP_EMBED_ZIP=%TEMP%\python-%HP_EMBED_LATEST_PATCH%-embed-amd64.zip" set "HP_EMBED_DIR=%HP_SCRIPT_ROOT%~embed_python" rem derived requirement: a single retry of the WHOLE download+verify cycle on either a download rem failure or a checksum mismatch -- mirrors the existing transient-network-retry pattern used rem by :try_conda_create/:conda_bulk_install (REQ-022) elsewhere in this file. A checksum mismatch rem here does not necessarily mean a bad pin; it can mean a truncated/corrupted download, so rem redownloading (not just re-verifying) is the correct remedy. Goto-based, not nested inside a rem parenthesized if/else, per "Provider-cascade dispatch is goto-based on purpose" in rem docs/agent-lessons-learned.md. set "HP_EMBED_DL_ATTEMPT=0" :embed_dl_retry set /a HP_EMBED_DL_ATTEMPT+=1 if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1 rem derived requirement: HP_TEST_FORCE_EMBED_DL_FAIL_ONCE (CLAUDE.md former Active Backlog item rem 12) deterministically fails ONLY the first attempt, without touching the real network, so rem this genuine mid-download-failure-then-retry-once path gets CI coverage -- mirrors rem HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL's established one-shot-then-succeed pattern. Checked rem BEFORE the real curl/PowerShell calls and cleared immediately, so attempt 2 always goes rem through for real. if "%HP_TEST_FORCE_EMBED_DL_FAIL_ONCE%"=="1" if "%HP_EMBED_DL_ATTEMPT%"=="1" goto :embed_dl_test_fail_once call :log "[INFO] Downloading embedded Python %HP_EMBED_LATEST_PATCH% from %HP_EMBED_URL%..." curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_EMBED_URL%" -o "%HP_EMBED_ZIP%" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%HP_EMBED_ZIP%" goto :embed_dl_ok echo *** curl download failed, trying PowerShell... powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_EMBED_URL%' -OutFile '%HP_EMBED_ZIP%' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1 if not errorlevel 1 if exist "%HP_EMBED_ZIP%" goto :embed_dl_ok goto :embed_dl_attempt_failed :embed_dl_test_fail_once call :log "[TEST] HP_TEST_FORCE_EMBED_DL_FAIL_ONCE: simulating download failure on attempt 1 (no network touched)." set "HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=" :embed_dl_attempt_failed if %HP_EMBED_DL_ATTEMPT% LSS 2 ( call :log "[WARN] embed fallback: download failed; retrying once." goto :embed_dl_retry ) call :log "[WARN] embed fallback: download failed (both curl and PowerShell)." exit /b 1 :embed_dl_ok call :emit_from_base64 "~embed_extract.ps1" HP_EMBED_EXTRACT if errorlevel 1 ( call :log "[WARN] embed fallback: could not write ~embed_extract.ps1." if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1 exit /b 1 ) set "HP_EMBED_PY=" for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -File "~embed_extract.ps1" "%HP_EMBED_ZIP%" "%HP_EMBED_LATEST_SHA256%" "%HP_EMBED_DIR%" 2^>^> "%LOG%"`) do set "HP_EMBED_PY=%%P" if exist "~embed_extract.ps1" del "~embed_extract.ps1" >nul 2>&1 if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1 if not exist "%HP_EMBED_PY%" ( if %HP_EMBED_DL_ATTEMPT% LSS 2 ( call :log "[WARN] embed fallback: checksum verification or extraction failed; retrying download once." goto :embed_dl_retry ) call :log "[WARN] embed fallback: checksum verification or extraction failed." exit /b 1 ) call :log "[INFO] embed fallback: %HP_EMBED_LATEST_PATCH% extracted and verified." rem --- Python stage: only place per-request version logic lives (deliberately not PowerShell rem a second time). A failure here is non-fatal -- the "latest" interpreter from the stage above rem is kept and used as-is; only a genuine version *mismatch* is lost, not the whole tier. rem derived requirement: this block used to be one big parenthesized "if not errorlevel 1 ( ... )" rem block. A for /f loop inside it set HP_EMBED_SWAP_DIR/_TAG/_MINOR, and code later in the SAME rem block read %HP_EMBED_SWAP_DIR% to decide whether to swap -- but CMD's parse-time %VAR% rem expansion substitutes every %VAR% in a parenthesized block using the value from BEFORE the rem block began, not a value a for /f loop set earlier in the same block's own execution. Since rem HP_EMBED_SWAP_DIR was never set before this point, that read was always empty, so the swap rem NEVER executed regardless of what the Python stage actually requested -- the entire rem "pull latest, then swap to the user's requested version" feature was dead code. Fixed via rem goto-based dispatch (see "Provider-cascade dispatch is goto-based on purpose" in rem docs/agent-lessons-learned.md) so every %VAR% read below reflects its true runtime value. call :emit_from_base64 "~embed_pyver_check.py" HP_EMBED_PYVER_CHECK if errorlevel 1 goto :embed_pyver_check_skip set "HP_EMBED_CHECK_OUT=~embed_pyver_check.txt" if exist "%HP_EMBED_CHECK_OUT%" del "%HP_EMBED_CHECK_OUT%" >nul 2>&1 "%HP_EMBED_PY%" "~embed_pyver_check.py" "%HP_EMBED_DIR%" > "%HP_EMBED_CHECK_OUT%" 2>> "%LOG%" if exist "~embed_pyver_check.py" del "~embed_pyver_check.py" >nul 2>&1 set "HP_EMBED_SWAP_TAG=" set "HP_EMBED_SWAP_MINOR=" set "HP_EMBED_SWAP_DIR=" for /f "usebackq tokens=1,2,3 delims=|" %%A in ("%HP_EMBED_CHECK_OUT%") do ( set "HP_EMBED_SWAP_TAG=%%A" set "HP_EMBED_SWAP_MINOR=%%B" set "HP_EMBED_SWAP_DIR=%%C" ) if exist "%HP_EMBED_CHECK_OUT%" del "%HP_EMBED_CHECK_OUT%" >nul 2>&1 rem derived requirement: the version-check process (the "%HP_EMBED_PY%" call above) has rem already fully exited by this point, so its file locks on HP_EMBED_DIR are released and rem it is now safe to replace that directory -- swapping while that process was still running rem would fail (Windows will not let a process delete/replace the files it is executing from), rem which is exactly why the Python stage extracted into a sibling _swap directory instead of rem overwriting HP_EMBED_DIR itself. See docs/agent-interconnect.md. if not defined HP_EMBED_SWAP_DIR goto :embed_pyver_check_tagcheck if not exist "%HP_EMBED_SWAP_DIR%\python.exe" goto :embed_pyver_check_tagcheck rem derived requirement: rd /s /q can return before an AV/indexer file handle on HP_EMBED_DIR rem fully releases (a real, if low-severity, Windows deletion race). Unlike a FILE move onto an rem existing destination (atomic replace-or-noop), a DIRECTORY move onto a destination that still rem exists does not fail cleanly -- it silently NESTS the source inside the destination instead rem (HP_EMBED_SWAP_DIR ends up at HP_EMBED_DIR\, not replacing HP_EMBED_DIR's rem contents). A post-hoc "does HP_EMBED_DIR\python.exe exist" check cannot detect this: if rd rem fails, the STALE prior python.exe is still sitting at HP_EMBED_DIR's top level regardless of rem what move did, so the check reads success either way. Fixed by gating move on rd having rem actually cleared the destination first -- move then only ever runs onto a nonexistent target rem (pure rename, no nesting possible), which makes the post-check reliable again. Retries the rem pair up to 3 total attempts with a short pause, so a rare transient lock doesn't needlessly rem fail the last-resort tier. Uses ping (not timeout /t) for the pause -- this file already has rem a proven-safe idiom for exactly this at line 1461's VISA-detection delay. set "HP_EMBED_SWAP_ATTEMPT=0" :embed_swap_retry set /a HP_EMBED_SWAP_ATTEMPT+=1 rd /s /q "%HP_EMBED_DIR%" >nul 2>&1 if exist "%HP_EMBED_DIR%" goto :embed_swap_rd_failed move /y "%HP_EMBED_SWAP_DIR%" "%HP_EMBED_DIR%" >nul 2>&1 if exist "%HP_EMBED_DIR%\python.exe" ( call :log "[INFO] embed fallback: swapped to requested Python %HP_EMBED_SWAP_MINOR%." goto :embed_pyver_check_tagcheck ) :embed_swap_rd_failed if %HP_EMBED_SWAP_ATTEMPT% LSS 3 ( ping -n 2 127.0.0.1 >nul 2>&1 goto :embed_swap_retry ) call :log "[WARN] embed fallback: swap move failed; interpreter may be missing." :embed_pyver_check_tagcheck if /i "%HP_EMBED_SWAP_TAG%"=="fellback" call :log "[WARN] REQ-009: requested Python not in embed table; using %HP_EMBED_SWAP_MINOR% instead." :embed_pyver_check_skip set "HP_EMBED_PY=%HP_EMBED_DIR%\python.exe" if not exist "%HP_EMBED_PY%" ( call :log "[WARN] embed fallback: interpreter missing after version-check stage." exit /b 1 ) rem --- bootstrap pip via get-pip.py (reuses the existing REQ-023b download) --- call :download_get_pip if not exist "%HP_GETPIP_PY%" ( call :log "[WARN] embed fallback: get-pip.py download failed." exit /b 1 ) "%HP_EMBED_PY%" "%HP_GETPIP_PY%" >> "%LOG%" 2>&1 if errorlevel 1 ( del "%HP_GETPIP_PY%" >nul 2>&1 call :log "[WARN] embed fallback: get-pip.py bootstrap failed." exit /b 1 ) del "%HP_GETPIP_PY%" >nul 2>&1 rem --- canary probe (matches REQ-023's venv canary probe) --- "%HP_EMBED_PY%" -c "import sys, pip" >nul 2>&1 if errorlevel 1 ( call :log "[WARN] embed fallback: interpreter created but failed canary probe (import sys, pip)." exit /b 1 ) set "HP_ENV_MODE=embed" set "HP_PY=%HP_EMBED_PY%" set "HP_BOOTSTRAP_STATE=embed_env" set "HP_SKIP_PIPREQS=" call :log "[INFO] embed fallback ready: %HP_PY%" call :log "[BOOT] REQ-009: Selected Python provider: Embedded Python (python.org)." exit /b 0 :append_env_mode_row if not defined HP_NDJSON exit /b 0 if not defined HP_PY exit /b 0 powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$mode = [Environment]::GetEnvironmentVariable('HP_ENV_MODE');" ^ "$py = [Environment]::GetEnvironmentVariable('HP_PY');" ^ "if (-not $mode) { $mode = 'unknown' }" ^ "$row = @{ id='env.mode'; pass=$true; details=@{ mode=$mode; py=$py } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 exit /b 0 :update_find_entry_abs rem derived requirement: CI skip lane could change the working directory while probing rem for helper crumbs. Recompute the absolute helper path each time so commands like rem `py -3` never see a dangling relative path. if exist "%HP_FIND_ENTRY_NAME%" ( for %%F in ("%HP_FIND_ENTRY_NAME%") do set "HP_FIND_ENTRY_ABS=%%~fF" ) else if defined HP_SCRIPT_ROOT ( rem derived requirement: helper lookups must stay rooted to the bootstrapper rem directory even if callers pushd elsewhere. Use HP_SCRIPT_ROOT so the CI rem skip lane never feeds CMD a dangling relative path. set "HP_FIND_ENTRY_ABS=%HP_SCRIPT_ROOT%%HP_FIND_ENTRY_NAME%" ) else ( set "HP_FIND_ENTRY_ABS=%CD%\%HP_FIND_ENTRY_NAME%" ) exit /b 0 :verify_find_entry_helper if "%HP_FIND_ENTRY_SYNTAX_OK%"=="1" exit /b 0 if "%HP_FIND_ENTRY_SYNTAX_OK%"=="0" exit /b 1 set "HP_HELPER_CMD=" set "HP_HELPER_ARGS=" set "HP_HELPER_SYNTAX_PASS=0" if defined HP_PY if exist "%HP_PY%" set "HP_HELPER_CMD=%HP_PY%" if not defined HP_HELPER_CMD ( where python >nul 2>&1 && set "HP_HELPER_CMD=python" ) if not defined HP_HELPER_CMD ( where py >nul 2>&1 && (set "HP_HELPER_CMD=py" & set "HP_HELPER_ARGS=-3") ) if defined HP_HELPER_CMD for %%C in ("%HP_HELPER_CMD%") do set "HP_HELPER_CMD=%%~C" if defined HP_HELPER_ARGS for %%A in ("%HP_HELPER_ARGS%") do set "HP_HELPER_ARGS=%%~A" if defined HP_HELPER_CMD ( if not defined HP_HELPER_CMD_LOGGED ( rem derived requirement: capture the helper command verbatim so future regressions can rem trace quoting issues without reproducing CI. Logged once per bootstrap run. if defined HP_HELPER_ARGS ( >> "%LOG%" echo Helper command: "%HP_HELPER_CMD%" %HP_HELPER_ARGS% "%HP_FIND_ENTRY_ABS%" ) else ( >> "%LOG%" echo Helper command: "%HP_HELPER_CMD%" "%HP_FIND_ENTRY_ABS%" ) set "HP_HELPER_CMD_LOGGED=1" ) if defined HP_HELPER_ARGS ( "%HP_HELPER_CMD%" %HP_HELPER_ARGS% -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul ) else ( "%HP_HELPER_CMD%" -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul ) if errorlevel 1 ( set "HP_HELPER_SYNTAX_PASS=0" ) else ( set "HP_HELPER_SYNTAX_PASS=1" ) ) else ( set "HP_HELPER_SYNTAX_PASS=0" ) set "HP_FIND_ENTRY_SYNTAX_OK=%HP_HELPER_SYNTAX_PASS%" call :append_helper_syntax_row set "HP_HELPER_CMD=" set "HP_HELPER_ARGS=" if "%HP_HELPER_SYNTAX_PASS%"=="1" exit /b 0 exit /b 1 :append_helper_syntax_row if "%HP_HELPER_SYNTAX_EMITTED%"=="1" exit /b 0 if not defined HP_NDJSON ( set "HP_HELPER_SYNTAX_EMITTED=1" exit /b 0 ) if "%HP_FIND_ENTRY_SYNTAX_OK%"=="" ( set "HP_HELPER_SYNTAX_EMITTED=1" exit /b 0 ) powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$flag = [Environment]::GetEnvironmentVariable('HP_FIND_ENTRY_SYNTAX_OK');" ^ "if (-not $flag) { $flag = '0' }" ^ "$ok = $flag -eq '1';" ^ "$row = @{ id='helper.find_entry.syntax'; pass=$ok; details=@{ } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 set "HP_HELPER_SYNTAX_EMITTED=1" exit /b 0 :emit_conda_probe_skip if not defined HP_NDJSON exit /b 0 powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$reason = [Environment]::GetEnvironmentVariable('HP_CONDA_PROBE_REASON');" ^ "if (-not $reason) { $reason = 'not-requested' }" ^ "$row = @{ id='conda.url'; pass=$true; details=@{ skipped=$true; reason=$reason; bytes=0 } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 exit /b 0 :probe_conda_url set "HP_DL_PATH=~miniconda.exe" if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1 curl -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_URL%" -o "%HP_DL_PATH%" >> "%LOG%" 2>&1 if errorlevel 1 goto :probe_conda_url_fail set "HP_DL_BYTES=0" if exist "%HP_DL_PATH%" for %%S in ("%HP_DL_PATH%") do set "HP_DL_BYTES=%%~zS" set "HP_DL_PASS=1" if not defined HP_DL_BYTES set "HP_DL_BYTES=0" for /f "tokens=*" %%B in ("%HP_DL_BYTES%") do set "HP_DL_BYTES=%%B" for /f "tokens=*" %%B in ("%HP_MINICONDA_MIN_BYTES%") do set "HP_MIN_BYTES_SAFE=%%B" if not defined HP_MIN_BYTES_SAFE set "HP_MIN_BYTES_SAFE=0" set /a HP_BYTES_CHECK=%HP_DL_BYTES% set /a HP_MIN_CHECK=%HP_MIN_BYTES_SAFE% if %HP_BYTES_CHECK% LSS %HP_MIN_CHECK% set "HP_DL_PASS=0" if "%HP_DL_PASS%"=="0" goto :probe_conda_url_fail_with_bytes call :log "[INFO] Miniconda probe downloaded %HP_DL_BYTES% bytes." if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$row = @{ id='conda.url'; pass=$true; details=@{ bytes=%HP_DL_BYTES% } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1 exit /b 0 :probe_conda_url_fail_with_bytes if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$row = @{ id='conda.url'; pass=$false; details=@{ bytes=%HP_DL_BYTES% } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1 exit /b 1 :probe_conda_url_fail set "HP_DL_BYTES=0" if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$row = @{ id='conda.url'; pass=$false; details=@{ bytes=0 } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1 exit /b 1 :extract_pep723_requirements set "HP_PEP723_IN=%~1" set "HP_PEP723_OUT=%~2" if exist "%HP_PEP723_OUT%" del "%HP_PEP723_OUT%" >nul 2>&1 powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$inside = $false; $deps = $false;" ^ "Get-Content -LiteralPath '%HP_PEP723_IN%' | ForEach-Object {" ^ " $line = $_;" ^ " if (-not $inside) { if ($line -eq '# /// script') { $inside = $true }; return }" ^ " if ($line -eq '# ///') { $inside = $false; $deps = $false; return }" ^ " $trim = $line.Trim();" ^ " $compact = ($trim -replace '\s','');" ^ " if ($compact.StartsWith('#dependencies=') -and $compact.EndsWith('[')) { $deps = $true; return }" ^ " if ($deps -and $compact -eq '#]') { $deps = $false; return }" ^ " if ($deps -and $trim.StartsWith('# ""')) { $item = $trim.Substring(3).Trim(); if ($item.EndsWith('""')) { $item = $item.Substring(0, $item.Length - 1) }; $item }" ^ "} | Set-Content -LiteralPath '%HP_PEP723_OUT%' -Encoding ASCII" >> "%LOG%" 2>&1 exit /b %errorlevel% :determine_entry set "HP_ENTRY=" set "HP_ENTRY_CMD=" set "HP_ENTRY_ARGS=" if not "%~1"=="" ( rem REQ-011: %~dp1 = caller's argument directory; %~dp0 = batch script directory -- both include a trailing \ if /i not "%~dp1"=="%~dp0" ( echo [ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness. call :log "[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder." exit /b 11 ) if exist "%~1" ( rem derived requirement: use the %~1 parameter directly, not %MAIN_FILE%. Inside this rem parenthesized block %MAIN_FILE% expands at parse time -- before "set MAIN_FILE" runs -- rem which yielded an empty HP_ENTRY and an empty "Using drag-and-drop file:" message. rem %~1 is the call parameter and expands to the argument value, so both are correct. set "HP_ENTRY=%~1" if not defined HP_DRAG_MSG_EMITTED ( echo *** Using drag-and-drop file: %~1 set "HP_DRAG_MSG_EMITTED=1" ) exit /b 0 ) ) call :emit_from_base64 "~find_entry.py" HP_FIND_ENTRY if errorlevel 1 exit /b 1 call :update_find_entry_abs call :verify_find_entry_helper if errorlevel 1 exit /b 1 if defined HP_PY if exist "%HP_PY%" set "HP_ENTRY_CMD=%HP_PY%" if not defined HP_ENTRY_CMD if defined CONDA_BASE_PY if exist "%CONDA_BASE_PY%" set "HP_ENTRY_CMD=%CONDA_BASE_PY%" if not defined HP_ENTRY_CMD ( where python >nul 2>&1 && set "HP_ENTRY_CMD=python" ) if not defined HP_ENTRY_CMD ( where py >nul 2>&1 && (set "HP_ENTRY_CMD=py" & set "HP_ENTRY_ARGS=-3") ) if defined HP_ENTRY_CMD for %%C in ("%HP_ENTRY_CMD%") do set "HP_ENTRY_CMD=%%~C" if defined HP_ENTRY_ARGS for %%A in ("%HP_ENTRY_ARGS%") do set "HP_ENTRY_ARGS=%%~A" if not defined HP_ENTRY_CMD exit /b 0 rem derived requirement: maintain split helper calls so `py -3` and bare `python` both expand rem without producing `python"` tokens. This mirrors the CI skip logic above and uses a crumb file rem so Windows never merges tokens when arguments are empty. set "HP_ENTRY_CRUMB=~entry.crumb" if exist "%HP_ENTRY_CRUMB%" del "%HP_ENTRY_CRUMB%" >nul 2>&1 if defined HP_ENTRY_ARGS ( "%HP_ENTRY_CMD%" %HP_ENTRY_ARGS% "%HP_FIND_ENTRY_ABS%" > "%HP_ENTRY_CRUMB%" 2>> "%LOG%" ) else ( "%HP_ENTRY_CMD%" "%HP_FIND_ENTRY_ABS%" > "%HP_ENTRY_CRUMB%" 2>> "%LOG%" ) rem REQ-002: capture find_entry's exit code immediately -- AMBIGUOUS_RC (3) marks the rem alphabetical fallback (multiple files, no clear winner), the only case that offers rem the timed interactive picker. Must read %ERRORLEVEL% before any other command. set "HP_FIND_RC=%ERRORLEVEL%" if exist "%HP_ENTRY_CRUMB%" ( for /f "usebackq delims=" %%M in ("%HP_ENTRY_CRUMB%") do if not defined HP_ENTRY set "HP_ENTRY=%%M" del "%HP_ENTRY_CRUMB%" >nul 2>&1 ) if not defined HP_ENTRY set "HP_ENTRY=" if defined HP_ENTRY if "%HP_FIND_RC%"=="3" call :pick_entry_interactive set "HP_FIND_RC=" exit /b 0 :pick_entry_interactive rem REQ-002: timed interactive entry picker for the ambiguous case. The default rem (Enter / timeout) is find_entry's alphabetical pick (HP_ENTRY). Skipped when rem non-interactive: NOINPUT, HP_NONINTERACTIVE, or CI (HP_CI_LANE) -- unless rem HP_TEST_FORCE_PICKER forces the path (it degrades to the default with no console). if defined NOINPUT exit /b 0 if defined HP_NONINTERACTIVE exit /b 0 if defined HP_CI_LANE if not defined HP_TEST_FORCE_PICKER exit /b 0 rem choice.exe drives the timed prompt; on a stripped image without it, keep the default. where choice >nul 2>&1 || exit /b 0 if exist "~entry.menu" del "~entry.menu" >nul 2>&1 (for /f "delims=" %%F in ('dir /b /a-d *.py 2^>nul ^| findstr /v /b /c:"~" ^| sort') do echo %%F) > "~entry.menu" set "HP_PICK_N=0" for /f "usebackq delims=" %%F in ("~entry.menu") do set /a HP_PICK_N+=1 if %HP_PICK_N% LSS 2 ( del "~entry.menu" >nul 2>&1 & exit /b 0 ) if %HP_PICK_N% GTR 9 ( call :log "[INFO] REQ-002: %HP_PICK_N% candidates exceed picker limit; keeping %HP_ENTRY% (alphabetical)." echo Tip: to avoid the alphabetical fallback next time, do any one of these: echo 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run echo that file directly. It must be in this same folder. echo 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. echo 3. Give exactly one script an if __name__ == "__main__": block. del "~entry.menu" >nul 2>&1 exit /b 0 ) echo. echo Multiple Python files detected -- no clear entry point, so please choose one to run: set "HP_PICK_I=0" set "HP_PICK_DEFAULT=1" for /f "usebackq delims=" %%F in ("~entry.menu") do call :pick_menu_line "%%F" echo. echo Tip: to skip this question next time, do any one of these: echo 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run echo that file directly. It must be in this same folder. echo 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. echo 3. Give exactly one script an if __name__ == "__main__": block. echo If you do nothing, the alphabetically-first file is used: %HP_ENTRY% echo. set "HP_PICK_T=30" if defined HP_TEST_FORCE_PICKER set "HP_PICK_T=2" choice /C 123456789 /N /T %HP_PICK_T% /D %HP_PICK_DEFAULT% /M "Type a number 1-%HP_PICK_N%, or wait %HP_PICK_T%s for the default [%HP_PICK_DEFAULT%]: " set "HP_PICK_SEL=%ERRORLEVEL%" if %HP_PICK_SEL% GEQ 1 if %HP_PICK_SEL% LEQ %HP_PICK_N% ( set "HP_PICK_I=0" for /f "usebackq delims=" %%F in ("~entry.menu") do call :pick_apply_line "%%F" ) del "~entry.menu" >nul 2>&1 call :log "[INFO] REQ-002: Picker entry selected: %HP_ENTRY%" set "HP_PICK_N=" set "HP_PICK_I=" set "HP_PICK_SEL=" set "HP_PICK_DEFAULT=" set "HP_PICK_T=" exit /b 0 :pick_menu_line set /a HP_PICK_I+=1 echo [%HP_PICK_I%] %~1 if /i "%~1"=="%HP_ENTRY%" set "HP_PICK_DEFAULT=%HP_PICK_I%" exit /b 0 :pick_apply_line set /a HP_PICK_I+=1 if "%HP_PICK_I%"=="%HP_PICK_SEL%" set "HP_ENTRY=%~1" exit /b 0 :record_chosen_entry rem %~1 is the RELATIVE crumb (what we want to show users and tests) set "HP_CRUMB=%~1" if "%HP_CRUMB%"=="" exit /b 0 set "HP_CRUMB_SHOW=%HP_CRUMB%" set "HP_CRUMB_DRIVE=" for %%A in ("%HP_CRUMB_SHOW%") do ( if not "%%~dA"=="" set "HP_CRUMB_DRIVE=%%~dA" ) if not defined HP_CRUMB_DRIVE ( set "HP_CRUMB_FIRST=%HP_CRUMB_SHOW:~0,1%" if not "%HP_CRUMB_FIRST%"=="\" ( if not "%HP_CRUMB_FIRST%"=="/" ( set "HP_CRUMB_PREFIX2=%HP_CRUMB_SHOW:~0,2%" if /I not "%HP_CRUMB_PREFIX2%"==".\" ( if not "%HP_CRUMB_PREFIX2%"=="./" ( if not "%HP_CRUMB_PREFIX2%"==".." ( set "HP_CRUMB_SHOW=.\%HP_CRUMB_SHOW%" ) ) ) ) ) ) set "HP_CRUMB=%HP_CRUMB_SHOW%" set "HP_CRUMB_DRIVE=" set "HP_CRUMB_FIRST=" set "HP_CRUMB_PREFIX2=" set "HP_CRUMB_SHOW=" rem Echo to console (no punctuation at end) echo Chosen entry: %HP_CRUMB% rem Append same line to setup log >> "%LOG%" echo Chosen entry: %HP_CRUMB% call :log "[BOOT] REQ-002: Entry selected: %HP_CRUMB%" rem If we also need an absolute path for execution, set HP_ENTRY elsewhere rem and keep the echo outside any ( ... ) block. exit /b 0 :compute_interactive_run rem Slice 2b-C: single interactivity determination shared by the fail-fast probe at both rem untimed user-code launch points (:try_fast_exe, :verify_no_exe_interpreter). Mirrors rem :pick_entry_interactive's three non-interactivity signals (NOINPUT, HP_NONINTERACTIVE, rem HP_CI_LANE) so a real double-click user gets the new probe with zero flags required, rem while every CI/automation signal keeps today's plain-redirect behavior byte-for-byte rem unchanged (self.fastpath / self.exe.fastpath.graceful stay deterministic). rem HP_TEST_FORCE_INTERACTIVE_PROBE=1 forces the probe branch under HP_CI_LANE for rem dedicated, deterministic CI coverage of the new state machine (mirrors HP_TEST_FORCE_PICKER). set "HP_INTERACTIVE_RUN=1" if defined NOINPUT set "HP_INTERACTIVE_RUN=" if defined HP_NONINTERACTIVE set "HP_INTERACTIVE_RUN=" if defined HP_CI_LANE if not defined HP_TEST_FORCE_INTERACTIVE_PROBE set "HP_INTERACTIVE_RUN=" exit /b 0 :run_failfast_probe rem Slice 2b-C: shared fail-fast probe for the two untimed user-code launch points. Reuses rem :run_exe_smokerun's ProcessStartInfo + ReadToEndAsync pattern (preserves ~run.out.txt / rem ~run.err.txt for existing consumers -- envsmoke's 'smoke-ok' token, spaced-path dist\ rem token capture) but replaces the single 30s-cap-then-Kill() wait with a two-stage wait rem inside ~failfast_probe.ps1 (HP_FAILFAST_PROBE): WaitForExit(HP_FAILFAST_PROBE_MS) rem classifies a fast exit vs. still-running; if still running, a SECOND, UNBOUNDED rem WaitForExit() follows and the process is NEVER killed -- this is the only difference rem from :run_exe_smokerun, which stays the sole place in this file allowed to force-kill rem (the fresh-build verification run). Caller sets HP_PROBE_EXE / HP_PROBE_ARGS ([REQ-026]: rem a full, pre-quoted Windows Arguments string used verbatim -- the CALLER quotes each token, rem not this subroutine) / HP_PROBE_CWD before calling; %1 is a short site tag rem ('fastpath'|'interpreter'|'checkpoint' -- the last added by :run_postexec_checkpoint, rem Slice 2b-C's post-execution checkpoint) used only for the NDJSON row and log text. Always rem leaves HP_SMOKE_RC set to the true final exit code and HP_PROBE_EXCEEDED set (1) iff the probe rem window was exceeded -- the caller decides what that means (:try_fast_exe discards a rem cached EXE only when NOT exceeded; :verify_no_exe_interpreter has no cached artifact and rem just reports the final outcome either way). Caller may optionally set HP_PROBE_OUT / rem HP_PROBE_ERR to redirect the captured stdout/stderr somewhere other than the default rem ~run.out.txt / ~run.err.txt -- :run_postexec_checkpoint uses this so its elective SECOND rem run never overwrites the FIRST (real) verification run's captured output. set "HP_PROBE_SITE=%~1" set "HP_PROBE_EXCEEDED=" set "HP_SMOKE_RC=" set "HP_PROBE_PS=~failfast_probe.ps1" if not defined HP_PROBE_OUT set "HP_PROBE_OUT=~run.out.txt" if not defined HP_PROBE_ERR set "HP_PROBE_ERR=~run.err.txt" if not defined HP_PROBE_RESULT set "HP_PROBE_RESULT=~probe_result.txt" if exist "%HP_PROBE_PS%" del "%HP_PROBE_PS%" >nul 2>&1 rem pre-truncate: the helper only writes these once, at process exit, so without this an rem unbounded ALIVE-AT-PROBE wait would leave stale prior-run content lingering for its rem full duration with no indication it is stale. if exist "%HP_PROBE_OUT%" del "%HP_PROBE_OUT%" >nul 2>&1 if exist "%HP_PROBE_ERR%" del "%HP_PROBE_ERR%" >nul 2>&1 if exist "%HP_PROBE_RESULT%" del "%HP_PROBE_RESULT%" >nul 2>&1 call :emit_from_base64 "%HP_PROBE_PS%" HP_FAILFAST_PROBE if errorlevel 1 ( rem Extremely rare -- disk/permission failure writing a work file; mirror :try_fast_exe's own rem emit-failure convention of skipping gracefully rather than hand-rolling an unsafe manual rem launch here -- [REQ-026]: HP_PROBE_ARGS is now a full, pre-quoted Windows Arguments string rem the CALLER is responsible for quoting correctly, see this subroutine's own header rem comment; a direct cmd invocation here would still need that same care. rem HP_SMOKE_RC stays unset; the safety net below turns that into -1 so callers still see a rem defined, non-zero, non-"exceeded" outcome -- :try_fast_exe's discard-and-rebuild fires. call :log "[WARN] Fail-fast probe: could not emit ~failfast_probe.ps1; treating as a failed run." ) else ( rem Invoked DIRECTLY, no for /f/backtick stdout capture -- the script's own live-teed child rem output reaches the console instead of being captured and silently swallowed. The result is rem read afterward from HP_PROBE_RESULT, a static file -- safe for /f target, unlike a live rem process's stdout. See docs/plan-cli-interactive-verification.md Finding 6. powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_PROBE_PS%" if exist "%HP_PROBE_RESULT%" ( for /f "usebackq tokens=1,2 delims=|" %%A in ("%HP_PROBE_RESULT%") do ( set "HP_PROBE_EXCEEDED=%%A" set "HP_SMOKE_RC=%%B" ) ) if exist "%HP_PROBE_PS%" del "%HP_PROBE_PS%" >nul 2>&1 if exist "%HP_PROBE_RESULT%" del "%HP_PROBE_RESULT%" >nul 2>&1 ) if "%HP_PROBE_EXCEEDED%"=="0" set "HP_PROBE_EXCEEDED=" if not defined HP_SMOKE_RC set "HP_SMOKE_RC=-1" call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%" if defined HP_PROBE_EXCEEDED ( call :log "[INFO] Fail-fast probe: still running after %HP_FAILFAST_PROBE_MS%ms; this is your program's real run, not a rebuild trigger. If it has a GUI it may have opened minimized or on another window; if it is a background/console app with no window, that is expected. The bootstrapper is waiting for it to finish so it can report the final result -- it will not force-stop it." ) if "%HP_SMOKE_RC%"=="0" ( call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)" ) else ( call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)" ) if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$c=[int]'%HP_SMOKE_RC%';$ex=[bool]'%HP_PROBE_EXCEEDED%';" ^ "$r=[ordered]@{id='self.failfast.probe';pass=($c -eq 0);details=[ordered]@{site='%HP_PROBE_SITE%';exitCode=$c;probeExceeded=$ex;probeMs=[int]'%HP_FAILFAST_PROBE_MS%'}}|ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $r -Encoding ASCII" >> "%LOG%" 2>&1 ) set "HP_PROBE_EXE=" set "HP_PROBE_ARGS=" set "HP_PROBE_CWD=" set "HP_PROBE_SITE=" set "HP_PROBE_PS=" set "HP_PROBE_OUT=" set "HP_PROBE_ERR=" exit /b 0 :run_postexec_checkpoint rem REQ-018 (2b-C): post-execution checkpoint. The FIRST run (EXE smoke or no-EXE interpreter rem run) has already happened and its [STATUS] telemetry has already been printed by the time rem this is called -- this offers an ELECTIVE second run via the interpreter as a diagnostic rem tool, never forced. Worth offering even after a reported SUCCESS: the first run's exit code 0 rem is not 100% confirmation the app actually worked correctly -- a frozen EXE can differ from rem source in bundled resources, DLL binding, etc., see "Honest ambiguous-exit messaging" in rem docs/agent-interconnect.md. The interpreter path is generally the more reliable one to catch a rem real problem the EXE's own exit code alone would not surface. Declining (the default and only rem outcome in CI/automation) leaves the run footprint at exactly one execution. Called rem unconditionally after every verification rem telemetry point (never after :try_fast_exe's fast-path reuse, which stays zero-friction by rem design -- see docs/agent-interconnect.md). %1 is a short site tag ('exe'|'interpreter') used rem only for the log lines below, so a reader can tell which verification path preceded this. rem rem Mirrors the existing 3-branch consent-gate pattern (:system_build_consent_gate, rem :cascade_consent_gate, :system_python_consent_gate): echo the prompt UNCONDITIONALLY (so rem prompt-text assertions see it even on auto-decline), then HP_TEST_CHECKPOINT_ANSWER rem (override, checked FIRST so an explicit Y reaches the accept branch even under HP_CI_LANE) rem -> HP_CI_LANE auto-decline -> interactive set /p. UNLIKE those 3 gates (each reached only on rem a narrow edge-case path), this one fires on essentially every successful bootstrap run, so it rem ALSO auto-declines on NOINPUT/HP_NONINTERACTIVE (the same signals :compute_interactive_run and rem :pick_entry_interactive already treat as authoritative non-interactivity elsewhere in this rem file) -- without this, any automation that sets those two flags but not HP_CI_LANE (a rem documented, supported way to run this bootstrapper headlessly), or a contributor running a rem full-bootstrap selfapps test locally (most do not pin HP_CI_LANE, see docs/agent-lessons- rem learned.md "Accepted gap"), would hang on set /p with no console input available. set "HP_CHECKPOINT_SITE=%~1" echo. echo *** Verification finished -- see the Run Status above. *** echo *** You can run your program again now via the interpreter as an extra diagnostic check. (Optional) *** set "HP_CHECKPOINT_RAW=" if defined HP_TEST_CHECKPOINT_ANSWER ( set "HP_CHECKPOINT_RAW=%HP_TEST_CHECKPOINT_ANSWER%" ) else if defined HP_CI_LANE ( set "HP_CHECKPOINT_RAW=n" ) else if defined NOINPUT ( set "HP_CHECKPOINT_RAW=n" ) else if defined HP_NONINTERACTIVE ( set "HP_CHECKPOINT_RAW=n" ) else ( set /p "HP_CHECKPOINT_RAW= Run again via the interpreter now? [Y/N] " ) set "HP_CHECKPOINT_CHOICE=%HP_CHECKPOINT_RAW:~0,1%" if /I not "%HP_CHECKPOINT_CHOICE%"=="Y" ( call :log "[INFO] REQ-018: post-execution checkpoint (%HP_CHECKPOINT_SITE%): declined (run footprint stays at one execution)." set "HP_CHECKPOINT_SITE=" set "HP_CHECKPOINT_RAW=" set "HP_CHECKPOINT_CHOICE=" exit /b 0 ) call :log "[INFO] REQ-018: post-execution checkpoint (%HP_CHECKPOINT_SITE%): accepted; running a second time via the interpreter." if /I "%HP_CHECKPOINT_SITE%"=="exe" call :log "[INFO] REQ-018: note -- this diagnostic run uses the interpreter, not the packaged EXE, so behavior can differ (e.g. bundled resources)." rem Reuses :run_failfast_probe (same never-kill, two-stage wait) rather than a fourth ad hoc rem launch mechanism -- this elective run is exactly the same class of "user consciously rem launched something that might run for a while" as the fail-fast probe's own interactive rem branch, so it gets the same guarantees (never hard-killed, final outcome always reported). rem Save/restore HP_SMOKE_RC and HP_PROBE_EXCEEDED around this second call: they belong to the rem FIRST (real) verification run in the caller's namespace, and :run_failfast_probe would rem otherwise overwrite both with the SECOND (elective) run's outcome, corrupting them for any rem future code a caller might add after this checkpoint call before its own exit /b 0. set "HP_CHECKPOINT_SAVED_SMOKE_RC=%HP_SMOKE_RC%" set "HP_CHECKPOINT_SAVED_PROBE_EXCEEDED=%HP_PROBE_EXCEEDED%" rem [REQ-026]: HP_PROBE_ARGS is a full, pre-quoted Windows Arguments string (entry path plus rem any forwarded extra args), not a single bare path -- see :run_failfast_probe's own header rem comment for the contract change this checkpoint's second run also relies on. set "HP_PROBE_EXE=%HP_PY%" set "HP_PROBE_ARGS="%HP_ENTRY%"%HP_APP_ARGS%" set "HP_PROBE_CWD=%CD%" rem Distinct output files from the primary run's ~run.out.txt/~run.err.txt: this is a genuinely rem SECOND, separate execution, and reusing those paths would silently overwrite the FIRST run's rem captured output out from under any downstream consumer (envsmoke's 'smoke-ok' token check, rem the spaced-path dist\ token capture) that expects them to reflect the verified build. set "HP_PROBE_OUT=~checkpoint_run.out.txt" set "HP_PROBE_ERR=~checkpoint_run.err.txt" call :run_failfast_probe checkpoint set "HP_PROBE_OUT=" set "HP_PROBE_ERR=" set "HP_SMOKE_RC=%HP_CHECKPOINT_SAVED_SMOKE_RC%" set "HP_PROBE_EXCEEDED=%HP_CHECKPOINT_SAVED_PROBE_EXCEEDED%" set "HP_CHECKPOINT_SAVED_SMOKE_RC=" set "HP_CHECKPOINT_SAVED_PROBE_EXCEEDED=" set "HP_CHECKPOINT_SITE=" set "HP_CHECKPOINT_RAW=" set "HP_CHECKPOINT_CHOICE=" exit /b 0 :offer_optimized_build rem AV-Safe Build Path requirement 9 (P1, docs/prd-av-safe-build-path.md): after a NORMAL, rem verified-successful PyInstaller build (never after Tier A, which already produced a Nuitka rem EXE -- see the HP_NUITKA_FALLBACK_USED guard below), offer an ELECTIVE, human-only, rem auto-declined-in-CI upsell: build a second, Nuitka-optimized version and swap it into place rem ONLY if it is confirmed to build AND run successfully. On any failure at any stage, the rem original (already-verified) EXE is left completely untouched and the user is told their app rem is still ready to use as-is -- this is a strictly safer sequence than Tier A's (which is rem free to delete-then-rebuild because the original PyInstaller build already failed there). rem rem Mirrors :run_postexec_checkpoint's exact CI-safe consent-gate pattern one call site above rem (auto-decline on HP_CI_LANE/NOINPUT/HP_NONINTERACTIVE, since like the checkpoint this fires rem on essentially every successful bootstrap run) -- see that subroutine's own header comment rem for why this broader 4-way auto-decline set is used here instead of the narrower 3-branch rem gates elsewhere in this file (:system_build_consent_gate etc.). if defined HP_NUITKA_FALLBACK_USED exit /b 0 if not "%HP_EXE_EXIT%"=="0" exit /b 0 echo. echo *** Your app is ready. *** echo *** Want to build an optimized version too? It takes a bit longer to build right now, *** echo *** but it starts up more reliably on Windows and runs faster once it is built. (Optional, safe to skip) *** set "HP_OPTBUILD_RAW=" if defined HP_TEST_OPTBUILD_ANSWER ( set "HP_OPTBUILD_RAW=%HP_TEST_OPTBUILD_ANSWER%" ) else if defined HP_CI_LANE ( set "HP_OPTBUILD_RAW=n" ) else if defined NOINPUT ( set "HP_OPTBUILD_RAW=n" ) else if defined HP_NONINTERACTIVE ( set "HP_OPTBUILD_RAW=n" ) else ( set /p "HP_OPTBUILD_RAW= Build the optimized version now? [Y/N] " ) set "HP_OPTBUILD_CHOICE=%HP_OPTBUILD_RAW:~0,1%" if /I not "%HP_OPTBUILD_CHOICE%"=="Y" ( call :log "[INFO] Optimized build: declined." set "HP_OPTBUILD_RAW=" set "HP_OPTBUILD_CHOICE=" exit /b 0 ) set "HP_OPTBUILD_RAW=" set "HP_OPTBUILD_CHOICE=" call :log "[INFO] Optimized build: accepted; building now (this may take a minute or two)." rem Build to a distinct temp filename first -- the already-working dist\%ENVNAME%.exe is never rem touched until a build AND a verification run both succeed. HP_OPTBUILD_TMP is set before rem the test-hook check below so :optbuild_cleanup can always reference it safely, including in rem the forced-fail case where it names a file that was never created (a no-op exist check). set "HP_OPTBUILD_TMP=%ENVNAME%.optimized_build.exe" if exist "dist\%HP_OPTBUILD_TMP%" del "dist\%HP_OPTBUILD_TMP%" >nul 2>&1 if defined HP_TEST_FORCE_OPTBUILD_FAIL ( call :log "[TEST] HP_TEST_FORCE_OPTBUILD_FAIL: simulating optimized-build failure." goto :optbuild_cleanup ) if "%HP_ENV_MODE%"=="uv" ( "%HP_UV_EXE%" pip install --python "%HP_PY%" -q nuitka >> "%LOG%" 2>&1 ) else ( "%HP_PY%" -m pip install -q nuitka >> "%LOG%" 2>&1 ) if errorlevel 1 ( call :log "[WARN] Optimized build: could not install the build tool; your app is still ready to use as-is." goto :optbuild_cleanup ) rem Same --assume-yes-for-downloads rationale as :try_nuitka_tier_a: Nuitka must never prompt rem interactively here. "%HP_PY%" -m nuitka --onefile --assume-yes-for-downloads --remove-output --output-dir=dist -o "%HP_OPTBUILD_TMP%" "%HP_ENTRY%" >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Optimized build did not complete; your app is still ready to use as-is." call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help." goto :optbuild_cleanup ) if not exist "dist\%HP_OPTBUILD_TMP%" ( call :log "[WARN] Optimized build finished but did not produce output; your app is still ready to use as-is." goto :optbuild_cleanup ) rem Verify the new build actually runs before touching the already-working original. Same rem 30s-cap / Kill()-on-timeout pattern as :run_exe_smokerun -- this is a genuine internal rem verification run (not the user's own session), so the same allowed-to-kill reasoning rem applies (see HP_FAILFAST_PROBE_MS's header comment on the two classes of run in this file). rem derived requirement: HP_APP_ARGS is read via $env:HP_APP_ARGS (PowerShell's own inherited- rem environment access), never substituted by cmd.exe's %VAR% expansion into this -Command rem string -- HP_APP_ARGS already contains literal embedded double-quotes per token (see its rem definition near the top of this file), and cmd.exe's naive quote-toggle parser has no rem concept of "this quote is inside a PowerShell string"; interpolating it directly would rem corrupt this -Command argument exactly like the hazard docs/agent-lessons-learned.md rem already documents for HP_FAILFAST_PROBE. Without this, a program that requires launch rem arguments to start would always fail this verification, regardless of whether the rem Nuitka build itself is fine. set "HP_OPTBUILD_VERIFY_EXIT=-1" rem CLAUDE.md Active Backlog Item 38: this internal verification of the elective Nuitka-optimized rem build still runs from dist\ (current working directory (CWD) = dist), unlike rem :run_exe_smokerun's own primary verification (now app root, see that subroutine's own rem comment). Deliberately left as a documented, deferred follow-up rather than folded into this rem slice -- no existing test depends on this specific CWD either way (selfapps_optimized_build.ps1's rem stub app does no file I/O at all), and this is a narrower-blast-radius, opt-in-only code path, rem not the widely-hit primary verification path Item 38 was actually about. Re-derive whether to rem unify this too the next time this subroutine is touched. pushd dist for /f "usebackq delims=" %%X in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$si=New-Object System.Diagnostics.ProcessStartInfo;$si.FileName='%HP_OPTBUILD_TMP%';if($env:HP_APP_ARGS){$si.Arguments=$env:HP_APP_ARGS};$si.UseShellExecute=$false;$si.RedirectStandardOutput=$true;$si.RedirectStandardError=$true;$p=[System.Diagnostics.Process]::Start($si);$so=$p.StandardOutput.ReadToEndAsync();$se=$p.StandardError.ReadToEndAsync();$done=$p.WaitForExit(30000);if(-not $done){try{$p.Kill()}catch{}};$so.Result|Out-Null;$se.Result|Out-Null;if($done){$p.ExitCode}else{-1}"`) do set "HP_OPTBUILD_VERIFY_EXIT=%%X" popd if not defined HP_OPTBUILD_VERIFY_EXIT set "HP_OPTBUILD_VERIFY_EXIT=-1" if not "%HP_OPTBUILD_VERIFY_EXIT%"=="0" ( call :log "[WARN] Optimized build verification did not pass (exit %HP_OPTBUILD_VERIFY_EXIT%); your app is still ready to use as-is." goto :optbuild_cleanup ) rem Verified good: swap it into place. A same-drive move is a fast rename on NTFS; /y overwrites rem the existing (already-verified) dist\%ENVNAME%.exe. if defined HP_TEST_FORCE_OPTBUILD_SWAP_FAIL ( call :log "[TEST] HP_TEST_FORCE_OPTBUILD_SWAP_FAIL: simulating a failed swap (temp file deliberately left in place)." ) else ( move /y "dist\%HP_OPTBUILD_TMP%" "dist\%ENVNAME%.exe" >nul 2>&1 ) rem A same-volume "move /y" onto an ALREADY-EXISTING destination is an atomic rename-replace: rem on success the source is consumed (gone); on failure (e.g. an AV/indexer lock on the rem destination -- the exact hazard class already documented for :try_embed_fallback's own rem rd/move swap in docs/agent-lessons-learned.md) the whole operation is rejected and the rem source is left untouched. Checking "does the destination exist" is NOT a valid success rem proxy here (unlike embed's fresh-destination case): the destination is the already-working rem original EXE, so it already exists before this line runs, success or failure alike. The rem correct check is whether the SOURCE is now gone. if exist "dist\%HP_OPTBUILD_TMP%" ( call :log "[WARN] Optimized build verified successfully but could not be swapped into place; your app is still ready to use as-is." goto :optbuild_cleanup ) set "HP_NUITKA_FALLBACK_USED=1" call :log "[INFO] Optimized build succeeded and verified: dist\%ENVNAME%.exe now uses the fallback build system." set "HP_OPTBUILD_TMP=" set "HP_OPTBUILD_VERIFY_EXIT=" exit /b 0 :optbuild_cleanup if exist "dist\%HP_OPTBUILD_TMP%" del "dist\%HP_OPTBUILD_TMP%" >nul 2>&1 set "HP_OPTBUILD_TMP=" set "HP_OPTBUILD_VERIFY_EXIT=" exit /b 0 :try_fast_exe set "HP_FASTPATH_USED=" set "HP_FAST_EXE=" set "HP_FAST_EXE_PATH=" if "%PYCOUNT%"=="0" exit /b 0 set "HP_FAST_EXE=dist\%ENVNAME%.exe" if not exist "%HP_FAST_EXE%" exit /b 0 set "HP_FAST_EXE_PATH=%HP_FAST_EXE%" set "HP_FASTPATH_TOKEN=" set "HP_FAST_CHECK_PS=~fast_check.ps1" set "HP_FAST_CHECK_OUT=~fast_check.txt" if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1 if exist "%HP_FAST_CHECK_OUT%" del "%HP_FAST_CHECK_OUT%" >nul 2>&1 call :emit_from_base64 "%HP_FAST_CHECK_PS%" HP_FAST_CHECK if errorlevel 1 ( call :log "[WARN] Fast path: could not emit ~fast_check.ps1; skipping fast path." exit /b 0 ) powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_FAST_CHECK_PS%" "%HP_FAST_EXE_PATH%" > "%HP_FAST_CHECK_OUT%" 2>> "%LOG%" if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1 for /f "usebackq delims=" %%T in ("%HP_FAST_CHECK_OUT%") do set "HP_FASTPATH_TOKEN=%%T" if exist "%HP_FAST_CHECK_OUT%" del "%HP_FAST_CHECK_OUT%" >nul 2>&1 if /I "%HP_FASTPATH_TOKEN%"=="fresh" ( set "HP_FASTPATH_USED=1" ) if not defined HP_FASTPATH_USED exit /b 0 rem REQ-012: super-user hook -- reuse the fresh cached EXE but do not run it. if defined HP_SKIP_EXE_SMOKERUN ( call :log "[INFO] REQ-012: HP_SKIP_EXE_SMOKERUN set; reusing cached EXE without running it (skipped by request)." set "HP_EXE_SKIPPED=1" exit /b 0 ) set "HP_PROBE_EXCEEDED=" rem Slice 2b-C: goto-based dispatch, NOT a parenthesized if/else block, is deliberate here -- rem see docs/agent-lessons-learned.md "Provider-cascade dispatch is goto-based on purpose". rem cmd.exe expands every %VAR% in a parenthesized ( ... ) block ONCE, at parse time, using rem values from BEFORE the block started -- an earlier revision of this code launched the EXE rem and read "set HP_SMOKE_RC=%ERRORLEVEL%" inside the else-branch's own parens, which silently rem froze %ERRORLEVEL% (and every in-block %HP_SMOKE_RC% read) to whatever it was right before rem the if/else began (almost always "0"), so a genuinely broken cached EXE was NEVER discarded rem in the legacy/CI branch. Each branch below is reached via goto so its statements are parsed rem and executed as fresh top-level lines, exactly like the rest of this file's %ERRORLEVEL% rem capture sites. if defined HP_INTERACTIVE_RUN goto :try_fast_exe_probe call :log "[INFO] Fast path: reusing %HP_FAST_EXE%" >> "%LOG%" echo Fast path command: "%HP_FAST_EXE%" ^> "~run.out.txt" 2^> "~run.err.txt" "%HP_FAST_EXE%"%HP_APP_ARGS% 1> "~run.out.txt" 2> "~run.err.txt" set "HP_SMOKE_RC=%ERRORLEVEL%" call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%" if "%HP_SMOKE_RC%"=="0" ( call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)" ) else ( call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)" ) goto :try_fast_exe_discard_check :try_fast_exe_probe rem Interactive fail-fast probe -- a real double-click user is told explicitly what is about rem to launch and never sees a hard kill here; a genuinely stale/broken cached EXE that fails rem within the probe window is still discarded+rebuilt below exactly as before. CI/automation rem (HP_INTERACTIVE_RUN unset) takes the branch above instead, byte-for-byte unchanged, so rem self.fastpath / self.exe.fastpath.graceful stay deterministic. call :log "[INFO] Launching your program now via the cached standalone EXE (PyInstaller build): %HP_FAST_EXE%" rem Resolve to an absolute path -- HP_FAST_EXE is relative (dist\%ENVNAME%.exe). .NET rem Process.Start's FileName resolution is a different mechanism from cmd.exe's own relative rem launch, so keep this unambiguous rather than relying on the child process inheriting the rem right CWD to resolve it, in case a future change alters how/where this is invoked from. set "HP_PROBE_EXE=%CD%\%HP_FAST_EXE%" rem [REQ-026]: the EXE is self-contained (no separate entry-file argv needed), so Arguments rem is exactly the forwarded extra args, or empty. HP_PROBE_ARGS is now a pre-quoted, ready rem Windows Arguments string (see :run_failfast_probe's own header comment) -- HP_APP_ARGS is rem already built that way by construction (each token individually re-quoted at capture time). set "HP_PROBE_ARGS=%HP_APP_ARGS%" set "HP_PROBE_CWD=%CD%" call :run_failfast_probe fastpath :try_fast_exe_discard_check rem REQ-007: a reused EXE that exits non-zero must NOT abort the bootstrapper. The cached rem EXE may be stale or carry an unbundled runtime dependency (DLL/data file) the fast-path rem freshness check cannot see. Drop the fast path and fall through to a full rebuild, which rem routes any persistent failure through :run_exe_smokerun's graceful handling + banner. rem Slice 2b-C: this discard only fires when the probe was NOT exceeded (a fast, genuine rem failure). Once a process is classified alive/healthy at the probe, a LATER non-zero exit rem is presumed to be the user's own program outcome, not proof of a stale artifact -- a rem rebuild would not fix a runtime bug in the user's own code, so the cached EXE is kept. if not "%HP_SMOKE_RC%"=="0" if not defined HP_PROBE_EXCEEDED ( call :log "[WARN] Fast path standalone EXE (PyInstaller build) exited %HP_SMOKE_RC%; discarding cached EXE and rebuilding." rem Delete the broken EXE so the next :try_fast_exe call does not re-detect it as rem "fresh" and run the known-bad binary again; the rebuild below recreates it. if exist "%HP_FAST_EXE%" del "%HP_FAST_EXE%" >nul 2>&1 set "HP_FASTPATH_USED=" set "HP_SMOKE_RC=" ) exit /b 0 :write_fast_hash rem derived requirement (CLAUDE.md Active Backlog Item 39): called only when the caller's own rem HP_FRESH_BUILD_OK gate confirms a genuine build succeeded THIS run (see :run_entry_smoke's rem reset + 4 success-branch sets) -- never for a fast-path reuse (nothing changed, stored hash rem already correct) and never for a skipped/failed rebuild (would pair the CURRENT sources' rem hash with a stale leftover EXE from an earlier run -- a real bug CodeRabbit's review caught rem on PR #460 in an earlier, less precise HP_FASTPATH_USED-only gate). (Re)writes the rem composite source-content hash the EXE fast path's freshness check compares against on the rem next run. A dedicated subroutine, not inlined at :success, specifically so no line here rem sits inside a parenthesized block -- see docs/agent-lessons-learned.md's "Parse-time rem vs. runtime variable expansion" entry for why a set-then-read-in-the-same-block rem pattern is unsafe, and this repo's own house preference for goto/call-based dispatch rem over inlined blocks wherever a payload emit + invoke sequence is involved. if not exist "dist\%ENVNAME%.exe" exit /b 0 set "HP_FAST_CHECK_PS=~fast_check.ps1" if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1 call :emit_from_base64 "%HP_FAST_CHECK_PS%" HP_FAST_CHECK if errorlevel 1 exit /b 0 powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_FAST_CHECK_PS%" "dist\%ENVNAME%.exe" write >> "%LOG%" 2>&1 set "HP_FAST_HASH_RC=%ERRORLEVEL%" rem derived requirement (CodeRabbit review, PR #460): a write failure here is not fatal -- the rem next run's missing-hash default safely forces exactly one rebuild -- but a silent failure rem could repeat unnoticed across many runs. Log it explicitly rather than swallowing it. if not "%HP_FAST_HASH_RC%"=="0" call :log "[WARN] Fast-path hash write failed; the next run will rebuild." if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1 set "HP_FAST_HASH_RC=" exit /b 0 :run_entry_smoke call :record_chosen_entry "%HP_ENTRY%" rem CLAUDE.md Active Backlog Item 45: a failed env-create can fall through :die (exit /b rem returns from the call frame, it does not halt the process -- see docs/agent-lessons- rem learned.md's ":die uses exit /b" entry) with HP_PY left pointing at a python.exe that rem was never produced. :after_env_mode_selection's own interpreter smoke test already sets rem HP_NO_INTERPRETER for this case today, but that protection is 800+ lines upstream and rem indirect; this is a direct, explicit backstop at the actual point of use, so the rem build/warnfix/repair block below can never be reached against a nonexistent HP_PY even rem if some future change removes or bypasses the upstream smoke test. Setting rem HP_NO_INTERPRETER here (instead of a bespoke message) also fixes a latent message-framing rem bug: without it, a broken HP_PY would instead fail :preflight_compile's own py_compile rem call and get misreported as a syntax error in the user's code, not a missing interpreter. if not exist "%HP_PY%" set "HP_NO_INTERPRETER=1" rem REQ-021: static pre-flight syntax check of the entry (no user code executed). A SyntaxError rem makes the program unrunnable under the interpreter AND unbuildable by PyInstaller, so report it rem clearly and stop here instead of failing later inside the doomed PyInstaller build. call :preflight_compile if defined HP_PREFLIGHT_FAILED ( set "HP_BOOTSTRAP_STATE=error" exit /b 0 ) set "HP_FASTPATH_USED=" set "HP_SMOKE_RC=" rem REQ-012: super-user hook -- skip the entry-script smoke test (and fast-path EXE rem reuse, which also executes the program) so no user code runs. Env creation, rem dependency install, and the PyInstaller build still proceed; the result is left rem unverified (HP_SMOKE_RC stays empty), not a fake pass or fail. if defined HP_SKIP_ENTRY_SMOKE ( call :log "[INFO] REQ-012: HP_SKIP_ENTRY_SMOKE set; skipping entry-script smoke test (no user code executed)." goto :run_entry_after_smoke ) call :try_fast_exe rem REQ-018 (2b-A.2): single verification -- the redundant pre-build interpreter smoke is removed. rem In the EXE path the timed EXE smoke (:run_exe_smokerun) is now the sole verification run, so the rem app is no longer executed twice (interpreter then EXE). When no EXE is produced (system-Python rem decline / build skipped), the interpreter runs ONCE via :verify_no_exe_interpreter after the rem build gate. The fast-path EXE run inside :try_fast_exe is the user's run and is left as-is here rem (its run-timing/consent is unified with the no-EXE interpreter in slice 2b-C). :run_entry_after_smoke rem derived requirement: the CI harness inspects the breadcrumb log to flag missing entries. set "HP_BREADCRUMB=~entry1_bootstrap.log" if exist "tests\~entry1\" set "HP_BREADCRUMB=tests\~entry1\~entry1_bootstrap.log" if not exist "%HP_BREADCRUMB%" ( rem derived requirement: create the breadcrumb when the smoke run succeeds so diagnostics stay consistent. for %%B in ("%HP_BREADCRUMB%") do if not "%%~dpB"=="" if not exist "%%~dpB" mkdir "%%~dpB" >nul 2>&1 type nul > "%HP_BREADCRUMB%" ) if exist "%HP_BREADCRUMB%" ( call :log "[INFO] Entry smoke breadcrumb exists: %HP_BREADCRUMB%" ) else ( call :log "[WARN] Entry smoke missing breadcrumb: %HP_BREADCRUMB%" ) rem derived requirement (REQ-007 / Windows Server 2025 + VS2026 image): on conda-forge rem Python, pyexpat.pyd is dynamically linked against a separate libexpat-*.dll in the rem env's Library\bin. PyInstaller bundles pyexpat.pyd but not that DLL, so the frozen EXE rem hits "DLL load failed while importing pyexpat" whenever a stdlib XML path runs (e.g. rem openpyxl -> xml.etree.ElementTree). Bundle the DLL explicitly via --add-binary. rem Stock python.org builds statically link expat, and venv/uv layouts have no Library\bin, rem so this detection finds nothing and HP_PYI_EXPAT stays empty (no behavior change). set "HP_PYI_EXPAT=" set "HP_EXPAT_DLL=" for %%I in ("%HP_PY%") do set "HP_PY_DIR=%%~dpI" if defined HP_PY_DIR for /f "delims=" %%D in ('dir /b /a-d "%HP_PY_DIR%Library\bin\libexpat*.dll" 2^>nul') do if not defined HP_EXPAT_DLL set "HP_EXPAT_DLL=%HP_PY_DIR%Library\bin\%%D" if defined HP_EXPAT_DLL ( call :log "[INFO] REQ-007: bundling conda libexpat DLL for pyexpat: %HP_EXPAT_DLL%" set "HP_PYI_EXPAT=--add-binary "%HP_EXPAT_DLL%;."" ) rem REQ-005.x: pre-build --collect-submodules for curated packages that load rem submodules dynamically (the warn file is silent about them, so warnfix never rem repairs them). Double-gated inside the helper (used-by-source AND installed) so rem a fat global env never bloats a lean app EXE. Computed in a subroutine so rem %HP_PYI_COLLECT% resolves at parse time inside the build block below. call :compute_collect_flags rem REQ-007: provider-independent build. The EXE build is attempted under every provider. System rem Python (Tier 4) is the one exception: building installs PyInstaller into the user's system rem interpreter, so it is gated on explicit consent (CI auto-declines); all other providers build. set "HP_BUILD_OK=1" if /i "%HP_ENV_MODE%"=="system" call :system_build_consent_gate if /i "%HP_ENV_MODE%"=="system" if errorlevel 1 set "HP_BUILD_OK=" rem Slice 2b-C: recompute the same HP_FASTPATH_RUN_FAILED check the top-of-file gate uses -- rem this is :try_fast_exe's SECOND call site, inside :run_entry_smoke; the first call's own rem HP_FASTPATH_RUN_FAILED does not reach here -- any first-call success or post-probe-failure rem outcome already took goto :success before this point, so this recomputation is normally a rem no-op today, but keeps this call site from silently reopening the same "HP_FASTPATH_USED rem alone is not proof of a clean run" gap the top-of-file gate closed, should a future change, rem e.g. a provider-cascade re-entry, ever reach here with HP_FASTPATH_USED still set from a rem probe-classified alive-then-failed run. Computed as a top-level statement, not inside the rem block below, for the same parse-time-expansion reason documented in :try_fast_exe. set "HP_FASTPATH_RUN_FAILED=" if defined HP_SMOKE_RC if not "%HP_SMOKE_RC%"=="0" set "HP_FASTPATH_RUN_FAILED=1" if not defined HP_BUILD_OK ( call :log "[INFO] REQ-007: system-Python EXE build not consented; skipping PyInstaller packaging. The environment and dependencies are installed; run the app directly via the prepared Python." ) else ( if defined HP_FASTPATH_USED ( if defined HP_FASTPATH_RUN_FAILED ( call :log "[WARN] dist\%ENVNAME%.exe (standalone EXE, PyInstaller build) ran to completion and exited non-zero after passing the fail-fast probe; treated as your program's own result, not a rebuild trigger." ) else ( call :log "[INFO] Fast path: skipping PyInstaller rebuild for existing dist\%ENVNAME%.exe" ) ) else ( rem derived requirement: PyInstaller install + build can take a minute or more; emit a rem user-facing message before the silent operation so the script never looks hung. call :log "[INFO] Building standalone executable -- this may take a minute or two..." rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: snapshot rem %LOG%'s size before this fresh build, and any warnfix rebuild that may follow it, rem writes anything, so :dll_bundle_recover can later scan only THIS run's own build rem output for an unresolved-native-DLL warning, never a stale one left over from an rem earlier run in the same persistent log file. for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ" rem derived requirement: on many machines, a benign one-line Windows message about a rem missing drive prints here from an unrelated background process around this same rem window, confirmed harmless and already tolerated by tests/selfapps_envsmoke.ps1's rem unexpectedSystemErrorIgnored allowlist -- not from this build command itself, whose rem own stdout/stderr are fully redirected to %LOG% below. A beginner watching the console rem could mistake it for a real error, so name it explicitly -- worded to avoid literally rem reproducing the trigger phrase, so this reassurance line itself is never mistaken by rem selfapps_envsmoke.ps1's Get-LineSnippet substring search for the real system line it rem allowlists -- which would misfire on the FIRST matching line, not necessarily this one. call :log "[INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.)" :: derived requirement: ~parse_warn.py was written against PyInstaller 5.x and 6.x warn-file formats. :: Version is intentionally unpinned so future PyInstaller releases are adopted automatically. :: If CI starts failing parse_warn tests after a PyInstaller update, review ~parse_warn.py :: against the new warn-file format and update the translation table as needed. if "%HP_ENV_MODE%"=="uv" ( "%HP_UV_EXE%" pip install --python "%HP_PY%" -q pyinstaller >> "%LOG%" 2>&1 ) else ( "%HP_PY%" -m pip install -q pyinstaller >> "%LOG%" 2>&1 ) if exist "%ENVNAME%.spec" set "HP_SPEC_PREEXIST=1" rem derived requirement: a real bug, found 2026-07-20 while scoping the AV-Safe Build Path rem PRD's failure-simulation tests, docs/prd-av-safe-build-path.md requirement 1: :die only rem returns from its own `call` frame -- see docs/agent-lessons-learned.md's ":die uses exit rem /b" note -- it does NOT halt the process, and nothing downstream re-checked rem dist\%ENVNAME%.exe or this call's outcome. Without HP_BOOTSTRAP_STATE=error, a genuine rem PyInstaller build failure fell through to :run_exe_smokerun, a silent no-op skip when rem the EXE is missing, and :verify_no_exe_interpreter, which runs the raw entry via the rem interpreter instead, then :after_cascade_decision unconditionally overwrote ~bootstrap.status.json rem back to state=ok and the process exited 0 -- silently masking the failed EXE build the rem user explicitly consented to, HP_BUILD_OK. Mirrors the existing, already-correct rem HP_BOOTSTRAP_STATE=error precedent in :run_entry_smoke's preflight-failure branch. rem Nested if/else, no goto, is used deliberately: a goto that jumps to a label inside this rem same parenthesized else-block risks the class of paren-tracking corruption documented rem under "Provider-cascade dispatch is goto-based on purpose" -- this block instead stays rem entirely within ordinary if/else nesting, using only call/if-errorlevel/if-defined/set, rem all of which are already-confirmed runtime-safe inside a parenthesized block. rem AV-Safe Build Path requirements 2-4, Tier A: each of the three failure points below -- rem forced-fail test hook, real build errorlevel, missing/vanished output, requirement 3's rem single trigger category -- now attempts :try_nuitka_tier_a before declaring final failure, rem instead of going straight to :die. On Tier A success, HP_NUITKA_FALLBACK_USED=1 and rem dist\%ENVNAME%.exe exists, built by Nuitka; the rest of this block treats it exactly rem like a PyInstaller-produced EXE -- parse_warn/warnfix below is NOT guaranteed to be a rem no-op for it: a stale warn-%ENVNAME%.txt from the earlier, failed PyInstaller attempt rem that triggered this Tier A fallback can survive, since build\%ENVNAME% is not cleared rem before this check -- warnfix can still fire and even rebuild over the Nuitka-built EXE, see rem the HP_NUITKA_FALLBACK_USED clear a few dozen lines below for how that case is handled. set "HP_NUITKA_FALLBACK_USED=" rem CLAUDE.md Active Backlog Item 39, CodeRabbit review, PR #460: a real correctness bug -- rem gating :write_fast_hash on "HP_FASTPATH_USED unset" alone does not prove a build actually rem SUCCEEDED this run; a stale dist\%ENVNAME%.exe left over from an EARLIER successful run rem would still be sitting there if THIS run's rebuild is skipped/fails, and the freshness rem hash would get rewritten for the CURRENT, changed, sources paired with that OLD binary rem -- the next run would then wrongly trust the stale EXE as "fresh." HP_FRESH_BUILD_OK is rem set ONLY in the genuine-success branches below -- PyInstaller producing rem dist\%ENVNAME%.exe, or Tier A/Nuitka succeeding after it didn't -- never on a rem warn_build_incomplete path. rem Reset once per fresh build attempt, same reasoning as HP_NUITKA_FALLBACK_USED above. set "HP_FRESH_BUILD_OK=" rem REQ-009/REQ-005.10, cascade-vs-postexec fix: reset the "this provider's dependencies rem look incomplete" flag at the start of every fresh build attempt, not just when warnfix rem happens to run again -- a provider that needs no warnfix repair at all must not inherit rem a stale flag left by an earlier, cascaded-away provider's failure. Set if warranted, and rem consumed further down; see :warnfix_cascade_detect and :warn_user_code_launch. set "HP_DEP_MAYBE_INCOMPLETE=" rem CLAUDE.md Item 29: :dll_bundle_recover can now be called MORE THAN ONCE per fresh build rem attempt -- a second pass after :hidden_import_recover, see that call site below -- so its rem own accumulated --add-binary flags, HP_PYI_DLLBIND, must survive across those calls rem rather than being wiped at the start of each one. Reset once here instead, at the start rem of the fresh build attempt itself -- the same "not just when X happens to run again" rem reasoning as HP_DEP_MAYBE_INCOMPLETE above, so a cascaded-away provider's own bindings rem never leak into the next tier's build. HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT need the rem same reset for the mirror-image reason: :dll_bundle_recover's OWN rebuild command now rem threads them through too, see its own comment, so a stale value from a PREVIOUS rem provider's :hidden_import_recover call must not leak into THIS provider's first rem :dll_bundle_recover call, before THIS provider's own :hidden_import_recover has run. set "HP_PYI_DLLBIND=" set "HP_PYI_HIDDEN_IMPORTS=" set "HP_PYI_HID_COLLECT=" rem docs/open-questions.md item 1, answered yes: the post-flight caveat panel's DLL-specific rem hint reads HP_DLL_HINT_STATE, set by :emit_dll_bundle_row on every call and otherwise left rem alone -- NOT reset at :dll_bundle_recover_exit, unlike HP_DLL_FAILED/HP_DLL_EXHAUSTED, so it rem survives that subroutine returning. Reset it here, once per fresh build attempt, for the rem same reason as HP_DEP_MAYBE_INCOMPLETE above -- a provider tier this run's own rem :dll_bundle_recover never even calls, e.g. this attempt is not conda, or no DLL warning rem appears at all, must not inherit a stale hint left by an earlier, cascaded-away provider. set "HP_DLL_HINT_STATE=" rem CLAUDE.md Active Backlog Item 41: the post-flight caveat panel's GUI-app-aware hint reads rem HP_EXE_TIMEDOUT_SILENT, set below where HP_EXE_VERIFY_FAILED is set. Reset here for the rem identical reason as HP_DLL_HINT_STATE above -- a cascaded-away provider's own timed-out rem verification must not leak a stale hint into a later provider tier's own caveat panel. set "HP_EXE_TIMEDOUT_SILENT=" if defined HP_TEST_FORCE_PYINSTALLER_FAIL ( call :log "[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure." call :try_nuitka_tier_a if errorlevel 1 ( rem CLAUDE.md Active Backlog Item 46, Bucket B: every build tool -- PyInstaller AND the rem Nuitka fallback -- has already failed by this point, but the environment/dependencies rem are still valid and the interpreter-fallback verification a few hundred lines below rem still genuinely runs and still genuinely decides success/failure -- this is not a rem doomed state, so :die's own mid-run pause, which would stop the user here before rem that verification even happens, and premature lock release are both wrong. Warn and rem keep going instead; :warn_build_incomplete still sets HP_BOOTSTRAP_STATE=error so the rem final ~bootstrap.status.json/postflight panel report this honestly once the run rem actually finishes. call :warn_build_incomplete "[WARN] PyInstaller execution failed; will verify your code directly via Python instead. reason=test_forced_fail" ) else ( set "HP_NUITKA_FALLBACK_USED=1" set "HP_FRESH_BUILD_OK=1" ) ) else ( "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1 if errorlevel 1 ( call :try_nuitka_tier_a if errorlevel 1 ( rem CLAUDE.md Item 33: reason=build_error means PyInstaller's own process exited rem nonzero -- distinct from reason=missing_output below, exit 0 but no EXE, and rem reason=test_forced_fail above, no real build ever ran. Mirrors the existing rem UV_FALLBACK reason= token convention; see docs/agent-lessons-learned.md. rem CLAUDE.md Active Backlog Item 46, Bucket B: see the test_forced_fail branch above rem for why this is a warn-and-continue site, not a :die site. call :warn_build_incomplete "[WARN] PyInstaller execution failed; will verify your code directly via Python instead. reason=build_error" ) else ( set "HP_NUITKA_FALLBACK_USED=1" set "HP_FRESH_BUILD_OK=1" ) ) else ( if defined HP_TEST_FORCE_OUTPUT_VANISH if exist "dist\%ENVNAME%.exe" ( call :log "[TEST] HP_TEST_FORCE_OUTPUT_VANISH: deleting freshly-built EXE to simulate post-creation removal." del "dist\%ENVNAME%.exe" >nul 2>&1 ) if not exist "dist\%ENVNAME%.exe" ( call :try_nuitka_tier_a if errorlevel 1 ( rem CLAUDE.md Active Backlog Item 46, Bucket B: see the test_forced_fail branch rem above for why this is a warn-and-continue site, not a :die site. call :warn_build_incomplete "[WARN] PyInstaller did not produce dist\%ENVNAME%.exe; will verify your code directly via Python instead. reason=missing_output" ) else ( set "HP_NUITKA_FALLBACK_USED=1" set "HP_FRESH_BUILD_OK=1" ) ) else ( call :log "[INFO] PyInstaller produced dist\%ENVNAME%.exe" set "HP_FRESH_BUILD_OK=1" ) ) ) rem parse_warn: check PyInstaller warn file for missing modules before cleanup rem derived requirement: build\ must still exist when ~parse_warn.py runs. rem derived requirement: use %ENVNAME%, set before this block, as the inline rem path, not a variable set inside the same else-block; cmd.exe expands rem %VAR% at parse time for the whole block, so HP_WARNFILE would be empty. if exist "build\%ENVNAME%\warn-%ENVNAME%.txt" ( call :log "[DEBUG] warnfix: warn file found" type "build\%ENVNAME%\warn-%ENVNAME%.txt" >> "%LOG%" copy "build\%ENVNAME%\warn-%ENVNAME%.txt" "~warnfile.txt" >nul 2>&1 rem derived requirement: the raw warn-file dump right above this line goes only to rem ~setup.log -- type ... >> "%LOG%" has no console echo -- so "the list above" was rem misleading on the console -- a user watching only the window never saw a list. rem Point at ~warnfile.txt, already copied next to the app a few lines up, instead. call :log "[INFO] warnfix: some modules could not be automatically bundled (full list in ~warnfile.txt / ~setup.log); modules such as posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, and _frozen_importlib_external are expected on Windows and are filtered out automatically; cStringIO and StringIO (Python-2-only compatibility shims some packages still reference) are filtered out automatically too." if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$row = @{ id='self.warnfix.platform_filter'; pass=$true; detail='posix_modules_expected_on_windows' } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 ) ) else ( call :log "[DEBUG] warnfix: warn file not found" ) if exist "~missing_modules.txt" del "~missing_modules.txt" >nul 2>&1 call :emit_from_base64 "~parse_warn.py" HP_PARSE_WARN "%HP_PY%" ~parse_warn.py "%ENVNAME%" > "~missing_modules.txt" 2>> "%LOG%" if exist "~parse_warn.py" del "~parse_warn.py" >nul 2>&1 set "HP_WARNFIX_NEEDED=" for /f "usebackq delims=" %%M in ("~missing_modules.txt") do set "HP_WARNFIX_NEEDED=1" if exist "~warnfix_repair_failed.flag" del "~warnfix_repair_failed.flag" >nul 2>&1 if defined HP_WARNFIX_NEEDED ( set "HP_WARNFIX_APPLIED=1" call :log "[REPAIR] missing modules detected; installing and rebuilding." if "%HP_ENV_MODE%"=="uv" ( for /f "usebackq delims=" %%M in ("~missing_modules.txt") do ( call :log "[INFO] Attempting to install: %%M" "%HP_UV_EXE%" pip install --python "%HP_PY%" %%M >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Repair failed: %%M" copy nul "~warnfix_repair_failed.flag" >nul 2>&1 ) else ( call :log "[INFO] Installed: %%M" ) ) ) else if "%HP_ENV_MODE%"=="conda" if defined CONDA_BAT ( rem derived requirement, CodeRabbit finding on Item 36's own PR: :select_conda_bat rem sets CONDA_BAT purely from Miniconda binary presence on disk, never clears it once rem set -- so a genuine venv/embed fallback, conda env CREATE failed, cascaded past conda, rem can still leave CONDA_BAT defined if Miniconda itself was already on disk. rem Gating on HP_ENV_MODE=="conda" too, not just "defined CONDA_BAT", stops that stale rem definedness from routing a real venv/embed repair into a conda-install command rem targeting an environment that was never created. for /f "usebackq delims=" %%M in ("~missing_modules.txt") do ( call :log "[INFO] Attempting to install: %%M" call "%CONDA_BAT%" install -y -n "%ENVNAME%" --override-channels -c conda-forge %%M >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Repair failed: %%M" copy nul "~warnfix_repair_failed.flag" >nul 2>&1 ) else ( call :log "[INFO] Installed: %%M" ) ) ) else if "%HP_ENV_MODE%"=="venv" ( rem derived requirement, CLAUDE.md Item 36: the repair-install dispatch above only had rem uv/conda branches, silently no-opping under venv/embed/system while still logging rem "installing and rebuilding" and "rebuild complete" as if it worked -- both a private, rem bootstrapper-owned interpreter -- venv/embed, same as the MAIN non-repair rem dependency-install dispatch a few hundred lines above -- already have a working pip; rem this just wires that existing capability into the second call site that was missed. for /f "usebackq delims=" %%M in ("~missing_modules.txt") do ( call :log "[INFO] Attempting to install: %%M" "%HP_PY%" -m pip install %%M >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Repair failed: %%M" copy nul "~warnfix_repair_failed.flag" >nul 2>&1 ) else ( call :log "[INFO] Installed: %%M" ) ) ) else if "%HP_ENV_MODE%"=="embed" ( for /f "usebackq delims=" %%M in ("~missing_modules.txt") do ( call :log "[INFO] Attempting to install: %%M" "%HP_PY%" -m pip install %%M >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Repair failed: %%M" copy nul "~warnfix_repair_failed.flag" >nul 2>&1 ) else ( call :log "[INFO] Installed: %%M" ) ) ) else ( rem system mode deliberately stays a no-op here too, matching the MAIN dependency-install rem dispatch's own "System fallback: skipping requirement installation." convention -- rem system is a shared, uncontrolled interpreter; REQ-009 avoids installing into it. call :log "[WARN] System fallback: skipping warnfix repair installation." ) if exist "~warnfix_repair_failed.flag" call :log "[WARN] One or more repair attempts failed" call :log "[INFO] Rebuilding standalone executable after warnfix -- this may take a minute or two..." "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1 rem derived requirement, bug-hunt pass: unlike the ORIGINAL build a few dozen lines rem above, which routes every failure through :try_nuitka_tier_a / :warn_build_incomplete / rem HP_BOOTSTRAP_STATE=error, this warnfix-triggered rebuild previously had NO failure rem handling at all -- the log line below always said "rebuild complete" and nothing rem re-checked dist\%ENVNAME%.exe, so a genuine rebuild failure -- e.g. the exact AV-lock rem class the whole AV-Safe Build Path PRD exists to route around -- fell through to rem :run_exe_smokerun's silent no-op-when-missing skip, then a clean interpreter-fallback rem run, ending in a false ~bootstrap.status.json state=ok. Deliberately NOT retried via rem :try_nuitka_tier_a here, unlike the original build -- this rebuild only exists to rem bundle a module warnfix already installed into an EXE that was already confirmed rem working before this rebuild attempt; the conservative, honest response to a failure rem is to report it, not to speculatively rebuild via a second tool inside an rem already-nested failure path. if errorlevel 1 ( call :log "[ERROR] PyInstaller execution failed during warnfix rebuild; the previous build may no longer be valid." set "HP_BOOTSTRAP_STATE=error" rem CodeRabbit review, PR #464: this branch never cleared HP_FRESH_BUILD_OK, so rem :write_fast_hash would still pair the CURRENT sources with whatever stale, rem warnfix-incomplete EXE is left in dist\ from before this failed rebuild -- the rem next run's fast path would then wrongly trust it as fresh and skip retrying the rem repair. Mirrors the identical PR #460 fix for the ORIGINAL build's own failure rem branches; unlike a DLL-bundle/hidden-import repair loop failure -- bundling-only, rem does not need this per docs/agent-interconnect.md -- a failed warnfix rebuild means rem the current dist\ EXE genuinely lacks a needed dependency. set "HP_FRESH_BUILD_OK=" ) else if not exist "dist\%ENVNAME%.exe" ( call :log "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe during warnfix rebuild." set "HP_BOOTSTRAP_STATE=error" set "HP_FRESH_BUILD_OK=" ) else ( call :log "[REPAIR] rebuild complete after warnfix." rem The warnfix rebuild always uses PyInstaller -- if the EXE it just replaced was rem previously Nuitka-built, Tier A, it no longer is; clear the flag so rem :hidden_import_recover's Nuitka-skip guard doesn't wrongly skip repair on what is rem now genuinely a PyInstaller-built EXE -- see docs/agent-interconnect.md's "Tier A and rem hidden-import auto-recovery" section. set "HP_NUITKA_FALLBACK_USED=" ) rem REQ-005.11: warnfix-trigger PEP 723 write-back. Must run here, not later -- rem ~missing_modules.txt and ~warnfix_repair_failed.flag are both still on disk at rem this point and are deleted shortly after, see below. call :pep723_writeback warnfix rem REQ-009/REQ-005.10, slice 1: detect only: flag when this provider could not rem resolve all modules. Must run before the repair-failed flag is deleted, next line. call :warnfix_cascade_detect ) if exist "~warnfix_repair_failed.flag" del "~warnfix_repair_failed.flag" >nul 2>&1 if exist "~missing_modules.txt" del "~missing_modules.txt" >nul 2>&1 set "HP_WARNFIX_NEEDED=" if not defined HP_SPEC_PREEXIST if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1 set "HP_SPEC_PREEXIST=" if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1 call :log "[INFO] PyInstaller build artifacts cleaned up." rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: build-time rem native-DLL bundling repair -- Requirement 2's chosen design: react to PyInstaller's own rem build-log warning here, before the smoke run below, rather than waiting for the rem guaranteed runtime DLL-load-failure crash a missing native dependency would otherwise rem produce. Must run before :run_exe_smokerun so a repaired EXE is what actually gets rem verified. See docs/agent-interconnect.md's "Conda native-DLL bundling repair loop" for rem the full mechanism and its relationship to :hidden_import_recover below. call :dll_bundle_recover rem REQ-009/REQ-005.10, cascade-vs-postexec fix: the smoke run itself is NOT skipped here, rem even when HP_CASCADE_APPROVED is set -- approval only means the NEXT provider tier will rem be TRIED; :provider_cascade, reached later from the top-level main line once this whole rem subroutine returns, can still find every remaining tier unavailable/declined and fall rem back to "keeping current build" -- the build this smoke run is about to verify. Skipping rem it here would leave that kept build completely unverified in the exhaustion case. Only rem the two ELECTIVE follow-up offers, postexec checkpoint and optimized build, are suppressed, rem inside :smokerun_ndjson below -- see that label's own comment for why that scope is safe. call :run_exe_smokerun ) ) call :verify_no_exe_interpreter set "HP_WARNFIX_APPLIED=" set "HP_FAST_EXE=" set "HP_FAST_EXE_PATH=" set "HP_FASTPATH_USED=" set "HP_FASTPATH_TOKEN=" set "HP_PYI_EXPAT=" set "HP_EXPAT_DLL=" set "HP_PY_DIR=" set "HP_LOG_SIZE_BEFORE=" set "HP_PYI_DLLBIND=" exit /b 0 :preflight_compile rem REQ-021: static pre-flight -- byte-compile the entry to catch a SyntaxError in the user's own rem code early and clearly, before the doomed PyInstaller build. py_compile uses the same parser as rem the interpreter (zero false positives for the entry) and writes NO .pyc on failure. No setlocal: rem HP_PREFLIGHT_FAILED must persist to the caller. Capture %ERRORLEVEL% immediately (the del/set rem below would clobber it). set "HP_PREFLIGHT_FAILED=" if defined HP_NO_INTERPRETER ( echo. echo *** [ERROR] No Python interpreter is available; your program was not run or built. *** echo *** This is not a syntax error -- the Python interpreter itself could not be used. *** echo *** Either every automatic Python-acquisition method -- uv, conda, a fresh download, *** echo *** or a local virtual environment -- failed, usually from no internet connection, a *** echo *** full disk, or a locked-down managed machine image -- or a PVW_PYTHON_EXE override *** echo *** points at a path that does not run. Scroll up in this window for the specific reason. *** call :log "[ERROR] REQ-021: preflight skipped, no Python interpreter resolved: %HP_ENTRY%" echo. set "HP_PREFLIGHT_FAILED=1" exit /b 0 ) if not defined HP_ENTRY exit /b 0 if not exist "%HP_ENTRY%" exit /b 0 if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1 "%HP_PY%" -m py_compile "%HP_ENTRY%" 2> "~preflight.err.txt" set "HP_PREFLIGHT_RC=%ERRORLEVEL%" if "%HP_PREFLIGHT_RC%"=="0" ( if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1 set "HP_PREFLIGHT_RC=" exit /b 0 ) echo. echo *** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. *** echo *** File: "%HP_ENTRY%" *** call :log "[ERROR] REQ-021: entry failed py_compile (syntax error): %HP_ENTRY%" if exist "~preflight.err.txt" type "~preflight.err.txt" if exist "~preflight.err.txt" type "~preflight.err.txt" >> "%LOG%" echo. echo *** Fix the syntax error shown above, then run this batch again. *** if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1 set "HP_PREFLIGHT_RC=" set "HP_PREFLIGHT_FAILED=1" exit /b 0 :compute_collect_flags rem Emit --collect-submodules flags for curated packages (sklearn, matplotlib, rem scipy, plotly) that load submodules dynamically -- PyInstaller static analysis rem misses them so the warn file is silent and warnfix never repairs them. The helper rem ~collect_submodules.py double-gates: a flag is emitted only when the package is rem BOTH imported by the user project source AND importable in the build interpreter, rem so a hello-world in a fat env stays lean. No setlocal: HP_PYI_COLLECT must persist rem to the caller and into the build command. set "HP_PYI_COLLECT=" if "%HP_ENV_MODE%"=="system" exit /b 0 if defined HP_FASTPATH_USED exit /b 0 call :emit_from_base64 "~collect_submodules.py" HP_COLLECT_SUBMODULES if exist "~collect_flags.txt" del "~collect_flags.txt" >nul 2>&1 "%HP_PY%" ~collect_submodules.py . > "~collect_flags.txt" 2>> "%LOG%" if exist "~collect_submodules.py" del "~collect_submodules.py" >nul 2>&1 for /f "usebackq delims=" %%F in ("~collect_flags.txt") do set "HP_PYI_COLLECT=%%F" if exist "~collect_flags.txt" del "~collect_flags.txt" >nul 2>&1 if defined HP_PYI_COLLECT call :log "[INFO] Pre-build collect-submodules:%HP_PYI_COLLECT%" exit /b 0 :system_build_consent_gate rem REQ-007: consent before installing PyInstaller into the user's system Python to build an EXE. rem CI-safe (mirrors :cascade_consent_gate): HP_TEST_SYSBUILD_ANSWER (Y/N) overrides; else rem HP_CI_LANE auto-declines with no set /p (no CI hang); else interactive prompt. The prompt rem string is echoed unconditionally so prompt assertions see it even on auto-decline. rem exit 0 = consent (build), exit 1 = decline (skip the build). echo. echo *** The standalone EXE build installs PyInstaller into your system Python. *** echo *** This is the same PyInstaller build used for every provider -- not a special path -- and *** echo *** its footprint is small and self-contained (it does not pin common libraries), so it is *** echo *** unlikely to conflict with your existing packages. *** echo. set "HP_SYSBUILD_RAW=" if defined HP_TEST_SYSBUILD_ANSWER ( set "HP_SYSBUILD_RAW=%HP_TEST_SYSBUILD_ANSWER%" ) else if defined HP_CI_LANE ( set "HP_SYSBUILD_RAW=n" ) else ( set /p "HP_SYSBUILD_RAW= Build the standalone EXE now? [Y/N] " ) set "HP_SYSBUILD_CHOICE=%HP_SYSBUILD_RAW:~0,1%" if /I "%HP_SYSBUILD_CHOICE%"=="Y" ( call :log "[INFO] REQ-007: system-Python EXE build consent: accepted." exit /b 0 ) call :log "[INFO] REQ-007: system-Python EXE build consent: declined." exit /b 1 :verify_no_exe_interpreter rem REQ-018 (2b-A.2): single-verification fallback for the NO-EXE path. When no EXE was built or rem run (system-Python build declined, or build skipped), run the entry once via the interpreter -- rem in those providers there is no EXE deliverable, so this IS the user's run, not a throwaway. rem Skipped when user code must not run (REQ-012) or already ran (fast path, or an EXE smoke rem verified the build). Emits the same "Entry smoke" vocabulary + [STATUS] readout. if defined HP_SKIP_ENTRY_SMOKE exit /b 0 if defined HP_FASTPATH_USED exit /b 0 rem skip only when an EXE smoke actually verified the build; if the EXE exists but its smoke was rem skipped by request (HP_SKIP_EXE_SMOKERUN without HP_SKIP_ENTRY_SMOKE), still verify via the rem interpreter so that REQ-012 "skip the EXE run" does not silently skip all verification. if exist "dist\%ENVNAME%.exe" if not defined HP_EXE_SKIPPED exit /b 0 set "HP_PROBE_EXCEEDED=" rem Slice 2b-C: goto-based dispatch, not a parenthesized if/else block -- see the identical rem rationale comment in :try_fast_exe -- cmd.exe freezes every %VAR% in a parenthesized block rem to its pre-block value at parse time; launching the interpreter and reading %ERRORLEVEL% rem inside the same parens would silently corrupt HP_SMOKE_RC for the legacy/CI branch. if defined HP_INTERACTIVE_RUN goto :verify_no_exe_probe call :log "[INFO] Running entry script smoke test via %HP_ENV_MODE% interpreter." rem derived requirement: execute the smoke command inline so cmd, not our logging, owns redirection parsing. >> "%LOG%" echo Smoke command: "%HP_PY%" "%HP_ENTRY%" ^> "~run.out.txt" 2^> "~run.err.txt" "%HP_PY%" "%HP_ENTRY%"%HP_APP_ARGS% 1> "~run.out.txt" 2> "~run.err.txt" set "HP_SMOKE_RC=%ERRORLEVEL%" call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%" if "%HP_SMOKE_RC%"=="0" ( call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)" rem derived requirement: clear a stale flag from an earlier failed REQ-009 cascade tier's rem no-EXE run -- mirrors HP_EXE_VERIFY_FAILED's own clear-on-success at :smokerun_ok. set "HP_NOEXE_VERIFY_FAILED=" ) else ( call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)" rem [REQ-027] P2 honest messaging: :print_no_exe_briefing reads this to stop claiming rem "your code ran successfully" when it did not. set "HP_NOEXE_VERIFY_FAILED=1" ) call :run_postexec_checkpoint interpreter exit /b 0 :verify_no_exe_probe rem Kept UNTIMED past the short probe window on purpose: a long-running app (GUI / server / rem loop) for system-mode users has no recourse if killed, so once the probe window rem (HP_FAILFAST_PROBE_MS) is crossed the wait becomes unbounded and the process is never rem force-stopped. There is no cached artifact at this call site, so the probe only adds an rem early, honest heads-up log line; the final outcome is reported either way once the rem interpreter actually exits. call :log "[INFO] Launching your program now via the %HP_ENV_MODE% interpreter: %HP_PY% %HP_ENTRY%" rem [REQ-026]: entry path re-quoted here (not passed bare) plus any forwarded extra args -- rem HP_PROBE_ARGS is now a full, pre-quoted Windows Arguments string, not a single bare path rem (see :run_failfast_probe's own header comment for the contract change). set "HP_PROBE_EXE=%HP_PY%" set "HP_PROBE_ARGS="%HP_ENTRY%"%HP_APP_ARGS%" set "HP_PROBE_CWD=%CD%" call :run_failfast_probe interpreter rem [REQ-027] P2 honest messaging: same flag as the legacy branch above. No -1/timeout rem ambiguity at this call site -- :run_failfast_probe never kills, so HP_SMOKE_RC is always rem the interpreter's true final exit code once it exits, not a force-stopped placeholder. if not "%HP_SMOKE_RC%"=="0" set "HP_NOEXE_VERIFY_FAILED=1" rem derived requirement: clear a stale flag from an earlier failed REQ-009 cascade tier's rem no-EXE run -- mirrors HP_EXE_VERIFY_FAILED's own clear-on-success at :smokerun_ok. if "%HP_SMOKE_RC%"=="0" set "HP_NOEXE_VERIFY_FAILED=" call :run_postexec_checkpoint interpreter exit /b 0 :warnfix_cascade_detect rem REQ-009/REQ-005.10 (slice 1: detect only). After the warnfix rebuild, re-parse the rem fresh PyInstaller warn file. If modules are STILL missing AND at least one repair rem install failed this round, the current provider genuinely cannot supply them -- mark a rem cascade candidate (HP_CASCADE_CANDIDATE). This slice only detects and logs; the actual rem provider cascade (re-attempt under the next REQ-009 tier) is added in a later change. rem Confidence gate: require BOTH the unresolved signal AND a recorded install failure so a rem parse_warn false-positive on an already-present module does not trigger a cascade. rem HP_TEST_FORCE_WARNFIX_UNRESOLVED=1 forces the candidate for deterministic CI coverage. rem No setlocal: HP_CASCADE_CANDIDATE must persist to the caller. set "HP_CASCADE_CANDIDATE=" set "HP_UNRESOLVED_AFTER=" if exist "build\%ENVNAME%\warn-%ENVNAME%.txt" ( call :emit_from_base64 "~parse_warn.py" HP_PARSE_WARN "%HP_PY%" ~parse_warn.py "%ENVNAME%" > "~missing_after.txt" 2>> "%LOG%" if exist "~parse_warn.py" del "~parse_warn.py" >nul 2>&1 for /f "usebackq delims=" %%M in ("~missing_after.txt") do set "HP_UNRESOLVED_AFTER=1" ) if exist "~warnfix_repair_failed.flag" if defined HP_UNRESOLVED_AFTER set "HP_CASCADE_CANDIDATE=1" if "%HP_TEST_FORCE_WARNFIX_UNRESOLVED%"=="1" set "HP_CASCADE_CANDIDATE=1" set "HP_CASCADE_APPROVED=" if defined HP_CASCADE_CANDIDATE ( call :log "[WARN] REQ-009: warnfix left modules unresolved under provider %HP_ENV_MODE%." call :log "[INFO] REQ-009: cascade candidate detected." ) rem Slice 2: ask for consent. Slice 3 will consume HP_CASCADE_APPROVED to re-attempt the rem dependency phase under the next REQ-009 provider tier. Detection-only until then. if defined HP_CASCADE_CANDIDATE call :cascade_consent_gate if defined HP_CASCADE_CANDIDATE if not errorlevel 1 set "HP_CASCADE_APPROVED=1" if defined HP_CASCADE_APPROVED call :log "[INFO] REQ-009: cascade approved; will re-attempt under the next provider tier." if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED call :log "[INFO] REQ-009: cascade declined; keeping current build." rem Not approved (declined, timed out, or auto-declined in CI) but the candidate signal was rem real: set a flag that persists for the rest of THIS provider's attempt (reset at the top of rem every fresh build attempt -- see HP_NUITKA_FALLBACK_USED's neighbor above) so later rem user-facing messages (:warn_user_code_launch) can add context the bootstrapper otherwise has rem no other way to surface. The user may know something the automated install missed and want rem to push through anyway -- this is a note, never a second gate. if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED set "HP_DEP_MAYBE_INCOMPLETE=1" if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED call :log "[WARN] REQ-009: dependencies for this provider may still be incomplete. If the run below fails or behaves unexpectedly, re-run and accept the cascade prompt to try a different provider, or check/edit requirements.txt yourself if you believe the automatic install missed something it shouldn't have." if exist "~missing_after.txt" del "~missing_after.txt" >nul 2>&1 exit /b 0 :pep723_writeback rem REQ-005.11: promotes resolved dependencies into %HP_ENTRY%'s PEP 723 header via rem uv add --script; see docs/plan-pep723-writeback.md Part 2. Called with one argument, rem 'fresh' or 'warnfix', for log-message differentiation only. Goto-based dispatch, not rem nested parens, per the established cascade-dispatch convention (docs/agent-lessons- rem learned.md "Provider-cascade dispatch is goto-based on purpose"). set "HP_PEP723_TRIGGER=%~1" rem v1 scope gate: uv lane only. Silent -- no log line, to avoid noise on every non-uv run. if not "%HP_ENV_MODE%"=="uv" exit /b 0 if defined HP_SKIP_PEP723_WRITEBACK ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (HP_SKIP_PEP723_WRITEBACK set)." exit /b 0 ) rem Defensive; technically unreachable given call-site ordering, cheap insurance only. if defined HP_CI_SKIP_ENV exit /b 0 if not defined HP_UV_EXE exit /b 0 if not defined HP_ENTRY exit /b 0 if not exist "%HP_ENTRY%" exit /b 0 rem Packages-source-exists check runs BEFORE the confirmed-installed gate below: rem an app with nothing to install (e.g. a stdlib-only app -- no requirements.txt is rem ever written for it) must exit silently here, not fall into the confirmed- rem installed gate and log a misleading "install did not fully succeed" message rem for a round where nothing was ever attempted in the first place. set "HP_PEP723_PKGS_SRC=" if /I "%HP_PEP723_TRIGGER%"=="fresh" set "HP_PEP723_PKGS_SRC=requirements.txt" if /I "%HP_PEP723_TRIGGER%"=="warnfix" set "HP_PEP723_PKGS_SRC=~missing_modules.txt" if not defined HP_PEP723_PKGS_SRC exit /b 0 if not exist "%HP_PEP723_PKGS_SRC%" exit /b 0 rem All-or-nothing confirmed-installed gate per trigger (see Part 2.0 point 3): never rem write back from a partially-failed round. if /I "%HP_PEP723_TRIGGER%"=="fresh" if not defined HP_UV_INSTALL_OK ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (dependency install did not fully succeed)." exit /b 0 ) if /I "%HP_PEP723_TRIGGER%"=="warnfix" if exist "~warnfix_repair_failed.flag" ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (one or more warnfix repairs failed)." exit /b 0 ) if exist "~pep723_pkgs.txt" del "~pep723_pkgs.txt" >nul 2>&1 copy /y "%HP_PEP723_PKGS_SRC%" "~pep723_pkgs.txt" >nul 2>&1 if errorlevel 1 goto :pep723_writeback_cleanup call :emit_from_base64 "~pep723_writeback.py" HP_PEP723_WRITEBACK if errorlevel 1 goto :pep723_writeback_cleanup if exist "~pep723_result.txt" del "~pep723_result.txt" >nul 2>&1 "%HP_PY%" "~pep723_writeback.py" "%HP_ENTRY%" "%HP_UV_EXE%" "%HP_PY%" "~pep723_pkgs.txt" > "~pep723_result.txt" 2>> "%LOG%" set "HP_PEP723_RESULT=" for /f "usebackq delims=" %%R in ("~pep723_result.txt") do set "HP_PEP723_RESULT=%%R" if exist "~pep723_result.txt" del "~pep723_result.txt" >nul 2>&1 if exist "~pep723_writeback.py" del "~pep723_writeback.py" >nul 2>&1 if not defined HP_PEP723_RESULT ( call :log "[WARN] REQ-005.11: PEP 723 write-back helper produced no output; continuing." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT:~0,3%"=="OK:" ( call :log "[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT%"=="SKIP:no_packages" ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (no packages to write)." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT%"=="SKIP:non_utf8" ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (entry file is not UTF-8)." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT%"=="SKIP:file_locked" ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (entry file is locked)." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT%"=="SKIP:lockfile" ( call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (a .py.lock sidecar already exists)." goto :pep723_writeback_cleanup ) if "%HP_PEP723_RESULT:~0,6%"=="ERROR:" ( call :log "[WARN] REQ-005.11: PEP 723 header write-back failed (%HP_PEP723_RESULT%); continuing." goto :pep723_writeback_cleanup ) call :log "[WARN] REQ-005.11: PEP 723 header write-back returned an unrecognized result; continuing." :pep723_writeback_cleanup if exist "~pep723_pkgs.txt" del "~pep723_pkgs.txt" >nul 2>&1 set "HP_PEP723_TRIGGER=" set "HP_PEP723_PKGS_SRC=" set "HP_PEP723_RESULT=" exit /b 0 :pvw_known_idempotent_run rem REQ-005.13 (Tier 2): actually runs %HP_ENTRY% via uvx autopep723 as opt-in execute-mode rem dependency discovery. Never fails the lane: any nonzero outcome here just leaves the rem Default Path (pyproject.toml/PEP 723 header/pipreqs, all still to come) untouched. call :log "[INFO] REQ-005.13: HP_PVW_KNOWN_IDEMPOTENT set; running entry via uvx autopep723 for execute-mode discovery." call :emit_from_base64 "~pvw_known_idempotent.py" HP_PVW_IDEMPOTENT if errorlevel 1 ( call :log "[WARN] REQ-005.13: could not write execute-mode discovery helper; falling back to Default Path." exit /b 0 ) rem derived requirement: only stderr is redirected here, never stdout -- the helper's own rem run step inherits stdout so the user's script output prints live to the console; the rem helper deliberately writes its own RAN:/ERROR: result marker to stderr for exactly this rem reason (see tools/pvw_known_idempotent.py's module docstring). if exist "~pvw_idempotent_result.txt" del "~pvw_idempotent_result.txt" >nul 2>&1 "%HP_PY%" "~pvw_known_idempotent.py" "%HP_ENTRY%" "%HP_UVX_EXE%" "%HP_UV_EXE%" "%HP_PY%" 2> "~pvw_idempotent_result.txt" set "HP_PVW_RUN_RC=%ERRORLEVEL%" set "HP_PVW_RESULT=" for /f "usebackq delims=" %%R in ("~pvw_idempotent_result.txt") do set "HP_PVW_RESULT=%%R" if exist "~pvw_idempotent_result.txt" del "~pvw_idempotent_result.txt" >nul 2>&1 if exist "~pvw_known_idempotent.py" del "~pvw_known_idempotent.py" >nul 2>&1 if not "%HP_PVW_RUN_RC%"=="0" ( call :log "[WARN] REQ-005.13: execute-mode discovery run failed (%HP_PVW_RESULT%); falling back to Default Path discovery." set "HP_PVW_RUN_RC=" set "HP_PVW_RESULT=" exit /b 0 ) call :log "[INFO] REQ-005.13: execute-mode discovery run succeeded (%HP_PVW_RESULT%)." set "HP_PVW_RUN_RC=" set "HP_PVW_RESULT=" rem Only the entry file's PEP 723 header was updated (uv add --script) -- requirements.txt rem itself still needs populating from it, exactly like the pre-existing-header case a few rem lines below already does. Reuses that existing subroutine rather than a new writer. set "HP_PVW_REQ=~requirements.pvw_idempotent.txt" if exist "%HP_PVW_REQ%" del "%HP_PVW_REQ%" >nul 2>&1 call :extract_pep723_requirements "%HP_ENTRY%" "%HP_PVW_REQ%" if exist "%HP_PVW_REQ%" for %%S in ("%HP_PVW_REQ%") do if %%~zS GTR 0 ( copy /y "%HP_PVW_REQ%" "requirements.txt" >nul 2>&1 call :log "[INFO] DEP_SOURCE=pvw_idempotent" call :log "[TRACE] dep source selected: pvw_idempotent" ) if exist "%HP_PVW_REQ%" del "%HP_PVW_REQ%" >nul 2>&1 set "HP_PVW_REQ=" exit /b 0 :cascade_consent_gate rem REQ-009/REQ-005.10: require explicit consent before cascading to the next provider tier. rem CI-safe (mirrors :conda_binary_corrupt heal prompt): HP_TEST_CASCADE_ANSWER (Y/N) overrides; rem otherwise HP_CI_LANE auto-declines with no prompt (no wait in CI) UNLESS rem HP_TEST_FORCE_INTERACTIVE_CASCADE forces the real-user branch for deterministic CI coverage rem of the timed prompt itself (mirrors HP_TEST_FORCE_PICKER's use for :pick_entry_interactive). rem A real interactive user gets a TIMED choice /T prompt (default: 30s), not an unbounded rem set /p -- if nobody answers in time it defaults to N (decline), so an unattended run still rem tries the current build once rather than hanging the whole bootstrap forever. Goto-based rem dispatch throughout (not nested parens) so `choice` + `if errorlevel` reads are never inside rem a block that could freeze an earlier value -- see docs/agent-lessons-learned.md's rem "Provider-cascade dispatch is goto-based on purpose". exit 0 = approved, exit 1 = declined. echo. echo *** Some dependencies could not be installed under the current Python provider. *** echo. set "HP_CASCADE_CHOICE=" if defined HP_TEST_CASCADE_ANSWER goto :cascade_consent_from_override if defined HP_CI_LANE if not defined HP_TEST_FORCE_INTERACTIVE_CASCADE goto :cascade_consent_ci_decline goto :cascade_consent_interactive :cascade_consent_from_override set "HP_CASCADE_RAW=%HP_TEST_CASCADE_ANSWER%" set "HP_CASCADE_CHOICE=%HP_CASCADE_RAW:~0,1%" goto :cascade_consent_resolve :cascade_consent_ci_decline set "HP_CASCADE_CHOICE=N" goto :cascade_consent_resolve :cascade_consent_interactive set "HP_CASCADE_T=30" if defined HP_TEST_FORCE_INTERACTIVE_CASCADE set "HP_CASCADE_T=2" where choice >nul 2>&1 if errorlevel 1 goto :cascade_consent_no_choice_exe choice /C YN /N /T %HP_CASCADE_T% /D N /M " Try the next Python provider to resolve them? [Y/N, defaults to N after %HP_CASCADE_T%s] " if errorlevel 2 goto :cascade_consent_ci_decline set "HP_CASCADE_CHOICE=Y" goto :cascade_consent_resolve :cascade_consent_no_choice_exe rem no choice.exe on this image (stripped install) -- keep the safe default (decline). set "HP_CASCADE_CHOICE=N" goto :cascade_consent_resolve :cascade_consent_resolve if /I "%HP_CASCADE_CHOICE%"=="Y" ( call :log "[INFO] REQ-009: cascade consent: accepted." exit /b 0 ) call :log "[INFO] REQ-009: cascade consent: declined." exit /b 1 :dll_bundle_recover rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: reactive, rem bounded repair loop for a conda-installed native extension whose compiled .pyd depends rem on a shared DLL PyInstaller's static analysis cannot trace (conda's own Library\bin rem convention -- e.g. eccodes.dll for pygrib). Mirrors :hidden_import_recover's bounded- rem iteration/tried-list shape (see docs/agent-lessons-learned.md's "--hidden-import rem auto-recovery must stay STRICT"), but detects at BUILD time rather than waiting for the rem guaranteed runtime crash: PyInstaller's own build log already announces an unresolved rem native dependency ("WARNING: Library not found: could not resolve 'X.dll'") before the rem EXE is ever smoke-run. Requirement 1's own CI experiment (PR #415, rem self.gribapi_hook_probe.hidden_import, conclusive:true, hiddenImportHelped:false) rem confirmed forcing --hidden-import=gribapi does NOT make hook-gribapi.py bundle rem eccodes.dll for a pygrib-only build, ruling out that free-lunch shortcut and motivating rem this loop. goto-based (not a parenthesized block) so each %VAR% reads its runtime rem value, not a parse-time one -- see docs/agent-lessons-learned.md's "Provider-cascade rem dispatch is goto-based on purpose". rem CLAUDE.md Item 29: this subroutine can now be called a SECOND time per fresh build rem attempt, after :hidden_import_recover's own loop finishes -- a hidden-import rebuild's rem own --collect-submodules=X can pull in a package whose compiled extension needs a native rem DLL never checked before (confirmed via a real pyproj/proj_9.dll failure). Reset this rem call's own "did I actually bundle something" signal FIRST, before any early-return path, rem so the caller has a reliable, per-call-scoped flag -- HP_DLL_ITER is NOT reliable for this rem across two calls (an early "nothing detected" return below never resets it, so a stale rem value from an EARLIER call could otherwise look like fresh repair activity). set "HP_DLL_REPAIRED=" if not exist "dist\%ENVNAME%.exe" exit /b 0 if not defined HP_LOG_SIZE_BEFORE exit /b 0 rem Requirement 3: detection is cheap and provider-agnostic -- always checked first, rem regardless of provider or Nuitka-vs-PyInstaller build, so the caveat panel's rem "detected, repair skipped" state (see docs/open-questions.md) is backed by a real rem signal instead of silence. Only the actual locate-and-bundle ACTION below is gated. call :emit_from_base64 "~dll_bundle_scan.py" HP_DLL_BUNDLE_SCAN "%HP_PY%" ~dll_bundle_scan.py --detect "%LOG%" "%HP_LOG_SIZE_BEFORE%" > "~dll_detect.txt" 2>> "%LOG%" if exist "~dll_bundle_scan.py" del "~dll_bundle_scan.py" >nul 2>&1 set "HP_DLL_DETECTED=" for /f "usebackq delims=" %%N in ("~dll_detect.txt") do set "HP_DLL_DETECTED=%%N" if exist "~dll_detect.txt" del "~dll_detect.txt" >nul 2>&1 if not defined HP_DLL_DETECTED exit /b 0 rem CodeRabbit finding: :run_entry_smoke (and therefore :dll_bundle_recover) can run more than rem once per process during a REQ-009 provider cascade re-entry. HP_NEXT_DLL/HP_DLL_ITER are rem normally reset later (line ~4017/inside the loop), which is AFTER the skipped_nuitka/ rem skipped_non_conda exits below -- so a stale value left by an EARLIER call's successful rem repair would leak into a LATER call's skip row via :emit_dll_bundle_row's own rem "$dll = if ($next) { $next } else { $detected }" fallback. Reset both here, before either rem skip branch can read them. set "HP_NEXT_DLL=" set "HP_DLL_ITER=0" rem :log echoes UNQUOTED (see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry) -- rem a DLL basename can legally contain &, |, <, or > on Windows, which cmd.exe would otherwise rem reinterpret as a live redirection/pipe operator once substituted into an unquoted echo line, rem corrupting the log line and/or creating a stray file. Sanitize a DISPLAY-ONLY copy here; the rem raw HP_DLL_DETECTED is never used for anything functional (no file lookup happens on it -- rem the real Library\bin search is redone from scratch, on the raw warning text, inside rem :dll_bundle_loop below), so this substitution cannot desync any matching logic. rem CodeRabbit finding: `call :log` performs a SECOND cmd.exe expansion pass on its own rem argument text (the documented "call re-parses its command line" behavior), so a raw rem % or ^ surviving into HP_DLL_DETECTED_SAFE could still expand an unrelated environment rem variable (e.g. a crafted "%SOME_SECRET%.dll" warning text) or alter escaping on that rem second pass, even though &/|/ are already neutralized. set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED%" set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:&=_%" set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:|=_%" set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:<=_%" set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:>=_%" rem derived requirement: a real CI-confirmed bug -- cmd.exe's own %VAR:%%=X% doubled-percent rem idiom does NOT reliably strip a literal percent sign (confirmed via rem batch.dll_bundle.pct_sanitizer's live cmd.exe fixture: the substitution silently produced rem an EMPTY value instead of the expected text, an undocumented cmd.exe parsing quirk this rem repo's own reasoning got wrong on the first attempt). A SECOND real CI-confirmed bug then rem hit an inline -Command replacement of it too: a lone, unpaired % anywhere on the same rem cmd.exe logical line as %LOG% got silently paired with %LOG%'s own opening %, deleting rem everything between them as one bogus undefined-variable reference. Both bugs share one rem root cause -- literal % text living on a cmd.exe-parsed line at all -- so the actual fix rem is tools/dll_pct_sanitize.ps1, a real emitted .ps1 file: its content is never parsed by rem cmd.exe's tokenizer (only the outer -File "path" arg... line is, and plain argv has no rem %-pairing hazard). See docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry for rem the full trace of both bugs. call :emit_from_base64 "~dll_pct_sanitize.ps1" HP_DLL_PCT_SANITIZE powershell -NoProfile -ExecutionPolicy Bypass -File "~dll_pct_sanitize.ps1" HP_DLL_DETECTED_SAFE "~dll_pct_safe.txt" >> "%LOG%" 2>&1 if exist "~dll_pct_sanitize.ps1" del "~dll_pct_sanitize.ps1" >nul 2>&1 set "HP_DLL_DETECTED_SAFE=" for /f "usebackq delims=" %%X in ("~dll_pct_safe.txt") do set "HP_DLL_DETECTED_SAFE=%%X" if exist "~dll_pct_safe.txt" del "~dll_pct_safe.txt" >nul 2>&1 rem Requirement 6: identical guard to :hidden_import_recover's own, for the identical rem reason -- --add-binary is a PyInstaller-specific flag with no Nuitka equivalent wired rem up here. Skip (not attempt-and-fail) rather than risk rebuilding a working Tier-A EXE rem via the wrong tool. if defined HP_NUITKA_FALLBACK_USED ( call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%'; skipping repair: dist\%ENVNAME%.exe was built via the fallback build system (Nuitka), which has its own DLL discovery mechanism, not this PyInstaller-specific repair." call :emit_dll_bundle_row skipped_nuitka exit /b 0 ) rem Requirement 3: the actual bundling ACTION only makes sense under the conda provider -- rem Library\bin is conda's own shared-DLL convention, meaningless under uv/embed/venv/system rem (those install from PyPI wheels, which are expected to vendor their own native deps). if not "%HP_ENV_MODE%"=="conda" ( call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%'; native-DLL bundling repair requires the conda provider (current provider: %HP_ENV_MODE%); skipping." call :emit_dll_bundle_row skipped_non_conda exit /b 0 ) if exist "~dll_bundle_tried.txt" del "~dll_bundle_tried.txt" >nul 2>&1 set "HP_DLL_ITER=0" rem CLAUDE.md Item 29: HP_PYI_DLLBIND is deliberately NOT reset here -- it is now reset once rem per fresh build attempt, in :run_entry_smoke, so a SECOND call to this subroutine (the rem post-hidden-import-recovery pass) keeps whatever this label's own earlier call already rem accumulated instead of silently discarding it. set "HP_DLL_FAILED=" set "HP_DLL_EXHAUSTED=" rem derived requirement: real CI evidence (self.layered_e2e.chain, cache lane, 2026-08-07) -- rem HP_PY_DIR (from %%~dpI) always ends in exactly ONE trailing backslash. Quoted as rem "%HP_PY_DIR%" immediately before another quoted argument, Python's own argv parser rem (the standard Windows CommandLineToArgvW backslash-quote-parity rule, the same hazard rem already documented for findstr in docs/agent-lessons-learned.md, but here hitting rem python.exe's own C-runtime startup instead) treats that single trailing backslash as rem ESCAPING the closing quote rather than closing it -- the quoted region never actually rem closes, silently merging conda_env_dir with the next argument (the tried-file path) into rem one garbage string. locate_dll() then fails os.path.isdir() on that garbage and reports rem "could not locate" even when the real DLL is genuinely sitting under Library\bin -- rem confirmed for real: eccodes.dll IS present in the eccodes-2.48.0-h3bec8ca_0 conda-forge rem package at exactly Library\bin\eccodes.dll, yet self.layered_e2e.chain's own CI run still rem reported "could not locate a matching file". Deterministic, not flaky -- HP_PY_DIR always rem ends in one backslash by construction, so this fires on every single conda-provider run. rem Fixed the same way lessons-learned's findstr entry recommends: double the trailing rem backslash so an EVEN count precedes the closing quote, collapsing back to the intended rem single backslash on the receiving end. set "HP_PY_DIR_ARG=%HP_PY_DIR%" if defined HP_PY_DIR_ARG set "HP_PY_DIR_ARG=%HP_PY_DIR_ARG%\" :dll_bundle_loop call :emit_from_base64 "~dll_bundle_scan.py" HP_DLL_BUNDLE_SCAN "%HP_PY%" ~dll_bundle_scan.py "%LOG%" "%HP_LOG_SIZE_BEFORE%" "%HP_PY_DIR_ARG%" "~dll_bundle_tried.txt" > "~next_dll.txt" 2>> "%LOG%" if exist "~dll_bundle_scan.py" del "~dll_bundle_scan.py" >nul 2>&1 set "HP_NEXT_DLL=" set "HP_NEXT_DLL_PATH=" for /f "usebackq tokens=1,2 delims=|" %%A in ("~next_dll.txt") do ( set "HP_NEXT_DLL=%%A" set "HP_NEXT_DLL_PATH=%%B" ) if not defined HP_NEXT_DLL ( if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1 goto :dll_bundle_recover_done ) if %HP_DLL_ITER% GEQ 3 ( rem derived requirement: CLAUDE.md Item 25 -- a real candidate was just found, the rem "if not defined HP_NEXT_DLL" early-return above already ruled out the empty case, but rem the 3-iteration cap discards it here without a trace. HP_DLL_EXHAUSTED distinguishes rem this from a clean "repaired" outcome at :dll_bundle_recover_done below -- without it, rem HP_DLL_ITER GEQ 1, true because 3 DLLs were genuinely bundled in earlier iterations, alone rem would claim "Native-DLL bundling complete" even though a real, locatable DLL was left rem unbundled. set "HP_DLL_EXHAUSTED=1" if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1 goto :dll_bundle_recover_done ) rem Append via file content (type), never via %VAR% expansion on an echo/set line -- rem a DLL basename can legally contain a space or a cmd.exe metacharacter (^, &, or |), rem which would corrupt or split the command line if routed through argv/echo text. type "~next_dll.txt">>"~dll_bundle_tried.txt" echo.>>"~dll_bundle_tried.txt" if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1 rem Same :log-unquoted-echo hazard as HP_DLL_DETECTED above -- sanitize DISPLAY-ONLY copies. rem The raw HP_NEXT_DLL/HP_NEXT_DLL_PATH are still used unchanged for the tried-file (pure file rem content, never shell-expanded) and the quoted --add-binary argument below. set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL%" set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:&=_%" set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:|=_%" set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:<=_%" set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:>=_%" set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH%" set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:&=_%" set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:|=_%" set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:<=_%" set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:>=_%" rem derived requirement: same %-parsing hazard as HP_DLL_DETECTED_SAFE above (two real rem CI-confirmed bugs -- see that comment and docs/agent-lessons-learned.md's ":log echoes rem UNQUOTED" entry for the full trace), fixed the same way: tools/dll_pct_sanitize.ps1, a rem real emitted .ps1 file whose content cmd.exe never parses. Two separate output files rem (one per env var), each read back via a plain single-value "for /f ... do set" -- this rem repo bans delayed expansion (!VAR!) repo-wide, which a multi-line parse would need. call :emit_from_base64 "~dll_pct_sanitize.ps1" HP_DLL_PCT_SANITIZE powershell -NoProfile -ExecutionPolicy Bypass -File "~dll_pct_sanitize.ps1" HP_NEXT_DLL_SAFE "~dll_pct_safe_a.txt" HP_NEXT_DLL_PATH_SAFE "~dll_pct_safe_b.txt" >> "%LOG%" 2>&1 if exist "~dll_pct_sanitize.ps1" del "~dll_pct_sanitize.ps1" >nul 2>&1 set "HP_NEXT_DLL_SAFE=" set "HP_NEXT_DLL_PATH_SAFE=" for /f "usebackq delims=" %%X in ("~dll_pct_safe_a.txt") do set "HP_NEXT_DLL_SAFE=%%X" for /f "usebackq delims=" %%X in ("~dll_pct_safe_b.txt") do set "HP_NEXT_DLL_PATH_SAFE=%%X" if exist "~dll_pct_safe_a.txt" del "~dll_pct_safe_a.txt" >nul 2>&1 if exist "~dll_pct_safe_b.txt" del "~dll_pct_safe_b.txt" >nul 2>&1 set /a HP_DLL_ITER+=1 set "HP_PYI_DLLBIND=%HP_PYI_DLLBIND% --add-binary "%HP_NEXT_DLL_PATH%;."" call :log "[REPAIR][DLL_BUNDLE] Bundling native DLL dependency: %HP_NEXT_DLL_SAFE% (found at %HP_NEXT_DLL_PATH_SAFE%); rebuilding EXE (iter %HP_DLL_ITER%/3)." if exist "%ENVNAME%.spec" (set "HP_DLL_SPEC_PRE=1") else (set "HP_DLL_SPEC_PRE=") for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ" rem CLAUDE.md Item 29: thread HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT through this rebuild rem too -- when this is the SECOND call (after :hidden_import_recover), omitting them would rem silently drop every hidden-import fix already applied, the mirror-image of the bug rem already fixed for HP_PYI_DLLBIND reaching :hidden_import_recover's own rebuild (see rem docs/agent-interconnect.md). Empty/undefined on a first call, so no behavior change there. "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% %HP_PYI_DLLBIND% %HP_PYI_HIDDEN_IMPORTS% %HP_PYI_HID_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[ERROR][DLL_BUNDLE] PyInstaller rebuild failed while bundling native DLL dependency: %HP_NEXT_DLL_SAFE%; the previous build may no longer be valid." set "HP_BOOTSTRAP_STATE=error" set "HP_DLL_FAILED=1" call :emit_dll_bundle_row failed_rebuild goto :dll_bundle_recover_done ) if not exist "dist\%ENVNAME%.exe" ( call :log "[ERROR][DLL_BUNDLE] PyInstaller did not produce dist\%ENVNAME%.exe while bundling native DLL dependency: %HP_NEXT_DLL_SAFE%." set "HP_BOOTSTRAP_STATE=error" set "HP_DLL_FAILED=1" call :emit_dll_bundle_row failed_missing_exe goto :dll_bundle_recover_done ) if not defined HP_DLL_SPEC_PRE if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1 if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1 goto :dll_bundle_loop :dll_bundle_recover_done rem derived requirement: a real bug caught by review -- HP_DLL_ITER GEQ 1 alone is NOT rem proof of success; both failure branches above also leave it >= 1 after their own rem increment. HP_DLL_FAILED is the actual success/failure signal; the "complete" line rem must never fire on a genuine rebuild failure (mirrors the warnfix-rebuild's own rem "never claim success without checking" precedent -- see docs/agent-lessons-learned.md). if defined HP_DLL_FAILED goto :dll_bundle_recover_exit if defined HP_DLL_EXHAUSTED ( call :log "[WARN][DLL_BUNDLE] Native-DLL bundling reached its 3-attempt cap with another native-DLL dependency still detected (first seen: '%HP_DLL_DETECTED_SAFE%'); %HP_DLL_ITER% DLL(s) were bundled, but at least one more remains unbundled -- the EXE may still fail to load it." call :emit_dll_bundle_row exhausted goto :dll_bundle_recover_exit ) if %HP_DLL_ITER% GEQ 1 ( call :log "[REPAIR][DLL_BUNDLE] Native-DLL bundling complete (%HP_DLL_ITER% DLL(s) added); EXE will be re-verified next." call :emit_dll_bundle_row repaired set "HP_DLL_REPAIRED=1" ) else ( call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%' but could not locate a matching file under the conda env's Library\bin; skipping." call :emit_dll_bundle_row unlocatable ) :dll_bundle_recover_exit if exist "~dll_bundle_tried.txt" del "~dll_bundle_tried.txt" >nul 2>&1 set "HP_DLL_SPEC_PRE=" set "HP_DLL_FAILED=" set "HP_DLL_EXHAUSTED=" exit /b 0 :emit_dll_bundle_row rem CodeRabbit finding on PR #414: ":dll_bundle_recover"'s detected/skipped/repaired/ rem unlocatable/failed outcomes previously only reached :log's console text, with no rem machine-readable record. %1 is always one of a small set of literal state tokens rem (skipped_nuitka/skipped_non_conda/repaired/unlocatable/exhausted/failed_rebuild/ rem failed_missing_exe -- CLAUDE.md Item 25 added "exhausted": the 3-iteration cap was rem hit with a real, locatable candidate still pending, distinct from a clean "repaired") rem written directly in THIS file, never derived from external rem content, so passing it as a call argument is safe. The DLL name/provider/iteration rem are pulled INSIDE PowerShell via [Environment]::GetEnvironmentVariable rather than rem %VAR% cmd.exe substitution into the -Command text -- same reasoning as the rem HP_DLL_DETECTED_SAFE/HP_NEXT_DLL_SAFE display-only sanitization above, but this rem time protecting cmd.exe's OWN command-line parsing (& and | are metacharacters even rem inside a quoted argument) rather than :log's unquoted echo. rem docs/open-questions.md item 1 (answered yes): captured BEFORE the HP_NDJSON early-return rem below, so the post-flight caveat panel's DLL-specific hint (:pfb_dll_hint) still works even rem when NDJSON emission itself is disabled -- this call's own %~1 state token is the same value rem either way. See the fresh-build-attempt reset of this variable above for its reset contract. set "HP_DLL_HINT_STATE=%~1" if not defined HP_NDJSON exit /b 0 set "HP_DLL_ROW_STATE=%~1" powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$state = [Environment]::GetEnvironmentVariable('HP_DLL_ROW_STATE');" ^ "$provider = [Environment]::GetEnvironmentVariable('HP_ENV_MODE');" ^ "$detected = [Environment]::GetEnvironmentVariable('HP_DLL_DETECTED');" ^ "$next = [Environment]::GetEnvironmentVariable('HP_NEXT_DLL');" ^ "$iterRaw = [Environment]::GetEnvironmentVariable('HP_DLL_ITER');" ^ "$iter = 0; [void][int]::TryParse($iterRaw, [ref]$iter);" ^ "$dll = if ($next) { $next } else { $detected };" ^ "$pass = -not ($state -like 'failed_*');" ^ "$row = [ordered]@{ id='self.dll_bundle.recover'; pass=$pass; details=[ordered]@{ state=$state; provider=$provider; dll=$dll; iteration=$iter } } | ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1 exit /b 0 :hidden_import_recover rem Slice 2 (REQ-016): strict, double-gated --hidden-import auto-recovery loop. rem Acts ONLY on `ModuleNotFoundError: No module named X` where X is INSTALLED in the rem build interpreter (~hidden_import_scan.py enforces both gates), so a user typo / rem ImportError / circular-import never triggers a rebuild. Bounded to 3 rebuilds; the rem helper's already-tried list plus the iter cap guarantee the loop cannot run forever. rem Sets HP_EXE_EXIT to the final EXE exit so the caller re-checks success. goto-based rem (not a parenthesized block) so each %VAR% reads its runtime value, not a parse-time one. rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): reset this call's own "did I rem actually rebuild" signal FIRST, before any early-return path -- mirrors HP_DLL_REPAIRED's rem identical reasoning in :dll_bundle_recover. Lets the caller (the second :dll_bundle_recover rem pass) skip its own scan entirely when this call did nothing, instead of always re-scanning rem an unchanged log for no reason. set "HP_HIDDEN_REPAIRED=" if not exist "dist\%ENVNAME%.exe" exit /b 0 rem AV-Safe Build Path (requirement 4 follow-up): this loop's ONLY repair mechanism is a rem PyInstaller rebuild with --hidden-import flags -- a PyInstaller-specific mechanism that rem does not apply to a Nuitka-produced EXE. If dist\%ENVNAME%.exe was built via Tier A rem (:try_nuitka_tier_a, HP_NUITKA_FALLBACK_USED=1), skip entirely rather than silently rem rebuilding via PyInstaller here -- that would risk reproducing the very failure Tier A rem exists to route around, or clobbering a working Nuitka build with a broken PyInstaller rem one. Nuitka has its own, different missing-import mechanism (--include-module / rem --follow-import-to); wiring that up is out of scope for this fix. if defined HP_NUITKA_FALLBACK_USED ( call :log "[INFO][HIDDEN_IMPORT] Skipping --hidden-import auto-recovery: dist\%ENVNAME%.exe was built via the fallback build system (Nuitka), which uses a different missing-import mechanism than PyInstaller's --hidden-import flag." exit /b 0 ) rem CLAUDE.md Item 28: pair --collect-submodules=X with each --hidden-import=X this loop adds. rem A --hidden-import target only guarantees PyInstaller follows X's own statically-discovered rem imports; it does NOT guarantee every real submodule under X/ is bundled. A compiled C rem extension elsewhere in the app (invisible to PyInstaller's source scan the same way the rem original missing import was) can still need a submodule of X that X's own __init__.py never rem references -- confirmed via pygrib 2.1.8's real source (`from packaging import version` inside rem a Cython extension, needing packaging.version even after --hidden-import=packaging alone). rem Broader than strictly necessary (collects every submodule of X, not just the one actually rem needed) but structurally safe: X is already find_spec-confirmed installed by rem ~hidden_import_scan.py's own gate, so this never targets an unresolvable package name, and it rem never guesses AT a package name the way inferring one from the failure text would. rem CLAUDE.md Item 29: HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT are deliberately NOT reset here rem (unlike HP_HIDDEN_ITER/HP_HIDDEN_TRIED just below, which DO get a fresh budget every call). rem This subroutine can now be called a SECOND time per fresh build attempt (after a rem :dll_bundle_recover repair -- see that call site) -- resetting these here would silently rem drop every hidden-import flag the FIRST call already accumulated from this SECOND call's rem own rebuild command, regressing an already-fixed package back to ModuleNotFoundError. Both rem are reset once per fresh build attempt instead, in :run_entry_smoke, mirroring the identical rem fix already applied to HP_PYI_DLLBIND in :dll_bundle_recover. set "HP_HIDDEN_ITER=0" set "HP_HIDDEN_TRIED=" rem preserve a user pre-existing spec across recovery rebuilds (the main-build spec-preexist rem flag was already consumed before run_exe_smokerun ran). set "HP_HID_SPEC_PRE=" if exist "%ENVNAME%.spec" set "HP_HID_SPEC_PRE=1" call :warn_user_code_launch hidden_import :hidden_import_loop rem run the EXE with a 30s cap and capture combined output for the scan. A cap is rem essential here: once recovery fixes a missing import the app may proceed into a rem long-running phase (server/GUI), and an uncapped run would hang the bootstrapper. set "HP_EXE_EXIT=" rem CLAUDE.md Active Backlog Item 38: this --hidden-import auto-recovery diagnostic re-run still rem runs from dist\ (current working directory (CWD) = dist), unlike :run_exe_smokerun's own rem primary verification (now app root). Deliberately left as a documented, deferred follow-up -- rem the write (~exe_out.txt, inside dist\) and the later read-back (explicit "dist\~exe_out.txt") rem a few lines below are already internally consistent with each other regardless of which CWD is rem chosen, and no test depends on this specific CWD (selfapps_hidden_import*.ps1's stub apps rem locate their own token file via sys.argv[0], not a CWD-relative path, so they are unaffected rem either way -- see docs/agent-interconnect.md "Single-verification smoke model"). Re-derive rem whether to unify this too the next time this subroutine is touched. pushd dist for /f "usebackq delims=" %%X in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$si=New-Object System.Diagnostics.ProcessStartInfo;$si.FileName='%ENVNAME%.exe';$si.UseShellExecute=$false;$si.RedirectStandardOutput=$true;$si.RedirectStandardError=$true;$p=[System.Diagnostics.Process]::Start($si);$so=$p.StandardOutput.ReadToEndAsync();$se=$p.StandardError.ReadToEndAsync();$done=$p.WaitForExit(30000);if(-not $done){try{$p.Kill()}catch{}};($so.Result+$se.Result)|Set-Content -Path '~exe_out.txt' -Encoding ASCII;if($done){$p.ExitCode}else{-1}"`) do set "HP_EXE_EXIT=%%X" if not defined HP_EXE_EXIT set "HP_EXE_EXIT=-1" popd if "%HP_EXE_EXIT%"=="0" goto :hidden_import_recover_done if %HP_HIDDEN_ITER% GEQ 3 goto :hidden_import_recover_done call :emit_from_base64 "~hidden_import_scan.py" HP_HIDDEN_IMPORT_SCAN "%HP_PY%" ~hidden_import_scan.py "dist\~exe_out.txt" %HP_HIDDEN_TRIED% > "~next_hidden.txt" 2>> "%LOG%" if exist "~hidden_import_scan.py" del "~hidden_import_scan.py" >nul 2>&1 set "HP_NEXT_HIDDEN=" for /f "usebackq delims=" %%M in ("~next_hidden.txt") do set "HP_NEXT_HIDDEN=%%M" if exist "~next_hidden.txt" del "~next_hidden.txt" >nul 2>&1 if not defined HP_NEXT_HIDDEN goto :hidden_import_recover_done set /a HP_HIDDEN_ITER+=1 set "HP_HIDDEN_REPAIRED=1" set "HP_PYI_HIDDEN_IMPORTS=%HP_PYI_HIDDEN_IMPORTS% --hidden-import=%HP_NEXT_HIDDEN%" set "HP_PYI_HID_COLLECT=%HP_PYI_HID_COLLECT% --collect-submodules=%HP_NEXT_HIDDEN%" set "HP_HIDDEN_TRIED=%HP_HIDDEN_TRIED% %HP_NEXT_HIDDEN%" call :log "[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=%HP_NEXT_HIDDEN% --collect-submodules=%HP_NEXT_HIDDEN%; rebuilding EXE (iter %HP_HIDDEN_ITER%/3)." rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): advance HP_LOG_SIZE_BEFORE to rem right before THIS rebuild, mirroring :dll_bundle_loop's own identical pattern for its own rem rebuilds. Narrows the SECOND :dll_bundle_recover pass's own scan window to just the LAST rem hidden-import rebuild's output (rather than everything since the first DLL-bundle pass, rem which included earlier, already-resolved rebuilds too) -- tighter and more precise, even rem though the wider window was not observed to cause a false re-detection in practice (an rem already-bundled DLL's own warning does not reappear in a later rebuild that still includes rem its --add-binary flag). for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ" "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% %HP_PYI_DLLBIND% %HP_PYI_HIDDEN_IMPORTS% %HP_PYI_HID_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[REPAIR][HIDDEN_IMPORT] PyInstaller rebuild failed; stopping recovery." set "HP_EXE_EXIT=1" goto :hidden_import_recover_done ) goto :hidden_import_loop :hidden_import_recover_done if "%HP_EXE_EXIT%"=="0" if %HP_HIDDEN_ITER% GEQ 1 call :log "[REPAIR][HIDDEN_IMPORT] EXE verified after hidden-import recovery." rem derived requirement: prior to this line, exhausting the 3-attempt cap with the EXE still rem failing logged nothing beyond the per-iteration [REPAIR][HIDDEN_IMPORT] lines -- the user rem only saw the generic post-build failure output, with no explicit signal that auto-recovery rem was attempted and gave up. if not "%HP_EXE_EXIT%"=="0" if %HP_HIDDEN_ITER% GEQ 3 call :log "[WARN][HIDDEN_IMPORT] Auto-recovery exhausted after 3 attempts; module(s) still missing." if exist "dist\~exe_out.txt" del "dist\~exe_out.txt" >nul 2>&1 rem clean up artifacts created by recovery rebuilds (mirror the main-build cleanup); rem preserve a user pre-existing spec. if not defined HP_HID_SPEC_PRE if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1 if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1 set "HP_HIDDEN_ITER=" set "HP_HIDDEN_TRIED=" set "HP_NEXT_HIDDEN=" set "HP_HID_SPEC_PRE=" rem CLAUDE.md Item 29: HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT are deliberately NOT reset rem here either (see this subroutine's own entry comment) -- a later :dll_bundle_recover call rem in the SAME fresh build attempt needs to read whatever this call accumulated, to thread rem it through its own rebuild command. Reset once per fresh build attempt instead, in rem :run_entry_smoke. exit /b 0 :warn_user_code_launch rem REQ-016: tightly-scoped heads-up before a launch that can be force-stopped at ~30s, so the rem user does not mistake a verification run for finished setup and start real work in it rem (which would be lost when the run is killed). Called only where a 30s cap actually applies rem -- the EXE smoke and hidden-import recovery -- not at the untimed entry smoke. rem derived requirement: this is called for BOTH a normal PyInstaller build and an AV-Safe rem Build Path Tier A fallback build (HP_NUITKA_FALLBACK_USED=1) -- the message previously rem hardcoded "(PyInstaller)" even when the EXE being verified was actually Nuitka-built. rem rem derived requirement (docs/plan-cli-interactive-verification.md Open Question 1, owner rem decision 2026-07-24): the main EXE smoke (~exe_smokerun.ps1, caller passes "main") no longer rem unconditionally kills at 30s -- it only does so when the process has stayed completely rem silent that whole time (see ~exe_smokerun.ps1's own header comment). The message must say so rem accurately, or it would now overclaim a kill that may not happen. The hidden-import recovery rem loop's own quick-check (caller passes "hidden_import") is UNCHANGED -- it still always kills rem at 30s regardless of output, since its purpose is a bounded repair-verification check, not a rem full run -- so it keeps the original, unconditional wording. if "%~1"=="hidden_import" ( if defined HP_NUITKA_FALLBACK_USED ( call :log "[WARN] Verifying the built standalone EXE (fallback build system) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost." ) else ( call :log "[WARN] Verifying the built standalone EXE (PyInstaller) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost." ) ) else ( if defined HP_NUITKA_FALLBACK_USED ( call :log "[WARN] Verifying the built standalone EXE (fallback build system) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost." ) else ( call :log "[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost." ) ) rem REQ-009/REQ-005.10 (cascade-vs-postexec fix): carry the "dependencies may be incomplete" rem context (set in :warnfix_cascade_detect when a cascade was offered but not approved) into rem this launch warning too, so a confusing runtime failure right after is not mistaken for a rem bug in the user's own code. if defined HP_DEP_MAYBE_INCOMPLETE call :log "[WARN] Note: dependency installation for this run may be incomplete (see the earlier warning above) -- unexpected behavior below could stem from that rather than a bug in your own code." exit /b 0 :run_exe_smokerun if not exist "dist\%ENVNAME%.exe" ( call :log "[WARN] EXE smokerun: dist\%ENVNAME%.exe not found; skipping" exit /b 0 ) rem REQ-012: super-user hook -- skip EXE verification by request, BEFORE announcing a rem test. Distinct from a verification failure, so HP_EXE_VERIFY_FAILED is NOT set; the rem post-flight note records that the EXE was built but intentionally not run. if defined HP_SKIP_EXE_SMOKERUN ( call :log "[INFO] REQ-012: HP_SKIP_EXE_SMOKERUN set; skipping EXE verification (skipped by request)." set "HP_EXE_SKIPPED=1" exit /b 0 ) call :log "[INFO] EXE smokerun: testing dist\%ENVNAME%.exe" call :log "[INFO] Running entry script smoke test via packaged EXE." call :warn_user_code_launch main set "HP_EXE_EXIT=-1" rem CLAUDE.md Active Backlog Item 38 (resolved): the EXE smoke now runs from the app root (the rem same current working directory (CWD) as :try_fast_exe/:verify_no_exe_interpreter and as the rem interpreter's own run), not dist\ -- previously this was the ONE verification point using a rem different CWD than every other one, so a CWD-relative-path app (e.g. open("config.json")) rem could pass on a fresh build and fail on the very next (fast-path) run, or vice versa, with no rem code change in between. Users place adjacent data files next to their .py source, which is the rem app root, not a dist\ folder that does not even exist until after the build -- so app root is rem the CWD that actually matches how a real double-clicked EXE finds its own adjacent files, not rem just an artifact of how PyInstaller happens to lay out its output. Capture the EXE stdout/ rem stderr to ~run.out.txt / ~run.err.txt (now CWD-relative to the app root directly, no ..\ rem prefix needed) so the single EXE verification produces the run artifacts the old interpreter rem smoke did -- the main 'smoke-ok' token is on stdout. Note: an app that writes a file next to rem sys.argv[0] (e.g. the spaced-path test's ~smoke_token.txt) still writes it into dist\ for a rem frozen EXE regardless of launch CWD (a separate, CWD-independent mechanism), so rem selfapps_envsmoke.ps1 still reads that specific token from dist\. rem rem ~exe_smokerun.ps1 (HP_EXE_SMOKERUN) replaces the old inline -Command one-liner: it live-tees rem the EXE's own stdout/stderr to the console as it arrives (docs/plan-cli-interactive-verification.md) rem and is invoked DIRECTLY (no for /f/backtick stdout capture), reading its exit-code result back rem from a static file instead -- capturing this script's own stdout would swallow the teed output rem and corrupt result parsing (see that doc's Finding 6). Kill()-after-30s is unchanged. rem CodeRabbit review, PR #470: resolve to an absolute path, matching :try_fast_exe_probe's own rem defensive precedent -- .NET Process.Start's FileName resolution is a different mechanism from rem cmd.exe's own relative launch, so keep this unambiguous rather than relying on the child rem process inheriting the right CWD to resolve it, in case a future change alters how/where this rem is invoked from. set "HP_SMOKERUN_EXE=%CD%\dist\%ENVNAME%.exe" rem [REQ-026]: the EXE is self-contained (no separate entry-file argv needed), so Arguments is rem exactly the forwarded extra args, or empty -- HP_APP_ARGS is already a pre-quoted, ready rem Windows Arguments string by construction. set "HP_SMOKERUN_ARGS=%HP_APP_ARGS%" set "HP_SMOKERUN_RESULT=~smokerun_result.txt" if exist "~exe_smokerun.ps1" del "~exe_smokerun.ps1" >nul 2>&1 if exist "%HP_SMOKERUN_RESULT%" del "%HP_SMOKERUN_RESULT%" >nul 2>&1 call :emit_from_base64 "~exe_smokerun.ps1" HP_EXE_SMOKERUN if errorlevel 1 ( rem Extremely rare -- disk/permission failure writing a work file; HP_EXE_EXIT stays -1. call :log "[WARN] EXE smokerun: could not emit ~exe_smokerun.ps1; treating as a failed run." ) else ( powershell -NoProfile -ExecutionPolicy Bypass -File "~exe_smokerun.ps1" if exist "%HP_SMOKERUN_RESULT%" ( for /f "usebackq delims=" %%X in ("%HP_SMOKERUN_RESULT%") do set "HP_EXE_EXIT=%%X" ) if exist "~exe_smokerun.ps1" del "~exe_smokerun.ps1" >nul 2>&1 if exist "%HP_SMOKERUN_RESULT%" del "%HP_SMOKERUN_RESULT%" >nul 2>&1 ) set "HP_SMOKERUN_EXE=" set "HP_SMOKERUN_ARGS=" set "HP_SMOKERUN_RESULT=" if not defined HP_EXE_EXIT set "HP_EXE_EXIT=-1" if "%HP_EXE_EXIT%"=="0" goto :smokerun_ok call :log "[WARN] EXE smokerun: exited %HP_EXE_EXIT% (non-zero)" rem Slice 2 (REQ-016): attempt strict --hidden-import auto-recovery before giving up. rem Skip when HP_EXE_EXIT is -1 (a timeout/hang) -- re-running a hung EXE in the recovery rem loop would hang too; only a real fast non-zero exit (e.g. ModuleNotFoundError) is fixable. if not "%HP_EXE_EXIT%"=="-1" call :hidden_import_recover rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): deliberately no early rem "if HP_EXE_EXIT==0 goto :smokerun_ok" here, unlike the check at line ~4454 above. A rem hidden-import rebuild above (--collect-submodules=X) can pull in a package whose OWN rem compiled extension needs a native DLL that was never checked -- the :dll_bundle_recover rem call inside :run_entry_smoke only ever saw the ORIGINAL build's own warnings, before any rem hidden-import rebuild ran. The rebuilt EXE can exit 0 on THIS smoke run's own code path rem while the build log still shows a fresh, unactioned "Library not found" warning for a DLL a rem DIFFERENT code path would need -- exactly the class of gap build-time detection exists to rem catch before it becomes a guaranteed runtime failure (confirmed via a real pyproj/proj_9.dll rem failure -- see docs/agent-interconnect.md's "Conda native-DLL bundling repair loop" section, rem "Detects at BUILD time, not runtime"). So the block below must run regardless of whether this rem rebuild already made the smoke run pass; it is gated on HP_HIDDEN_REPAIRED (below), not on rem HP_EXE_EXIT -- if the call above did NOT actually rebuild anything, the log has not grown rem since the FIRST :dll_bundle_recover call already scanned it, so there is nothing new to find, rem and the gate skips the pointless re-scan in the common case instead of always paying for one. if not "%HP_EXE_EXIT%"=="-1" if defined HP_HIDDEN_REPAIRED call :dll_bundle_recover rem Only worth a fresh verification pass if this call actually bundled something -- the rem common case (nothing new detected) must not pay for an extra EXE launch/wait. if defined HP_DLL_REPAIRED ( rem A DLL fix can also unblock a package whose OWN hidden-import gap was previously rem unreachable -- the DLL failure short-circuited the app before it got that far, e.g. rem colorama's own gap in self.layered_e2e.chain, only reached once pyproj's DLL is fixed. rem Give :hidden_import_recover one more bounded pass. Deliberately not chained further: rem each subroutine call already gets its own fresh, capped 3-iteration budget, and a rem THIRD round risks an unbounded repair cascade for a pathological dependency tree -- a rem case ever found needing more than this is its own future backlog item, not solved rem speculatively here. No -1 guard needed here: HP_DLL_REPAIRED can only be defined if rem the :dll_bundle_recover call just above actually ran, which itself required HP_EXE_EXIT rem to already be non--1, see the guard on that call -- and dll_bundle_recover never rem touches HP_EXE_EXIT, so that fact still holds at this point. call :hidden_import_recover ) if "%HP_EXE_EXIT%"=="0" goto :smokerun_ok rem REQ-016: record that the packaged EXE could not be verified so the post-flight rem briefing can guide the user to run the app directly instead of claiming success. set "HP_EXE_VERIFY_FAILED=1" rem a -1 is a timeout/hang: no parseable error to hint on, and re-running the EXE in rem :exe_smokerun_hints would hang too -- skip straight to the post-flight caveat. if not "%HP_EXE_EXIT%"=="-1" call :exe_smokerun_hints rem CLAUDE.md Active Backlog Item 41: ~exe_smokerun.ps1's own kill only fires when ZERO rem stdout/stderr bytes were observed before the 30s deadline (its $sawOutput gate) -- exactly rem the shape of a correctly-behaving GUI app (tkinter/PyQt, a mainloop with no console output), rem not just a genuinely hung program. HP_EXE_EXIT=="-1" already implies this at this point in rem the file, so no new runtime signal is needed -- just record it for the caveat panel below. if "%HP_EXE_EXIT%"=="-1" set "HP_EXE_TIMEDOUT_SILENT=1" goto :smokerun_ndjson :smokerun_ok call :log "[INFO] EXE smokerun: exited 0 (ok)" set "HP_EXE_VERIFY_FAILED=" :smokerun_ndjson rem REQ-018 (2b-A.2): unified verification vocabulary -- emit the same "Entry smoke exit=" line the rem interpreter smoke used, so the single EXE verification satisfies the existing run assertions rem (self.env.smoke.run / self.prime.run / self.prime.bootstrap) without re-pointing them. call :log "[INFO] Entry smoke exit=%HP_EXE_EXIT%" rem REQ-018 (2b-A): telemetry readout of the single verification run. HP_EXE_EXIT is the final rem EXE exit after any hidden-import recovery: 0 = clean, -1 = the 30s cap was hit (force-stopped, rem not necessarily broken), other = a real non-zero exit. This [STATUS] line is the readout the rem 2b-C post-execution checkpoint (:run_postexec_checkpoint, called below) shows the user before rem offering the elective second run. :log echoes unquoted, so keep the message free of rem < > | & (parentheses are literal to echo outside if/for blocks). if "%HP_EXE_EXIT%"=="0" ( call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)" ) else if "%HP_EXE_EXIT%"=="-1" ( call :log "[STATUS] Run Status: TIMED OUT (hit the ~30s verification cap; force-stopped, not necessarily broken)" ) else ( call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_EXE_EXIT%)" ) if defined HP_NDJSON ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$c=[int]'%HP_EXE_EXIT%';" ^ "$r=[ordered]@{id='self.exe.smokerun';pass=($c -eq 0);details=[ordered]@{exitCode=$c}}|ConvertTo-Json -Compress -Depth 8;" ^ "Add-Content -Path '%HP_NDJSON%' -Value $r -Encoding ASCII" >> "%LOG%" 2>&1 ) rem REQ-009/REQ-005.10 (cascade-vs-postexec fix, CLAUDE.md docs/open-questions.md item 1): rem when the user has already agreed to cascade to the next provider tier, this build is about rem to be replaced -- do not also ask them to verify-again or optimize a build they just opted rem away from. Both offers are purely elective (never required for correctness; the smoke run rem above already happened regardless -- see the comment at its call site for why THAT must rem never be skipped), so suppressing them here costs nothing if :provider_cascade later finds rem every remaining tier unavailable and keeps this build after all -- the user was never asked rem to verify/optimize it, but it was still verified once, just without the elective extras. if defined HP_CASCADE_APPROVED ( call :log "[INFO] REQ-009: cascade approved; skipping the post-verification offers for this build." ) else ( call :run_postexec_checkpoint exe rem AV-Safe Build Path requirement 9, P1: offer an elective optimized build right after the rem verification telemetry above, while %HP_EXE_EXIT% still holds this run's real outcome -- rem the next line clears it. See :offer_optimized_build's own header comment for the full gating. call :offer_optimized_build ) set "HP_EXE_EXIT=" exit /b 0 :exe_smokerun_hints rem derived requirement: re-run EXE briefly to capture stderr for pattern-based hints only. rem Former Active Backlog item 15 (docs/agent-closed-backlog.md): a genuine ModuleNotFoundError/ rem FileNotFoundError exits immediately, but this is a FRESH re-run of the same binary, and any rem non-determinism (a race, an env check that sometimes succeeds, anything that occasionally rem blocks on inherited stdin) could hang this second invocation even though the FIRST one rem legitimately classified as "fast, real, non-hang failure" -- this was the one user-code rem launch point in this file with no timeout at all. Fixed via a dedicated bounded helper rem (~exe_hint_rerun.ps1, HP_EXE_HINT_RERUN) with an UNCONDITIONAL kill deadline (not rem activity-aware like ~exe_smokerun.ps1/~failfast_probe.ps1 -- this run is diagnostic-only, rem never shown live, so partial output on a hang is fine and preferred over hanging the rem bootstrap). Existing success/failure logic and the hint-matching below are unchanged. rem CLAUDE.md Active Backlog Item 38 (resolved): this diagnostic re-run now shares the SAME rem current working directory (CWD) as :run_exe_smokerun's own primary run (the app root, not rem dist\) -- this rerun exists purely to explain why the primary run just failed, so it must rem reproduce the exact same launch conditions the primary run used, or the hint could describe a rem failure the primary run never actually hit (or miss one it did). if not exist "dist\%ENVNAME%.exe" exit /b 0 if exist "~exe_hint_rerun.ps1" del "~exe_hint_rerun.ps1" >nul 2>&1 if exist "~exe_out.txt" del "~exe_out.txt" >nul 2>&1 call :emit_from_base64 "~exe_hint_rerun.ps1" HP_EXE_HINT_RERUN if errorlevel 1 ( rem Extremely rare -- disk/permission failure writing a work file; leave ~exe_out.txt absent rem so the findstr checks below simply find nothing and fall through to the generic hint. call :log "[WARN] EXE hint rerun: could not emit ~exe_hint_rerun.ps1; skipping hint capture." ) else ( rem CodeRabbit review, PR #470: absolute path, same reasoning as :run_exe_smokerun's own rem HP_SMOKERUN_EXE assignment above. set "HP_HINT_RERUN_EXE=%CD%\dist\%ENVNAME%.exe" rem derived requirement: set explicitly rather than relying on the helper's own default -- rem an inherited/leaked HP_HINT_RERUN_OUT from elsewhere in the environment would otherwise rem make the helper write somewhere other than what HP_HINT_FILE, below, reads and deletes. set "HP_HINT_RERUN_OUT=~exe_out.txt" powershell -NoProfile -ExecutionPolicy Bypass -File "~exe_hint_rerun.ps1" set "HP_HINT_RERUN_EXE=" set "HP_HINT_RERUN_OUT=" if exist "~exe_hint_rerun.ps1" del "~exe_hint_rerun.ps1" >nul 2>&1 ) set "HP_HINT_FILE=~exe_out.txt" set "HP_HINT_FILE_NAME=" set "HP_HINT_MOD=" findstr /i /c:"FileNotFoundError" /c:"No such file or directory" "%HP_HINT_FILE%" >nul 2>&1 if not errorlevel 1 goto :hint_data_file goto :hint_check_mod :hint_data_file for /f "usebackq delims=" %%F in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$t=[IO.File]::ReadAllText('%HP_HINT_FILE%');$m=[regex]::Match($t,'No such file or directory: ''([^'']+)''');if($m.Success){$m.Groups[1].Value}else{''}"`) do set "HP_HINT_FILE_NAME=%%F" call :log "[HINT][DATA_FILE] Missing data file detected: %HP_HINT_FILE_NAME%" rem derived requirement (CLAUDE.md Active Backlog Item 38): --add-data bundles a file into the rem onefile extraction folder (_MEIxxxxxx under TEMP), never the current working directory -- rem correct advice only when the missing path itself is already under a _MEIxxxxxx folder (the rem code reads it via a bundled-resource-style path). For a bare/CWD-relative path, --add-data rem would NOT fix the failure at all; give honest advice for that far more common beginner case rem instead of a suggestion that cannot work. rem derived requirement (CodeRabbit review, PR #458): a plain findstr substring match on "_MEI" rem false-positives on a coincidental filename (config_MEI.json) or a user-named folder with no rem digits (C:\work\_MEI\data.txt) -- neither is a genuine PyInstaller extraction directory. rem Matched via PowerShell instead, anchored to the real _MEIxxxxxx path-component shape rem (a path separator, then _MEI, then a nonempty suffix, then a path separator) so a bare rem substring never matches; the value is read from the environment at PowerShell runtime, rem never substituted into the -Command text, so no cmd.exe metacharacter risk either. rem derived requirement (real CI failure, PR #458's own mei_genuine test): the suffix is NOT rem always decimal digits -- a genuine capture from this same PR's CI run showed rem _MEI00001a4c2 (hex characters), not just _MEI41642 (decimal) from the earlier reference rem capture. [0-9]+ missed the hex case and wrongly gave the CWD-relative advice for a rem genuine extraction path. Widened to [^\\/]+ (any nonempty non-separator run) -- the rem structural bounding (a real path separator immediately before AND after) is what rem actually distinguishes a genuine extraction-directory component from a coincidental rem substring or a no-suffix folder name, not the exact character class of the suffix. powershell -NoProfile -ExecutionPolicy Bypass -Command "exit [int](-not ($env:HP_HINT_FILE_NAME -match '(?i)[\\/]_MEI[^\\/]+[\\/]'))" if errorlevel 1 ( call :log "[HINT][DATA_FILE] --add-data will not fix this: it bundles into a temp extraction folder, not the current directory. Place a copy of '%HP_HINT_FILE_NAME%' next to the built .exe instead, or read the file via a path relative to your own script file, not the working directory." ) else ( call :log "[HINT][DATA_FILE] Consider adding: --add-data %HP_HINT_FILE_NAME%;." ) if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='DATA_FILE';file=$env:HP_HINT_FILE_NAME}|ConvertTo-Json -Compress)" :hint_check_mod findstr /i /c:"ModuleNotFoundError" /c:"No module named" "%HP_HINT_FILE%" >nul 2>&1 if not errorlevel 1 goto :hint_hidden_import goto :hint_packaging :hint_hidden_import for /f "usebackq delims=" %%M in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$t=[IO.File]::ReadAllText('%HP_HINT_FILE%');$m=[regex]::Match($t,'No module named ''([^'']+)''');if($m.Success){$m.Groups[1].Value}else{''}"`) do set "HP_HINT_MOD=%%M" call :log "[HINT][HIDDEN_IMPORT] Hidden import likely missing: %HP_HINT_MOD%" call :log "[HINT][HIDDEN_IMPORT] Consider adding: --hidden-import=%HP_HINT_MOD%" if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='HIDDEN_IMPORT';module=$env:HP_HINT_MOD}|ConvertTo-Json -Compress)" :hint_packaging call :log "[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies)" if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='RUNTIME_MISMATCH'}|ConvertTo-Json -Compress)" if exist "%HP_HINT_FILE%" del "%HP_HINT_FILE%" >nul 2>&1 set "HP_HINT_FILE=" set "HP_HINT_FILE_NAME=" set "HP_HINT_MOD=" exit /b 0 :write_pipreqs_summary if "%HP_JOB_SUMMARY%"=="" exit /b 0 set "HP_SUMMARY_PATH=%HP_JOB_SUMMARY%" if not defined HP_PIPREQS_SUMMARY_PHASE set "HP_PIPREQS_SUMMARY_PHASE=" > "%HP_SUMMARY_PATH%" echo Interpreter: %HP_PY% >> "%HP_SUMMARY_PATH%" echo Pipreqs command: pipreqs . --force --mode compat --savepath "%HP_PIPREQS_SUMMARY_CMD_PATH%"%HP_PIPREQS_SUMMARY_IGNORE% if defined HP_PIPREQS_SUMMARY_NOTE ( >> "%HP_SUMMARY_PATH%" echo Phase: %HP_PIPREQS_SUMMARY_PHASE% %HP_PIPREQS_SUMMARY_NOTE% ) else ( >> "%HP_SUMMARY_PATH%" echo Phase: %HP_PIPREQS_SUMMARY_PHASE% ) if defined HP_PIPREQS_FAILURE_LOG ( powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$summary = '%HP_SUMMARY_PATH%'; $logPath = '%HP_PIPREQS_FAILURE_LOG%';" ^ "Add-Content -Path $summary -Value '---' -Encoding ASCII;" ^ "Add-Content -Path $summary -Value 'Pipreqs log tail:' -Encoding ASCII;" ^ "if (Test-Path $logPath) {" ^ " Get-Content -LiteralPath $logPath -Tail 20 | Out-File -FilePath $summary -Encoding ASCII -Append;" ^ "} else {" ^ " Add-Content -Path $summary -Value '' -Encoding ASCII;" ^ "}" >> "%LOG%" 2>&1 ) exit /b 0 :write_status set "HP_STATE=%~1" set "HP_EXIT=%~2" set "HP_PYFILES=%~3" if "%HP_STATE%"=="" set "HP_STATE=error" if "%HP_EXIT%"=="" set "HP_EXIT=0" if "%HP_PYFILES%"=="" set "HP_PYFILES=%PYCOUNT%" echo {"state":"%HP_STATE%","exitCode":%HP_EXIT%,"pyFiles":%HP_PYFILES%}> "%STATUS_FILE%" if exist "%STATUS_FILE%" ( if exist "~bootstrap.status.txt" del "~bootstrap.status.txt" >nul 2>&1 ) else if /i "%HP_STATE%"=="no_python_files" ( > "~bootstrap.status.txt" echo [INFO] Python file count: %HP_PYFILES% >> "~bootstrap.status.txt" echo [INFO] No Python files detected; skipping environment bootstrap. ) set "HP_STATE=" set "HP_EXIT=" set "HP_PYFILES=" exit /b 0 :emit_from_base64 rem Decode helper payloads with PowerShell Convert.FromBase64String (see https://learn.microsoft.com/dotnet/api/system.convert.frombase64string). rem Keep these helpers in sync with README.md regeneration notes. set "DST=%~1" set "VAR=%~2" if not defined DST exit /b 1 if not defined VAR exit /b 1 rem derived requirement: CLAUDE.md Item 46 Bucket A Batch regression coverage -- deterministically rem force a SPECIFIC payload's own write to fail (simulating the disk-write/AV-lock class of rem failure every real call site of this subroutine can hit) without touching any other payload rem in the same run. Absence changes nothing (REQ-019): only fires when HP_TEST_FORCE_EMIT_FAIL rem exactly matches this call's own %VAR%. if defined HP_TEST_FORCE_EMIT_FAIL if /i "%HP_TEST_FORCE_EMIT_FAIL%"=="%VAR%" ( call :log "[TEST] HP_TEST_FORCE_EMIT_FAIL=%VAR%: simulating embedded helper write failure." exit /b 1 ) powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "$varName = '%VAR%'; $envItem = Get-Item Env:$varName -ErrorAction SilentlyContinue; if (-not $envItem) { exit 1 }; $base64 = $envItem.Value; if (-not $base64) { exit 1 }; $outFile = Join-Path (Get-Location) '%DST%'; $bytes = [Convert]::FromBase64String($base64); [IO.File]::WriteAllBytes($outFile, $bytes)" >> "%LOG%" 2>&1 exit /b %errorlevel% :define_helper_payloads rem Helper payloads are base64-encoded so run_setup.bat stays self-contained. rem Regenerate with python - <<'PY' snippets as noted in README.md (base64 docs: https://docs.python.org/3/library/base64.html). set "HP_PYPROJ_DEPS=IiIicHlwcm9qX2RlcHMgKEhQX1BZUFJPSl9ERVBTKSAtLSBleHRyYWN0cyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGZyb20KcHlwcm9qZWN0LnRvbWwsIHdyaXRpbmcgb25lIGRlcGVuZGVuY3kgcGVyIGxpbmUgdG8gYW4gb3V0cHV0IGZpbGUuCgpSdW4gZnJvbSB0aGUgYXBwbGljYXRpb24gZGlyZWN0b3J5IChyZWFkcyAicHlwcm9qZWN0LnRvbWwiIGZyb20gQ1dEKS4KVXNhZ2U6IHB5dGhvbiBweXByb2pfZGVwcy5weSBbb3V0cHV0X3BhdGhdICAoZGVmYXVsdCAifnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0IikKCkV4aXQgY29kZXM6CiAgMCAtIHN1Y2Nlc3MsIGRlcGVuZGVuY2llcyB3cml0dGVuIHRvIHRoZSBvdXRwdXQgZmlsZQogIDEgLSBub3QtZm91bmQvZXJyb3IgKG5vIHB5cHJvamVjdC50b21sLCBubyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGtleSwKICAgICAgb3IgYW4gZW1wdHkgZGVwZW5kZW5jaWVzIGxpc3QpCiAgMiAtIG1hbGZvcm1lZCBUT01MICh0b21sbGliIHJhaXNlZCwgb3IgLS0gd2hlbiB0b21sbGliIGlzIHVuYXZhaWxhYmxlIC0tCiAgICAgIHRoZSByZWdleCBmYWxsYmFjayBmb3VuZCBhbiB1bmNsb3NlZCAiW3Byb2plY3QiIGhlYWRlcikKICAzIC0gdW5leHBlY3RlZCBpbnRlcm5hbCBlcnJvciAoZS5nLiBweXByb2plY3QudG9tbCBleGlzdHMgYnV0IGNhbm5vdCBiZQogICAgICByZWFkIGFzIGEgZmlsZSAtLSBhIGRpcmVjdG9yeSwgYSBwZXJtaXNzaW9uIGZhaWx1cmUsIGV0Yy4pLiBEaXN0aW5jdAogICAgICBmcm9tIDEgc28gYSBnZW51aW5lIGJ1ZyBvciB1bnVzdWFsIEkvTyBmYWlsdXJlIG5ldmVyIG1hc3F1ZXJhZGVzIGFzCiAgICAgIHRoZSBiZW5pZ24gIm5vdGhpbmcgdG8gZG8gaGVyZSIgY2FzZSAtLSBzZWUgdGhlIG91dGVyIGV4Y2VwdCBiZWxvdy4KClByZWZlcnMgc3RkbGliIHRvbWxsaWIgKDMuMTErKSB3aGVuIGF2YWlsYWJsZS4gRmFsbHMgYmFjayB0byBhIHJlZ2V4LWJhc2VkCmV4dHJhY3RvciB3aGVuIHRvbWxsaWIgaXMgbWlzc2luZyBPUiB0aGUgW3Byb2plY3RdIHRhYmxlIGhhcyBubwoiZGVwZW5kZW5jaWVzIiBrZXkgdmlhIHRvbWxsaWIgKHRoZSB0d28gY2FzZXMgYXJlIGluZGlzdGluZ3Vpc2hhYmxlIGZyb20KdG9tbGxpYidzIG93biByZXR1cm4gdmFsdWUsIHNvIHRoZSBmYWxsYmFjayBhbHdheXMgcmUtc2NhbnMgaW4gdGhhdCBjYXNlIC0tCmhhcm1sZXNzLCBzaW5jZSBhIGdlbnVpbmVseS1hYnNlbnQga2V5IGFsc28gZmluZHMgbm90aGluZyB2aWEgcmVnZXgpLiBUaGUKZmFsbGJhY2sncyBkZXBlbmRlbmN5LWFycmF5IHdhbGsgaXMgY2hhci1ieS1jaGFyIG92ZXIgcXVvdGVkIHN0cmluZ3MgKG5vdCBhCm5haXZlIGNvbW1hL25ld2xpbmUgc3BsaXQpIHNvIGl0IHByZXNlcnZlcyBleHRyYXMgKCJwa2dbYWxsXSIpIGFuZAptdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKCJwa2c+PTQsPDUiKSBpbnRhY3QsIGFuZCBza2lwcyAiIyItdG8tZW5kLW9mLQpsaW5lIGNvbW1lbnRzIChvdXRzaWRlIG9mIHF1b3Rlcykgc28gYSBjb21tZW50IG1lbnRpb25pbmcgYnJhY2tldCBzeW50YXgKY2Fubm90IGJlIG1pc3Rha2VuIGZvciB0aGUgYXJyYXkncyBvd24gY2xvc2luZyAiXSIuCgpUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfUFlQUk9KX0RFUFMgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQKaW4gcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZQpgc2V0ICJIUF9QWVBST0pfREVQUz0uLi4iYCBsaW5lOyB0ZXN0cy90ZXN0X3B5cHJval9kZXBzLnB5IGFzc2VydHMgdGhlCmVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUuCiIiIgppbXBvcnQgc3lzLCBwYXRobGliCgp0cnk6CiAgICBpbXBvcnQgdG9tbGxpYgpleGNlcHQgSW1wb3J0RXJyb3I6CiAgICB0b21sbGliID0gTm9uZQoKb3V0ID0gc3lzLmFyZ3ZbMV0gaWYgbGVuKHN5cy5hcmd2KSA+IDEgZWxzZSAnfnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0Jwp0cnk6CiAgICBzcmMgPSBwYXRobGliLlBhdGgoJ3B5cHJvamVjdC50b21sJykKICAgIHRyeToKICAgICAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IGEgbWlzc2luZyBweXByb2plY3QudG9tbCBpcyB0aGUgZGVsaWJlcmF0ZSwKICAgICAgICAjIGRvY3VtZW50ZWQgZXhpdC0xICJub3QgZm91bmQiIGNhc2UgKGluIHByYWN0aWNlIHJ1bl9zZXR1cC5iYXQgb25seQogICAgICAgICMgZXZlciBpbnZva2VzIHRoaXMgc2NyaXB0IHdoZW4gdGhlIGZpbGUgZXhpc3RzLCBidXQgdGhpcyBzY3JpcHQncwogICAgICAgICMgb3duIGNvbnRyYWN0IHByZWRhdGVzIGFuZCBkb2VzIG5vdCBhc3N1bWUgdGhhdCBjYWxsZXIpLiBDYXVnaHQKICAgICAgICAjIHNwZWNpZmljYWxseSBoZXJlLCBhcm91bmQgdGhlIHJlYWQgaXRzZWxmLCByYXRoZXIgdGhhbiB2aWEgYQogICAgICAgICMgcHJlY2VkaW5nIFBhdGguZXhpc3RzKCkgY2hlY2sgLS0gUHl0aG9uIDMuMTQrIG1hZGUgZXhpc3RzKCkgc3dhbGxvdwogICAgICAgICMgQU5ZIE9TRXJyb3IgKGluY2x1ZGluZyBQZXJtaXNzaW9uRXJyb3IpIGFuZCByZXR1cm4gRmFsc2UgaW5zdGVhZCBvZgogICAgICAgICMgcmFpc2luZywgd2hpY2ggd291bGQgc2lsZW50bHkgbWlzY2xhc3NpZnkgYSBnZW51aW5lIHBlcm1pc3Npb24KICAgICAgICAjIGZhaWx1cmUgYXMgIm5vdCBmb3VuZCIgKDEpIGluc3RlYWQgb2YgdGhlIHJlYWwtZXJyb3IgY2FzZSAoMykKICAgICAgICAjIGJlbG93LiBBbnkgT1RIRVIgZXhjZXB0aW9uIGhlcmUgKGEgZGlyZWN0b3J5LCBwZXJtaXNzaW9uIGRlbmllZCwKICAgICAgICAjIGV0Yy4pIGlzIGEgZ2VudWluZSBidWcvZmFpbHVyZSBhbmQgZmFsbHMgdGhyb3VnaCB0byB0aGUgb3V0ZXIKICAgICAgICAjIGV4Y2VwdCwgdW5jYXVnaHQgYnkgdGhpcyBuYXJyb3dlciBvbmUuCiAgICAgICAgdHh0ID0gc3JjLnJlYWRfdGV4dChlbmNvZGluZz0ndXRmLTgnLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgZXhjZXB0IEZpbGVOb3RGb3VuZEVycm9yOgogICAgICAgIHN5cy5leGl0KDEpCiAgICBkZXBzID0gTm9uZQogICAgaWYgdG9tbGxpYjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGEgPSB0b21sbGliLmxvYWRzKHR4dCkKICAgICAgICAgICAgZGVwcyA9IGRhdGEuZ2V0KCdwcm9qZWN0Jywge30pLmdldCgnZGVwZW5kZW5jaWVzJykKICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAjIEV4aXQgMiBzaWduYWxzIGNhbGxlciB0byBlbWl0IFtXQVJOXTogcHlwcm9qZWN0LnRvbWwgaXMgbm90IHZhbGlkIFRPTUwuCiAgICAgICAgICAgIHN5cy5leGl0KDIpCiAgICBpZiBkZXBzIGlzIE5vbmU6CiAgICAgICAgaW1wb3J0IHJlCiAgICAgICAgbSA9IHJlLnNlYXJjaChyJ15cW3Byb2plY3RcXScsIHR4dCwgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBtOgogICAgICAgICAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHdpdGhvdXQgdG9tbGxpYiwgZGV0ZWN0IG9idmlvdXNseSBtYWxmb3JtZWQgW3Byb2plY3QgaGVhZGVyCiAgICAgICAgICAgICMgKG1pc3NpbmcgY2xvc2luZyBicmFja2V0IC0tIGUuZy4gIltwcm9qZWN0XG4iKS4gRXhpdCAyIHNvIGNhbGxlciBlbWl0cyBUT01MIHBhcnNlIHdhcm5pbmcuCiAgICAgICAgICAgIGlmIHJlLnNlYXJjaChyJ15cW3Byb2plY3RccyokJywgdHh0LCByZS5NVUxUSUxJTkUpOgogICAgICAgICAgICAgICAgc3lzLmV4aXQoMikKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICBzZWMgPSB0eHRbbS5lbmQoKTpdCiAgICAgICAgc3RvcCA9IHJlLnNlYXJjaChyJ15cWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIHN0b3A6CiAgICAgICAgICAgIHNlYyA9IHNlY1s6c3RvcC5zdGFydCgpXQogICAgICAgIGRtID0gcmUuc2VhcmNoKHInXlxzKmRlcGVuZGVuY2llc1xzKj1ccypcWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBkbToKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICByZXN0ID0gc2VjW2RtLmVuZCgpOl0KICAgICAgICAjIFdhbGsgY2hhci1ieS1jaGFyOiBjb2xsZWN0IG9ubHkgcXVvdGVkIHN0cmluZ3M7IHN0b3AgYXQgdW5xdW90ZWQgXQogICAgICAgICMgVGhpcyBwcmVzZXJ2ZXMgZnVsbCBkZXAgc3RyaW5ncyBpbmNsdWRpbmcgZXh0cmFzIChbYWxsXSkgYW5kCiAgICAgICAgIyBtdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKD49NCw8NSkgd2l0aG91dCBuYWl2ZSBjb21tYS9uZXdsaW5lIHNwbGl0cy4KICAgICAgICBkZXBzID0gW10KICAgICAgICBpID0gMAogICAgICAgIHdoaWxlIGkgPCBsZW4ocmVzdCk6CiAgICAgICAgICAgIGMgPSByZXN0W2ldCiAgICAgICAgICAgIGlmIGMgaW4gKCciJywgIiciKToKICAgICAgICAgICAgICAgIHEgPSBjCiAgICAgICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgICAgIHN0YXJ0ID0gaQogICAgICAgICAgICAgICAgd2hpbGUgaSA8IGxlbihyZXN0KSBhbmQgcmVzdFtpXSAhPSBxOgogICAgICAgICAgICAgICAgICAgIGlmIHJlc3RbaV0gPT0gJ1xcJzoKICAgICAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICBkZXBzLmFwcGVuZChyZXN0W3N0YXJ0OmldKQogICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnIyc6CiAgICAgICAgICAgICAgICAjIFRPTUwgY29tbWVudHMgcnVuIGZyb20gIyB0byBlbmQgb2YgbGluZSBhbmQgY2Fubm90IGFwcGVhcgogICAgICAgICAgICAgICAgIyBpbnNpZGUgYSBzdHJpbmcgKHRoZSBicmFuY2ggYWJvdmUgYWxyZWFkeSBjb25zdW1lZCBhbnkgIwogICAgICAgICAgICAgICAgIyB0aGF0IHdhcyBxdW90ZWQpLiBXaXRob3V0IHRoaXMsIGEgY29tbWVudCByZWZlcmVuY2luZyBhcnJheQogICAgICAgICAgICAgICAgIyBzeW50YXggLS0gZS5nLiAnInJlcXVlc3RzIiwgICMgc3VwcG9ydHMgWzEsMl0gc3ludGF4JyAtLQogICAgICAgICAgICAgICAgIyB3b3VsZCBoYXZlIGl0cyB1bnF1b3RlZCAiXSIgd3JvbmdseSB0cmVhdGVkIGFzIHRoZSBlbmQgb2YKICAgICAgICAgICAgICAgICMgdGhlIGRlcGVuZGVuY2llcyBhcnJheSwgc2lsZW50bHkgZHJvcHBpbmcgZXZlcnkgZGVwZW5kZW5jeQogICAgICAgICAgICAgICAgIyBsaXN0ZWQgYWZ0ZXIgdGhhdCBsaW5lLgogICAgICAgICAgICAgICAgbmwgPSByZXN0LmZpbmQoJ1xuJywgaSkKICAgICAgICAgICAgICAgIGlmIG5sID09IC0xOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBpID0gbmwgKyAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnXSc6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaSArPSAxCiAgICBpZiBub3QgZGVwczoKICAgICAgICBzeXMuZXhpdCgxKQogICAgcGF0aGxpYi5QYXRoKG91dCkud3JpdGVfdGV4dCgnXG4nLmpvaW4oZGVwcykgKyAnXG4nLCBlbmNvZGluZz0nYXNjaWknLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgc3lzLmV4aXQoMCkKZXhjZXB0IEV4Y2VwdGlvbjoKICAgICMgZGVyaXZlZCByZXF1aXJlbWVudDogYSBnZW51aW5lbHkgdW5leHBlY3RlZCBleGNlcHRpb24gKG5vdCBvbmUgb2YgdGhlCiAgICAjIGRlbGliZXJhdGUgc3lzLmV4aXQoMSkvc3lzLmV4aXQoMikgY2FsbHMgYWJvdmUgLS0gdGhvc2UgcmFpc2UKICAgICMgU3lzdGVtRXhpdCwgd2hpY2ggdGhpcyBFeGNlcHRpb24tb25seSBoYW5kbGVyIGRvZXMgbm90IGNhdGNoKSBtdXN0IG5vdAogICAgIyBleGl0IDEsIG9yIHJ1bl9zZXR1cC5iYXQncyBjYWxsZXIgY2Fubm90IHRlbGwgYSByZWFsIGJ1ZyBhcGFydCBmcm9tIHRoZQogICAgIyBpbnRlbnRpb25hbCAibm8gZGVwZW5kZW5jaWVzIGZvdW5kIiBjYXNlLiBTZWUgZXhpdCBjb2RlIDMgYWJvdmUuCiAgICBzeXMuZXhpdCgzKQo=" set "HP_CONDARC=Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpjaGFubmVsX3ByaW9yaXR5OiBzdHJpY3QKc2hvd19jaGFubmVsX3VybHM6IHRydWUK" set "HP_DETECT_PY=IiIiZGV0ZWN0X3B5dGhvbiAoSFBfREVURUNUX1BZKSAtLSBSRVEtMDA0IFRpZXIgMS8yIHJlcXVpcmVzLXB5dGhvbiBkZXRlY3Rvci4KClJ1biBmcm9tIHRoZSBhcHBsaWNhdGlvbiBkaXJlY3Rvcnk7IHByaW50cyBhIG5vcm1hbGl6ZWQgY29uZGEtc3ludGF4CiJweXRob248b3A+PHZlcj5bLC4uLl0iIGNvbnN0cmFpbnQgdG8gc3Rkb3V0LCBvciBhbiBlbXB0eSBsaW5lIGlmIG5laXRoZXIKdGllciBmb3VuZCBhbnl0aGluZyAodGhlIGNhbGxlciB0aGVuIGxldHMgdGhlIHNlbGVjdGVkIHByb3ZpZGVyIHBpY2sgdGhlCmxhdGVzdCBhdmFpbGFibGUgUHl0aG9uIC0tIFJFUS0wMDQgVGllciAzLCBoYW5kbGVkIG91dHNpZGUgdGhpcyBoZWxwZXIpLgpEZXRlY3Rpb24gb3JkZXI6CgogIDEuIHJ1bnRpbWUudHh0IC0tIGEgYmFyZSAicHl0aG9uLVguWVsuWl0iIG9yICJYLllbLlpdIiBsaW5lIHBpbnMgYW4gZXhhY3QKICAgICBtaW5vciB2ZXJzaW9uIChweXRob249WC5ZOyB0aGUgcGF0Y2ggY29tcG9uZW50LCBpZiBwcmVzZW50LCBpcyBub3QKICAgICBmb3J3YXJkZWQgLS0gcHJvdmlkZXJzIHBpbiBieSBtaW5vciBvbmx5KS4KICAyLiBweXByb2plY3QudG9tbCdzIFtwcm9qZWN0XSByZXF1aXJlcy1weXRob24gLS0gYSBQRVAgNDQwIHNwZWNpZmllcgogICAgIChlLmcuICI+PTMuMTAsPDQiLCAifj0zLjExIikgaXMgcGFyc2VkIGFuZCBleHBhbmRlZCBpbnRvIG9uZSBvciBtb3JlCiAgICAgY29uZGEtc3ludGF4IGNsYXVzZXMgdmlhIHBlcDQ0MF90b19jb25kYSgpLgoKcGVwNDQwX3RvX2NvbmRhKCkgYWxzbyBkb3VibGVzIGFzIGEgc3RhbmRhbG9uZSBDTEkgdXRpbGl0eSAoc2VlIG1haW4oKSkgZm9yCnRyYW5zbGF0aW5nIGFyYml0cmFyeSBQRVAgNDQwIHNwZWNpZmllcnMsIGluY2x1ZGluZyB0aGUgIn49IiBjb21wYXRpYmxlLXJlbGVhc2UKb3BlcmF0b3IsIHdoaWNoIGNvbmRhIGhhcyBubyBuYXRpdmUgZXF1aXZhbGVudCBmb3IgYW5kIG11c3QgYmUgZXhwYW5kZWQgaW50bwphbiBleHBsaWNpdCA+PS88IHJhbmdlLgoKVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0RFVEVDVF9QWSBiYXNlNjQgcGF5bG9hZCBlbWJlZGRlZCBpbgpydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlCmBzZXQgIkhQX0RFVEVDVF9QWT0uLi4iYCBsaW5lOyB0ZXN0cy90ZXN0X2RldGVjdF9weXRob24ucHkgYXNzZXJ0cyB0aGUKZW1iZWRkZWQgcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZS4KIiIiCl9fdmVyc2lvbl9fID0gImRldGVjdF9weXRob24gdjIgKDIwMjUtMDktMjQpIgpfX2FsbF9fID0gWyJwZXA0NDBfdG9fY29uZGEiLCAiZGV0ZWN0X3JlcXVpcmVzX3B5dGhvbiIsICJtYWluIl0KT1JERVIgPSB7Ij09IjogMCwgIiE9IjogMSwgIj49IjogMiwgIj4iOiAzLCAiPD0iOiA0LCAiPCI6IDV9CgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCBzeXMKCiMgSGVscGVyIGltcGxlbWVudHMgdGhlIFJFQURNRSBib290c3RyYXAgY29udHJhY3QuIFBFUCA0NDAgZGV0YWlsczoKIyBodHRwczovL3BlcHMucHl0aG9uLm9yZy9wZXAtMDQ0MC8KCkNEID0gb3MuZ2V0Y3dkKCkKUlVOVElNRV9QQVRIID0gb3MucGF0aC5qb2luKENELCAicnVudGltZS50eHQiKQpQWVBST0pFQ1RfUEFUSCA9IG9zLnBhdGguam9pbihDRCwgInB5cHJvamVjdC50b21sIikKUFlQUk9KRUNUX1JFID0gcmUuY29tcGlsZSgicmVxdWlyZXMtcHl0aG9uXFxzKj1cXHMqWydcIl0oW14nXCJdKylbJ1wiXSIsIHJlLklHTk9SRUNBU0UpClNQRUNfUEFUVEVSTiA9IHJlLmNvbXBpbGUocicofj18PT18IT18Pj18PD18Pnw8KVxzKihbMC05XSsoPzpcLlswLTldKykqKScpCgoKZGVmIHZlcnNpb25fa2V5KHRleHQ6IHN0cik6CiAgICAiIiJSZXR1cm4gYSB0dXBsZSB1c2FibGUgZm9yIG51bWVyaWMgb3JkZXJpbmcgb2YgZG90dGVkIHZlcnNpb25zLiIiIgogICAgcGFydHMgPSBbXQogICAgZm9yIGNodW5rIGluIHRleHQuc3BsaXQoJy4nKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHBhcnRzLmFwcGVuZChpbnQoY2h1bmspKQogICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOgogICAgICAgICAgICBwYXJ0cy5hcHBlbmQoMCkKICAgIHJldHVybiB0dXBsZShwYXJ0cykKCgpkZWYgYnVtcF9mb3JfY29tcGF0aWJsZSh2ZXJzaW9uOiBzdHIpIC0+IHN0cjoKICAgICIiIlRyYW5zbGF0ZSB0aGUgUEVQIDQ0MCBjb21wYXRpYmxlIHJlbGVhc2UgdXBwZXIgYm91bmQuIiIiCiAgICBwaWVjZXMgPSBbaW50KGl0ZW0pIGZvciBpdGVtIGluIHZlcnNpb24uc3BsaXQoJy4nKSBpZiBpdGVtLmlzZGlnaXQoKV0KICAgIGlmIG5vdCBwaWVjZXM6CiAgICAgICAgcmV0dXJuIHZlcnNpb24KICAgIGlmIGxlbihwaWVjZXMpID49IDM6CiAgICAgICAgcmV0dXJuIGYie3BpZWNlc1swXX0ue3BpZWNlc1sxXSArIDF9IgogICAgaWYgbGVuKHBpZWNlcykgPT0gMjoKICAgICAgICByZXR1cm4gZiJ7cGllY2VzWzBdICsgMX0uMCIKICAgIHJldHVybiBzdHIocGllY2VzWzBdICsgMSkKCgpkZWYgZXhwYW5kX2NsYXVzZShvcDogc3RyLCB2ZXJzaW9uOiBzdHIpOgogICAgaWYgb3AgPT0gIn49IjoKICAgICAgICB1cHBlciA9IGJ1bXBfZm9yX2NvbXBhdGlibGUodmVyc2lvbikKICAgICAgICByZXR1cm4gWygiPj0iLCB2ZXJzaW9uKSwgKCI8IiwgdXBwZXIpXQogICAgcmV0dXJuIFsob3AsIHZlcnNpb24pXQoKCmRlZiBwZXA0NDBfdG9fY29uZGEoc3BlYzogc3RyKSAtPiBzdHI6CiAgICAiIiJSZXR1cm4gInB5dGhvbiIgY29uc3RyYWludHMgZXhwYW5kZWQgZnJvbSBhIHJlcXVpcmVzLXB5dGhvbiBzcGVjLiIiIgogICAgY2xhdXNlcyA9IFtdCiAgICBmb3IgcmF3IGluIHNwZWMuc3BsaXQoJywnKToKICAgICAgICByYXcgPSByYXcuc3RyaXAoKQogICAgICAgIGlmIG5vdCByYXc6CiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgbWF0Y2ggPSBTUEVDX1BBVFRFUk4ubWF0Y2gocmF3KQogICAgICAgIGlmIG5vdCBtYXRjaDoKICAgICAgICAgICAgY29udGludWUKICAgICAgICBvcCwgdmVyc2lvbiA9IG1hdGNoLmdyb3VwcygpCiAgICAgICAgY2xhdXNlcy5leHRlbmQoZXhwYW5kX2NsYXVzZShvcCwgdmVyc2lvbikpCiAgICBpZiBub3QgY2xhdXNlczoKICAgICAgICByZXR1cm4gIiIKICAgIGRlZHVwID0ge30KICAgIGZvciBvcCwgdmVyc2lvbiBpbiBjbGF1c2VzOgogICAgICAgIGRlZHVwWyhvcCwgdmVyc2lvbildID0gKG9wLCB2ZXJzaW9uKQogICAgb3JkZXJlZCA9IHNvcnRlZChkZWR1cC52YWx1ZXMoKSwga2V5PWxhbWJkYSBpdGVtOiAoT1JERVIuZ2V0KGl0ZW1bMF0sIDk5KSwgdmVyc2lvbl9rZXkoaXRlbVsxXSkpKQogICAgcmV0dXJuICJweXRob24iICsgIiwiLmpvaW4oZiJ7b3B9e3ZlcnNpb259IiBmb3Igb3AsIHZlcnNpb24gaW4gb3JkZXJlZCkKCgpkZWYgcmVhZF9ydW50aW1lX3NwZWMoKSAtPiBzdHI6CiAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoUlVOVElNRV9QQVRIKToKICAgICAgICByZXR1cm4gIiIKICAgIHdpdGggb3BlbihSVU5USU1FX1BBVEgsICdyJywgZW5jb2Rpbmc9J3V0Zi04JywgZXJyb3JzPSdpZ25vcmUnKSBhcyBoYW5kbGU6CiAgICAgICAgdGV4dCA9IGhhbmRsZS5yZWFkKCkKICAgIG1hdGNoID0gcmUuc2VhcmNoKHInKD86cHl0aG9uWy09XSk/XHMqKFswLTldKyg/OlwuWzAtOV0rKXswLDJ9KScsIHRleHQpCiAgICBpZiBub3QgbWF0Y2g6CiAgICAgICAgcmV0dXJuICIiCiAgICBwYXJ0cyA9IG1hdGNoLmdyb3VwKDEpLnNwbGl0KCcuJykKICAgIG1ham9yX21pbm9yID0gJy4nLmpvaW4ocGFydHNbOjJdKQogICAgcmV0dXJuIGYncHl0aG9uPXttYWpvcl9taW5vcn0nCgoKZGVmIHJlYWRfcHlwcm9qZWN0X3NwZWMoKSAtPiBzdHI6CiAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoUFlQUk9KRUNUX1BBVEgpOgogICAgICAgIHJldHVybiAiIgogICAgd2l0aCBvcGVuKFBZUFJPSkVDVF9QQVRILCAncicsIGVuY29kaW5nPSd1dGYtOCcsIGVycm9ycz0naWdub3JlJykgYXMgaGFuZGxlOgogICAgICAgIHRleHQgPSBoYW5kbGUucmVhZCgpCiAgICBtYXRjaCA9IFBZUFJPSkVDVF9SRS5zZWFyY2godGV4dCkKICAgIGlmIG5vdCBtYXRjaDoKICAgICAgICByZXR1cm4gIiIKICAgIHJldHVybiBwZXA0NDBfdG9fY29uZGEobWF0Y2guZ3JvdXAoMSkpCgoKZGVmIGRldGVjdF9yZXF1aXJlc19weXRob24oKSAtPiBzdHI6CiAgICAiIiJSZXR1cm4gYmVzdC1lZmZvcnQgcmVxdWlyZXMtcHl0aG9uIGNvbnN0cmFpbnQgZm9yIHRoZSBjdXJyZW50IHByb2plY3QuIiIiCiAgICBydW50aW1lX3NwZWMgPSByZWFkX3J1bnRpbWVfc3BlYygpCiAgICBpZiBydW50aW1lX3NwZWM6CiAgICAgICAgcmV0dXJuIHJ1bnRpbWVfc3BlYwogICAgcmV0dXJuIHJlYWRfcHlwcm9qZWN0X3NwZWMoKQoKCmRlZiBtYWluKGFyZ3Y9Tm9uZSkgLT4gTm9uZToKICAgICIiIkNMSSBlbnRyeSBwb2ludCB0aGF0IHByaW50cyBub3JtYWxpemVkIHJlcXVpcmVzLXB5dGhvbiBjb25zdHJhaW50cy4iIiIKICAgIGFyZ3MgPSBsaXN0KHN5cy5hcmd2WzE6XSBpZiBhcmd2IGlzIE5vbmUgZWxzZSBhcmd2KQogICAgaWYgYXJncyBhbmQgYXJnc1swXSA9PSAiLS1zZWxmLXRlc3QiOgogICAgICAgIGZvciBzYW1wbGUgaW4gKCJ+PTMuMTAiLCAifj0zLjguMSIpOgogICAgICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKHBlcDQ0MF90b19jb25kYShzYW1wbGUpICsgIlxuIikKCiAgICAgICAgcmV0dXJuCiAgICBpZiBhcmdzOgogICAgICAgIGZvciBpdGVtIGluIGFyZ3M6CiAgICAgICAgICAgIHN5cy5zdGRvdXQud3JpdGUocGVwNDQwX3RvX2NvbmRhKGl0ZW0pICsgIlxuIikKCiAgICAgICAgcmV0dXJuCiAgICBzeXMuc3Rkb3V0LndyaXRlKGRldGVjdF9yZXF1aXJlc19weXRob24oKSArICJcbiIpCgoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK" set "HP_PRINT_PYVER=aW1wb3J0IHN5cwoKcHJpbnQoZiJweXRob24te3N5cy52ZXJzaW9uX2luZm9bMF19LntzeXMudmVyc2lvbl9pbmZvWzFdfS57c3lzLnZlcnNpb25faW5mb1syXX0iKQo=" rem HP_FAST_CHECK decoded content (CLAUDE.md Active Backlog Item 39): content-hash freshness rem check over the same *.py file set (unchanged filter), extended to requirements.txt/ rem pyproject.toml/runtime.txt. Two modes via the second positional arg: 'check' (default, rem called from :try_fast_exe) prints 'fresh' iff a stored ~fast_check.hash.txt exists and rem matches the current source hash; 'write' (called from :success) unconditionally rem (re)writes it. See tools/fast_check.ps1 (the canonical source) for the full script and rem docs/agent-lessons-learned.md's "Prefer raw .NET types over Utility-module cmdlets" rem entry for why this uses [System.Security.Cryptography.SHA256] directly, not Get-FileHash. rem If HP_FAST_CHECK changes, update this decoded comment block to match the base64 payload. set "HP_FAST_CHECK=IyBBU0NJSSBvbmx5LiBDTEFVREUubWQgQWN0aXZlIEJhY2tsb2cgSXRlbSAzOTogdGhlIEVYRSBmYXN0IHBhdGgncyBmcmVzaG5lc3MgY2hlY2sgd2FzCiMgbXRpbWUtb25seSBvdmVyICoucHkgZmlsZXMsIHNvIChhKSBhIHRpbWVzdGFtcC1wcmVzZXJ2aW5nIGRlbGl2ZXJ5IG1ldGhvZCAoYSBaSVAsIHhjb3B5LAojIHJvYm9jb3B5KSBjb3VsZCBjYXJyeSBhIGdlbnVpbmVseSBjaGFuZ2VkIGZpbGUgd2hvc2UgbXRpbWUgc3RpbGwgcHJlZGF0ZXMgdGhlIGJ1aWx0IEVYRSwKIyBzaWxlbnRseSByZXVzaW5nIHN0YWxlIGxvZ2ljIHdpdGggbm8gc2lnbmFsIHRvIHRoZSB1c2VyLCBhbmQgKGIpIGEgcmVxdWlyZW1lbnRzLnR4dC8KIyBweXByb2plY3QudG9tbC9ydW50aW1lLnR4dCBjaGFuZ2Ugd2FzIGludmlzaWJsZSB0byB0aGUgc2NhbiBlbnRpcmVseSwgbXRpbWUgb3Igbm90LiBGaXhlcwojIGJvdGg6IGZyZXNobmVzcyBpcyBub3cgYSBjb250ZW50LWhhc2ggY29tcGFyaXNvbiAobmV2ZXIgZm9vbGVkIGJ5IGEgcHJlc2VydmVkL2JhY2tkYXRlZAojIG10aW1lKSBvdmVyIHRoZSBTQU1FICoucHkgZmlsZSBzZXQgYWxyZWFkeSBzY2FubmVkLCBleHRlbmRlZCB0byBpbmNsdWRlIHRob3NlIHRocmVlCiMgZGVwZW5kZW5jeSBmaWxlcy4KIwojIFR3byBtb2Rlcywgc2VsZWN0ZWQgYnkgdGhlIHNlY29uZCBwb3NpdGlvbmFsIGFyZ3VtZW50IChkZWZhdWx0ICdjaGVjaycpOgojICAgY2hlY2sgIChydW4gZnJvbSA6dHJ5X2Zhc3RfZXhlKTogcHJpbnQgJ2ZyZXNoJyBpZmYgYSBzdG9yZWQgaGFzaCBleGlzdHMgQU5EIG1hdGNoZXMgdGhlCiMgICAgICAgICAgaGFzaCBvZiB0aGUgQ1VSUkVOVCBzb3VyY2Ugc2V0LiBQcmludHMgbm90aGluZyAoZmFsbHMgdGhyb3VnaCB0byBhIHJlYnVpbGQpIG9uIGFueQojICAgICAgICAgIG1pc21hdGNoLCBvciB3aGVuIG5vIHN0b3JlZCBoYXNoIGV4aXN0cyB5ZXQgKGUuZy4gdGhlIGZpcnN0IHJ1biB1bmRlciB0aGlzIGZpeCwKIyAgICAgICAgICBvciBkaXN0XDxlbnY+LmV4ZSB3YXMgYnVpbHQgYnkgYW4gb2xkZXIgcnVuX3NldHVwLmJhdCB3aXRoIG5vIGhhc2ggZmlsZSBhdCBhbGwpIC0tCiMgICAgICAgICAgYSBzYWZlLCBvbmUtdGltZS1jb3N0IGRlZmF1bHQgdGhhdCBuZXZlciBtaXMtcmVwb3J0cyBzdGFsZW5lc3MgYXMgZnJlc2huZXNzLgojICAgd3JpdGUgIChydW4gZnJvbSA6c3VjY2Vzcywgb25seSB3aGVuIGEgZnJlc2ggYnVpbGQgYXR0ZW1wdCBqdXN0IGhhcHBlbmVkIC0tIHNlZSB0aGUKIyAgICAgICAgICBjYWxsIHNpdGUncyBvd24gSFBfRkFTVFBBVEhfVVNFRCBnYXRlIGZvciB3aHkgdGhlIGZhc3QtcmV1c2UgY2FzZSBza2lwcyB0aGlzIGFuZAojICAgICAgICAgIGF2b2lkcyBhIHJlZHVuZGFudCBzZWNvbmQgaGFzaCBwYXNzKTogdW5jb25kaXRpb25hbGx5IChyZSl3cml0ZSB0aGUgaGFzaCBvZiB0aGUKIyAgICAgICAgICBDVVJSRU5UIHNvdXJjZSBzZXQuIEFsd2F5cyBydW4gZnJvbSB0aGUgYXBwIHJvb3QgYXMgQ1dEIChib3RoIGNhbGwgc2l0ZXMgYWxyZWFkeQojICAgICAgICAgIGd1YXJhbnRlZSB0aGlzKSwgc28gcmVsYXRpdmUgcGF0aHMgY2FwdHVyZWQgdmlhIFJlc29sdmUtUGF0aCAtUmVsYXRpdmUgbWF0Y2gKIyAgICAgICAgICBiZXR3ZWVuIHRoZSB3cml0ZSBhbmQgYSBsYXRlciBjaGVjay4KIwojIFVzZXMgW1N5c3RlbS5TZWN1cml0eS5DcnlwdG9ncmFwaHkuU0hBMjU2XSBkaXJlY3RseSwgbm90IEdldC1GaWxlSGFzaCAtLSBzZWUKIyBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCdzICJQcmVmZXIgcmF3IC5ORVQgdHlwZXMgb3ZlciBVdGlsaXR5LW1vZHVsZSBjbWRsZXRzIiBlbnRyeToKIyBNaWNyb3NvZnQuUG93ZXJTaGVsbC5VdGlsaXR5IGlzIG5vdCBndWFyYW50ZWVkIHRvIGF1dG8tbG9hZCBpbiB0aGUgZm9yL2YtYmFja3RpY2sgYW5kCiMgLUZpbGUgaW52b2NhdGlvbiBzaGFwZXMgdGhpcyByZXBvJ3MgZW1iZWRkZWQgaGVscGVycyBydW4gdW5kZXIgb24gcmVhbCBXaW5kb3dzIFBvd2VyU2hlbGwKIyA1LjEsIGFuZCB0aGlzIGNsYXNzIG9mIGdhcCBpcyBpbnZpc2libGUgdG8gbG9jYWwgcHdzaCAoTGludXgsIFBvd2VyU2hlbGwgNykgdGVzdGluZy4KIwojIFRoaXMgaXMgdGhlIGNhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9GQVNUX0NIRUNLIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluCiMgcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtc3luYyB2aWE6CiMgICBweXRob24gdG9vbHMvc3luY19wYXlsb2FkLnB5IEhQX0ZBU1RfQ0hFQ0sgdG9vbHMvZmFzdF9jaGVjay5wczEKIyB0ZXN0cy90ZXN0X2Zhc3RfY2hlY2sucHkgYXNzZXJ0cyB0aGUgZW1iZWRkZWQgcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZSBieXRlLWZvci1ieXRlCiMgKGFmdGVyIENSTEYvTEYgbm9ybWFsaXphdGlvbiwgc2luY2UgdGhpcyBmaWxlIGlzIGVvbD1jcmxmIGJ1dCB3YXMgYXV0aG9yZWQgb24gTEYpLgoKJGV4ZSA9ICRhcmdzWzBdCmlmICgtbm90ICRleGUpIHsgJGV4ZSA9ICRlbnY6SFBfRkFTVF9FWEUgfQokbW9kZSA9ICRhcmdzWzFdCmlmICgtbm90ICRtb2RlKSB7ICRtb2RlID0gJ2NoZWNrJyB9CgokaGFzaEZpbGUgPSAnfmZhc3RfY2hlY2suaGFzaC50eHQnCiRpbmZyYVBhdHRlcm4gPSAnKD9pKShefFsvXFxdKShcLmdpdHxcLmdpdGh1YnxkaXN0fFwudmVudnxcLnV2X2VudnxfX3B5Y2FjaGVfX3xcLmNvbmRhKShbL1xcXXwkKScKJHB5ID0gQChHZXQtQ2hpbGRJdGVtIC1SZWN1cnNlIC1GaWxlIC1GaWx0ZXIgJyoucHknIHwgV2hlcmUtT2JqZWN0IHsgJF8uRnVsbE5hbWUgLW5vdG1hdGNoICRpbmZyYVBhdHRlcm4gLWFuZCAkXy5OYW1lIC1ub3RsaWtlICd+Ki5weScgfSkKJGRlcE5hbWVzID0gQCgncmVxdWlyZW1lbnRzLnR4dCcsICdweXByb2plY3QudG9tbCcsICdydW50aW1lLnR4dCcpCiRkZXBzID0gQCgkZGVwTmFtZXMgfCBXaGVyZS1PYmplY3QgeyBUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRfIC1QYXRoVHlwZSBMZWFmIH0gfCBGb3JFYWNoLU9iamVjdCB7IEdldC1JdGVtIC1MaXRlcmFsUGF0aCAkXyB9KQokc291cmNlcyA9IEAoJHB5KSArIEAoJGRlcHMpCgppZiAoLW5vdCAkc291cmNlcykgewogIGlmICgkbW9kZSAtZXEgJ2NoZWNrJykgeyBleGl0IDEgfSBlbHNlIHsgZXhpdCAwIH0KfQoKJHNoYSA9IFtTeXN0ZW0uU2VjdXJpdHkuQ3J5cHRvZ3JhcGh5LlNIQTI1Nl06OkNyZWF0ZSgpCiRsaW5lcyA9IGZvcmVhY2ggKCRmIGluICgkc291cmNlcyB8IFNvcnQtT2JqZWN0IC1Qcm9wZXJ0eSBGdWxsTmFtZSkpIHsKICAkcmVsUGF0aCA9IFJlc29sdmUtUGF0aCAtTGl0ZXJhbFBhdGggJGYuRnVsbE5hbWUgLVJlbGF0aXZlCiAgJGJ5dGVzID0gW1N5c3RlbS5JTy5GaWxlXTo6UmVhZEFsbEJ5dGVzKCRmLkZ1bGxOYW1lKQogICRoYXNoQnl0ZXMgPSAkc2hhLkNvbXB1dGVIYXNoKCRieXRlcykKICAkaGV4ID0gLWpvaW4gKCRoYXNoQnl0ZXMgfCBGb3JFYWNoLU9iamVjdCB7ICRfLlRvU3RyaW5nKCd4MicpIH0pCiAgIiRyZWxQYXRofCRoZXgiCn0KJGNvbWJpbmVkID0gW3N0cmluZ106OkpvaW4oImBuIiwgJGxpbmVzKQokY29tYmluZWRCeXRlcyA9IFtTeXN0ZW0uVGV4dC5FbmNvZGluZ106OlVURjguR2V0Qnl0ZXMoJGNvbWJpbmVkKQokZGlnZXN0Qnl0ZXMgPSAkc2hhLkNvbXB1dGVIYXNoKCRjb21iaW5lZEJ5dGVzKQokZGlnZXN0ID0gLWpvaW4gKCRkaWdlc3RCeXRlcyB8IEZvckVhY2gtT2JqZWN0IHsgJF8uVG9TdHJpbmcoJ3gyJykgfSkKCmlmICgkbW9kZSAtZXEgJ3dyaXRlJykgewogIFtTeXN0ZW0uSU8uRmlsZV06OldyaXRlQWxsVGV4dCgkaGFzaEZpbGUsICRkaWdlc3QpCiAgZXhpdCAwCn0KCmlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRoYXNoRmlsZSkpIHsgZXhpdCAxIH0KaWYgKC1ub3QgJGV4ZSkgeyBleGl0IDEgfQppZiAoLW5vdCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkZXhlKSkgeyBleGl0IDEgfQokc3RvcmVkID0gW1N5c3RlbS5JTy5GaWxlXTo6UmVhZEFsbFRleHQoJGhhc2hGaWxlKS5UcmltKCkKaWYgKCRzdG9yZWQgLWVxICRkaWdlc3QpIHsgJ2ZyZXNoJyB9Cg==" set "HP_EMBED_EXTRACT=IyBSRVEtMDA5IFRpZXIgNTogdmVyaWZpZXMgY2hlY2tzdW0sIGV4dHJhY3RzLCBhbmQgcGF0Y2hlcyB0aGUgZGlzYWJsZWQtc2l0ZS1pbXBvcnRzIC5fcHRoIGZpbGUKIyBmb3IgYW4gYWxyZWFkeS1kb3dubG9hZGVkIGVtYmVkZGFibGUgUHl0aG9uIHppcC4gQmF0Y2ggaGFzIGFscmVhZHkgZG93bmxvYWRlZCB0aGUgemlwICh2aWEgdGhlCiMgc2FtZSBjdXJsLXRoZW4tSW52b2tlLVdlYlJlcXVlc3QgcGF0dGVybiBhcyA6ZG93bmxvYWRfbWluaWNvbmRhX2V4ZS86ZG93bmxvYWRfZ2V0X3BpcCkgYW5kCiMgcGFzc2VzIGl0cyBwYXRoIHBsdXMgdGhlIGV4cGVjdGVkIFNIQTI1NiBhbmQgZGVzdGluYXRpb24gZGlyZWN0b3J5IGFzIGFyZ3MuIFRoaXMgc2NyaXB0IGRvZXMKIyBOT1QgZG93bmxvYWQgYW55dGhpbmcgaXRzZWxmIGFuZCBkb2VzIE5PVCBicmFuY2ggb24gcmVxdWVzdGVkIHZlcnNpb24gLS0gc2VlIHRoZSBQeXRob24tc2lkZQojIHN0YWdlIGZvciBwZXItcmVxdWVzdCB2ZXJzaW9uIHNlbGVjdGlvbi4KIyBBcmdzOiAkMSA9IHppcCBwYXRoLCAkMiA9IGV4cGVjdGVkIHNoYTI1NiAobG93ZXJjYXNlIGhleCksICQzID0gZGVzdGluYXRpb24gZGlyZWN0b3J5LgojIE91dHB1dDogb24gc3VjY2VzcywgcHJpbnRzIHRoZSBleHRyYWN0ZWQgcHl0aG9uLmV4ZSBwYXRoIG9uIHN0ZG91dCwgZXhpdCAwLiBPbiBmYWlsdXJlCiMgKGNoZWNrc3VtIG1pc21hdGNoLCBleHRyYWN0aW9uIGZhaWx1cmUsIG1pc3NpbmcgLl9wdGgsIG1pc3NpbmcgcHl0aG9uLmV4ZSksIHByaW50cyBub3RoaW5nIGFuZAojIGV4aXRzIDEgLS0gY2FsbGVyIGNoZWNrcyBib3RoIHN0ZG91dCBhbmQgZXhpdCBjb2RlLgojCiMgZGVyaXZlZCByZXF1aXJlbWVudDogaGFzaGluZy9leHRyYWN0aW9uL3RleHQtSU8gZGVsaWJlcmF0ZWx5IHVzZSByYXcgLk5FVCBBUElzCiMgKFtTeXN0ZW0uU2VjdXJpdHkuQ3J5cHRvZ3JhcGh5LlNIQTI1Nl0sIFtTeXN0ZW0uSU8uQ29tcHJlc3Npb24uWmlwRmlsZV0sIFtTeXN0ZW0uSU8uRmlsZV0pCiMgaW5zdGVhZCBvZiB0aGUgR2V0LUZpbGVIYXNoIC8gRXhwYW5kLUFyY2hpdmUgLyBHZXQtQ29udGVudCAvIFNldC1Db250ZW50IGNtZGxldHMuIENvbmZpcm1lZCB2aWEKIyByZWFsIENJIGZhaWx1cmUgKFdpbmRvd3MgUG93ZXJTaGVsbCA1LjEsIGludm9rZWQgYXMgYSBmb3IgL2YgYmFja3RpY2sgc3Vic2hlbGwgZnJvbSBydW5fc2V0dXAuYmF0KQojIHRoYXQgR2V0LUZpbGVIYXNoIHRocm93cyAibm90IHJlY29nbml6ZWQgYXMgdGhlIG5hbWUgb2YgYSBjbWRsZXQiIGluIHRoYXQgZXhhY3QgaW52b2NhdGlvbgojIGNvbnRleHQgLS0gaXRzIG1vZHVsZSAoTWljcm9zb2Z0LlBvd2VyU2hlbGwuVXRpbGl0eSkgd2FzIG5vdCBhdXRvLWxvYWRpbmcsIGV2ZW4gdGhvdWdoIHRoaXMKIyBzY3JpcHQgdGVzdGVkIGZpbmUgbG9jYWxseSB1bmRlciBwd3NoIChQb3dlclNoZWxsIDcgb24gTGludXgpIGJlZm9yZWhhbmQsIHdoaWNoIGRvZXMgbm90IHNoYXJlCiMgdGhlIHNhbWUgbW9kdWxlLWxvYWRpbmcgYmVoYXZpb3IuIFRlc3QtUGF0aC9HZXQtSXRlbSAoTWljcm9zb2Z0LlBvd2VyU2hlbGwuTWFuYWdlbWVudCkgd29ya2VkCiMgZmluZSBpbiB0aGUgc2FtZSBydW4sIHNvIHRoaXMgaXMgc2NvcGVkIHRvIFV0aWxpdHktbW9kdWxlIGNtZGxldHMgc3BlY2lmaWNhbGx5OyAuTkVUIHR5cGVzIGhhdmUKIyBubyBtb2R1bGUtbG9hZGluZyBkZXBlbmRlbmN5IGF0IGFsbCBhbmQgc2lkZXN0ZXAgdGhlIHdob2xlIGNsYXNzIG9mIGZhaWx1cmUuIFNlZQojIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kLgokWmlwUGF0aCA9ICRhcmdzWzBdCiRFeHBlY3RlZFNoYTI1NiA9ICRhcmdzWzFdCiREZXN0RGlyID0gJGFyZ3NbMl0KCnRyeSB7CiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkWmlwUGF0aCkpIHsKICAgICAgICBbQ29uc29sZV06OkVycm9yLldyaXRlTGluZSgiW2VtYmVkX2V4dHJhY3RdIHppcCBub3QgZm91bmQ6ICRaaXBQYXRoIikKICAgICAgICBleGl0IDEKICAgIH0KICAgICRaaXBTaXplID0gKEdldC1JdGVtIC1MaXRlcmFsUGF0aCAkWmlwUGF0aCkuTGVuZ3RoCiAgICAkU2hhMjU2UHJvdmlkZXIgPSBbU3lzdGVtLlNlY3VyaXR5LkNyeXB0b2dyYXBoeS5TSEEyNTZdOjpDcmVhdGUoKQogICAgJEZpbGVTdHJlYW0gPSBbU3lzdGVtLklPLkZpbGVdOjpPcGVuUmVhZCgkWmlwUGF0aCkKICAgIHRyeSB7CiAgICAgICAgJEhhc2hCeXRlcyA9ICRTaGEyNTZQcm92aWRlci5Db21wdXRlSGFzaCgkRmlsZVN0cmVhbSkKICAgIH0gZmluYWxseSB7CiAgICAgICAgJEZpbGVTdHJlYW0uRGlzcG9zZSgpCiAgICAgICAgJFNoYTI1NlByb3ZpZGVyLkRpc3Bvc2UoKQogICAgfQogICAgJEFjdHVhbEhhc2ggPSAoW0JpdENvbnZlcnRlcl06OlRvU3RyaW5nKCRIYXNoQnl0ZXMpIC1yZXBsYWNlICctJywgJycpLlRvTG93ZXIoKQogICAgaWYgKCRBY3R1YWxIYXNoIC1uZSAkRXhwZWN0ZWRTaGEyNTYuVG9Mb3dlcigpKSB7CiAgICAgICAgW0NvbnNvbGVdOjpFcnJvci5Xcml0ZUxpbmUoIltlbWJlZF9leHRyYWN0XSBjaGVja3N1bSBtaXNtYXRjaDogc2l6ZT0kWmlwU2l6ZSBleHBlY3RlZD0kKCRFeHBlY3RlZFNoYTI1Ni5Ub0xvd2VyKCkpIGFjdHVhbD0kQWN0dWFsSGFzaCIpCiAgICAgICAgZXhpdCAxCiAgICB9CgogICAgaWYgKFRlc3QtUGF0aCAtTGl0ZXJhbFBhdGggJERlc3REaXIpIHsgUmVtb3ZlLUl0ZW0gLVJlY3Vyc2UgLUZvcmNlIC1MaXRlcmFsUGF0aCAkRGVzdERpciB9CiAgICBBZGQtVHlwZSAtQXNzZW1ibHlOYW1lIFN5c3RlbS5JTy5Db21wcmVzc2lvbi5GaWxlU3lzdGVtCiAgICBbU3lzdGVtLklPLkNvbXByZXNzaW9uLlppcEZpbGVdOjpFeHRyYWN0VG9EaXJlY3RvcnkoJFppcFBhdGgsICREZXN0RGlyKQoKICAgICRQdGhGaWxlID0gR2V0LUNoaWxkSXRlbSAtTGl0ZXJhbFBhdGggJERlc3REaXIgLUZpbHRlciAicHl0aG9uKi5fcHRoIiAtRmlsZSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEKICAgIGlmICgtbm90ICRQdGhGaWxlKSB7CiAgICAgICAgW0NvbnNvbGVdOjpFcnJvci5Xcml0ZUxpbmUoIltlbWJlZF9leHRyYWN0XSBubyBweXRob24qLl9wdGggZmlsZSBmb3VuZCB1bmRlciAkRGVzdERpciIpCiAgICAgICAgZXhpdCAxCiAgICB9CiAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHRoZSBlbWJlZGRhYmxlIHppcCdzIC5fcHRoIGZpbGUgc2hpcHMgd2l0aCBDUkxGIGxpbmUgZW5kaW5ncywgYW5kCiAgICAjIC5ORVQgcmVnZXggJCBpbiBtdWx0aWxpbmUgbW9kZSBtYXRjaGVzIGltbWVkaWF0ZWx5IGJlZm9yZSBcbiAtLSBpdCBkb2VzIG5vdCBza2lwIGEKICAgICMgcHJlY2VkaW5nIFxyLCBzbyBhbiBhbmNob3Igb2YgIl4jaW1wb3J0IHNpdGUkIiBhZ2FpbnN0IGEgQ1JMRiBsaW5lIHNpbGVudGx5IG5ldmVyIG1hdGNoZXMKICAgICMgKHRoZSBcciBzaXRzIGJldHdlZW4gInNpdGUiIGFuZCB0aGUgbWF0Y2ggcG9zaXRpb24pLiBccj8gaGFuZGxlcyBib3RoIGxpbmUtZW5kaW5nIHN0eWxlcy4KICAgICRQdGhDb250ZW50ID0gW1N5c3RlbS5JTy5GaWxlXTo6UmVhZEFsbFRleHQoJFB0aEZpbGUuRnVsbE5hbWUpCiAgICAkUHRoQ29udGVudCA9ICRQdGhDb250ZW50IC1yZXBsYWNlICcoP20pXiNpbXBvcnQgc2l0ZVxyPyQnLCAnaW1wb3J0IHNpdGUnCiAgICBbU3lzdGVtLklPLkZpbGVdOjpXcml0ZUFsbFRleHQoJFB0aEZpbGUuRnVsbE5hbWUsICRQdGhDb250ZW50LCBbU3lzdGVtLlRleHQuRW5jb2RpbmddOjpBU0NJSSkKCiAgICAkUHlFeGUgPSBKb2luLVBhdGggJERlc3REaXIgInB5dGhvbi5leGUiCiAgICBpZiAoLW5vdCAoVGVzdC1QYXRoIC1MaXRlcmFsUGF0aCAkUHlFeGUpKSB7CiAgICAgICAgW0NvbnNvbGVdOjpFcnJvci5Xcml0ZUxpbmUoIltlbWJlZF9leHRyYWN0XSBweXRob24uZXhlIG1pc3NpbmcgYWZ0ZXIgZXh0cmFjdGlvbjogJFB5RXhlIikKICAgICAgICBleGl0IDEKICAgIH0KICAgIFtDb25zb2xlXTo6V3JpdGUoJFB5RXhlKQp9IGNhdGNoIHsKICAgIFtDb25zb2xlXTo6RXJyb3IuV3JpdGVMaW5lKCJbZW1iZWRfZXh0cmFjdF0gZXhjZXB0aW9uOiAkKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIpCiAgICBleGl0IDEKfQo=" set "HP_EMBED_PYVER_CHECK=IyBSRVEtMDA5IFRpZXIgNSwgUHl0aG9uIHN0YWdlOiBydW5zIHVuZGVyIHRoZSAiYWx3YXlzIGxhdGVzdCIgaW50ZXJwcmV0ZXIgfmVtYmVkX2V4dHJhY3QucHMxCiMgKFBvd2VyU2hlbGwgc3RhZ2UpIGFscmVhZHkgZG93bmxvYWRlZC92ZXJpZmllZC9leHRyYWN0ZWQuIFRoaXMgaXMgdGhlIE9OTFkgcGxhY2UgcGVyLXJlcXVlc3QKIyB2ZXJzaW9uIGxvZ2ljIGxpdmVzIC0tIGRlbGliZXJhdGVseSBQeXRob24sIG5vdCBQb3dlclNoZWxsLCByZXVzaW5nIHRoaXMgY29kZWJhc2UncyBwcm92ZW4KIyB2ZXJzaW9uLWRldGVjdGlvbiBwYXR0ZXJuIGluc3RlYWQgb2YgcmUtZGVyaXZpbmcgaXQgaW4gUG93ZXJTaGVsbC4gRnVsbCByYXRpb25hbGU6CiMgZG9jcy9hZ2VudC1pbnRlcmNvbm5lY3QubWQgIlN0YW5kYWxvbmUgUHl0aG9uLWRvd25sb2FkIHRpZXIiLiAiMy4xNCIgZW50cnkgYmVsb3cgTVVTVCBtYXRjaAojIEhQX0VNQkVEX0xBVEVTVF9QQVRDSC9IUF9FTUJFRF9MQVRFU1RfU0hBMjU2IGluIHJ1bl9zZXR1cC5iYXQgLS0gYSBQYXlsb2FkU3luYy1zdHlsZSB1bml0IHRlc3QKIyBhc3NlcnRzIHRoaXMuIExhc3QgcmVmcmVzaGVkOiAyMDI2LTA3LTA5LgppbXBvcnQgaGFzaGxpYgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCBzaHV0aWwKaW1wb3J0IHNvY2tldAppbXBvcnQgc3lzCmltcG9ydCB1cmxsaWIucmVxdWVzdAppbXBvcnQgemlwZmlsZQoKIyBkZXJpdmVkIHJlcXVpcmVtZW50OiB1cmxsaWIucmVxdWVzdC51cmxyZXRyaWV2ZSBoYXMgbm8gdGltZW91dD0gcGFyYW1ldGVyICh2ZXJpZmllZCB2aWEKIyBpbnNwZWN0LnNpZ25hdHVyZSAtLSBwYXNzaW5nIG9uZSByYWlzZXMgVHlwZUVycm9yKSwgc28gYSBzdGFsbGVkIChub3QgcmVmdXNlZCkgY29ubmVjdGlvbgojIGR1cmluZyBkb3dubG9hZF9hbmRfdmVyaWZ5KCkgd291bGQgb3RoZXJ3aXNlIGhhbmcgdGhpcyBvbmUtc2hvdCBzY3JpcHQgZm9yZXZlci4gQSBnbG9iYWwKIyBkZWZhdWx0IHRpbWVvdXQgaXMgc2FmZSBoZXJlIHNpbmNlIHRoZSB3aG9sZSBzY3JpcHQgZXhpdHMgaW1tZWRpYXRlbHkgYWZ0ZXIgdXNlIC0tIG5vdGhpbmcKIyBlbHNlIGluIHRoaXMgc2hvcnQtbGl2ZWQgcHJvY2VzcyBpcyBhZmZlY3RlZC4gTWlycm9ycyB0aGUgY3VybCAtLW1heC10aW1lIDEyMCBhbHJlYWR5IHVzZWQKIyBmb3IgdGhlIFBvd2VyU2hlbGwtc3RhZ2UgZG93bmxvYWQgb2YgdGhlIHNhbWUgemlwIGZhbWlseS4Kc29ja2V0LnNldGRlZmF1bHR0aW1lb3V0KDEyMCkKCiMgbWlub3IgLT4gKHBhdGNoLCBzaGEyNTYpCkVNQkVEX1BZVEhPTl9UQUJMRSA9IHsKICAgICIzLjEwIjogKCIzLjEwLjExIiwgIjYwODYxOWY4NjE5MDc1NjI5YzljNjlmMzYxMzUyYTBkYTZlZDdlNjJmODNhMGUxOWM2M2UwZWEzMmViNzYyOWQiKSwKICAgICIzLjExIjogKCIzLjExLjkiLCAiMDA5ZDZiZjdlM2IyZGRjYTNkNzg0ZmEwOWY5MGZlNTQzMzZkNWI2MGYwZTBmMzA1YzM3ZjQwMGJmODNjZmQzYiIpLAogICAgIjMuMTIiOiAoIjMuMTIuMTAiLCAiNGFjYmVkNmRkMWM3NDRiMDM3NmUzYjFjZjU3Y2U5MDZmOWRjOWU5NWU2ODgyNDU4NGM4MDk5YTYzMDI1YTNjMyIpLAogICAgIjMuMTMiOiAoIjMuMTMuMTQiLCAiOTBiNGU1Yjk4OThiNzJkNzQ0NjUwNTI0YmZmOTIzNzdjMzY3ZjQ0YmQ1ZmJkMDllMzE0ODY1NmMwODBhZDkwNyIpLAogICAgIjMuMTQiOiAoIjMuMTQuNiIsICJkZjkwMWU4NGE4OTZmZjFlZTcyMGFkMDMzNzdlMGM4ZDhjMjI0NGZkYTc5ODA4YWVlYWZmNjMxNmRmMWNiNzVjIiksCn0KTEFURVNUX01JTk9SID0gIjMuMTQiCkZMT09SX01JTk9SID0gIjMuMTAiCgpTUEVDX01JTk9SX1JFID0gcmUuY29tcGlsZShyIihbMC05XStcLlswLTldKykiKQoKCmRlZiBfbWlub3Jfa2V5KG1pbm9yKToKICAgIHRyeToKICAgICAgICBtYWpvciwgc3ViID0gbWlub3Iuc3BsaXQoIi4iKQogICAgICAgIHJldHVybiAoaW50KG1ham9yKSwgaW50KHN1YikpCiAgICBleGNlcHQgKFZhbHVlRXJyb3IsIEF0dHJpYnV0ZUVycm9yKToKICAgICAgICByZXR1cm4gKDAsIDApCgoKZGVmIHJlc29sdmVfcmVxdWVzdGVkX21pbm9yKHB5c3BlYyk6CiAgICAjIEV4dHJhY3RzICJYLlkiIGZyb20gYSBQWVNQRUMgc3RyaW5nIChlLmcuICJweXRob24+PTMuMTAsPDQuMCIpOyBOb25lIGlmIGVtcHR5L3VucGFyc2VhYmxlLgogICAgaWYgbm90IHB5c3BlYzoKICAgICAgICByZXR1cm4gTm9uZQogICAgbWF0Y2ggPSBTUEVDX01JTk9SX1JFLnNlYXJjaChweXNwZWMpCiAgICByZXR1cm4gbWF0Y2guZ3JvdXAoMSkgaWYgbWF0Y2ggZWxzZSBOb25lCgoKZGVmIHJlc29sdmVfdGFibGVfZW50cnkocmVxdWVzdGVkX21pbm9yKToKICAgICMgUmV0dXJucyAobWlub3IsIHBhdGNoLCBzaGEyNTYsIGZlbGxfYmFjayk7IG1pcnJvcnMgdGhlIFBvd2VyU2hlbGwgc3RhZ2UncyBvd24gcnVsZXMuCiAgICBpZiByZXF1ZXN0ZWRfbWlub3IgaW4gRU1CRURfUFlUSE9OX1RBQkxFOgogICAgICAgIHBhdGNoLCBzaGEyNTYgPSBFTUJFRF9QWVRIT05fVEFCTEVbcmVxdWVzdGVkX21pbm9yXQogICAgICAgIHJldHVybiByZXF1ZXN0ZWRfbWlub3IsIHBhdGNoLCBzaGEyNTYsIEZhbHNlCiAgICBtaW5vciA9IEZMT09SX01JTk9SIGlmIF9taW5vcl9rZXkocmVxdWVzdGVkX21pbm9yKSA8IF9taW5vcl9rZXkoRkxPT1JfTUlOT1IpIGVsc2UgTEFURVNUX01JTk9SCiAgICBwYXRjaCwgc2hhMjU2ID0gRU1CRURfUFlUSE9OX1RBQkxFW21pbm9yXQogICAgcmV0dXJuIG1pbm9yLCBwYXRjaCwgc2hhMjU2LCBUcnVlCgoKZGVmIGRvd25sb2FkX2FuZF92ZXJpZnkodXJsLCBleHBlY3RlZF9zaGEyNTYsIGRlc3RfemlwKToKICAgIHVybGxpYi5yZXF1ZXN0LnVybHJldHJpZXZlKHVybCwgZGVzdF96aXApCiAgICBkaWdlc3QgPSBoYXNobGliLnNoYTI1NigpCiAgICB3aXRoIG9wZW4oZGVzdF96aXAsICJyYiIpIGFzIGZoOgogICAgICAgIGZvciBjaHVuayBpbiBpdGVyKGxhbWJkYTogZmgucmVhZCgxIDw8IDIwKSwgYiIiKToKICAgICAgICAgICAgZGlnZXN0LnVwZGF0ZShjaHVuaykKICAgIGFjdHVhbCA9IGRpZ2VzdC5oZXhkaWdlc3QoKS5sb3dlcigpCiAgICBpZiBhY3R1YWwgIT0gZXhwZWN0ZWRfc2hhMjU2Lmxvd2VyKCk6CiAgICAgICAgb3MucmVtb3ZlKGRlc3RfemlwKQogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoImNoZWNrc3VtIG1pc21hdGNoOiBleHBlY3RlZCB7fSwgZ290IHt9Ii5mb3JtYXQoZXhwZWN0ZWRfc2hhMjU2LCBhY3R1YWwpKQoKCmRlZiBleHRyYWN0X2FuZF9wYXRjaCh6aXBfcGF0aCwgZGVzdF9kaXIpOgogICAgaWYgb3MucGF0aC5pc2RpcihkZXN0X2Rpcik6CiAgICAgICAgc2h1dGlsLnJtdHJlZShkZXN0X2RpcikKICAgIHdpdGggemlwZmlsZS5aaXBGaWxlKHppcF9wYXRoKSBhcyB6ZjoKICAgICAgICB6Zi5leHRyYWN0YWxsKGRlc3RfZGlyKQogICAgcHRoX2ZpbGVzID0gW2YgZm9yIGYgaW4gb3MubGlzdGRpcihkZXN0X2RpcikgaWYgcmUubWF0Y2gociJecHl0aG9uXGQrXC5fcHRoJCIsIGYpXQogICAgaWYgbm90IHB0aF9maWxlczoKICAgICAgICByYWlzZSBGaWxlTm90Rm91bmRFcnJvcigibm8gcHl0aG9uKi5fcHRoIGZpbGUgZm91bmQgYWZ0ZXIgZXh0cmFjdGlvbiIpCiAgICBwdGhfcGF0aCA9IG9zLnBhdGguam9pbihkZXN0X2RpciwgcHRoX2ZpbGVzWzBdKQogICAgd2l0aCBvcGVuKHB0aF9wYXRoLCAiciIsIGVuY29kaW5nPSJhc2NpaSIpIGFzIGZoOgogICAgICAgIGNvbnRlbnQgPSBmaC5yZWFkKCkKICAgIGNvbnRlbnQgPSByZS5zdWIociIoP20pXiNpbXBvcnQgc2l0ZSQiLCAiaW1wb3J0IHNpdGUiLCBjb250ZW50KQogICAgd2l0aCBvcGVuKHB0aF9wYXRoLCAidyIsIGVuY29kaW5nPSJhc2NpaSIsIG5ld2xpbmU9IiIpIGFzIGZoOgogICAgICAgIGZoLndyaXRlKGNvbnRlbnQpCiAgICBweV9leGUgPSBvcy5wYXRoLmpvaW4oZGVzdF9kaXIsICJweXRob24uZXhlIikKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShweV9leGUpOgogICAgICAgIHJhaXNlIEZpbGVOb3RGb3VuZEVycm9yKCJweXRob24uZXhlIG1pc3NpbmcgYWZ0ZXIgZXh0cmFjdGlvbiIpCiAgICByZXR1cm4gcHlfZXhlCgoKZGVmIG1haW4oKToKICAgICMgZGVzdF9kaXIgaXMgd2hlcmUgVEhJUyBydW5uaW5nIGludGVycHJldGVyIGxpdmVzOyBXaW5kb3dzIHdvbid0IGxldCBhIHByb2Nlc3MgcmVwbGFjZSBpdHMKICAgICMgb3duIGZpbGVzLCBzbyBhIHN3YXAgZXh0cmFjdHMgaW50byBhIHNpYmxpbmcgX3N3YXAgZGlyIGFuZCBiYXRjaCBtb3ZlcyBpdCBpbnRvIHBsYWNlIG9ubHkKICAgICMgYWZ0ZXIgdGhpcyBwcm9jZXNzIGV4aXRzIChsb2NrcyByZWxlYXNlZCkuIFNlZSBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZC4KICAgIGRlc3RfZGlyID0gc3lzLmFyZ3ZbMV0gaWYgbGVuKHN5cy5hcmd2KSA+IDEgZWxzZSAiIgogICAgc3dhcF9kaXIgPSBkZXN0X2Rpci5yc3RyaXAoIlxcLyIpICsgIl9zd2FwIgogICAgcHlzcGVjID0gb3MuZW52aXJvbi5nZXQoIlBZU1BFQyIsICIiKQogICAgcmVxdWVzdGVkX21pbm9yID0gcmVzb2x2ZV9yZXF1ZXN0ZWRfbWlub3IocHlzcGVjKQoKICAgIGlmIHJlcXVlc3RlZF9taW5vciBpcyBOb25lIG9yIHJlcXVlc3RlZF9taW5vciA9PSBMQVRFU1RfTUlOT1I6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgidW5jaGFuZ2VkfHt9XG4iLmZvcm1hdChMQVRFU1RfTUlOT1IpKQogICAgICAgIHJldHVybiAwCgogICAgbWlub3IsIHBhdGNoLCBzaGEyNTYsIGZlbGxfYmFjayA9IHJlc29sdmVfdGFibGVfZW50cnkocmVxdWVzdGVkX21pbm9yKQogICAgaWYgbWlub3IgPT0gTEFURVNUX01JTk9SOgogICAgICAgICMgQWJvdmUtY2VpbGluZyByZXF1ZXN0IChvbmx5IHBhdGggaGVyZSwgc2luY2UgZXhhY3QtbWF0Y2gtbGF0ZXN0IGlzIGhhbmRsZWQgYWJvdmUpOgogICAgICAgICMgbm8gc3dhcCBuZWVkZWQsIGJ1dCB0YWcgImZlbGxiYWNrIiBub3QgInVuY2hhbmdlZCIgc28gdGhlIGNhbGxlcidzIFdBUk4gc3RpbGwgZmlyZXMuCiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgiZmVsbGJhY2t8e31cbiIuZm9ybWF0KG1pbm9yKSkKICAgICAgICByZXR1cm4gMAoKICAgIHVybCA9ICJodHRwczovL3d3dy5weXRob24ub3JnL2Z0cC9weXRob24ve3B9L3B5dGhvbi17cH0tZW1iZWQtYW1kNjQuemlwIi5mb3JtYXQocD1wYXRjaCkKICAgIHppcF9wYXRoID0gb3MucGF0aC5qb2luKG9zLmVudmlyb24uZ2V0KCJURU1QIiwgIi4iKSwgInB5dGhvbi17fS1lbWJlZC1hbWQ2NC56aXAiLmZvcm1hdChwYXRjaCkpCiAgICB0cnk6CiAgICAgICAgZG93bmxvYWRfYW5kX3ZlcmlmeSh1cmwsIHNoYTI1NiwgemlwX3BhdGgpCiAgICAgICAgZXh0cmFjdF9hbmRfcGF0Y2goemlwX3BhdGgsIHN3YXBfZGlyKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6CiAgICAgICAgc3lzLnN0ZGVyci53cml0ZSgiZW1iZWQgdmVyc2lvbiBzd2FwIGZhaWxlZDoge31cbiIuZm9ybWF0KGV4YykpCiAgICAgICAgaWYgb3MucGF0aC5pc2Rpcihzd2FwX2Rpcik6CiAgICAgICAgICAgIHNodXRpbC5ybXRyZWUoc3dhcF9kaXIsIGlnbm9yZV9lcnJvcnM9VHJ1ZSkKICAgICAgICByZXR1cm4gMQoKICAgIHRhZyA9ICJmZWxsYmFjayIgaWYgZmVsbF9iYWNrIGVsc2UgInN3YXBwZWQiCiAgICBzeXMuc3Rkb3V0LndyaXRlKCJ7fXx7fXx7fVxuIi5mb3JtYXQodGFnLCBtaW5vciwgc3dhcF9kaXIpKQogICAgcmV0dXJuIDAKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" set "HP_FAILFAST_PROBE=IyBTbGljZSAyYi1DIGZhaWwtZmFzdCBwcm9iZTogbGF1bmNoZXMgdGhlIGNhbGxlcidzIHByb2dyYW0sIHdhaXRzIHVwIHRvIEhQX0ZBSUxGQVNUX1BST0JFX01TIHRvCiMgY2xhc3NpZnkgaXQgYXMgImV4aXRlZCBmYXN0IiAoc3RhbGUvYnJva2VuIGNhY2hlZCBhcnRpZmFjdCAtLSBkaXNjYXJkK3JlYnVpbGQgY2FuZGlkYXRlKSB2cy4KIyAic3RpbGwgcnVubmluZyIgKHRoZSB1c2VyJ3MgcmVhbCwgcG9zc2libHkgbG9uZy1ydW5uaW5nIHByb2dyYW0gLS0gbmV2ZXIgdG91Y2hlZCBhZ2FpbikuIE5ldmVyCiMgY2FsbHMgJHAuS2lsbCgpIC0tIHBhc3QgdGhlIHByb2JlIHdpbmRvdyB0aGUgd2FpdCBpcyB1bmJvdW5kZWQgc28gYSBoZWFsdGh5IGFwcCBpcyBuZXZlcgojIGZvcmNlLXN0b3BwZWQuCiMKIyBJbnB1dHMgdmlhIGVudiB2YXJzIChhdm9pZHMgY21kLmV4ZSBxdW90aW5nIGhhemFyZHMpOiBIUF9QUk9CRV9FWEUsIEhQX1BST0JFX0FSR1MsIEhQX1BST0JFX0NXRCwKIyBIUF9GQUlMRkFTVF9QUk9CRV9NUywgSFBfUFJPQkVfT1VUL0hQX1BST0JFX0VSUiAoZGVmYXVsdCB+cnVuLm91dC50eHQvfnJ1bi5lcnIudHh0KSwKIyBIUF9QUk9CRV9SRVNVTFQgKGRlZmF1bHQgfnByb2JlX3Jlc3VsdC50eHQgLS0gIiRleGNlZWRlZHwkZXhpdGNvZGUiLCBOT1Qgc3Rkb3V0KS4gQ2FsbGVyIG11c3QKIyBwcmUtdHJ1bmNhdGUgb3V0cHV0L3Jlc3VsdCBmaWxlcyBiZWZvcmUgaW52b2tpbmcuCiMKIyBkZXJpdmVkIHJlcXVpcmVtZW50IChbUkVRLTAyNl0gYXJndiBwYXNzdGhyb3VnaCk6IEhQX1BST0JFX0FSR1MgaXMgbm93IGEgZnVsbCwgYWxyZWFkeS1xdW90ZWQKIyBBcmd1bWVudHMgc3RyaW5nIChlLmcuIGAiZW50cnkucHkiICItLWZvbyIgImJhciJgKSwgdXNlZCB2ZXJiYXRpbSAtLSBub3QgYSBzaW5nbGUgYmFyZSBwYXRoCiMgcmUtcXVvdGVkIGhlcmUuIENhbGxlciBxdW90ZXMgZWFjaCB0b2tlbjsgc2VlIHJ1bl9zZXR1cC5iYXQncyBIUF9BUFBfQVJHUy4KIwojIExpdmUtdGVlcyB0aGUgY2hpbGQncyBzdGRvdXQvc3RkZXJyIHNvIGEgc3RkaW4taW50ZXJhY3RpdmUgcHJvZ3JhbSdzIHByb21wdHMgcmVhY2ggYSByZWFsCiMgZG91YmxlLWNsaWNrZWQgdXNlciwgaW5zdGVhZCBvZiBvbmx5IHdyaXRpbmcgY2FwdHVyZWQgb3V0cHV0IHRvIGRpc2sgYXQgZXhpdC4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IGRvZXMgTk9UIHVzZSBSZWdpc3Rlci1PYmplY3RFdmVudCBvbiBPdXRwdXREYXRhUmVjZWl2ZWQvRXJyb3JEYXRhUmVjZWl2ZWQuCiMgVGhhdCB3YXMgdGhlIG9yaWdpbmFsIGRlc2lnbiBhbmQgd2FzIGZvdW5kLCB2aWEgdGhpcyByZXBvJ3Mgb3duIGxvY2FsIHB3c2ggdGVzdGluZywgdG8KIyByZW9yZGVyIGxpbmVzIFdJVEhJTiBhIHNpbmdsZSBzdHJlYW0gKGUuZy4gcm91bmQgMidzIG91dHB1dCBsYW5kaW5nIGJlZm9yZSByb3VuZCAxJ3MgaW4gdGhlCiMgY2FwdHVyZWQvdGVlZCB0ZXh0LCBub24tZGV0ZXJtaW5pc3RpY2FsbHkpIC0tIHJvb3QtY2F1c2VkIHRvIGEgY29uZmlybWVkLCBmaWxlZCBQb3dlclNoZWxsIGJ1ZwojIChQb3dlclNoZWxsL1Bvd2VyU2hlbGwjMTE5MzcpOiB0aG9zZSBldmVudHMgZGlzcGF0Y2ggdmlhIFRocmVhZFBvb2wuUXVldWVVc2VyV29ya0l0ZW0sIHdoaWNoCiMgZG9lcyBub3QgZ3VhcmFudGVlIGRlbGl2ZXJ5IG9yZGVyIHdoZW4gc2V2ZXJhbCBsaW5lcyBhcnJpdmUgY2xvc2UgdG9nZXRoZXIuIEZpeGVkIGJ5IHBvbGxpbmcKIyByZWFkcyBkaXJlY3RseSBpbnN0ZWFkOiBvbmx5IE9ORSByZWFkIGlzIGV2ZXIgaW4gZmxpZ2h0IHBlciBzdHJlYW0gYXQgYSB0aW1lICh0aGUgbmV4dCByZWFkIGlzCiMgbm90IGlzc3VlZCB1bnRpbCB0aGUgY3VycmVudCBvbmUgaXMgY29uc3VtZWQpLCBzbyB0aGVyZSBpcyBubyBwb3NzaWJsZSBvdXQtb2Ytb3JkZXIgZGVsaXZlcnkKIyBmb3IgYSBzaW5nbGUgc3RyZWFtIC0tIG9yZGVyaW5nIGlzIHNlbGYtc2VxdWVuY2VkLCBub3QgZGVwZW5kZW50IG9uIGFueSBydW50aW1lJ3MKIyBjYWxsYmFjay1zY2hlZHVsaW5nIGd1YXJhbnRlZS4gQ3Jvc3Mtc3RyZWFtIChzdGRvdXQgdnMgc3RkZXJyKSBpbnRlcmxlYXZpbmcgd2FzIG5ldmVyCiMgZ3VhcmFudGVlZCBhbmQgc3RpbGwgaXNuJ3QgLS0gdGhhdCByZWZsZWN0cyB0aGUgY2hpbGQncyBvd24gdHdvIGluZGVwZW5kZW50IHBpcGVzLCBub3QgYSBidWcuCiMKIyBkZXJpdmVkIHJlcXVpcmVtZW50IChGaW5kaW5nIDksIDIwMjYtMDctMjQpOiByZWFkcyB2aWEgU3RyZWFtUmVhZGVyLlJlYWRBc3luYyhjaGFyW10sIGludCwgaW50KQojIChyYXcgY2h1bmtzKSwgTk9UIFJlYWRMaW5lQXN5bmMoKS4gUmVhZExpbmVBc3luYygpIG9ubHkgcmV0dXJucyBvbmNlIGl0IHNlZXMgYSBmdWxsCiMgbmV3bGluZS10ZXJtaW5hdGVkIGxpbmUgLS0gY29uZmlybWVkIGVtcGlyaWNhbGx5IHRoYXQgUHl0aG9uJ3Mgb3duIGBpbnB1dCgicHJvbXB0IilgIChubwojIHRyYWlsaW5nIG5ld2xpbmUgYnkgZGVzaWduLCBzbyB0aGUgY3Vyc29yIHN0YXlzIG9uIHRoZSBzYW1lIGxpbmUpIGlzIGdlbnVpbmVseSBmbHVzaGVkIHRvIHRoZQojIE9TIHBpcGUgaW1tZWRpYXRlbHkgYnV0IHN0YXlzIGludmlzaWJsZSB0byBhIFJlYWRMaW5lQXN5bmMtYmFzZWQgcmVhZGVyIHVudGlsIHNvbWV0aGluZyBlbHNlCiMgbGF0ZXIgZmx1c2hlcyBhIG5ld2xpbmUsIG9yIHRoZSBwcm9jZXNzIGV4aXRzLiBBIGNodW5rLWJhc2VkIHJlYWQgc3VyZmFjZXMgd2hhdGV2ZXIgYnl0ZXMgYXJlCiMgYWN0dWFsbHkgYXZhaWxhYmxlIHRoZSBtb21lbnQgdGhleSBhcnJpdmUsIG1hdGNoaW5nIGhvdyBhIHJlYWwgdGVybWluYWwgYmVoYXZlcy4gRU9GIGlzIGEKIyAwLWxlbmd0aCByZWFkIChub3QgYSBudWxsIHJlc3VsdCB0aGUgd2F5IFJlYWRMaW5lQXN5bmMgc2lnbmFscyBpdCkuIFNlZQojIGRvY3MvcGxhbi1jbGktaW50ZXJhY3RpdmUtdmVyaWZpY2F0aW9uLm1kIEZpbmRpbmcgOSBmb3IgdGhlIGZ1bGwgZW1waXJpY2FsIHRyYWNlLgojCiMgRnVsbCByYXRpb25hbGUgKyBjaXRhdGlvbnM6IGRvY3MvcGxhbi1jbGktaW50ZXJhY3RpdmUtdmVyaWZpY2F0aW9uLm1kIEZpbmRpbmdzIDViLzYvNy84LzkuCiMKIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRkFJTEZBU1RfUFJPQkUgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQgaW4KIyBydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlIGBzZXQgIkhQX0ZBSUxGQVNUX1BST0JFPS4uLiJgCiMgbGluZTsgdGVzdHMvdGVzdF9mYWlsZmFzdF9wcm9iZS5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlICh3aXRoCiMgQ1JMRi9MRiBub3JtYWxpemVkLCBwZXIgdGhlIC5wczEgUGF5bG9hZFN5bmMgY29udmVudGlvbiAtLSBzZWUKIyBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCAiRW1iZWRkZWQgSGVscGVyIFVwZGF0ZSBXb3JrZmxvdyIpLgokZXhlID0gJGVudjpIUF9QUk9CRV9FWEUKJHJhd0FyZ3MgPSAkZW52OkhQX1BST0JFX0FSR1MKJHdvcmtEaXIgPSAkZW52OkhQX1BST0JFX0NXRAokcHJvYmVNcyA9IFtpbnRdJGVudjpIUF9GQUlMRkFTVF9QUk9CRV9NUwokb3V0UGF0aCA9ICRlbnY6SFBfUFJPQkVfT1VUCmlmICgtbm90ICRvdXRQYXRoKSB7ICRvdXRQYXRoID0gJ35ydW4ub3V0LnR4dCcgfQokZXJyUGF0aCA9ICRlbnY6SFBfUFJPQkVfRVJSCmlmICgtbm90ICRlcnJQYXRoKSB7ICRlcnJQYXRoID0gJ35ydW4uZXJyLnR4dCcgfQokcmVzdWx0UGF0aCA9ICRlbnY6SFBfUFJPQkVfUkVTVUxUCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35wcm9iZV9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRyYXdBcmdzKSB7ICRzaS5Bcmd1bWVudHMgPSAkcmF3QXJncyB9CiRzaS5Xb3JraW5nRGlyZWN0b3J5ID0gJHdvcmtEaXIKJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQokc2kuUmVkaXJlY3RTdGFuZGFyZE91dHB1dCA9ICR0cnVlCiRzaS5SZWRpcmVjdFN0YW5kYXJkRXJyb3IgPSAkdHJ1ZQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MKJHAuU3RhcnRJbmZvID0gJHNpCiRwLlN0YXJ0KCkgfCBPdXQtTnVsbApXcml0ZS1Ib3N0ICJbSU5GT10gUHJvY2VzcyBJRCAkKCRwLklkKS4gSWYgaXQgc2VlbXMgc3R1Y2s6IFRhc2sgTWFuYWdlciA+IERldGFpbHMgdGFiID4gZmluZCB0aGlzIFBJRCA+IEVuZCBUYXNrICh0aGlzIHdpbmRvdyBzdGF5cyBvcGVuKS4iCgokb3V0QnVmID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5TdHJpbmdCdWlsZGVyCiRlcnJCdWYgPSBOZXctT2JqZWN0IFN5c3RlbS5UZXh0LlN0cmluZ0J1aWxkZXIKJG91dENodW5rQnVmID0gTmV3LU9iamVjdCBjaGFyW10gNDA5NgokZXJyQ2h1bmtCdWYgPSBOZXctT2JqZWN0IGNoYXJbXSA0MDk2CiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkKJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRBc3luYygkZXJyQ2h1bmtCdWYsIDAsICRlcnJDaHVua0J1Zi5MZW5ndGgpCiRvdXREb25lID0gJGZhbHNlCiRlcnJEb25lID0gJGZhbHNlCgokc3cgPSBbU3lzdGVtLkRpYWdub3N0aWNzLlN0b3B3YXRjaF06OlN0YXJ0TmV3KCkKJGV4Y2VlZGVkID0gMAp3aGlsZSAoKC1ub3QgJHAuSGFzRXhpdGVkKSAtb3IgKC1ub3QgJG91dERvbmUpIC1vciAoLW5vdCAkZXJyRG9uZSkpIHsKICAgIGlmICgoLW5vdCAkb3V0RG9uZSkgLWFuZCAkb3V0VGFzay5Jc0NvbXBsZXRlZCkgewogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0CiAgICAgICAgaWYgKCRuIC1lcSAwKSB7CiAgICAgICAgICAgICRvdXREb25lID0gJHRydWUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRvdXRDaHVua0J1ZiwgMCwgJG4pCiAgICAgICAgICAgIFtDb25zb2xlXTo6T3V0LldyaXRlKCRjaHVuaykKICAgICAgICAgICAgJG51bGwgPSAkb3V0QnVmLkFwcGVuZCgkY2h1bmspCiAgICAgICAgICAgICRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkKICAgICAgICB9CiAgICB9CiAgICBpZiAoKC1ub3QgJGVyckRvbmUpIC1hbmQgJGVyclRhc2suSXNDb21wbGV0ZWQpIHsKICAgICAgICAkbiA9ICRlcnJUYXNrLlJlc3VsdAogICAgICAgIGlmICgkbiAtZXEgMCkgewogICAgICAgICAgICAkZXJyRG9uZSA9ICR0cnVlCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJGNodW5rID0gW3N0cmluZ106Om5ldygkZXJyQ2h1bmtCdWYsIDAsICRuKQogICAgICAgICAgICBbQ29uc29sZV06OkVycm9yLldyaXRlKCRjaHVuaykKICAgICAgICAgICAgJG51bGwgPSAkZXJyQnVmLkFwcGVuZCgkY2h1bmspCiAgICAgICAgICAgICRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQogICAgICAgIH0KICAgIH0KICAgIGlmICgoLW5vdCAkZXhjZWVkZWQpIC1hbmQgKCRzdy5FbGFwc2VkTWlsbGlzZWNvbmRzIC1nZSAkcHJvYmVNcykpIHsKICAgICAgICAkZXhjZWVkZWQgPSAxCiAgICB9CiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwCn0KJHAuV2FpdEZvckV4aXQoKQoKJG91dEJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJCiRlcnJCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRlcnJQYXRoIC1FbmNvZGluZyBBU0NJSQoiJGV4Y2VlZGVkfCQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQo=" set "HP_EXE_SMOKERUN=IyA6cnVuX2V4ZV9zbW9rZXJ1bidzIGRlZGljYXRlZCBoZWxwZXIgLS0gdGhlIE9OTFkgcGxhY2UgaW4gdGhpcyBmaWxlIGZhbWlseSBhbGxvd2VkIHRvDQojIGZvcmNlLWtpbGwgKEtpbGwoKSkgdGhlIHZlcmlmaWNhdGlvbiBydW4uIFVubGlrZSB+ZmFpbGZhc3RfcHJvYmUucHMxIChuZXZlciBraWxscyAtLSBjb3ZlcnMNCiMgdGhlIHVudGltZWQgZmFzdHBhdGgvaW50ZXJwcmV0ZXIvY2hlY2twb2ludCBjYWxsIHNpdGVzKSwgdGhpcyBJUyB0aGUgZnJlc2gtYnVpbGQgdmVyaWZpY2F0aW9uDQojIHJ1biBpdHNlbGY6IG5vdGhpbmcgZWxzZSB3aWxsIGV2ZXIgY29uZmlybSB0aGlzIHBhcnRpY3VsYXIgYnVpbGQgd29ya2VkLCBzbyBhbiB1bnJlc3BvbnNpdmUNCiMgcHJvY2VzcyBoZXJlIGNhbm5vdCBiZSB0cnVzdGVkIHRvIGV2ZW50dWFsbHkgZmluaXNoIHRoZSB3YXkgYSBwcmV2aW91c2x5LXZlcmlmaWVkIGNhY2hlZA0KIyBhcnRpZmFjdCBvciBpbnRlcnByZXRlciBydW4gY2FuLg0KIw0KIyBSZWFkcyBpbnB1dHMgZnJvbSBlbnYgdmFycyAoc2FtZSBjbWQuZXhlLXF1b3RpbmctaGF6YXJkLWF2b2lkYW5jZSByZWFzb25pbmcgYXMNCiMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIG93biBoZWFkZXIgY29tbWVudCk6IEhQX1NNT0tFUlVOX0VYRSBpcyBhIHBhdGggdG8gdGhlIGJ1aWx0IEVYRSAoZS5nLg0KIyBkaXN0XDxlbnY+LmV4ZSksIENXRC1yZWxhdGl2ZS4gQ2FsbGVyIENXRCA9IGFwcCByb290IChDTEFVREUubWQgSXRlbSAzODogbWF0Y2hlcyA6dHJ5X2Zhc3RfZXhlLw0KIyA6dmVyaWZ5X25vX2V4ZV9pbnRlcnByZXRlcidzIG93biBDV0Qgbm93LCBub3QgZGlzdFwgYXMgYmVmb3JlIC0tIHNlZSBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZA0KIyAiU2luZ2xlLXZlcmlmaWNhdGlvbiBzbW9rZSBtb2RlbCIpLiBIUF9TTU9LRVJVTl9PVVQvRVJSIGRlZmF1bHQgdG8gfnJ1bi5vdXQudHh0IC8gfnJ1bi5lcnIudHh0DQojIChDV0QtcmVsYXRpdmUsIGkuZS4gdGhlIGFwcCByb290KS4gSFBfU01PS0VSVU5fUkVTVUxUIChkZWZhdWx0IH5zbW9rZXJ1bl9yZXN1bHQudHh0KSBpcyB3aGVyZQ0KIyB0aGlzIHNjcmlwdCB3cml0ZXMgaXRzIGV4aXQtY29kZSByZXN1bHQgLS0gTk9UIHN0ZG91dDsgc2VlIH5mYWlsZmFzdF9wcm9iZS5wczEncyBoZWFkZXIgY29tbWVudA0KIyAoc2FtZSByZWFzb25pbmc6IHRoZSBjYWxsZXIgaW52b2tlcyB0aGlzIHNjcmlwdCBkaXJlY3RseSwgbm8gZm9yIC9mL2JhY2t0aWNrIHN0ZG91dCBjYXB0dXJlLCBzbw0KIyBsaXZlLXRlZWQgb3V0cHV0IHJlYWNoZXMgdGhlIGNvbnNvbGUgaW5zdGVhZCBvZiBiZWluZyBzaWxlbnRseSBzd2FsbG93ZWQgYW5kIGNvcnJ1cHRpbmcgcmVzdWx0DQojIHBhcnNpbmcpLiBDYWxsZXIgbXVzdCBwcmUtdHJ1bmNhdGUgdGhlIG91dHB1dC9yZXN1bHQgZmlsZXMgYmVmb3JlIGludm9raW5nLCBzYW1lIGFzDQojIH5mYWlsZmFzdF9wcm9iZS5wczEuDQojDQojIFNhbWUgbGl2ZS10ZWUgYXMgfmZhaWxmYXN0X3Byb2JlLnBzMSAtLSBzZWUgdGhhdCBmaWxlJ3MgaGVhZGVyIGNvbW1lbnQgZm9yIHRoZSBmdWxsIHJhdGlvbmFsZQ0KIyAoc2VsZi1zZXF1ZW5jZWQgY2h1bmsgcmVhZHMgdmlhIFN0cmVhbVJlYWRlci5SZWFkQXN5bmMoY2hhcltdLCBpbnQsIGludCksIE5PVA0KIyBSZWdpc3Rlci1PYmplY3RFdmVudCBvciBSZWFkTGluZUFzeW5jKCkgLS0gUG93ZXJTaGVsbC9Qb3dlclNoZWxsIzExOTM3IGFuZCBGaW5kaW5nIDksDQojIGRvY3MvcGxhbi1jbGktaW50ZXJhY3RpdmUtdmVyaWZpY2F0aW9uLm1kLCBjb3ZlciB3aHkpLg0KIw0KIyBkZXJpdmVkIHJlcXVpcmVtZW50IChPcGVuIFF1ZXN0aW9uIDEsIG93bmVyIGRlY2lzaW9uIDIwMjYtMDctMjQpOiBIUF9TTU9LRVJVTl9LSUxMX01TIChkZWZhdWx0DQojIDMwMDAwLCB1bmNoYW5nZWQpIGlzIGEgY2xhc3NpZmljYXRpb24gY2hlY2twb2ludCwgbm90IGFuIHVuY29uZGl0aW9uYWwgZGVhZGxpbmUgLS0gS2lsbCgpIGZpcmVzDQojIG9ubHkgaWYgJHNhd091dHB1dCBpcyBzdGlsbCBmYWxzZSBhdCBraWxsTXMgKGZ1bGx5IHNpbGVudCA9IHByZXN1bWVkIGh1bmcpLiBBbnkgYnl0ZXMgb2JzZXJ2ZWQNCiMgb24gZWl0aGVyIHN0cmVhbSBza2lwcyB0aGUga2lsbCBhbmQgdGhlIHdhaXQgYmVjb21lcyB1bmJvdW5kZWQsIG1pcnJvcmluZw0KIyB+ZmFpbGZhc3RfcHJvYmUucHMxJ3MgcGhpbG9zb3BoeSAtLSBzZWUgZG9jcy9hZ2VudC1pbnRlcmNvbm5lY3QubWQgIkFjdGl2aXR5LWF3YXJlIEVYRS1zbW9rZQ0KIyBraWxsIiBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlL3RyYWRlLW9mZi4gQ2h1bmstYmFzZWQgKG5vdCBsaW5lLWJhc2VkKSByZWFkcyBhcmUgd2hhdCBtYWtlIHRoaXMNCiMgYWN0dWFsbHkgZmlyZSBmb3IgdGhlIGNhbm9uaWNhbCBgaW5wdXQoInByb21wdCIpYCBjYXNlIC0tIHNlZSBGaW5kaW5nIDkgaW4gdGhlIHBsYW4gZG9jIGFib3ZlLg0KIw0KIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRVhFX1NNT0tFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuDQojIEFmdGVyIGVkaXRpbmcsIHJlLWVuY29kZSBhbmQgcGFzdGUgaXQgaW50byB0aGUgYHNldCAiSFBfRVhFX1NNT0tFUlVOPS4uLiJgIGxpbmU7DQojIHRlc3RzL3Rlc3RfZXhlX3Ntb2tlcnVuLnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUgKENSTEYvTEYgbm9ybWFsaXplZCwNCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kDQojICJFbWJlZGRlZCBIZWxwZXIgVXBkYXRlIFdvcmtmbG93IikuDQojDQojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKFtSRVEtMDI2XSBhcmd2IHBhc3N0aHJvdWdoKTogSFBfU01PS0VSVU5fQVJHUywgaWYgc2V0LCBpcyBhIGZ1bGwsDQojIGFscmVhZHktcXVvdGVkIFdpbmRvd3MgQXJndW1lbnRzIHN0cmluZyAodGhlIEVYRSBpcyBzZWxmLWNvbnRhaW5lZCwgc28gbm8gc2VwYXJhdGUgZW50cnktZmlsZQ0KIyBhcmd2IGlzIG5lZWRlZCBoZXJlIC0tIGp1c3QgYW55IGZvcndhcmRlZCBleHRyYSBhcmdzKS4gVXNlZCB2ZXJiYXRpbSwgbm8gcmUtcXVvdGluZy4NCiRleGUgPSAkZW52OkhQX1NNT0tFUlVOX0VYRQ0KJGtpbGxNcyA9IDMwMDAwDQppZiAoJGVudjpIUF9TTU9LRVJVTl9LSUxMX01TKSB7ICRraWxsTXMgPSBbaW50XSRlbnY6SFBfU01PS0VSVU5fS0lMTF9NUyB9DQokYXJnc1JhdyA9ICRlbnY6SFBfU01PS0VSVU5fQVJHUw0KJG91dFBhdGggPSAkZW52OkhQX1NNT0tFUlVOX09VVA0KaWYgKC1ub3QgJG91dFBhdGgpIHsgJG91dFBhdGggPSAnfnJ1bi5vdXQudHh0JyB9DQokZXJyUGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fRVJSDQppZiAoLW5vdCAkZXJyUGF0aCkgeyAkZXJyUGF0aCA9ICd+cnVuLmVyci50eHQnIH0NCiRyZXN1bHRQYXRoID0gJGVudjpIUF9TTU9LRVJVTl9SRVNVTFQNCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35zbW9rZXJ1bl9yZXN1bHQudHh0JyB9DQoNCiRzaSA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3NTdGFydEluZm8NCiRzaS5GaWxlTmFtZSA9ICRleGUNCmlmICgkYXJnc1JhdykgeyAkc2kuQXJndW1lbnRzID0gJGFyZ3NSYXcgfQ0KJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MNCiRwLlN0YXJ0SW5mbyA9ICRzaQ0KJHAuU3RhcnQoKSB8IE91dC1OdWxsDQpXcml0ZS1Ib3N0ICJbSU5GT10gUHJvY2VzcyBJRCAkKCRwLklkKS4gSWYgaXQgc2VlbXMgc3R1Y2s6IFRhc2sgTWFuYWdlciA+IERldGFpbHMgdGFiID4gZmluZCB0aGlzIFBJRCA+IEVuZCBUYXNrICh0aGlzIHdpbmRvdyBzdGF5cyBvcGVuKS4iDQoNCiRvdXRCdWYgPSBOZXctT2JqZWN0IFN5c3RlbS5UZXh0LlN0cmluZ0J1aWxkZXINCiRlcnJCdWYgPSBOZXctT2JqZWN0IFN5c3RlbS5UZXh0LlN0cmluZ0J1aWxkZXINCiRvdXRDaHVua0J1ZiA9IE5ldy1PYmplY3QgY2hhcltdIDQwOTYNCiRlcnJDaHVua0J1ZiA9IE5ldy1PYmplY3QgY2hhcltdIDQwOTYNCiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkNCiRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQ0KJG91dERvbmUgPSAkZmFsc2UNCiRlcnJEb25lID0gJGZhbHNlDQoNCiRzdyA9IFtTeXN0ZW0uRGlhZ25vc3RpY3MuU3RvcHdhdGNoXTo6U3RhcnROZXcoKQ0KJGtpbGxlZCA9ICRmYWxzZQ0KJHNhd091dHB1dCA9ICRmYWxzZQ0Kd2hpbGUgKCgtbm90ICRwLkhhc0V4aXRlZCkgLW9yICgtbm90ICRvdXREb25lKSAtb3IgKC1ub3QgJGVyckRvbmUpKSB7DQogICAgaWYgKCgtbm90ICRvdXREb25lKSAtYW5kICRvdXRUYXNrLklzQ29tcGxldGVkKSB7DQogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0DQogICAgICAgIGlmICgkbiAtZXEgMCkgew0KICAgICAgICAgICAgJG91dERvbmUgPSAkdHJ1ZQ0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgJHNhd091dHB1dCA9ICR0cnVlDQogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRvdXRDaHVua0J1ZiwgMCwgJG4pDQogICAgICAgICAgICBbQ29uc29sZV06Ok91dC5Xcml0ZSgkY2h1bmspDQogICAgICAgICAgICAkbnVsbCA9ICRvdXRCdWYuQXBwZW5kKCRjaHVuaykNCiAgICAgICAgICAgICRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkNCiAgICAgICAgfQ0KICAgIH0NCiAgICBpZiAoKC1ub3QgJGVyckRvbmUpIC1hbmQgJGVyclRhc2suSXNDb21wbGV0ZWQpIHsNCiAgICAgICAgJG4gPSAkZXJyVGFzay5SZXN1bHQNCiAgICAgICAgaWYgKCRuIC1lcSAwKSB7DQogICAgICAgICAgICAkZXJyRG9uZSA9ICR0cnVlDQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICAkc2F3T3V0cHV0ID0gJHRydWUNCiAgICAgICAgICAgICRjaHVuayA9IFtzdHJpbmddOjpuZXcoJGVyckNodW5rQnVmLCAwLCAkbikNCiAgICAgICAgICAgIFtDb25zb2xlXTo6RXJyb3IuV3JpdGUoJGNodW5rKQ0KICAgICAgICAgICAgJG51bGwgPSAkZXJyQnVmLkFwcGVuZCgkY2h1bmspDQogICAgICAgICAgICAkZXJyVGFzayA9ICRwLlN0YW5kYXJkRXJyb3IuUmVhZEFzeW5jKCRlcnJDaHVua0J1ZiwgMCwgJGVyckNodW5rQnVmLkxlbmd0aCkNCiAgICAgICAgfQ0KICAgIH0NCiAgICBpZiAoKC1ub3QgJGtpbGxlZCkgLWFuZCAoLW5vdCAkc2F3T3V0cHV0KSAtYW5kICgtbm90ICRwLkhhc0V4aXRlZCkgLWFuZCAoJHN3LkVsYXBzZWRNaWxsaXNlY29uZHMgLWdlICRraWxsTXMpKSB7DQogICAgICAgIHRyeSB7ICRwLktpbGwoKSB9IGNhdGNoIHt9DQogICAgICAgICRraWxsZWQgPSAkdHJ1ZQ0KICAgIH0NCiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwDQp9DQokcC5XYWl0Rm9yRXhpdCgpDQoNCiRvdXRCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRvdXRQYXRoIC1FbmNvZGluZyBBU0NJSQ0KJGVyckJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJGVyclBhdGggLUVuY29kaW5nIEFTQ0lJDQppZiAoJGtpbGxlZCkgew0KICAgICItMSIgfCBTZXQtQ29udGVudCAtUGF0aCAkcmVzdWx0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCn0gZWxzZSB7DQogICAgIiQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQ0KfQ0KDQpHZXQtRXZlbnRTdWJzY3JpYmVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgVW5yZWdpc3Rlci1FdmVudCAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQ0K" set "HP_EXE_HINT_RERUN=IyA6ZXhlX3Ntb2tlcnVuX2hpbnRzJyBib3VuZGVkIGRpYWdub3N0aWMgcmUtcnVuIGhlbHBlci4gRGlhZ25vc3RpYy1vbmx5IHN0ZG91dCtzdGRlcnIgc25hcHNob3QNCiMgZm9yIHN0ZGVyciBwYXR0ZXJuLW1hdGNoaW5nIChNb2R1bGVOb3RGb3VuZEVycm9yL0ZpbGVOb3RGb3VuZEVycm9yKTsgbmV2ZXIgc2hvd24gbGl2ZS4gS2lsbCBpcw0KIyBVTkNPTkRJVElPTkFMIGF0IHRoZSBkZWFkbGluZSAobm90IGFjdGl2aXR5LWF3YXJlIGxpa2UgZXhlX3Ntb2tlcnVuLnBzMS9mYWlsZmFzdF9wcm9iZS5wczEpIC0tDQojIHBhcnRpYWwgb3V0cHV0IG9uIGEgaGFuZyBiZWF0cyBoYW5naW5nIHRoZSBib290c3RyYXAgb24gYSBydW4gbm9ib2R5IGlzIHdhdGNoaW5nLiBTZWUgQ0xBVURFLm1kDQojIGZvcm1lciBpdGVtIDE1IC8gZG9jcy9hZ2VudC1jbG9zZWQtYmFja2xvZy5tZCBmb3IgdGhlIHVudGltZWQtcmVydW4gZ2FwIHRoaXMgY2xvc2VkLg0KIw0KIyBSZWFkczogSFBfSElOVF9SRVJVTl9FWEUgKGEgcGF0aCB0byB0aGUgYnVpbHQgRVhFLCBlLmcuIGRpc3RcPGVudj4uZXhlLCByZWxhdGl2ZSB0byB0aGUgY3VycmVudA0KIyB3b3JraW5nIGRpcmVjdG9yeSAoQ1dEKSAtLSB0aGUgY2FsbGVyIHJ1bnMgdGhpcyBzY3JpcHQgd2l0aCBDV0Qgc2V0IHRvIHRoZSBhcHAgcm9vdCwgbWF0Y2hpbmcNCiMgOnJ1bl9leGVfc21va2VydW4ncyBvd24gQ1dEIHBlciBDTEFVREUubWQgQWN0aXZlIEJhY2tsb2cgSXRlbSAzODsgc2VlDQojIGRvY3MvYWdlbnQtaW50ZXJjb25uZWN0Lm1kICJTaW5nbGUtdmVyaWZpY2F0aW9uIHNtb2tlIG1vZGVsIikuIEhQX0hJTlRfUkVSVU5fT1VUIChkZWZhdWx0DQojIH5leGVfb3V0LnR4dCwgQ1dELXJlbGF0aXZlIC0tIHRoZSBhcHAgcm9vdCkgZ2V0cyBjb21iaW5lZCBzdGRvdXQrc3RkZXJyLCBtYXRjaGluZyB0aGUgb3JpZ2luYWwNCiMgYDI+JjFgIG1lcmdlLiBIUF9ISU5UX1JFUlVOX0tJTExfTVMgKGRlZmF1bHQgMTAwMDApIGlzIGEgdGVzdCBvdmVycmlkZSwgbWlycm9yaW5nDQojIEhQX1NNT0tFUlVOX0tJTExfTVMuDQojDQojICRraWxsTXMgaXMgYWx3YXlzIHdyaXR0ZW4gdG8gSFBfSElOVF9SRVJVTl9LSUxMTVNfT1VUIChkZWZhdWx0IH5leGVfaGludF9raWxsbXMudHh0KSBzbyB0ZXN0cyBjYW4NCiMgYXNzZXJ0IHRoZSBvdmVycmlkZSB3YXMgaG9ub3JlZCBkaXJlY3RseSAtLSB3YWxsLWNsb2NrIGVsYXBzZWQgcHJvdmVkIHVucmVsaWFibGUgb24gc2hhcmVkDQojIHJlYWwtV2luZG93cyBDSSAoc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kJ3MgInRoaXJkIGJvdW5kZWQtbGF1bmNoIGhlbHBlciIgZW50cnkpLg0KIw0KIyBDYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRVhFX0hJTlRfUkVSVU4gcGF5bG9hZCBpbiBydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nOg0KIyBgcHl0aG9uIHRvb2xzL3N5bmNfcGF5bG9hZC5weSBIUF9FWEVfSElOVF9SRVJVTiB0b29scy9leGVfaGludF9yZXJ1bi5wczFgLiBQYXlsb2FkU3luYyBpbg0KIyB0ZXN0cy90ZXN0X2V4ZV9oaW50X3JlcnVuLnB5IGFzc2VydHMgdGhlIG1hdGNoIChDUkxGL0xGIG5vcm1hbGl6ZWQpLg0KJGV4ZSA9ICRlbnY6SFBfSElOVF9SRVJVTl9FWEUNCiRvdXRQYXRoID0gJGVudjpIUF9ISU5UX1JFUlVOX09VVA0KaWYgKC1ub3QgJG91dFBhdGgpIHsgJG91dFBhdGggPSAnfmV4ZV9vdXQudHh0JyB9DQoka2lsbE1zID0gMTAwMDANCmlmICgkZW52OkhQX0hJTlRfUkVSVU5fS0lMTF9NUykgeyAka2lsbE1zID0gW2ludF0kZW52OkhQX0hJTlRfUkVSVU5fS0lMTF9NUyB9DQoka2lsbE1zT3V0UGF0aCA9ICRlbnY6SFBfSElOVF9SRVJVTl9LSUxMTVNfT1VUDQppZiAoLW5vdCAka2lsbE1zT3V0UGF0aCkgeyAka2lsbE1zT3V0UGF0aCA9ICd+ZXhlX2hpbnRfa2lsbG1zLnR4dCcgfQ0KIiRraWxsTXMiIHwgU2V0LUNvbnRlbnQgLVBhdGggJGtpbGxNc091dFBhdGggLUVuY29kaW5nIEFTQ0lJDQoNCiRzaSA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3NTdGFydEluZm8NCiRzaS5GaWxlTmFtZSA9ICRleGUNCiRzaS5Vc2VTaGVsbEV4ZWN1dGUgPSAkZmFsc2UNCiRzaS5SZWRpcmVjdFN0YW5kYXJkT3V0cHV0ID0gJHRydWUNCiRzaS5SZWRpcmVjdFN0YW5kYXJkRXJyb3IgPSAkdHJ1ZQ0KJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzDQokcC5TdGFydEluZm8gPSAkc2kNCiRwLlN0YXJ0KCkgfCBPdXQtTnVsbA0KDQokb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRUb0VuZEFzeW5jKCkNCiRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkVG9FbmRBc3luYygpDQojICRkcmFpbk1zIGJvdW5kcyB0YXNra2lsbC5leGUsIHRoZSBwb3N0LWtpbGwgV2FpdEZvckV4aXQsIEFORCB0aGUgcGlwZSBkcmFpbiBiZWxvdyAtLSBtb3ZlZCB1cA0KIyBzbyBpdCdzIGRlZmluZWQgYmVmb3JlIGZpcnN0IHVzZS4NCiRkcmFpbk1zID0gNTAwMA0KJGV4aXRlZCA9ICRwLldhaXRGb3JFeGl0KCRraWxsTXMpDQppZiAoLW5vdCAkZXhpdGVkKSB7DQogICAgIyBQcm9jZXNzLktpbGwoKSAoV2luIFBTIDUuMSA9IC5ORVQgRnJhbWV3b3JrLCBubyBlbnRpcmVQcm9jZXNzVHJlZSBvdmVybG9hZCkgb25seSBraWxscyAkcDsNCiAgICAjIGEgY2hpbGQgaW5oZXJpdGluZyB0aGUgcmVkaXJlY3RlZCBwaXBlcyBjb3VsZCBvdXRsaXZlIGl0IGFuZCBoYW5nIFJlYWRUb0VuZEFzeW5jIGZvcmV2ZXIuDQogICAgIyB0YXNra2lsbCAvVCBraWxscyB0aGUgd2hvbGUgdHJlZSAoY29uZmlybWVkIGV4ZXJjaXNlZCBvbiByZWFsIENJLCBQUiAjNDEwKS4NCiAgICAjDQogICAgIyBkZXJpdmVkIHJlcXVpcmVtZW50OiB0YXNra2lsbC5leGUgbm93IHJ1bnMgdmlhIGl0cyBvd24gUHJvY2VzcyBvYmplY3Qgd2l0aCBhIEJPVU5ERUQNCiAgICAjIFdhaXRGb3JFeGl0LCBhbmQgdGhlIFdhaXRGb3JFeGl0IGJlbG93IGlzIGJvdW5kZWQgdG9vIC0tIGJvdGggdXNlZCB0byBiZSB1bmJvdW5kZWQuIFJlYWwgQ0kNCiAgICAjICh0aHJlZSBydW5zLCAyMDI2LTA4LTA3KSBzaG93ZWQgdGhpcyBoZWxwZXIgdGFraW5nIDQ4LTYwK3MgYWdhaW5zdCBhIDUwMG1zIHRlc3QgZGVhZGxpbmU7DQogICAgIyBzZWUgZG9jcy9hZ2VudC1sZXNzb25zLWxlYXJuZWQubWQncyAidGhpcmQgYm91bmRlZC1sYXVuY2ggaGVscGVyIiBlbnRyeSBmb3IgdGhlIGZ1bGwNCiAgICAjIGluY2lkZW50LiAkcCBoYXMgYWxyZWFkeSBiZWVuIHNlbnQgL0YgL1QgcGx1cyBLaWxsKCksIHNvIGEgc2xvdyBjb25maXJtYXRpb24ganVzdCBtZWFucw0KICAgICMgInN0b3Agd2FpdGluZywiIG5vdCAic3RpbGwgYWxpdmUuIg0KICAgIHRyeSB7DQogICAgICAgICR0a0luZm8gPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvDQogICAgICAgICR0a0luZm8uRmlsZU5hbWUgPSAndGFza2tpbGwuZXhlJw0KICAgICAgICAkdGtJbmZvLkFyZ3VtZW50cyA9ICIvRiAvVCAvUElEICQoJHAuSWQpIg0KICAgICAgICAkdGtJbmZvLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KICAgICAgICAkdGtJbmZvLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KICAgICAgICAkdGtJbmZvLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQogICAgICAgICR0ayA9IFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzc106OlN0YXJ0KCR0a0luZm8pDQogICAgICAgIGlmICgtbm90ICR0ay5XYWl0Rm9yRXhpdCgkZHJhaW5NcykpIHsgdHJ5IHsgJHRrLktpbGwoKSB9IGNhdGNoIHt9IH0NCiAgICB9IGNhdGNoIHt9DQogICAgdHJ5IHsgJHAuS2lsbCgpIH0gY2F0Y2gge30NCiAgICAkcC5XYWl0Rm9yRXhpdCgkZHJhaW5NcykgfCBPdXQtTnVsbA0KfSBlbHNlIHsNCiAgICAkcC5XYWl0Rm9yRXhpdCgpDQp9DQoNCiMgQm91bmRlZCBmaW5hbCByZWFkLCBub3QgYSBibGluZCAuUmVzdWx0IGJsb2NrIC0tIGEgZGVzY2VuZGFudCB0YXNra2lsbC9UIG1pc3NlZCAob3Igc29tZQ0KIyBleG90aWMgaGFuZGxlLWluaGVyaXRhbmNlIGVkZ2UgY2FzZSkgbXVzdCBub3QgaGFuZyB0aGlzIGRpYWdub3N0aWMtb25seSBoZWxwZXIuIFRhc2suV2FpdChtcykNCiMgcmV0dXJucyBmYWxzZSBvbiB0aW1lb3V0IHdpdGhvdXQgdGhyb3dpbmc7IGEgc3R1Y2sgcGlwZSBkZWdyYWRlcyB0byBwYXJ0aWFsL2VtcHR5IG91dHB1dC4NCiRvdXQgPSAnJw0KJGVyciA9ICcnDQppZiAoJG91dFRhc2suV2FpdCgkZHJhaW5NcykpIHsgdHJ5IHsgJG91dCA9ICRvdXRUYXNrLlJlc3VsdCB9IGNhdGNoIHt9IH0NCmlmICgkZXJyVGFzay5XYWl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkZXJyID0gJGVyclRhc2suUmVzdWx0IH0gY2F0Y2gge30gfQ0KKCRvdXQgKyAkZXJyKSB8IFNldC1Db250ZW50IC1QYXRoICRvdXRQYXRoIC1FbmNvZGluZyBBU0NJSQ0K" set "HP_INSTALLER_TIMEOUT=IyBSdW5zIGFuIGV4dGVybmFsIGluc3RhbGxlciBleGVjdXRhYmxlIHdpdGggYSBnZW5lcm91cywgY29uZmlndXJhYmxlIHRpbWVvdXQgY2VpbGluZy4KIwojIENsb3NlcyBDTEFVREUubWQgQWN0aXZlIEJhY2tsb2cgaXRlbSAxNDogdGhlIHRocmVlICJzdGFydCAiIiAvd2FpdCIgZXh0ZXJuYWwtaW5zdGFsbGVyCiMgbGF1bmNoZXMgKE1pbmljb25kYSBBbGxVc2VycywgTWluaWNvbmRhIEp1c3RNZSwgTkktVklTQSkgcHJldmlvdXNseSBoYWQgbm8gcHJvY2Vzcy1sZXZlbAojIHRpbWVvdXQgYXQgYWxsLCB1bmxpa2UgdGhpcyBmaWxlJ3MgZGVsaWJlcmF0ZWx5LXdyYXBwZWQgdXNlci1jb2RlIGxhdW5jaGVzIC0tIGEgZ2VudWluZWx5CiMgc3R1Y2sgaW5zdGFsbGVyIChhIHNpbGVudGx5LWJsb2NraW5nIFVBQy9yZWJvb3QgcHJvbXB0LCBhIHdlZGdlZCBzdWItaW5zdGFsbGVyKSBjb3VsZCBoYW5nCiMgdGhlIHdob2xlIGJvb3RzdHJhcCBmb3JldmVyIHdpdGggemVybyByZWNvdXJzZS4KIwojIERlbGliZXJhdGVseSBOT1QgbW9kZWxlZCBvbiB0b29scy9leGVfc21va2VydW4ucHMxJ3MgfjMwcyBhZ2dyZXNzaXZlIGtpbGwgd2luZG93IC0tIHRoZXNlIGFyZQojIFJFQUwgaW5zdGFsbGVyIHByb2Nlc3NlcywgYW5kIGtpbGxpbmcgb25lIHRvbyBlYXJseSAod2hpbGUgaXQncyBzdGlsbCBsZWdpdGltYXRlbHkgd3JpdGluZwojIGZpbGVzL3JlZ2lzdHJ5IGtleXMpIHJpc2tzIGEgd29yc2Ugb3V0Y29tZSB0aGFuIGEgc2xvdy1idXQtc3VjY2VlZGluZyBpbnN0YWxsOiBhIGdlbnVpbmVseQojIGNvcnJ1cHRlZCBoYWxmLWluc3RhbGxlZCB0YXJnZXQuIFRoZSB0aW1lb3V0IGhlcmUgaXMgYSBnZW5lcm91cyBzYWZldHkgQ0VJTElORyBhZ2FpbnN0IGEgdHJ1bHkKIyBodW5nIHByb2Nlc3MsIG5vdCBhIHJlc3BvbnNpdmVuZXNzIGNoZWNrIC0tIHNlZSB0aGUgcGVyLWNhbGwtc2l0ZSB0aW1lb3V0IHZhbHVlcyBpbgojIHJ1bl9zZXR1cC5iYXQncyBvd24gY29tbWVudHMgZm9yIHRoZSByZWFsLXdvcmxkIHJlc2VhcmNoIGJlaGluZCBlYWNoIG51bWJlciAoZG9jdW1lbnRlZAojIE1pbmljb25kYS9OSS1WSVNBIGluc3RhbGwtdGltZSByZXBvcnRzLCBub3QgZ3Vlc3NlcykuCiMKIyBJbnB1dHMgdmlhIGVudiB2YXJzIChhdm9pZHMgY21kLmV4ZSBxdW90aW5nIGhhemFyZHMsIG1hdGNoZXMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIGNvbnRyYWN0KToKIyAgIEhQX0lOU1RBTExFUl9FWEUgICAgICAgICAgLSBwYXRoIHRvIHRoZSBpbnN0YWxsZXIgZXhlY3V0YWJsZS4KIyAgIEhQX0lOU1RBTExFUl9BUkdTICAgICAgICAgLSBhIHNpbmdsZSwgYWxyZWFkeS1wcmVwYXJlZCBBcmd1bWVudHMgc3RyaW5nIChjYWxsZXIncwojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zaWJpbGl0eSB0byBxdW90ZS9qb2luIHRva2VuczsgbWF0Y2hlcyBIUF9QUk9CRV9BUkdTJ3MKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3duICJjYWxsZXIgcHJvdmlkZXMgYSByZWFkeSBzdHJpbmciIGNvbnRyYWN0KS4KIyAgIEhQX0lOU1RBTExFUl9USU1FT1VUX01TICAgLSB0aW1lb3V0IGluIG1pbGxpc2Vjb25kcyBiZWZvcmUgdGhlIHByb2Nlc3MgaXMgZm9yY2Uta2lsbGVkLgojICAgSFBfSU5TVEFMTEVSX1JFU1VMVCAgICAgICAtIG91dHB1dCByZXN1bHQgZmlsZSBwYXRoIChkZWZhdWx0IH5pbnN0YWxsZXJfcmVzdWx0LnR4dCksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdyaXR0ZW4gYXMgIjxleGl0Y29kZT58PHRpbWVkb3V0OjAvMT4iLgojCiMgVXNlU2hlbGxFeGVjdXRlPSR0cnVlIChub3QgJGZhbHNlKSBpcyBkZWxpYmVyYXRlOiBpdCBwcmVzZXJ2ZXMgdGhlIHNhbWUgVUFDLWVsZXZhdGlvbi12aWEtCiMgbWFuaWZlc3QgYmVoYXZpb3IgInN0YXJ0ICIiIC93YWl0IiBhbHJlYWR5IGhhZCAoU2hlbGxFeGVjdXRlIGlzIHdoYXQgc3VwcG9ydHMgYW4gZXhlJ3Mgb3duCiMgInJlcXVpcmVBZG1pbmlzdHJhdG9yIiBtYW5pZmVzdCBzaGltIHRyYW5zcGFyZW50bHkpLiBObyBzdGRvdXQvc3RkZXJyIHJlZGlyZWN0aW9uIGlzIG5lZWRlZCAtLQojIHRoZXNlIGluc3RhbGxlcnMgcnVuIHNpbGVudGx5ICgvUywgLS1xdWlldCkgLS0gc28gdGhpcyBkb2Vzbid0IGNvc3QgYW55dGhpbmcgbXkgY2FsbGVyIG5lZWRzLgojCiMgT24gdGltZW91dCwgdXNlcyB0YXNra2lsbCAvRiAvVCAobm90ICRwLktpbGwoKSkgZGVsaWJlcmF0ZWx5OiBXaW5kb3dzIFBvd2VyU2hlbGwgNS4xJ3MKIyBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzcy5LaWxsKCkgKC5ORVQgRnJhbWV3b3JrKSBoYXMgbm8gImtpbGwgdGhlIHdob2xlIHByb2Nlc3MgdHJlZSIKIyBvdmVybG9hZCAtLSB0aGF0J3MgYSAuTkVUIENvcmUgMy4wKyBhZGRpdGlvbiB0aGlzIHJ1bnRpbWUgZG9lc24ndCBoYXZlIC0tIHNvIGl0IHdvdWxkIG9ubHkKIyBraWxsIHRoZSBkaXJlY3QgcHJvY2VzcywgcG90ZW50aWFsbHkgb3JwaGFuaW5nIGNoaWxkIHN1Yi1pbnN0YWxsZXIgcHJvY2Vzc2VzICh0aGlzIHJlcG8ncyBvd24KIyBOSS1WSVNBIGNvbW1lbnQgYWxyZWFkeSBub3RlcyAiTkkgaW5zdGFsbGVycyBtYXkgc3Bhd24gY2hpbGQgcHJvY2Vzc2VzIikuIHRhc2traWxsJ3MgL1QgZmxhZwojIGtpbGxzIHRoZSBmdWxsIHByb2Nlc3MgdHJlZSByZWdhcmRsZXNzIG9mIC5ORVQgcnVudGltZSB2ZXJzaW9uLgojCiMgVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0lOU1RBTExFUl9USU1FT1VUIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluCiMgcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtc3luYyB3aXRoIHRvb2xzL3N5bmNfcGF5bG9hZC5weTsgdGVzdHMvdGVzdF9ydW5faW5zdGFsbGVyXwojIHdpdGhfdGltZW91dC5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlIChDUkxGL0xGIG5vcm1hbGl6ZWQsIHBlciB0aGUKIyAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24pLgojCiMgZGVyaXZlZCByZXF1aXJlbWVudDogYSByZWFsIFdpbmRvd3MgQ0kgcnVuIGNhdWdodCBhIGdlbnVpbmUgYnVnIHRoaXMgcmVwbydzIG90aGVyIGVtaXR0ZWQKIyAucHMxIGhlbHBlcnMgbmV2ZXIgaGl0OiB3aGVuIGEgbmF0aXZlIEVYRSBpbnZva2VkIHZpYSB0aGUgIiYiIGNhbGwgb3BlcmF0b3IgKHRhc2traWxsLmV4ZQojIGhlcmUgLS0gdGhlIE9OTFkgbmF0aXZlLWNvbW1hbmQgaW52b2NhdGlvbiBpbiB0aGlzIGZpbGU7IGV4ZV9zbW9rZXJ1bi5wczEvZmFpbGZhc3RfcHJvYmUucHMxCiMgb25seSBldmVyIGxhdW5jaCB0aGVpciBtb25pdG9yZWQgcHJvY2VzcyB2aWEgLk5FVCdzIFByb2Nlc3MgQVBJLCB3aGljaCBkb2VzIG5vdCBzZXQKIyAkTEFTVEVYSVRDT0RFKSBzZXRzICRMQVNURVhJVENPREUgdG8gYSBub256ZXJvIHZhbHVlLCBwd3NoIC1GaWxlIHNpbGVudGx5IGluaGVyaXRzIHRoYXQgYXMKIyBJVFMgT1dOIHByb2Nlc3MgZXhpdCBjb2RlIHdoZW4gdGhlIHNjcmlwdCBlbmRzIHdpdGhvdXQgYW4gZXhwbGljaXQgImV4aXQiIHN0YXRlbWVudCAtLSBldmVuCiMgdGhvdWdoIHRhc2traWxsIHdhcyBub3QgdGhlIGxhc3Qgc3RhdGVtZW50IGV4ZWN1dGVkIGFuZCBpdHMgb3duIGZhaWx1cmUgd2FzIGFscmVhZHkgY2F1Z2h0CiMgYW5kIHN3YWxsb3dlZCBieSB0cnkvY2F0Y2guIFRoaXMgaXMgYSB3ZWxsLWtub3duIFBvd2VyU2hlbGwgZ290Y2hhLCBub3Qgc3BlY2lmaWMgdG8gdGFza2tpbGw6CiMgYW55IGVhcmxpZXIgbmF0aXZlLWNvbW1hbmQgZmFpbHVyZSBjYW4gbGVhayB0aHJvdWdoIGFzIHRoZSB3aG9sZSBzY3JpcHQncyBleGl0IGNvZGUgdW5sZXNzCiMgZXhwbGljaXRseSByZXNldC4gdGFza2tpbGwgY2FuIGxlZ2l0aW1hdGVseSByZXR1cm4gbm9uemVybyBoZXJlIChlLmcuIHRoZSB0aW1lZC1vdXQgcHJvY2VzcwojIGFscmVhZHkgZXhpdGVkIG9uIGl0cyBvd24gaW4gdGhlIHJhY2UgYmV0d2VlbiBXYWl0Rm9yRXhpdCh0aW1lb3V0TXMpIHJldHVybmluZyBmYWxzZSBhbmQKIyB0YXNra2lsbCBhY3R1YWxseSBydW5uaW5nKSB3aXRob3V0IHRoYXQgYmVpbmcgYSByZWFsIHByb2JsZW0gLS0gdGhlIHJlc3VsdCBGSUxFIChub3QgdGhlCiMgc2NyaXB0J3Mgb3duIHByb2Nlc3MgZXhpdCBjb2RlKSBpcyB0aGlzIHNjcmlwdCdzIHJlYWwgY29udHJhY3Qgd2l0aCBpdHMgY2FsbGVyLCBzbyB0aGUgZml4IGlzCiMgYW4gZXhwbGljaXQgImV4aXQgMCIgYXMgdGhlIHNjcmlwdCdzIGxhc3Qgc3RhdGVtZW50LCBndWFyYW50ZWVpbmcgcHdzaCdzIG93biBleGl0IGNvZGUgaXMKIyBhbHdheXMgY2xlYW4gcmVnYXJkbGVzcyBvZiB3aGF0ICRMQVNURVhJVENPREUgaGFwcGVuZWQgdG8gYmUgbGVmdCBob2xkaW5nLgokZXhlID0gJGVudjpIUF9JTlNUQUxMRVJfRVhFCiRhcmdTdHIgPSAkZW52OkhQX0lOU1RBTExFUl9BUkdTCiR0aW1lb3V0TXMgPSBbaW50XSRlbnY6SFBfSU5TVEFMTEVSX1RJTUVPVVRfTVMKJHJlc3VsdFBhdGggPSAkZW52OkhQX0lOU1RBTExFUl9SRVNVTFQKaWYgKC1ub3QgJHJlc3VsdFBhdGgpIHsgJHJlc3VsdFBhdGggPSAnfmluc3RhbGxlcl9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRhcmdTdHIpIHsgJHNpLkFyZ3VtZW50cyA9ICRhcmdTdHIgfQokc2kuVXNlU2hlbGxFeGVjdXRlID0gJHRydWUKJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzCiRwLlN0YXJ0SW5mbyA9ICRzaQokcC5TdGFydCgpIHwgT3V0LU51bGwKCmlmICgkcC5XYWl0Rm9yRXhpdCgkdGltZW91dE1zKSkgewogICAgIiQoJHAuRXhpdENvZGUpfDAiIHwgU2V0LUNvbnRlbnQgLVBhdGggJHJlc3VsdFBhdGggLUVuY29kaW5nIEFTQ0lJCn0gZWxzZSB7CiAgICB0cnkgeyAmIHRhc2traWxsLmV4ZSAvRiAvVCAvUElEICRwLklkIDI+JG51bGwgfCBPdXQtTnVsbCB9IGNhdGNoIHt9CiAgICAiMXwxIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9CmV4aXQgMAo=" :: --- Embedded helper: HP_PREP_REQUIREMENTS (~prep_requirements.py) --- :: Purpose: :: - Normalize pip/conda specifiers from requirements.txt :: - Apply heuristic extras (REQ-005.8) :: :: Current heuristics applied (set HP_DISABLE_HEURISTICS=1 to skip): :: REQ-005.8.1 -- pandas -> openpyxl (+ xlsxwriter) :: REQ-005.8.2 -- requests -> certifi :: REQ-005.8.3 -- sqlalchemy -> pymysql :: REQ-005.8.4 -- matplotlib -> tk :: REQ-005.8.5 -- cryptography/pycryptodome -> cffi :: :: Log format: [HEURISTIC] target> (emitted to stderr -> ~setup.log) :: Tests: selfapps_pandas_excel.ps1 validates REQ-005.8.1 translation :: -------------------------------------------------------------------- set "HP_PREP_REQUIREMENTS=T1BfT1JERVIgPSAoIj09IiwgIiE9IiwgIj49IiwgIj4iLCAiPD0iLCAiPCIpCk9QX1JBTksgPSB7b3A6IGlkeCBmb3IgaWR4LCBvcCBpbiBlbnVtZXJhdGUoT1BfT1JERVIpfQoKaW1wb3J0IG9zCmltcG9ydCByZQppbXBvcnQgc3lzCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IE9yZGVyZWREaWN0CgpJTlAgPSBzeXMuYXJndlsxXSBpZiBsZW4oc3lzLmFyZ3YpID4gMSBlbHNlICJyZXF1aXJlbWVudHMudHh0IgpPVVRfQ09OREEgPSAifnJlcXNfY29uZGEudHh0IgpPVVRfUElQID0gIn5yZXFzX3BpcC50eHQiClNQRUNfUEFUVEVSTiA9IHJlLmNvbXBpbGUociIofj18PT18IT18Pj18Pnw8PXw8KVxzKihbXlxzLDtdKylccyokIikKTkFNRV9QQVRURVJOID0gcmUuY29tcGlsZShyIl5ccyooW0EtWmEtejAtOV8uLV0rKVxzKiguKikkIikKCmRlZiBzcGxpdF9tYXJrZXIodGV4dDogc3RyKSAtPiBzdHI6CiAgICByZXR1cm4gdGV4dC5zcGxpdCgiOyIpWzBdLnN0cmlwKCkKCmRlZiBfdmVyc2lvbl9rZXkodGV4dDogc3RyKToKICAgIHBhcnRzID0gW10KICAgIGZvciBjaHVuayBpbiB0ZXh0LnNwbGl0KCcuJyk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBwYXJ0cy5hcHBlbmQoaW50KGNodW5rKSkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvcjoKICAgICAgICAgICAgcGFydHMuYXBwZW5kKDApCiAgICByZXR1cm4gdHVwbGUocGFydHMpCgpkZWYgX2J1bXBfY29tcGF0aWJsZSh2YWx1ZTogc3RyKSAtPiBzdHI6CiAgICBwaWVjZXMgPSB2YWx1ZS5zcGxpdCgnLicpCiAgICBpZiBub3QgcGllY2VzIG9yIG5vdCBwaWVjZXNbMF0uaXNkaWdpdCgpOgogICAgICAgIHJldHVybiB2YWx1ZQogICAgbWFqb3IgPSBpbnQocGllY2VzWzBdKQogICAgaWYgbGVuKHBpZWNlcykgPj0gMyBhbmQgcGllY2VzWzFdLmlzZGlnaXQoKToKICAgICAgICByZXR1cm4gZiJ7bWFqb3J9LntpbnQocGllY2VzWzFdKSArIDF9IgogICAgaWYgbGVuKHBpZWNlcykgPj0gMjoKICAgICAgICByZXR1cm4gZiJ7bWFqb3IgKyAxfS4wIgogICAgcmV0dXJuIHN0cihtYWpvciArIDEpCgpkZWYgX2V4cGFuZF9mcmFnbWVudChmcmFnbWVudDogc3RyKToKICAgIGlmIG5vdCBmcmFnbWVudDoKICAgICAgICByZXR1cm4gW10KICAgIHZhbHVlID0gZnJhZ21lbnQuc3RyaXAoKQogICAgaWYgbm90IHZhbHVlOgogICAgICAgIHJldHVybiBbXQogICAgbWF0Y2ggPSBTUEVDX1BBVFRFUk4uZnVsbG1hdGNoKHZhbHVlKQogICAgaWYgbm90IG1hdGNoOgogICAgICAgIHJldHVybiBbXQogICAgb3AsIHZlciA9IG1hdGNoLmdyb3VwcygpCiAgICB2ZXIgPSB2ZXIuc3RyaXAoKQogICAgaWYgbm90IHZlcjoKICAgICAgICByZXR1cm4gW10KICAgIGlmIG9wID09ICJ+PSI6CiAgICAgICAgdXBwZXIgPSBfYnVtcF9jb21wYXRpYmxlKHZlcikKICAgICAgICByZXR1cm4gW2YiPj17dmVyfSIsIGYiPHt1cHBlcn0iXQogICAgcmV0dXJuIFtmIntvcH17dmVyfSJdCgpkZWYgY2Fub25pY2FsX29wcyhzcGVjcykgLT4gbGlzdDoKICAgIGJ1Y2tldCA9IE9yZGVyZWREaWN0KCkKICAgIGZvciByYXcgaW4gc3BlY3M6CiAgICAgICAgZm9yIG5vcm1hbGl6ZWQgaW4gX2V4cGFuZF9mcmFnbWVudChyYXcpOgogICAgICAgICAgICBidWNrZXRbbm9ybWFsaXplZF0gPSBOb25lCiAgICBvcmRlcmVkID0gbGlzdChidWNrZXQua2V5cygpKQogICAgb3JkZXJlZC5zb3J0KGtleT1fc3BlY19zb3J0X2tleSkKICAgIHJldHVybiBfZW5mb3JjZV9ib3VuZHNfb3JkZXIob3JkZXJlZCkKCmRlZiBfc3BlY19zb3J0X2tleSh2YWx1ZTogc3RyKToKICAgIGZvciBvcCBpbiBPUF9PUkRFUjoKICAgICAgICBpZiB2YWx1ZS5zdGFydHN3aXRoKG9wKToKICAgICAgICAgICAgdmVyID0gdmFsdWVbbGVuKG9wKTpdCiAgICAgICAgICAgIHJldHVybiBPUF9SQU5LW29wXSwgX3ZlcnNpb25fa2V5KHZlciksIHZlcgogICAgcmV0dXJuIGxlbihPUF9PUkRFUiksIF92ZXJzaW9uX2tleSh2YWx1ZSksIHZhbHVlCgpkZWYgX2VuZm9yY2VfYm91bmRzX29yZGVyKGl0ZW1zOiBsaXN0KSAtPiBsaXN0OgogICAgb3BzID0gbGlzdChpdGVtcykKICAgIGxvd2VyX2luZGV4ID0gbmV4dCgoaWR4IGZvciBpZHgsIHRleHQgaW4gZW51bWVyYXRlKG9wcykgaWYgdGV4dC5zdGFydHN3aXRoKCI+PSIpKSwgTm9uZSkKICAgIGlmIGxvd2VyX2luZGV4IGlzIE5vbmU6CiAgICAgICAgcmV0dXJuIG9wcwogICAgZm9yIHVwcGVyX29wIGluICgiPD0iLCAiPCIpOgogICAgICAgIHVwcGVyX2luZGV4ID0gbmV4dCgoaWR4IGZvciBpZHgsIHRleHQgaW4gZW51bWVyYXRlKG9wcykgaWYgdGV4dC5zdGFydHN3aXRoKHVwcGVyX29wKSksIE5vbmUpCiAgICAgICAgaWYgdXBwZXJfaW5kZXggaXMgbm90IE5vbmUgYW5kIHVwcGVyX2luZGV4IDwgbG93ZXJfaW5kZXg6CiAgICAgICAgICAgIHZhbHVlID0gb3BzLnBvcChsb3dlcl9pbmRleCkKICAgICAgICAgICAgb3BzLmluc2VydCh1cHBlcl9pbmRleCwgdmFsdWUpCiAgICAgICAgICAgIGxvd2VyX2luZGV4ID0gdXBwZXJfaW5kZXgKICAgIHJldHVybiBvcHMKCmRlZiBmb3JtYXRfbGluZShuYW1lOiBzdHIsIHNwZWNzKSAtPiBsaXN0OgogICAgb3BzID0gY2Fub25pY2FsX29wcyhzcGVjcykKICAgIHJldHVybiBbZiJ7bmFtZX0gIiArICIsIi5qb2luKG9wcyldIGlmIG9wcyBlbHNlIFtuYW1lXQoKZGVmIHRvX2NvbmRhKGxpbmU6IHN0cik6CiAgICBzZWN0aW9uID0gc3BsaXRfbWFya2VyKGxpbmUpCiAgICBpZiBub3Qgc2VjdGlvbiBvciBzZWN0aW9uLnN0YXJ0c3dpdGgoJyMnKToKICAgICAgICByZXR1cm4gW10KICAgIG1hdGNoID0gTkFNRV9QQVRURVJOLm1hdGNoKHNlY3Rpb24pCiAgICBpZiBub3QgbWF0Y2g6CiAgICAgICAgcmV0dXJuIFtdCiAgICBuYW1lLCByZXN0ID0gbWF0Y2guZ3JvdXBzKCkKICAgIHJlc3QgPSByZS5zdWIociJcWy4qP1xdIiwgIiIsIHJlc3QpCiAgICBzcGVjcyA9IFtjaHVuay5zdHJpcCgpIGZvciBjaHVuayBpbiByZXN0LnNwbGl0KCcsJykgaWYgY2h1bmsuc3RyaXAoKV0KICAgIHJldHVybiBmb3JtYXRfbGluZShuYW1lLCBzcGVjcykKCmRlZiB0b19waXAobGluZTogc3RyKToKICAgIHNlY3Rpb24gPSBzcGxpdF9tYXJrZXIobGluZSkKICAgIGlmIG5vdCBzZWN0aW9uIG9yIHNlY3Rpb24uc3RhcnRzd2l0aCgnIycpOgogICAgICAgIHJldHVybiBOb25lCiAgICBtYXRjaCA9IE5BTUVfUEFUVEVSTi5tYXRjaChzZWN0aW9uKQogICAgaWYgbm90IG1hdGNoOgogICAgICAgIHJldHVybiBzZWN0aW9uLnN0cmlwKCkKICAgIG5hbWUsIHJlc3QgPSBtYXRjaC5ncm91cHMoKQogICAgcmV0dXJuIChuYW1lICsgcmVzdCkuc3RyaXAoKQoKZGVmIG1haW4oKToKICAgIGhhdmVfZmlsZSA9IG9zLnBhdGguZXhpc3RzKElOUCkgYW5kIG9zLnBhdGguZ2V0c2l6ZShJTlApID4gMAogICAgbGluZXMgPSBbXQogICAgaWYgaGF2ZV9maWxlOgogICAgICAgIHdpdGggb3BlbihJTlAsICdyJywgZW5jb2Rpbmc9J3V0Zi04JywgZXJyb3JzPSdpZ25vcmUnKSBhcyBoYW5kbGU6CiAgICAgICAgICAgIGxpbmVzID0gW2l0ZW0uc3RyaXAoKSBmb3IgaXRlbSBpbiBoYW5kbGUgaWYgaXRlbS5zdHJpcCgpXQogICAgY29uZGFfc3BlY3MgPSBbXQogICAgcGlwX3NwZWNzID0gW10KICAgIGZvciBsaW5lIGluIGxpbmVzOgogICAgICAgIGNvbmRhX3NwZWNzLmV4dGVuZCh0b19jb25kYShsaW5lKSkKICAgICAgICBwaXBfZW50cnkgPSB0b19waXAobGluZSkKICAgICAgICBpZiBwaXBfZW50cnk6CiAgICAgICAgICAgIHBpcF9zcGVjcy5hcHBlbmQocGlwX2VudHJ5KQogICAgbmFtZXNfbG93ZXIgPSBbcmUuc3BsaXQociJbPD49IX4sXHNcW10iLCB2YWx1ZSwgbWF4c3BsaXQ9MSlbMF0uc3RyaXAoKS5sb3dlcigpIGZvciB2YWx1ZSBpbiBwaXBfc3BlY3NdCiAgICBuMCA9IGxlbihwaXBfc3BlY3MpCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnSFBfRElTQUJMRV9IRVVSSVNUSUNTJykgIT0gJzEnOgogICAgICAgIGlmICdwYW5kYXMnIGluIG5hbWVzX2xvd2VyIGFuZCAnb3BlbnB5eGwnIG5vdCBpbiBuYW1lc19sb3dlcjoKICAgICAgICAgICAgcGlwX3NwZWNzLmFwcGVuZCgnb3BlbnB5eGwnKQogICAgICAgICAgICBjb25kYV9zcGVjcy5leHRlbmQoZm9ybWF0X2xpbmUoJ29wZW5weXhsJywgW10pKQogICAgICAgICAgICBzeXMuc3RkZXJyLndyaXRlKCdbSEVVUklTVElDXSBwYW5kYXMtPm9wZW5weXhsXG4nKQogICAgICAgIGlmICdwYW5kYXMnIGluIG5hbWVzX2xvd2VyIGFuZCAneGxzeHdyaXRlcicgbm90IGluIG5hbWVzX2xvd2VyOgogICAgICAgICAgICBwaXBfc3BlY3MuYXBwZW5kKCd4bHN4d3JpdGVyJykKICAgICAgICAgICAgY29uZGFfc3BlY3MuZXh0ZW5kKGZvcm1hdF9saW5lKCd4bHN4d3JpdGVyJywgW10pKQogICAgICAgICAgICBzeXMuc3RkZXJyLndyaXRlKCdbSEVVUklTVElDXSBwYW5kYXMtPnhsc3h3cml0ZXJcbicpCiAgICAgICAgaWYgJ3JlcXVlc3RzJyBpbiBuYW1lc19sb3dlciBhbmQgJ2NlcnRpZmknIG5vdCBpbiBuYW1lc19sb3dlcjoKICAgICAgICAgICAgcGlwX3NwZWNzLmFwcGVuZCgnY2VydGlmaScpCiAgICAgICAgICAgIGNvbmRhX3NwZWNzLmV4dGVuZChmb3JtYXRfbGluZSgnY2VydGlmaScsIFtdKSkKICAgICAgICAgICAgc3lzLnN0ZGVyci53cml0ZSgnW0hFVVJJU1RJQ10gcmVxdWVzdHMtPmNlcnRpZmlcbicpCiAgICAgICAgaWYgJ3NxbGFsY2hlbXknIGluIG5hbWVzX2xvd2VyIGFuZCAncHlteXNxbCcgbm90IGluIG5hbWVzX2xvd2VyOgogICAgICAgICAgICBwaXBfc3BlY3MuYXBwZW5kKCdweW15c3FsJykKICAgICAgICAgICAgY29uZGFfc3BlY3MuZXh0ZW5kKGZvcm1hdF9saW5lKCdweW15c3FsJywgW10pKQogICAgICAgICAgICBzeXMuc3RkZXJyLndyaXRlKCdbSEVVUklTVElDXSBzcWxhbGNoZW15LT5weW15c3FsXG4nKQogICAgICAgIGlmICdtYXRwbG90bGliJyBpbiBuYW1lc19sb3dlciBhbmQgJ3RrJyBub3QgaW4gbmFtZXNfbG93ZXI6CiAgICAgICAgICAgIGNvbmRhX3NwZWNzLmV4dGVuZChmb3JtYXRfbGluZSgndGsnLCBbXSkpCiAgICAgICAgICAgIHN5cy5zdGRlcnIud3JpdGUoJ1tIRVVSSVNUSUNdIG1hdHBsb3RsaWItPnRrXG4nKQogICAgICAgIGlmICgnY3J5cHRvZ3JhcGh5JyBpbiBuYW1lc19sb3dlciBvciAncHljcnlwdG9kb21lJyBpbiBuYW1lc19sb3dlcikgYW5kICdjZmZpJyBub3QgaW4gbmFtZXNfbG93ZXI6CiAgICAgICAgICAgIHBpcF9zcGVjcy5hcHBlbmQoJ2NmZmknKQogICAgICAgICAgICBjb25kYV9zcGVjcy5leHRlbmQoZm9ybWF0X2xpbmUoJ2NmZmknLCBbXSkpCiAgICAgICAgICAgIHN5cy5zdGRlcnIud3JpdGUoJ1tIRVVSSVNUSUNdIGNyeXB0by0+Y2ZmaVxuJykKICAgIHdpdGggb3BlbihPVVRfQ09OREEsICd3JywgZW5jb2Rpbmc9J2FzY2lpJykgYXMgaGFuZGxlOgogICAgICAgIGZvciBpdGVtIGluIGNvbmRhX3NwZWNzOgogICAgICAgICAgICBpZiBpdGVtOgogICAgICAgICAgICAgICAgaGFuZGxlLndyaXRlKGl0ZW0gKyAnXG4nKQogICAgd2l0aCBvcGVuKE9VVF9QSVAsICd3JywgZW5jb2Rpbmc9J2FzY2lpJykgYXMgaGFuZGxlOgogICAgICAgIGZvciBpdGVtIGluIHBpcF9zcGVjczoKICAgICAgICAgICAgaWYgaXRlbToKICAgICAgICAgICAgICAgIGhhbmRsZS53cml0ZShpdGVtICsgJ1xuJykKICAgIGFkZGVkID0gcGlwX3NwZWNzW24wOl0KICAgIGlmIGFkZGVkOgogICAgICAgIHdpdGggb3BlbihJTlAsICdhJywgZW5jb2Rpbmc9J3V0Zi04JykgYXMgaGFuZGxlOgogICAgICAgICAgICBoYW5kbGUud3JpdGUoJ1xuJyArICdcbicuam9pbihhZGRlZCkgKyAnXG4nKQogICAgc3lzLnN0ZG91dC53cml0ZSgnT0tcbicpCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgbWFpbigpCg==" set "HP_DETECT_VISA=IiIiZGV0ZWN0X3Zpc2EgKEhQX0RFVEVDVF9WSVNBKSAtLSBzY2FucyB0aGUgcHJvamVjdCBkaXJlY3RvcnkgZm9yCk5JLVZJU0EtaW5zdHJ1bWVudC1jb250cm9sIGltcG9ydHMgKHB5dmlzYSkgdG8gZGVjaWRlIHdoZXRoZXIgdGhlIFJFUS0wMDgKTkktVklTQSBkcml2ZXIgaW5zdGFsbCBicmFuY2ggc2hvdWxkIHJ1bi4KClJ1biBmcm9tIHRoZSBhcHBsaWNhdGlvbiBkaXJlY3Rvcnk7IHJlY3Vyc2l2ZWx5IHdhbGtzIHRoZSBjdXJyZW50IGRpcmVjdG9yeQooc2tpcHBpbmcgIn4iLyIuIi1wcmVmaXhlZCBzdWJkaXJlY3RvcmllcyBhbmQgZmlsZXMsIG1hdGNoaW5nIHRoaXMgcmVwbydzCnVzdWFsIHNjcmF0Y2gtZmlsZSBjb252ZW50aW9uKSBhbmQgcHJpbnRzICIxIiB0byBzdGRvdXQgKG5vIHRyYWlsaW5nCm5ld2xpbmUpIGlmIGFueSBzY2FubmVkIC5weSBmaWxlJ3Mgc291cmNlIG1hdGNoZXMgYSBweXZpc2Etc3R5bGUgaW1wb3J0LAplbHNlICIwIi4KClBBVFRFUk5TIGFuY2hvciBvbiB0aGUgZnVsbCBtb2R1bGUgbmFtZSAoInB5dmlzYSIvInZpc2EiKSB3aXRoIGEgdHJhaWxpbmcKXGIsIG5vdCBhIHRydW5jYXRlZCAicHl2aXMiLyJ2aXMiIHByZWZpeCAtLSB0aGlzIGRlbGliZXJhdGVseSBleGNsdWRlcyBhbgp1bnJlbGF0ZWQgaW1wb3J0IHRoYXQgbWVyZWx5IFNUQVJUUyB3aXRoIHRob3NlIGxldHRlcnMgKGUuZy4gImltcG9ydApweXZpc3RhIiwgYSByZWFsLCBwb3B1bGFyLCB1bnJlbGF0ZWQgM0QtdmlzdWFsaXphdGlvbiBwYWNrYWdlOyAiaW1wb3J0CnZpc2lvbiI7ICJpbXBvcnQgcHl2aXNjb2VsYXN0aWMiKS4gXGIgYWZ0ZXIgdGhlIGZ1bGwgd29yZCBhbHNvIGFsbG93cyBhCmRvdHRlZCBzdWJtb2R1bGUgaW1wb3J0ICgiaW1wb3J0IHB5dmlzYS5jb25zdGFudHMiKSB0byBzdGlsbCBtYXRjaCwgc2luY2UKIi4iIGlzIGEgbm9uLXdvcmQgY2hhcmFjdGVyLiBOb3RlOiBhbmNob3JpbmcgXGIgcmlnaHQgYWZ0ZXIgdGhlIHRydW5jYXRlZAoidmlzIi8icHl2aXMiIHByZWZpeCAocmF0aGVyIHRoYW4gdGhlIGZ1bGwgd29yZCkgd291bGQgTk9UIHdvcmsgaGVyZSAtLSBcYgpvbmx5IG1hdGNoZXMgYXQgYSB3b3JkL25vbi13b3JkIHRyYW5zaXRpb24sIGFuZCB0aGVyZSBpcyBubyBzdWNoIGJvdW5kYXJ5CmJldHdlZW4gInZpcyIgYW5kIHRoZSAiYSIgaW4gInZpc2EiIChib3RoIGFyZSB3b3JkIGNoYXJhY3RlcnMpLCBzbyBhCiJ2aXNcYiIgcGF0dGVybiB3b3VsZCBmYWlsIHRvIG1hdGNoIHRoZSBnZW51aW5lICJpbXBvcnQgdmlzYSIgY2FzZS4KClRoaXMgaXMgdGhlIGNhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9ERVRFQ1RfVklTQSBiYXNlNjQgcGF5bG9hZCBlbWJlZGRlZAppbiBydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlCmBzZXQgIkhQX0RFVEVDVF9WSVNBPS4uLiJgIGxpbmU7IHRlc3RzL3Rlc3RfZGV0ZWN0X3Zpc2EucHkgYXNzZXJ0cyB0aGUKZW1iZWRkZWQgcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZS4KIiIiCmltcG9ydCBvcywgcmUsIHN5cwoKUk9PVCA9IG9zLmdldGN3ZCgpClBBVFRFUk5TID0gWwogICAgciIoP20pXlxzKig/OmZyb21ccytweXZpc2FcYnxpbXBvcnRccytweXZpc2FcYikiLAogICAgciIoP20pXlxzKmltcG9ydFxzK3Zpc2FcYiIsCl0KCmRlZiBuZWVkc192aXNhKCk6CiAgICBmb3IgY3VycmVudCwgZGlycywgZmlsZXMgaW4gb3Mud2FsayhST09UKToKICAgICAgICBkaXJzWzpdID0gW2l0ZW0gZm9yIGl0ZW0gaW4gZGlycyBpZiBub3QgaXRlbS5zdGFydHN3aXRoKCgnficsICcuJykpXQogICAgICAgIGZvciBuYW1lIGluIGZpbGVzOgogICAgICAgICAgICBpZiBub3QgbmFtZS5lbmRzd2l0aCgnLnB5Jykgb3IgbmFtZS5zdGFydHN3aXRoKCd+Jyk6CiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwYXRoID0gb3MucGF0aC5qb2luKGN1cnJlbnQsIG5hbWUpCiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihwYXRoLCAncicsIGVuY29kaW5nPSd1dGYtOCcsIGVycm9ycz0naWdub3JlJykgYXMgaGFuZGxlOgogICAgICAgICAgICAgICAgICAgIHRleHQgPSBoYW5kbGUucmVhZCgpCiAgICAgICAgICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgZm9yIHBhdHRlcm4gaW4gUEFUVEVSTlM6CiAgICAgICAgICAgICAgICBpZiByZS5zZWFyY2gocGF0dGVybiwgdGV4dCk6CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgIHJldHVybiBGYWxzZQoKZGVmIG1haW4oKToKICAgIHN5cy5zdGRvdXQud3JpdGUoJzEnIGlmIG5lZWRzX3Zpc2EoKSBlbHNlICcwJykKCmlmIF9fbmFtZV9fID09ICdfX21haW5fXyc6CiAgICBtYWluKCkK" rem ~find_entry.py emits a normalized crumb, logs it for tests, and skip mode reads its stdout set "HP_FIND_ENTRY=IiIiZmluZF9lbnRyeSAoUkVRLTAwMikgLS0gZGV0ZXJtaW5pc3RpYyBlbnRyeS1wb2ludCBzZWxlY3Rvci4KClJ1biBmcm9tIHRoZSBhcHBsaWNhdGlvbiBkaXJlY3Rvcnk7IHByaW50cyB0aGUgY2hvc2VuIGVudHJ5IHNjcmlwdCAob25lIGxpbmUpIHRvCnN0ZG91dCBhbmQgZGlhZ25vc3RpYyBbQk9PVF0gbGluZXMgdG8gc3RkZXJyLiBTZWxlY3Rpb24gb3JkZXI6CgogIDEuIFBSRUZFUlJFRCBuYW1lIG1hdGNoIChtYWluLnB5ID4gYXBwLnB5ID4gcnVuLnB5ID4gY2xpLnB5KS4KICAyLiBUaGUgc29sZSAucHkgZmlsZSwgd2hlbiB0aGVyZSBpcyBleGFjdGx5IG9uZS4KICAzLiBUaGUgc29sZSBmaWxlIGNvbnRhaW5pbmcgYSBzdWJzdGFudGl2ZSBgaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjpgIGd1YXJkLgogIDQuIERldGVybWluaXN0aWMgYWxwaGFiZXRpY2FsIGZhbGxiYWNrIC0tIHByZWZlciBmaWxlcyB0aGF0IGRlY2xhcmVkIGEgX19tYWluX18KICAgICBndWFyZCwgZWxzZSBhbnkgLnB5IGZpbGUgLS0gc28gc29tZXRoaW5nIGFsd2F5cyBydW5zL2J1aWxkcyByYXRoZXIgdGhhbiB0aGUKICAgICBlbnRyeSByZXNvbHZpbmcgdG8gZW1wdHkgKHdoaWNoIHNpbGVudGx5IHNraXBzIHJ1biArIHBhY2thZ2luZykuCgpUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRklORF9FTlRSWSBiYXNlNjQgcGF5bG9hZCBlbWJlZGRlZCBpbgpydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlIGBzZXQgIkhQX0ZJTkRfRU5UUlk9Li4uImAKbGluZTsgdGVzdHMvdGVzdF9maW5kX2VudHJ5LnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUuCiIiIgppbXBvcnQgYXN0CmltcG9ydCBvcwppbXBvcnQgc3lzCgpQUkVGRVJSRUQgPSAoIm1haW4ucHkiLCAiYXBwLnB5IiwgInJ1bi5weSIsICJjbGkucHkiKQoKCmRlZiBpc19weShuYW1lKToKICAgIGxvd2VyID0gbmFtZS5sb3dlcigpCiAgICAjICIuXyItcHJlZml4OiBtYWNPUyBBcHBsZURvdWJsZSBtZXRhZGF0YSBmaWxlcyAoZS5nLiAiLl9tYWluLnB5IiksIGEgY29tbW9uCiAgICAjIGNyb3NzLXBsYXRmb3JtIHBhcGVyY3V0IHdoZW4gYSBXaW5kb3dzIHVzZXIgdW56aXBzIHNvbWV0aGluZyBhIE1hYyB1c2VyIHppcHBlZC4KICAgIHJldHVybiAoCiAgICAgICAgbG93ZXIuZW5kc3dpdGgoIi5weSIpCiAgICAgICAgYW5kIG5vdCBsb3dlci5zdGFydHN3aXRoKCJ+IikKICAgICAgICBhbmQgbm90IGxvd2VyLnN0YXJ0c3dpdGgoIi5fIikKICAgICAgICBhbmQgb3MucGF0aC5pc2ZpbGUobmFtZSkKICAgICkKCgpkZWYgX2lzX21haW5fZ3VhcmQodGVzdCk6CiAgICAiIiJUcnVlIGlmIGFuIElmIHRlc3QgaXMgYF9fbmFtZV9fID09ICJfX21haW5fXyJgIChlaXRoZXIgb3BlcmFuZCBvcmRlcikuIiIiCiAgICBpZiBub3QgaXNpbnN0YW5jZSh0ZXN0LCBhc3QuQ29tcGFyZSkgb3IgbGVuKHRlc3Qub3BzKSAhPSAxOgogICAgICAgIHJldHVybiBGYWxzZQogICAgaWYgbm90IGlzaW5zdGFuY2UodGVzdC5vcHNbMF0sIGFzdC5FcSk6CiAgICAgICAgcmV0dXJuIEZhbHNlCiAgICBuYW1lcyA9IHNldCgpCiAgICBjb25zdHMgPSBzZXQoKQogICAgZm9yIHNpZGUgaW4gKHRlc3QubGVmdCwgdGVzdC5jb21wYXJhdG9yc1swXSk6CiAgICAgICAgaWYgaXNpbnN0YW5jZShzaWRlLCBhc3QuTmFtZSk6CiAgICAgICAgICAgIG5hbWVzLmFkZChzaWRlLmlkKQogICAgICAgIGVsaWYgaXNpbnN0YW5jZShzaWRlLCBhc3QuQ29uc3RhbnQpOgogICAgICAgICAgICBjb25zdHMuYWRkKHNpZGUudmFsdWUpCiAgICByZXR1cm4gIl9fbmFtZV9fIiBpbiBuYW1lcyBhbmQgIl9fbWFpbl9fIiBpbiBjb25zdHMKCgpkZWYgX2lzX3N1YnN0YW50aXZlKHN0bXQpOgogICAgIiIiQSBzdGF0ZW1lbnQgdGhhdCBkb2VzIHJlYWwgd29yayAobm90IHBhc3MgLyBkb2NzdHJpbmcgLyBiYXJlIC4uLikuIiIiCiAgICBpZiBpc2luc3RhbmNlKHN0bXQsIGFzdC5QYXNzKToKICAgICAgICByZXR1cm4gRmFsc2UKICAgIGlmIGlzaW5zdGFuY2Uoc3RtdCwgYXN0LkV4cHIpIGFuZCBpc2luc3RhbmNlKHN0bXQudmFsdWUsIGFzdC5Db25zdGFudCk6CiAgICAgICAgcmV0dXJuIEZhbHNlCiAgICByZXR1cm4gVHJ1ZQoKCmRlZiBoYXNfbWFpbihwYXRoKToKICAgICIiIlRydWUgaWYgdGhlIGZpbGUgaGFzIGFuIGBpZiBfX25hbWVfXyA9PSAiX19tYWluX18iOmAgZ3VhcmQgdGhhdCBkb2VzIHJlYWwgd29yay4KCiAgICBBIGd1YXJkIHdob3NlIGJvZHkgaXMgZXhjbHVzaXZlbHkgcGFzcy9jb21tZW50cy9kb2NzdHJpbmcvYC4uLmAgZG9lcyBub3QgY291bnQKICAgIChpdCBzZWxmLWlkZW50aWZpZXMgdGhlIG1vZHVsZSBhcyBub24tcnVubmFibGUpLCBzbyBhIHNpYmxpbmcgcmVhbCBlbnRyeSB3aW5zLgogICAgIiIiCiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKHBhdGgsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBoYW5kbGU6CiAgICAgICAgICAgIHNyYyA9IGhhbmRsZS5yZWFkKCkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcmV0dXJuIEZhbHNlCiAgICB0cnk6CiAgICAgICAgdHJlZSA9IGFzdC5wYXJzZShzcmMpCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICMgVW5wYXJzZWFibGUgc291cmNlIGlzIGEgcG9vciBlbnRyeSBjYW5kaWRhdGU7IGRvIG5vdCByZXN1cnJlY3QgdGhlIG9sZAogICAgICAgICMgc3Vic3RyaW5nIGhldXJpc3RpYyAod2hpY2ggbWlzLWZpcmVkIG9uICJfX21haW5fXyIgaW4gc3RyaW5ncy9jb21tZW50cykuCiAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAjIE9ubHkgYSBtb2R1bGUtbGV2ZWwgZ3VhcmQgbWFya3MgYSBydW5uYWJsZSBzY3JpcHQ7IGEgZ3VhcmQgbmVzdGVkIGluc2lkZSBhCiAgICAjIGZ1bmN0aW9uIG9yIGNsYXNzIChlLmcuIGEgaGVscGVyJ3MgYGRlZiBydW4oKTogLi4uIGlmIF9fbmFtZV9fID09IC4uLmApIGlzIG5vdC4KICAgIGZvciBub2RlIGluIHRyZWUuYm9keToKICAgICAgICBpZiBpc2luc3RhbmNlKG5vZGUsIGFzdC5JZikgYW5kIF9pc19tYWluX2d1YXJkKG5vZGUudGVzdCk6CiAgICAgICAgICAgIGlmIGFueShfaXNfc3Vic3RhbnRpdmUoc3RtdCkgZm9yIHN0bXQgaW4gbm9kZS5ib2R5KToKICAgICAgICAgICAgICAgIHJldHVybiBUcnVlCiAgICByZXR1cm4gRmFsc2UKCgpkZWYgZW1pdChwYXRoKToKICAgIHByaW50KG9zLnBhdGgubm9ybXBhdGgocGF0aCkpCgoKIyBFeGl0IGNvZGVzOiAwID0gYSBjbGVhciwgdW5hbWJpZ3VvdXMgcGljayB3YXMgZW1pdHRlZDsgQU1CSUdVT1VTX1JDICgzKSA9IHRoZQojIGFscGhhYmV0aWNhbCBmYWxsYmFjayB3YXMgdXNlZCAobXVsdGlwbGUgZmlsZXMsIG5vIGNsZWFyIHdpbm5lcikgLS0gcnVuX3NldHVwLmJhdAojIHJlYWRzIHRoaXMgdG8gZGVjaWRlIHdoZXRoZXIgdG8gb2ZmZXIgdGhlIGludGVyYWN0aXZlIHBpY2tlci4gc3Rkb3V0IGFsd2F5cyBob2xkcwojIHRoZSBjaG9zZW4gZW50cnkgcmVnYXJkbGVzcywgc28gbm9uLWludGVyYWN0aXZlIGNhbGxlcnMgYXJlIHVuYWZmZWN0ZWQgYnkgdGhlIGNvZGUuCkFNQklHVU9VU19SQyA9IDMKCgpkZWYgbWFpbigpOgogICAgZmlsZXMgPSBbbmFtZSBmb3IgbmFtZSBpbiBvcy5saXN0ZGlyKCIuIikgaWYgaXNfcHkobmFtZSldCgogICAgaWYgbGVuKGZpbGVzKSA+IDE6CiAgICAgICAgc3lzLnN0ZGVyci53cml0ZSgiW0JPT1RdIFJFUS0wMDI6IE11bHRpcGxlIHNjcmlwdHMgZm91bmQ6ICVyXG4iICUgc29ydGVkKGZpbGVzKSkKCiAgICBmb3IgY2FuZGlkYXRlIGluIFBSRUZFUlJFRDoKICAgICAgICBpZiBjYW5kaWRhdGUgaW4gZmlsZXM6CiAgICAgICAgICAgIGlmIGxlbihmaWxlcykgPiAxOgogICAgICAgICAgICAgICAgc3lzLnN0ZGVyci53cml0ZSgKICAgICAgICAgICAgICAgICAgICAiW0JPT1RdIFJFUS0wMDI6IFByaW9yaXR5IExvZ2ljIFRyaWdnZXJlZDogRm91bmQgJXIuICIKICAgICAgICAgICAgICAgICAgICAiU2VsZWN0aW5nICVyIChSRVEtMDAyOiBQcmlvcml0eSBuYW1lIG1hdGNoKS5cbiIKICAgICAgICAgICAgICAgICAgICAlIChzb3J0ZWQoZmlsZXMpLCBjYW5kaWRhdGUpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVtaXQoY2FuZGlkYXRlKQogICAgICAgICAgICByZXR1cm4gMAoKICAgIGlmIGxlbihmaWxlcykgPT0gMToKICAgICAgICBlbWl0KGZpbGVzWzBdKQogICAgICAgIHJldHVybiAwCgogICAgY2FuZGlkYXRlcyA9IFtuYW1lIGZvciBuYW1lIGluIGZpbGVzIGlmIGhhc19tYWluKG5hbWUpXQogICAgaWYgbGVuKGNhbmRpZGF0ZXMpID09IDE6CiAgICAgICAgZW1pdChjYW5kaWRhdGVzWzBdKQogICAgICAgIHJldHVybiAwCgogICAgIyBEZXRlcm1pbmlzdGljIGZhbGxiYWNrIChSRVEtMDAyKTogbm8gUFJFRkVSUkVEIG5hbWUgYW5kIG5vdCBleGFjdGx5IG9uZQogICAgIyBzdWJzdGFudGl2ZSBfX21haW5fXyBndWFyZC4gUHJlZmVyIGZpbGVzIHRoYXQgZGVjbGFyZWQgYSBndWFyZCwgZWxzZSBhbnkgZmlsZS4KICAgICMgVGhpcyBhbWJpZ3VvdXMgcGF0aCBleGl0cyBBTUJJR1VPVVNfUkMgc28gdGhlIGJhdGNoIG1heSBvZmZlciB0aGUgaW50ZXJhY3RpdmUKICAgICMgcGlja2VyIGJlZm9yZSBhY2NlcHRpbmcgdGhpcyBhbHBoYWJldGljYWwgZGVmYXVsdC4KICAgIHBvb2wgPSBjYW5kaWRhdGVzIGlmIGNhbmRpZGF0ZXMgZWxzZSBmaWxlcwogICAgaWYgcG9vbDoKICAgICAgICBjaG9pY2UgPSBzb3J0ZWQocG9vbClbMF0KICAgICAgICBzeXMuc3RkZXJyLndyaXRlKAogICAgICAgICAgICAiW0JPT1RdIFJFUS0wMDI6IE5vIGNsZWFyIGVudHJ5IGZvdW5kOyBzZWxlY3RpbmcgJXIgKGFscGhhYmV0aWNhbCBmYWxsYmFjaykuXG4iCiAgICAgICAgICAgICUgY2hvaWNlCiAgICAgICAgKQogICAgICAgIGVtaXQoY2hvaWNlKQogICAgICAgIHJldHVybiBBTUJJR1VPVVNfUkMKCiAgICAjIE5vIC5weSBmaWxlcyBhdCBhbGwgLS0gdGhlIGJvb3RzdHJhcHBlciBoYW5kbGVzIG5vLXB5dGhvbi1maWxlcyBzZXBhcmF0ZWx5LgogICAgcmV0dXJuIDAKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" rem ~env_state.py records envMode/envName/envPath/lockSize after a successful rem conda bootstrap; prints 'skip' on --check when the env is still valid, rem 'run' otherwise. Writes ~env.state.json on --write. set "HP_ENV_STATE=IiIiZW52X3N0YXRlIHYzICgyMDI2LTAzLTI3KQpXcml0ZXMgYW5kIHZhbGlkYXRlcyB+ZW52LnN0YXRlLmpzb24gZm9yIHRoZSBydW5fc2V0dXAuYmF0IGJvb3RzdHJhcCBmYXN0IHBhdGguClVzYWdlOgogIHB5dGhvbiB+ZW52X3N0YXRlLnB5IC0tY2hlY2sgIDogcHJpbnQgJ3NraXAnIGlmIHRoZSBzYXZlZCBlbnYgc3RhdGUgaXMgc3RpbGwgdmFsaWQKICBweXRob24gfmVudl9zdGF0ZS5weSAtLXdyaXRlICA6IHdyaXRlIGN1cnJlbnQgZW52IHN0YXRlIHRvIH5lbnYuc3RhdGUuanNvbgpOb3RlOiBweVNwZWMgaXMgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZyb20gdGhlIHN0YXRlIGNoZWNrIGJlY2F1c2UgcnVuX3NldHVwLmJhdAp3cml0ZXMgcnVudGltZS50eHQgKmR1cmluZyogdGhlIGZpcnN0IGJvb3RzdHJhcCwgc28gdGhlIGRldGVjdGVkIHNwZWMgb24gcnVuIDIKZGlmZmVycyBmcm9tIHRoZSBlbXB0eSBzcGVjIG9uIHJ1biAxLCBjYXVzaW5nIGEgc3B1cmlvdXMgY2FjaGUgbWlzcy4KClRoaXMgaXMgdGhlIGNhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9FTlZfU1RBVEUgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQgaW4KcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZQpgc2V0ICJIUF9FTlZfU1RBVEU9Li4uImAgbGluZTsgdGVzdHMvdGVzdF9lbnZfc3RhdGUucHkgYXNzZXJ0cyB0aGUgZW1iZWRkZWQKcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZS4KIiIiCl9fdmVyc2lvbl9fID0gImVudl9zdGF0ZSB2MyAoMjAyNi0wMy0yNykiCl9fYWxsX18gPSBbInJlYWRfc3RhdGUiLCAid3JpdGVfc3RhdGUiLCAiY2hlY2tfc3RhdGUiXQoKaW1wb3J0IGpzb24KaW1wb3J0IG9zCmltcG9ydCBzeXMKClNUQVRFX0ZJTEUgPSAifmVudi5zdGF0ZS5qc29uIgpMT0NLX0ZJTEUgPSAifmVudmlyb25tZW50LmxvY2sudHh0IgoKCmRlZiBfbG9ja19zaXplKCk6CiAgICB0cnk6CiAgICAgICAgcmV0dXJuIG9zLnBhdGguZ2V0c2l6ZShMT0NLX0ZJTEUpCiAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICByZXR1cm4gMAoKCmRlZiByZWFkX3N0YXRlKCk6CiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKFNUQVRFX0ZJTEUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICAgICAgcmV0dXJuIGpzb24ubG9hZChmaCkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcmV0dXJuIHt9CgoKZGVmIHdyaXRlX3N0YXRlKCk6CiAgICBlbnZfbW9kZSA9IG9zLmVudmlyb24uZ2V0KCJIUF9FTlZfTU9ERSIsICIiKQogICAgZW52X25hbWUgPSBvcy5lbnZpcm9uLmdldCgiRU5WTkFNRSIsICIiKQogICAgZW52X3BhdGggPSBvcy5lbnZpcm9uLmdldCgiRU5WX1BBVEgiLCAiIikKICAgIGxvY2tfc2l6ZSA9IF9sb2NrX3NpemUoKQogICAgc3RhdGUgPSB7CiAgICAgICAgImVudk1vZGUiOiBlbnZfbW9kZSwKICAgICAgICAiZW52TmFtZSI6IGVudl9uYW1lLAogICAgICAgICJlbnZQYXRoIjogZW52X3BhdGgsCiAgICAgICAgImxvY2tTaXplIjogbG9ja19zaXplLAogICAgfQogICAgdHJ5OgogICAgICAgIHdpdGggb3BlbihTVEFURV9GSUxFLCAidyIsIGVuY29kaW5nPSJ1dGYtOCIpIGFzIGZoOgogICAgICAgICAgICBqc29uLmR1bXAoc3RhdGUsIGZoKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgc3lzLmV4aXQoMSkKCgpkZWYgY2hlY2tfc3RhdGUoKToKICAgIHN0YXRlID0gcmVhZF9zdGF0ZSgpCiAgICBpZiBub3Qgc3RhdGU6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgZW52X25hbWUgPSBvcy5lbnZpcm9uLmdldCgiRU5WTkFNRSIsICIiKQogICAgaWYgbm90IGVudl9uYW1lIG9yIHN0YXRlLmdldCgiZW52TmFtZSIpICE9IGVudl9uYW1lOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKICAgICAgICByZXR1cm4KICAgIGlmIHN0YXRlLmdldCgiZW52TW9kZSIpICE9ICJjb25kYSI6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgZW52X3BhdGggPSBzdGF0ZS5nZXQoImVudlBhdGgiLCAiIikKICAgIGlmIG5vdCBlbnZfcGF0aDoKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKCJydW5cbiIpCiAgICAgICAgcmV0dXJuCiAgICBweV9leGUgPSBvcy5wYXRoLmpvaW4oZW52X3BhdGgsICJweXRob24uZXhlIikKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhweV9leGUpOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKICAgICAgICByZXR1cm4KICAgIGxvY2tfc2l6ZSA9IF9sb2NrX3NpemUoKQogICAgaWYgbG9ja19zaXplID09IDAgb3IgbG9ja19zaXplICE9IHN0YXRlLmdldCgibG9ja1NpemUiLCAtMSk6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgc3lzLnN0ZG91dC53cml0ZSgic2tpcFxuIikKCgpkZWYgbWFpbigpOgogICAgYXJncyA9IHN5cy5hcmd2WzE6XQogICAgaWYgIi0td3JpdGUiIGluIGFyZ3M6CiAgICAgICAgd3JpdGVfc3RhdGUoKQogICAgZWxpZiAiLS1jaGVjayIgaW4gYXJnczoKICAgICAgICBjaGVja19zdGF0ZSgpCiAgICBlbHNlOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbWFpbigpCg==" rem ~parse_warn.py reads PyInstaller warn file, extracts missing module names, rem applies import-to-conda-package translations, and prints one package per line. set "HP_PARSE_WARN=IiIicGFyc2Vfd2FybiB2MyAoMjAyNi0wNS0wMykKUmVhZHMgUHlJbnN0YWxsZXIgd2FybiBmaWxlLCBleHRyYWN0cyBtaXNzaW5nIG1vZHVsZSBuYW1lcywgYXBwbGllcyB0cmFuc2xhdGlvbnMuClVzYWdlOiBweXRob24gfnBhcnNlX3dhcm4ucHkgPGVudm5hbWU+ClByaW50cyBvbmUgY29uZGEgcGFja2FnZSBuYW1lIHBlciBsaW5lIHRvIHN0ZG91dC4KU2tpcHMgaW50ZXJuYWwgbW9kdWxlcyAoc3RhcnRpbmcgd2l0aCBfKSBhbmQga25vd24tc2FmZS91bml4LW9ubHkgbmFtZXMuClN1cHBvcnRzIFB5SW5zdGFsbGVyIDUueCBmb3JtYXQgKFc6IG5vIG1vZHVsZSBuYW1lZCAnZm9vJykgYW5kClB5SW5zdGFsbGVyIDYueCBmb3JtYXQgKG1pc3NpbmcgbW9kdWxlIG5hbWVkIGZvbyAtIGltcG9ydGVkIGJ5IC4uLiAoZGVsYXllZHx0b3AtbGV2ZWx8Y29uZGl0aW9uYWwpKS4KIiIiCl9fdmVyc2lvbl9fID0gInBhcnNlX3dhcm4gdjMgKDIwMjYtMDUtMDMpIgpfX2FsbF9fID0gWyJtYWluIiwgInBhcnNlX3dhcm5fZmlsZSIsICJUUkFOU0xBVElPTlMiLCAiU0tJUCJdCgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCBzeXMKClRSQU5TTEFUSU9OUyA9IHsKICAgICJjdjIiOiAib3BlbmN2IiwKICAgICJQSUwiOiAicGlsbG93IiwKICAgICJJbWFnZSI6ICJwaWxsb3ciLAogICAgInNrbGVhcm4iOiAic2Npa2l0LWxlYXJuIiwKICAgICJiczQiOiAiYmVhdXRpZnVsc291cDQiLAogICAgInNlcmlhbCI6ICJweXNlcmlhbCIsCiAgICAieWFtbCI6ICJweXlhbWwiLAogICAgImdpdCI6ICJnaXRweXRob24iLAogICAgInd4IjogInd4cHl0aG9uIiwKICAgICJkYXRldXRpbCI6ICJweXRob24tZGF0ZXV0aWwiLAogICAgImRvdGVudiI6ICJweXRob24tZG90ZW52IiwKICAgICJDcnlwdG8iOiAicHljcnlwdG9kb21lIiwKICAgICJPcGVuU1NMIjogInB5b3BlbnNzbCIsCiAgICAiand0IjogInB5and0IiwKICAgICJ1c2IiOiAicHl1c2IiLAogICAgImF0dHIiOiAiYXR0cnMiLAogICAgIndpbjMyYXBpIjogInB5d2luMzIiLAogICAgIndpbjMyY29uIjogInB5d2luMzIiLAogICAgIndpbjMyY29tIjogInB5d2luMzIiLAogICAgIndpbjMyZ3VpIjogInB5d2luMzIiLAogICAgIndpbjMyZmlsZSI6ICJweXdpbjMyIiwKICAgICJ3aW4zMnByb2Nlc3MiOiAicHl3aW4zMiIsCiAgICAid2luMzJldmVudCI6ICJweXdpbjMyIiwKICAgICJweXdpbnR5cGVzIjogInB5d2luMzIiLAogICAgInB5dGhvbmNvbSI6ICJweXdpbjMyIiwKICAgICJ3aW5lcnJvciI6ICJweXdpbjMyIiwKICAgICJmaXR6IjogInB5bXVwZGYiLAogICAgImRvY3giOiAicHl0aG9uLWRvY3giLAogICAgInBwdHgiOiAicHl0aG9uLXBwdHgiLAogICAgInB5ZGFudGljX2NvcmUiOiAicHlkYW50aWMtY29yZSIsCiAgICAic2tpbWFnZSI6ICJzY2lraXQtaW1hZ2UiLAogICAgIkNyeXB0b2RvbWUiOiAicHljcnlwdG9kb21lIiwKICAgICJ6bXEiOiAicHl6bXEiLAp9CgpTS0lQID0gZnJvemVuc2V0KFsKICAgICJwa2dfcmVzb3VyY2VzIiwKICAgICJkaXN0dXRpbHMiLAogICAgInNldHVwdG9vbHMiLAogICAgImltcG9ydGxpYiIsCiAgICAiaW1wb3J0bGliLm1ldGFkYXRhIiwKICAgICJpbXBvcnRsaWIucmVzb3VyY2VzIiwKICAgICJpbXBvcnRsaWIuYWJjIiwKICAgICMgc3RkbGliIHBhY2thZ2VzIHdob3NlIHN1Ym1vZHVsZXMgc3VyZmFjZSBhcyAibWlzc2luZyIgaW4gdGhlIHdhcm4gZmlsZQogICAgIyAoZS5nLiBjb2xsZWN0aW9ucy5hYmMgLT4gY29sbGVjdGlvbnMpLiBUaGV5IGFyZSBuZXZlciBjb25kYSBwYWNrYWdlcywgc28KICAgICMgZm9yd2FyZGluZyB0aGVtIHRvICJjb25kYSBpbnN0YWxsIiBvbmx5IHByb2R1Y2VzIGEgbm9pc3kgUGFja2FnZXNOb3RGb3VuZC4KICAgICJjb2xsZWN0aW9ucyIsCiAgICAjIFVuaXgtb25seSBwbGF0Zm9ybSBtb2R1bGVzIGFic2VudCBvbiBXaW5kb3dzOyB0aGVzZSBhcHBlYXIgYXMgb3B0aW9uYWwvZGVsYXllZAogICAgIyBzdGRsaWIgaW1wb3J0cyBpbiB0aGUgd2FybiBmaWxlIGFuZCBhcmUgc2FmZSB0byBpZ25vcmUuCiAgICAiZ3JwIiwKICAgICJwd2QiLAogICAgInBvc2l4IiwKICAgICJyZXNvdXJjZSIsCiAgICAiZmNudGwiLAogICAgInJlYWRsaW5lIiwKICAgICJ0ZXJtaW9zIiwKICAgICJ0dHkiLAogICAgInB0eSIsCiAgICAiY3J5cHQiLAogICAgInNwd2QiLAogICAgIm5pcyIsCiAgICAic3lzbG9nIiwKICAgICJvc3NhdWRpb2RldiIsCiAgICAjIFB5dGhvbi0yLW9ubHkgc3RkbGliIG1vZHVsZXMgKHJlbW92ZWQgZW50aXJlbHkgaW4gUHl0aG9uIDMsIG5ldmVyIGEgcmVhbCBQeVBJL2NvbmRhCiAgICAjIHBhY2thZ2UpIHRoYXQgc3RpbGwgYXBwZWFyIGFzIGNvbmRpdGlvbmFsIGltcG9ydHMgaW5zaWRlIHJlYWwgcGFja2FnZXMnIG93biBQeXRob24KICAgICMgMi8zIGNvbXBhdGliaWxpdHkgc2hpbXMgLS0gZS5nLiB4bHJkL3RpbWVtYWNoaW5lLnB5IGRvZXMKICAgICMgInRyeTogZnJvbSBjU3RyaW5nSU8gaW1wb3J0IFN0cmluZ0lPIGV4Y2VwdCBJbXBvcnRFcnJvcjogZnJvbSBpbyBpbXBvcnQgU3RyaW5nSU8iLAogICAgIyBhIGRlYWQgY29kZSBwYXRoIHVuZGVyIGFueSBQeXRob24gMyBpbnRlcnByZXRlciwgYnV0IFB5SW5zdGFsbGVyJ3Mgd2FybiBmaWxlIGZsYWdzIGl0CiAgICAjIHJlZ2FyZGxlc3MuIENvbmZpcm1lZCB2aWEgYSByZWFsLCB1bmZsYWdnZWQgQ0kgcnVuIG9mIHRoZSBsYXllcmVkLWRlcGVuZGVuY3ktY2hhaW4KICAgICMgRTJFIHRlc3QgKHNlbGYubGF5ZXJlZF9lMmUuY2hhaW4pOiAibWlzc2luZyBtb2R1bGUgbmFtZWQgY1N0cmluZ0lPIC0gaW1wb3J0ZWQgYnkKICAgICMgeGxyZC50aW1lbWFjaGluZSAoY29uZGl0aW9uYWwpIiAtLSB3YXJuZml4IGdlbnVpbmVseSBhdHRlbXB0ZWQgYW5kIGZhaWxlZCB0byBpbnN0YWxsCiAgICAjIGEgcGFja2FnZSBsaXRlcmFsbHkgbmFtZWQgImNTdHJpbmdJTyIsIHdoaWNoIGNhbiBuZXZlciBleGlzdCwgZm9yY2luZyBhbiB1bm5lY2Vzc2FyeQogICAgIyBleHRyYSBwcm92aWRlciBjYXNjYWRlIHBhc3QgY29uZGEuIERpZmZlcmVudCBheGlzIGZyb20gdGhlIFVuaXgtb25seSBsaXN0IGFib3ZlCiAgICAjIChQeXRob24tdmVyc2lvbi1vbmx5LCBub3QgcGxhdGZvcm0tb25seSksIGJ1dCB0aGUgc2FtZSAiZ3VhcmFudGVlZCB0byBuZXZlciBiZSBhCiAgICAjIHJlYWwgaW5zdGFsbGFibGUgcGFja2FnZSIgcHJvcGVydHkgdGhhdCBtYWtlcyBpdCBzYWZlIHRvIGZpbHRlciB1bmNvbmRpdGlvbmFsbHkuCiAgICAiY1N0cmluZ0lPIiwKICAgICJTdHJpbmdJTyIsCl0pCgoKZGVmIHBhcnNlX3dhcm5fZmlsZSh3YXJuX3BhdGgpOgogICAgIiIiUGFyc2UgYSBQeUluc3RhbGxlciB3YXJuIGZpbGU7IHJldHVybiBsaXN0IG9mIGNvbmRhIHBhY2thZ2UgbmFtZXMgKGRlZHVwbGljYXRlZCwgb3JkZXJlZCkuCgogICAgUmV0dXJucyBhbiBlbXB0eSBsaXN0IGlmIHdhcm5fcGF0aCBkb2VzIG5vdCBleGlzdC4KICAgIEVhY2ggZW50cnkgaW4gVFJBTlNMQVRJT05TIG1hcHMgYW4gaW1wb3J0LW5hbWUgdG8gYSBjb25kYSBwYWNrYWdlIG5hbWUuCiAgICBVbmtub3duIGltcG9ydCBuYW1lcyBwYXNzIHRocm91Z2ggdW5jaGFuZ2VkLgogICAgIiIiCiAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMod2Fybl9wYXRoKToKICAgICAgICByZXR1cm4gW10KICAgIHNlZW4gPSBzZXQoKQogICAgcmVzdWx0ID0gW10KICAgIHdpdGggb3Blbih3YXJuX3BhdGgsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICBmb3IgbGluZSBpbiBmaDoKICAgICAgICAgICAgbGluZSA9IGxpbmUuc3RyaXAoKQogICAgICAgICAgICAjIFB5SW5zdGFsbGVyIDUueCBmb3JtYXQ6IFc6IG5vIG1vZHVsZSBuYW1lZCAnZm9vJwogICAgICAgICAgICBtID0gcmUubWF0Y2gociJXOiBubyBtb2R1bGUgbmFtZWQgJyhbXiddKyknIiwgbGluZSkKICAgICAgICAgICAgaWYgbToKICAgICAgICAgICAgICAgIG1vZCA9IG0uZ3JvdXAoMSkuc3BsaXQoIi4iKVswXQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBQeUluc3RhbGxlciA2LnggZm9ybWF0OgogICAgICAgICAgICAgICAgIyBtaXNzaW5nIG1vZHVsZSBuYW1lZCBmb28gLSBpbXBvcnRlZCBieSBiYXIgKHRvcC1sZXZlbCkKICAgICAgICAgICAgICAgICMgbWlzc2luZyBtb2R1bGUgbmFtZWQgZm9vIC0gaW1wb3J0ZWQgYnkgYmFyIChkZWxheWVkKQogICAgICAgICAgICAgICAgIyBtaXNzaW5nIG1vZHVsZSBuYW1lZCBmb28gLSBpbXBvcnRlZCBieSBiYXIgKGNvbmRpdGlvbmFsKQogICAgICAgICAgICAgICAgIyBTa2lwIGVudHJpZXMgdGhhdCBhcmUgT05MWSBvcHRpb25hbCAodHJ5LWV4Y2VwdCBndWFyZHMgd2l0aCBubwogICAgICAgICAgICAgICAgIyBvdGhlciBxdWFsaWZpZXIpIC0tIHRob3NlIGFyZSBpbnRlbnRpb25hbGx5IHJlc2lsaWVudCB0byBtaXNzaW5nCiAgICAgICAgICAgICAgICAjIG1vZHVsZXMuIHRvcC1sZXZlbCwgZGVsYXllZCwgYW5kIGNvbmRpdGlvbmFsIGltcG9ydHMgYXJlIHJlcXVpcmVkCiAgICAgICAgICAgICAgICAjIGF0IHJ1bnRpbWUuIFVuaXgtb25seSBzdGRsaWIgc2hpbXMgKGdycCwgcHdkLCBwb3NpeCwgZXRjLikgbGFuZAogICAgICAgICAgICAgICAgIyBpbiB0aG9zZSBjYXRlZ29yaWVzIHRvbyBidXQgYXJlIGFscmVhZHkgZmlsdGVyZWQgYnkgU0tJUC4KICAgICAgICAgICAgICAgIG0gPSByZS5tYXRjaChyIm1pc3NpbmcgbW9kdWxlIG5hbWVkIChcUyspIiwgbGluZSkKICAgICAgICAgICAgICAgIGlmIG5vdCBtOgogICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgICAgICBpZiBub3QgcmUuc2VhcmNoKHInXChbXildKig/OnRvcC1sZXZlbHxkZWxheWVkfGNvbmRpdGlvbmFsKVteKV0qXCknLCBsaW5lKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgbW9kID0gbS5ncm91cCgxKS5zdHJpcCgiJ1wiIikuc3BsaXQoIi4iKVswXQogICAgICAgICAgICBpZiBtb2Quc3RhcnRzd2l0aCgiXyIpOgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgaWYgbW9kIGluIFNLSVA6CiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwa2cgPSBUUkFOU0xBVElPTlMuZ2V0KG1vZCwgbW9kKQogICAgICAgICAgICBpZiBwa2cgbm90IGluIHNlZW46CiAgICAgICAgICAgICAgICBzZWVuLmFkZChwa2cpCiAgICAgICAgICAgICAgICByZXN1bHQuYXBwZW5kKHBrZykKICAgIHJldHVybiByZXN1bHQKCgpkZWYgbWFpbigpOgogICAgaWYgbGVuKHN5cy5hcmd2KSA8IDI6CiAgICAgICAgc3lzLmV4aXQoMSkKICAgIGVudm5hbWUgPSBzeXMuYXJndlsxXQogICAgd2Fybl9wYXRoID0gb3MucGF0aC5qb2luKCJidWlsZCIsIGVudm5hbWUsICJ3YXJuLSIgKyBlbnZuYW1lICsgIi50eHQiKQogICAgZm9yIHBrZyBpbiBwYXJzZV93YXJuX2ZpbGUod2Fybl9wYXRoKToKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKHBrZyArICJcbiIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=" rem ~dep_check.py compares requirements.auto.txt against ~environment.lock.txt; rem prints 'skip' when all pipreqs packages are already installed, 'run' otherwise. set "HP_DEP_CHECK=IiIiZGVwX2NoZWNrIHYxICgyMDI2LTAzLTI3KQpDb21wYXJlcyByZXF1aXJlbWVudHMuYXV0by50eHQgKHBpcHJlcXMgb3V0cHV0KSBhZ2FpbnN0IH5lbnZpcm9ubWVudC5sb2NrLnR4dAooY29uZGEgbGlzdCAtLWV4cG9ydCBzbmFwc2hvdCkuIFByaW50cyAnc2tpcCcgd2hlbiBldmVyeSBwYWNrYWdlIGRldGVjdGVkIGJ5CnBpcHJlcXMgaXMgYWxyZWFkeSBwcmVzZW50IGluIHRoZSBsb2NrOyBwcmludHMgJ3J1bicgb3RoZXJ3aXNlIHNvIHRoZSBjYWxsZXIKcHJvY2VlZHMgd2l0aCBjb25kYSBpbnN0YWxsLgoKVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0RFUF9DSEVDSyBiYXNlNjQgcGF5bG9hZCBlbWJlZGRlZCBpbgpydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlCmBzZXQgIkhQX0RFUF9DSEVDSz0uLi4iYCBsaW5lOyB0ZXN0cy90ZXN0X2RlcF9jaGVjay5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZApwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlLgoiIiIKX192ZXJzaW9uX18gPSAiZGVwX2NoZWNrIHYxICgyMDI2LTAzLTI3KSIKX19hbGxfXyA9IFsicGFyc2VfbG9jayIsICJwYXJzZV9yZXFzIiwgIm1haW4iXQoKaW1wb3J0IG9zCmltcG9ydCByZQppbXBvcnQgc3lzCgpSRVFfRklMRSA9ICJyZXF1aXJlbWVudHMuYXV0by50eHQiCkxPQ0tfRklMRSA9ICJ+ZW52aXJvbm1lbnQubG9jay50eHQiCgoKZGVmIF9ub3JtKG5hbWUpOgogICAgIiIiUEVQIDUwMyBzdHlsZSBub3JtYWxpemF0aW9uOiBydW5zIG9mIC1fLiBjb2xsYXBzZSB0byBvbmUgJy0nLCBsb3dlcmNhc2VkLgoKICAgIGNvbmRhLWZvcmdlIGFuZCBQeVBJIHNvbWV0aW1lcyBzcGVsbCB0aGUgc2FtZSBsb2dpY2FsIHBhY2thZ2Ugd2l0aAogICAgZGlmZmVyZW50IHNlcGFyYXRvcnMgZm9yIHRoZSBzYW1lIG5hbWUgKGUuZy4gdHlwaW5nX2V4dGVuc2lvbnMgdnMuCiAgICB0eXBpbmctZXh0ZW5zaW9ucykgLS0gd2l0aG91dCB0aGlzLCBzdWNoIGEgcGFja2FnZSBsb29rcyAibWlzc2luZyIgZnJvbQogICAgdGhlIGxvY2sgb24gZXZlcnkgcnVuLCBkZWZlYXRpbmcgdGhlIGZhc3QtcGF0aCBza2lwIHRoaXMgZmlsZSBleGlzdHMgZm9yLgogICAgIiIiCiAgICByZXR1cm4gcmUuc3ViKHIiWy1fLl0rIiwgIi0iLCBuYW1lKS5sb3dlcigpCgoKZGVmIHBhcnNlX2xvY2socGF0aCk6CiAgICAiIiJSZXR1cm4gZnJvemVuc2V0IG9mIG5vcm1hbGl6ZWQgcGFja2FnZSBuYW1lcyBmcm9tIGNvbmRhIGxpc3QgLS1leHBvcnQuIiIiCiAgICBuYW1lcyA9IHNldCgpCiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKHBhdGgsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICAgICAgZm9yIGxpbmUgaW4gZmg6CiAgICAgICAgICAgICAgICBsaW5lID0gbGluZS5zdHJpcCgpCiAgICAgICAgICAgICAgICBpZiBub3QgbGluZSBvciBsaW5lLnN0YXJ0c3dpdGgoIiMiKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgIyBjb25kYSBsaXN0IC0tZXhwb3J0OiBuYW1lPXZlcnNpb249YnVpbGRbPWNoYW5uZWxdCiAgICAgICAgICAgICAgICBuYW1lID0gX25vcm0obGluZS5zcGxpdCgiPSIpWzBdLnN0cmlwKCkpCiAgICAgICAgICAgICAgICBpZiBuYW1lOgogICAgICAgICAgICAgICAgICAgIG5hbWVzLmFkZChuYW1lKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgcGFzcwogICAgcmV0dXJuIGZyb3plbnNldChuYW1lcykKCgpkZWYgcGFyc2VfcmVxcyhwYXRoKToKICAgICIiIlJldHVybiBsaXN0IG9mIG5vcm1hbGl6ZWQgcGFja2FnZSBuYW1lcyBmcm9tIHBpcC1zdHlsZSByZXF1aXJlbWVudHMgZmlsZS4iIiIKICAgIG5hbWVzID0gW10KICAgIHRyeToKICAgICAgICB3aXRoIG9wZW4ocGF0aCwgInIiLCBlbmNvZGluZz0idXRmLTgiLCBlcnJvcnM9Imlnbm9yZSIpIGFzIGZoOgogICAgICAgICAgICBmb3IgbGluZSBpbiBmaDoKICAgICAgICAgICAgICAgIGxpbmUgPSBsaW5lLnN0cmlwKCkKICAgICAgICAgICAgICAgIGlmIG5vdCBsaW5lIG9yIGxpbmUuc3RhcnRzd2l0aCgiIyIpOgogICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgICAgICAjIFN0cmlwIHZlcnNpb24gc3BlY2lmaWVyOiBudW1weT49MS4yMCAtPiBudW1weQogICAgICAgICAgICAgICAgbmFtZSA9IF9ub3JtKHJlLnNwbGl0KHIiWz49PCF+LDtcc1xbXSIsIGxpbmUsIG1heHNwbGl0PTEpWzBdLnN0cmlwKCkpCiAgICAgICAgICAgICAgICBpZiBuYW1lOgogICAgICAgICAgICAgICAgICAgIG5hbWVzLmFwcGVuZChuYW1lKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgcGFzcwogICAgcmV0dXJuIG5hbWVzCgoKZGVmIG1haW4oKToKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhMT0NLX0ZJTEUpOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKICAgICAgICByZXR1cm4KICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhSRVFfRklMRSk6CiAgICAgICAgIyBObyBwaXByZXFzIG91dHB1dDsgbm90aGluZyByZXF1aXJlcyBpbnN0YWxsYXRpb24KICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKCJza2lwXG4iKQogICAgICAgIHJldHVybgogICAgbG9ja19uYW1lcyA9IHBhcnNlX2xvY2soTE9DS19GSUxFKQogICAgaWYgbm90IGxvY2tfbmFtZXM6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgcmVxX25hbWVzID0gcGFyc2VfcmVxcyhSRVFfRklMRSkKICAgIGlmIG5vdCByZXFfbmFtZXM6CiAgICAgICAgIyBFbXB0eSByZXF1aXJlbWVudHMgZmlsZTsgY29uZGEgaW5zdGFsbCB3b3VsZCBiZSBhIG5vLW9wCiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgic2tpcFxuIikKICAgICAgICByZXR1cm4KICAgIG1pc3NpbmcgPSBbbmFtZSBmb3IgbmFtZSBpbiByZXFfbmFtZXMgaWYgbmFtZSBub3QgaW4gbG9ja19uYW1lc10KICAgIGlmIG1pc3Npbmc6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgZWxzZToKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKCJza2lwXG4iKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK" rem ~autopep_merge.py (Tier 1, docs/plan-autopep723-two-tier.md) merges rem autopep723 check discoveries into requirements.txt, additive/best-effort only. set "HP_AUTOPEP_MERGE=IiIiYXV0b3BlcF9tZXJnZSAoSFBfQVVUT1BFUF9NRVJHRSkgLS0gVGllciAxIG9mIGRvY3MvcGxhbi1hdXRvcGVwNzIzLXR3by10aWVyLm1kLgoKTWVyZ2VzIHRoZSBkZXBlbmRlbmN5IG5hbWVzIGRpc2NvdmVyZWQgYnkgYHV2eCBhdXRvcGVwNzIzIGNoZWNrIDxlbnRyeT5gIChyZWRpcmVjdGVkIHRvIGEKUEVQLTcyMy1oZWFkZXItc2hhcGVkIG91dHB1dCBmaWxlKSBpbnRvIHJlcXVpcmVtZW50cy50eHQsIGFkZGl0aXZlbHkuIEJlc3QtZWZmb3J0IG9ubHk6IG5ldmVyCnJhaXNlcywgbmV2ZXIgcmVtb3ZlcyBvciByZW9yZGVycyBhbnl0aGluZyBhbHJlYWR5IGluIHJlcXVpcmVtZW50cy50eHQsIGFuZCBhbHdheXMgZXhpdHMgMCBzbwphIGNhbGxlciBjYW4gdHJlYXQgaXQgYXMgYSBkaWFnbm9zdGljLW9ubHkgc3RlcCAoc2VlIHJ1bl9zZXR1cC5iYXQncyBSRVEtMDA1LjEyIGNhbGwgc2l0ZSAtLQphdXRvcGVwNzIzIGF1Z21lbnRzIHBpcHJlcXMncyByZXN1bHRzLCBpdCBuZXZlciByZXBsYWNlcyBvciBnYXRlcyB0aGVtKS4KClVzYWdlOiBweXRob24gYXV0b3BlcF9tZXJnZS5weSA8YXV0b3BlcF9vdXRwdXRfZmlsZT4gPHJlcXVpcmVtZW50c19maWxlPgogIChib3RoIGRlZmF1bHQgdG8gInJlcXVpcmVtZW50cy5hdXRvcGVwLnR4dCIgLyAicmVxdWlyZW1lbnRzLnR4dCIgZm9yIGRpcmVjdCB0ZXN0aW5nKQoKVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0FVVE9QRVBfTUVSR0UgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQgaW4gcnVuX3NldHVwLmJhdC4KQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZSBgc2V0ICJIUF9BVVRPUEVQX01FUkdFPS4uLiJgIGxpbmU7CnRlc3RzL3Rlc3RfYXV0b3BlcF9tZXJnZS5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlLgoiIiIKaW1wb3J0IG9zCmltcG9ydCByZQppbXBvcnQgc3lzCgpERVBfTElORV9SRSA9IHJlLmNvbXBpbGUocideI1xzKiIoW14iXSspIiw/XHMqJCcpCk5BTUVfUkUgPSByZS5jb21waWxlKHInXlxzKihbQS1aYS16MC05Xy4tXSspJykKCgpkZWYgZXh0cmFjdF9hdXRvcGVwX2RlcHMocGF0aCk6CiAgICAiIiJQdWxsIHF1b3RlZCBkZXBlbmRlbmN5IG5hbWVzIG91dCBvZiBhdXRvcGVwNzIzIGNoZWNrJ3MgUEVQLTcyMy1zdHlsZSBzdGRvdXQgY2FwdHVyZS4iIiIKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhwYXRoKToKICAgICAgICByZXR1cm4gW10KICAgIGRlcHMgPSBbXQogICAgdHJ5OgogICAgICAgIHdpdGggb3BlbihwYXRoLCAiciIsIGVuY29kaW5nPSJ1dGYtOCIsIGVycm9ycz0iaWdub3JlIikgYXMgaGFuZGxlOgogICAgICAgICAgICBmb3IgbGluZSBpbiBoYW5kbGU6CiAgICAgICAgICAgICAgICBtYXRjaCA9IERFUF9MSU5FX1JFLm1hdGNoKGxpbmUucnN0cmlwKCJcbiIpKQogICAgICAgICAgICAgICAgaWYgbWF0Y2g6CiAgICAgICAgICAgICAgICAgICAgZGVwcy5hcHBlbmQobWF0Y2guZ3JvdXAoMSkuc3RyaXAoKSkKICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgIHJldHVybiBbXQogICAgcmV0dXJuIGRlcHMKCgpkZWYgZXhpc3RpbmdfbmFtZXMocGF0aCk6CiAgICAiIiJMb3dlcmNhc2VkIHRvcC1sZXZlbCBwYWNrYWdlIG5hbWVzIGFscmVhZHkgcHJlc2VudCBpbiByZXF1aXJlbWVudHMudHh0LiIiIgogICAgbmFtZXMgPSBzZXQoKQogICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKHBhdGgpOgogICAgICAgIHJldHVybiBuYW1lcwogICAgd2l0aCBvcGVuKHBhdGgsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBoYW5kbGU6CiAgICAgICAgZm9yIGxpbmUgaW4gaGFuZGxlOgogICAgICAgICAgICBzdHJpcHBlZCA9IGxpbmUuc3RyaXAoKQogICAgICAgICAgICBpZiBub3Qgc3RyaXBwZWQgb3Igc3RyaXBwZWQuc3RhcnRzd2l0aCgiIyIpOgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgbWF0Y2ggPSBOQU1FX1JFLm1hdGNoKHN0cmlwcGVkKQogICAgICAgICAgICBpZiBtYXRjaDoKICAgICAgICAgICAgICAgIG5hbWVzLmFkZChtYXRjaC5ncm91cCgxKS5sb3dlcigpKQogICAgcmV0dXJuIG5hbWVzCgoKZGVmIGVuc3VyZV90cmFpbGluZ19uZXdsaW5lKHBhdGgpOgogICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKHBhdGgpIG9yIG9zLnBhdGguZ2V0c2l6ZShwYXRoKSA9PSAwOgogICAgICAgIHJldHVybgogICAgd2l0aCBvcGVuKHBhdGgsICJyYiIpIGFzIGhhbmRsZToKICAgICAgICBoYW5kbGUuc2VlaygtMSwgb3MuU0VFS19FTkQpCiAgICAgICAgbGFzdCA9IGhhbmRsZS5yZWFkKDEpCiAgICBpZiBsYXN0IG5vdCBpbiAoYiJcbiIsIGIiXHIiKToKICAgICAgICB3aXRoIG9wZW4ocGF0aCwgImEiLCBlbmNvZGluZz0iYXNjaWkiLCBuZXdsaW5lPSJcbiIpIGFzIGhhbmRsZToKICAgICAgICAgICAgaGFuZGxlLndyaXRlKCJcbiIpCgoKZGVmIG1haW4oKToKICAgIGF1dG9wZXBfcGF0aCA9IHN5cy5hcmd2WzFdIGlmIGxlbihzeXMuYXJndikgPiAxIGVsc2UgInJlcXVpcmVtZW50cy5hdXRvcGVwLnR4dCIKICAgIHJlcXNfcGF0aCA9IHN5cy5hcmd2WzJdIGlmIGxlbihzeXMuYXJndikgPiAyIGVsc2UgInJlcXVpcmVtZW50cy50eHQiCgogICAgYXV0b3BlcF9kZXBzID0gZXh0cmFjdF9hdXRvcGVwX2RlcHMoYXV0b3BlcF9wYXRoKQogICAgaWYgbm90IGF1dG9wZXBfZGVwczoKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKCJuby1vcDogbm8gYXV0b3BlcDcyMyBkZXBlbmRlbmNpZXMgZGlzY292ZXJlZFxuIikKICAgICAgICByZXR1cm4gMAoKICAgIGN1cnJlbnQgPSBleGlzdGluZ19uYW1lcyhyZXFzX3BhdGgpCiAgICBzZWVuID0gc2V0KCkKICAgIGFkZGl0aW9ucyA9IFtdCiAgICBmb3IgZGVwIGluIGF1dG9wZXBfZGVwczoKICAgICAgICBrZXkgPSBkZXAubG93ZXIoKQogICAgICAgIGlmIGtleSBpbiBjdXJyZW50IG9yIGtleSBpbiBzZWVuOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgIHNlZW4uYWRkKGtleSkKICAgICAgICBhZGRpdGlvbnMuYXBwZW5kKGRlcCkKCiAgICBpZiBub3QgYWRkaXRpb25zOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoIm5vLW9wOiBhbGwgYXV0b3BlcDcyMyBkZXBlbmRlbmNpZXMgYWxyZWFkeSBwcmVzZW50XG4iKQogICAgICAgIHJldHVybiAwCgogICAgZW5zdXJlX3RyYWlsaW5nX25ld2xpbmUocmVxc19wYXRoKQogICAgd2l0aCBvcGVuKHJlcXNfcGF0aCwgImEiLCBlbmNvZGluZz0iYXNjaWkiLCBuZXdsaW5lPSJcbiIpIGFzIGhhbmRsZToKICAgICAgICBmb3IgZGVwIGluIGFkZGl0aW9uczoKICAgICAgICAgICAgaGFuZGxlLndyaXRlKGRlcCArICJcbiIpCiAgICBzeXMuc3Rkb3V0LndyaXRlKCJhZGRlZDogIiArICIsIi5qb2luKGFkZGl0aW9ucykgKyAiXG4iKQogICAgcmV0dXJuIDAKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" rem ~pvw_known_idempotent.py (Tier 2, docs/plan-autopep723-two-tier.md, rem HP_PVW_KNOWN_IDEMPOTENT) runs the entry script via uvx autopep723 for rem execute-mode dependency discovery; opt-in only. set "HP_PVW_IDEMPOTENT=IiIicHZ3X2tub3duX2lkZW1wb3RlbnQgdjEgLS0gVGllciAyLCBkb2NzL3BsYW4tYXV0b3BlcDcyMy10d28tdGllci5tZCAoSFBfUFZXX0tOT1dOX0lERU1QT1RFTlQpLgpSdW5zIHRoZSBlbnRyeSB2aWEgYHV2eCBhdXRvcGVwNzIzIDxlbnRyeT5gIGFzIGV4ZWN1dGUtbW9kZSBkaXNjb3ZlcnkgZm9yIG9wdGVkLWluIHVzZXJzCihIUF9QVldfS05PV05fSURFTVBPVEVOVD0xKTsgcmVsb2NhdGVzIFJFQURNRSdzICJKdXN0IHJ1biBpdCIgUXVpY2tTdGFydCBsb2dpYyBpbnRvCnJ1bl9zZXR1cC5iYXQgKHNhbWUgMC8yL290aGVyLW5vbnplcm8gYnJhbmNoaW5nLCBzYW1lIHN0cmlwLWFuZC1yZXRyeS1vbmNlKS4KClJ1biBpbmhlcml0cyBzdGRvdXQgbGl2ZTsgb25seSBgYXV0b3BlcDcyMyBjaGVja2AgY2FsbHMgY2FwdHVyZSBvdXRwdXQuIFJlc3VsdCBtYXJrZXJzCihSQU46PGRldGFpbD4vRVJST1I6PHJlYXNvbj4pIGdvIHRvIFNUREVSUiBzaW5jZSBzdGRvdXQgaXMgcmVzZXJ2ZWQgZm9yIHRoZSBwYXNzdGhyb3VnaCBzY3JpcHQuCgpQb3N0LXBlcnNpc3QgcmV0cmllcyBwYXNzIFVWX05PX0NBQ0hFPTEgKGZvcmNlX2ZyZXNoKSB0byBkb2RnZSBhc3RyYWwtc2gvdXYjMTUxNTY6IHBlcnNpc3QoKQpyZXdyaXRlcyB0aGUgaGVhZGVyIHRoZW4gcnVuX3NjcmlwdCgpIHJlcnVucyB0aGUgc2FtZSBmaWxlIC0tIHRoZSBidWcncyBleGFjdCB0cmlnZ2VyLiBPbmx5CnJldHJpZXMgcGF5IHRoZSBuby1jYWNoZSBjb3N0OyB0aGUgZmlyc3QgYXR0ZW1wdCBrZWVwcyBub3JtYWwgY2FjaGluZy4KClVzYWdlOiBweXRob24gcHZ3X2tub3duX2lkZW1wb3RlbnQucHkgPGVudHJ5LnB5PiA8dXZ4X2V4ZT4gPHV2X2V4ZT4gPHB5dGhvbl9leGU+CkV4aXQgMCA9IHJhbiAocmVnYXJkbGVzcyBvZiBwZXJzaXN0IG91dGNvbWUpOyBub256ZXJvID0gZmFpbGVkIGFmdGVyIG9uZSByZXRyeS4KQ2Fub25pY2FsIHNvdXJjZSBmb3IgSFBfUFZXX0lERU1QT1RFTlQ7IHRlc3RzL3Rlc3RfcHZ3X2tub3duX2lkZW1wb3RlbnQucHkgY2hlY2tzIHBheWxvYWQgc3luYy4KIiIiCmltcG9ydCBvcwppbXBvcnQgcmUKaW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IHN5cwoKREVQX0xJTkVfUkUgPSByZS5jb21waWxlKHInXiNccyoiKFteIl0rKSIsP1xzKiQnKQoKCmRlZiBzdHJpcF9wZXA3MjNfYmxvY2sodGV4dCk6CiAgICAiIiJSZW1vdmUgYW4gZXhpc3RpbmcgJyMgLy8vIHNjcmlwdCcgLi4uICcjIC8vLycgYmxvY2ssIGxpbmUgYnkgbGluZS4gTWlycm9ycwogICAgdG9vbHMvcGVwNzIzX3dyaXRlYmFjay5weSdzIGZ1bmN0aW9uIChzdGF0ZSBtYWNoaW5lLCB0b2xlcmFudCBvZiB0cmFpbGluZyB3aGl0ZXNwYWNlCiAgICBvbiB0aGUgY2xvc2luZyBmZW5jZSBwZXIgYXN0cmFsLXNoL3V2IzEwOTE4KS4iIiIKICAgIGxpbmVzID0gdGV4dC5zcGxpdGxpbmVzKGtlZXBlbmRzPVRydWUpCiAgICBvdXQgPSBbXQogICAgaW5fYmxvY2sgPSBGYWxzZQogICAgZm9yIGxpbmUgaW4gbGluZXM6CiAgICAgICAgc3RyaXBwZWQgPSBsaW5lLnJzdHJpcCgiXHJcbiIpCiAgICAgICAgaWYgbm90IGluX2Jsb2NrIGFuZCBzdHJpcHBlZCA9PSAiIyAvLy8gc2NyaXB0IjoKICAgICAgICAgICAgaW5fYmxvY2sgPSBUcnVlCiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgaWYgaW5fYmxvY2s6CiAgICAgICAgICAgIGlmIHN0cmlwcGVkLnJzdHJpcCgpID09ICIjIC8vLyI6CiAgICAgICAgICAgICAgICBpbl9ibG9jayA9IEZhbHNlCiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgb3V0LmFwcGVuZChsaW5lKQogICAgcmV0dXJuICIiLmpvaW4ob3V0KQoKCmRlZiBydW5fc2NyaXB0KHV2eF9leGUsIGVudHJ5X3BhdGgsIGZvcmNlX2ZyZXNoPUZhbHNlKToKICAgICIiIkV4ZWN1dGUgdmlhIGB1dnggYXV0b3BlcDcyMyA8ZW50cnk+YCwgaW5oZXJpdGluZyBzdGRpby4gZm9yY2VfZnJlc2g9VHJ1ZSBzZXRzCiAgICBVVl9OT19DQUNIRT0xIChzZWUgbW9kdWxlIGRvY3N0cmluZyk7IG9ubHkgdXNlZCBvbiBwb3N0LXBlcnNpc3QgcmV0cmllcy4iIiIKICAgIGVudiA9IE5vbmUKICAgIGlmIGZvcmNlX2ZyZXNoOgogICAgICAgIGVudiA9IGRpY3Qob3MuZW52aXJvbikKICAgICAgICBlbnZbIlVWX05PX0NBQ0hFIl0gPSAiMSIKICAgIHRyeToKICAgICAgICBwcm9jID0gc3VicHJvY2Vzcy5ydW4oW3V2eF9leGUsICJhdXRvcGVwNzIzIiwgZW50cnlfcGF0aF0sIGVudj1lbnYsIHRpbWVvdXQ9MTIwKQogICAgZXhjZXB0IChPU0Vycm9yLCBzdWJwcm9jZXNzLlRpbWVvdXRFeHBpcmVkKToKICAgICAgICByZXR1cm4gMQogICAgcmV0dXJuIHByb2MucmV0dXJuY29kZQoKCmRlZiBkaXNjb3Zlcl9kZXBfbmFtZXModXZ4X2V4ZSwgZW50cnlfcGF0aCk6CiAgICAiIiJSdW4gYHV2eCBhdXRvcGVwNzIzIGNoZWNrIDxlbnRyeT5gIGFuZCBleHRyYWN0IGRlcGVuZGVuY3kgbmFtZXMgZnJvbSBpdHMgb3V0cHV0LiIiIgogICAgdHJ5OgogICAgICAgIHByb2MgPSBzdWJwcm9jZXNzLnJ1bigKICAgICAgICAgICAgW3V2eF9leGUsICJhdXRvcGVwNzIzIiwgImNoZWNrIiwgZW50cnlfcGF0aF0sCiAgICAgICAgICAgIGNhcHR1cmVfb3V0cHV0PVRydWUsIHRleHQ9VHJ1ZSwgdGltZW91dD02MCwKICAgICAgICApCiAgICBleGNlcHQgKE9TRXJyb3IsIHN1YnByb2Nlc3MuVGltZW91dEV4cGlyZWQpOgogICAgICAgIHJldHVybiBbXQogICAgaWYgcHJvYy5yZXR1cm5jb2RlICE9IDA6CiAgICAgICAgcmV0dXJuIFtdCiAgICBuYW1lcyA9IFtdCiAgICBmb3IgbGluZSBpbiBwcm9jLnN0ZG91dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgbWF0Y2ggPSBERVBfTElORV9SRS5tYXRjaChsaW5lLnJzdHJpcCgiXG4iKSkKICAgICAgICBpZiBtYXRjaDoKICAgICAgICAgICAgbmFtZXMuYXBwZW5kKG1hdGNoLmdyb3VwKDEpLnN0cmlwKCkpCiAgICByZXR1cm4gbmFtZXMKCgpkZWYgcGVyc2lzdCh1dnhfZXhlLCB1dl9leGUsIHB5dGhvbl9leGUsIGVudHJ5X3BhdGgpOgogICAgIiIiQmVzdC1lZmZvcnQ6IGRpc2NvdmVyIGRlcGVuZGVuY3kgbmFtZXMgYW5kIHdyaXRlIHRoZW0gaW50byB0aGUgaGVhZGVyIHZpYSBgdXYgYWRkCiAgICAtLXNjcmlwdGAuIFJldHVybnMgVHJ1ZSBvbiBhIHN1Y2Nlc3NmdWwgKG9yIG5vLW9wLCBub3RoaW5nLXRvLWFkZCkgcGVyc2lzdCwgRmFsc2UgaWYKICAgIGRpc2NvdmVyeSBvciB0aGUgdXYgY2FsbCBpdHNlbGYgZmFpbGVkLiBOZXZlciByYWlzZXMuIiIiCiAgICBuYW1lcyA9IGRpc2NvdmVyX2RlcF9uYW1lcyh1dnhfZXhlLCBlbnRyeV9wYXRoKQogICAgaWYgbm90IG5hbWVzOgogICAgICAgIHJldHVybiBUcnVlCiAgICBjbWQgPSBbdXZfZXhlLCAiYWRkIiwgIi0tc2NyaXB0IiwgZW50cnlfcGF0aCwgIi1wIiwgcHl0aG9uX2V4ZV0gKyBuYW1lcwogICAgdHJ5OgogICAgICAgIHByb2MgPSBzdWJwcm9jZXNzLnJ1bihjbWQsIGNhcHR1cmVfb3V0cHV0PVRydWUsIHRleHQ9VHJ1ZSwgdGltZW91dD0xMjApCiAgICBleGNlcHQgKE9TRXJyb3IsIHN1YnByb2Nlc3MuVGltZW91dEV4cGlyZWQpOgogICAgICAgIHJldHVybiBGYWxzZQogICAgcmV0dXJuIHByb2MucmV0dXJuY29kZSA9PSAwCgoKZGVmIG1haW4oYXJndj1Ob25lKToKICAgIGFyZ3MgPSBsaXN0KHN5cy5hcmd2WzE6XSBpZiBhcmd2IGlzIE5vbmUgZWxzZSBhcmd2KQogICAgaWYgbGVuKGFyZ3MpIDwgNDoKICAgICAgICBwcmludCgiRVJST1I6YmFkX2FyZ3MiLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgcmV0dXJuIDEKICAgIGVudHJ5X3BhdGgsIHV2eF9leGUsIHV2X2V4ZSwgcHl0aG9uX2V4ZSA9IGFyZ3NbMF0sIGFyZ3NbMV0sIGFyZ3NbMl0sIGFyZ3NbM10KCiAgICByYyA9IHJ1bl9zY3JpcHQodXZ4X2V4ZSwgZW50cnlfcGF0aCkKCiAgICBpZiByYyA9PSAwOgogICAgICAgIGlmIHBlcnNpc3QodXZ4X2V4ZSwgdXZfZXhlLCBweXRob25fZXhlLCBlbnRyeV9wYXRoKToKICAgICAgICAgICAgcHJpbnQoIlJBTjpwZXJzaXN0ZWQiLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHJpbnQoIlJBTjpwZXJzaXN0X2ZhaWxlZCIsIGZpbGU9c3lzLnN0ZGVycikKICAgICAgICByZXR1cm4gMAoKICAgIGlmIHJjID09IDI6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB3aXRoIG9wZW4oZW50cnlfcGF0aCwgInIiLCBlbmNvZGluZz0idXRmLTgiLCBuZXdsaW5lPSIiKSBhcyBmaDoKICAgICAgICAgICAgICAgIG9yaWdpbmFsID0gZmgucmVhZCgpCiAgICAgICAgZXhjZXB0IFVuaWNvZGVEZWNvZGVFcnJvcjoKICAgICAgICAgICAgcHJpbnQoIkVSUk9SOnN0cmlwX3JldHJ5X3NraXBwZWQ6bm9uX3V0ZjgiLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgICAgIHJldHVybiByYwogICAgICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgICAgICBwcmludCgiRVJST1I6c3RyaXBfcmV0cnlfZmFpbGVkOnJlYWQiLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgICAgIHJldHVybiByYwogICAgICAgIHN0cmlwcGVkID0gc3RyaXBfcGVwNzIzX2Jsb2NrKG9yaWdpbmFsKQogICAgICAgIHRyeToKICAgICAgICAgICAgd2l0aCBvcGVuKGVudHJ5X3BhdGgsICJ3IiwgZW5jb2Rpbmc9InV0Zi04IiwgbmV3bGluZT0iIikgYXMgZmg6CiAgICAgICAgICAgICAgICBmaC53cml0ZShzdHJpcHBlZCkKICAgICAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICAgICAgcHJpbnQoIkVSUk9SOnN0cmlwX3JldHJ5X2ZhaWxlZDp3cml0ZSIsIGZpbGU9c3lzLnN0ZGVycikKICAgICAgICAgICAgcmV0dXJuIHJjCiAgICAgICAgcmMyID0gcnVuX3NjcmlwdCh1dnhfZXhlLCBlbnRyeV9wYXRoLCBmb3JjZV9mcmVzaD1UcnVlKQogICAgICAgIGlmIHJjMiA9PSAwOgogICAgICAgICAgICBpZiBwZXJzaXN0KHV2eF9leGUsIHV2X2V4ZSwgcHl0aG9uX2V4ZSwgZW50cnlfcGF0aCk6CiAgICAgICAgICAgICAgICBwcmludCgiUkFOOnBlcnNpc3RlZF9hZnRlcl9yZXBhaXIiLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcmludCgiUkFOOnBlcnNpc3RfZmFpbGVkX2FmdGVyX3JlcGFpciIsIGZpbGU9c3lzLnN0ZGVycikKICAgICAgICAgICAgcmV0dXJuIDAKICAgICAgICAjIFJldHJ5IGFsc28gZmFpbGVkIC0tIHJlc3RvcmUgb3JpZ2luYWwgY29udGVudCByYXRoZXIgdGhhbiBsZWF2ZSBpdCBzdHJpcHBlZC4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHdpdGggb3BlbihlbnRyeV9wYXRoLCAidyIsIGVuY29kaW5nPSJ1dGYtOCIsIG5ld2xpbmU9IiIpIGFzIGZoOgogICAgICAgICAgICAgICAgZmgud3JpdGUob3JpZ2luYWwpCiAgICAgICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgICAgIHBhc3MKICAgICAgICBwcmludCgiRVJST1I6c3RyaXBfcmV0cnlfZmFpbGVkOiVkIiAlIHJjMiwgZmlsZT1zeXMuc3RkZXJyKQogICAgICAgIHJldHVybiByYzIKCiAgICAjIE90aGVyIG5vbnplcm86IGhlYWRlciAoaWYgYW55KSBpcyBwcmVzdW1hYmx5IHZhbGlkOyBydW4gZmFpbGVkIGZvciBhbm90aGVyIHJlYXNvbiAtLQogICAgIyBtb3N0IGNvbW1vbmx5IGEgbWlzc2luZyBkZXBlbmRlbmN5LiBCZXN0LWVmZm9ydCBmaWxsLWluIHdpdGhvdXQgc3RyaXBwaW5nLCByZXRyeSBvbmNlLgogICAgcGVyc2lzdCh1dnhfZXhlLCB1dl9leGUsIHB5dGhvbl9leGUsIGVudHJ5X3BhdGgpCiAgICByYzMgPSBydW5fc2NyaXB0KHV2eF9leGUsIGVudHJ5X3BhdGgsIGZvcmNlX2ZyZXNoPVRydWUpCiAgICBpZiByYzMgPT0gMDoKICAgICAgICBwcmludCgiUkFOOnBlcnNpc3RlZF9hZnRlcl9maWxsaW4iLCBmaWxlPXN5cy5zdGRlcnIpCiAgICAgICAgcmV0dXJuIDAKICAgIHByaW50KCJFUlJPUjpydW5fZmFpbGVkX25vdF9kZXBlbmRlbmN5X2dhcDolZCIgJSByYzMsIGZpbGU9c3lzLnN0ZGVycikKICAgIHJldHVybiByYzMKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" rem ~collect_submodules.py emits pre-build --collect-submodules flags for packages rem that load submodules dynamically; double-gated on used-by-source AND installed. set "HP_COLLECT_SUBMODULES=IiIiY29sbGVjdF9zdWJtb2R1bGVzIHYxICgyMDI2LTA2LTI3KQpQcmUtYnVpbGQgUHlJbnN0YWxsZXIgZmxhZyBnZW5lcmF0b3IgZm9yIHBhY2thZ2VzIHRoYXQgbG9hZCBzdWJtb2R1bGVzCmR5bmFtaWNhbGx5IChwbHVnaW4vYmFja2VuZC9yZWdpc3RyeSBzeXN0ZW1zKSB3aGljaCBQeUluc3RhbGxlcidzIHN0YXRpYwphbmFseXNpcyBjYW5ub3QgdHJhY2UuIFN1Y2ggcGFja2FnZXMgcHJvZHVjZSBOTyB3YXJuLWZpbGUgZW50cnkgKHRoZSBpbXBvcnQKaXRzZWxmIHJlc29sdmVzKSwgc28gd2FybmZpeCBuZXZlciBzZWVzIHRoZW07IHRoZSBmcm96ZW4gRVhFIHRoZW4gZmFpbHMgYXQKcnVudGltZSB3aGVuIGl0IHJlYWNoZXMgdGhlIHVuLWJ1bmRsZWQgc3VibW9kdWxlLgoKRW1pdHMgb25lIHNwYWNlLXNlcGFyYXRlZCBsaW5lIG9mIGAtLWNvbGxlY3Qtc3VibW9kdWxlcz1QS0dgIGZsYWdzIHRvIHN0ZG91dC4KCkRPVUJMRS1HQVRFIChkZWxpYmVyYXRlIC0tIHNlZSBkZXJpdmVkIHJlcXVpcmVtZW50IGJlbG93KTogYSBmbGFnIGlzIGVtaXR0ZWQKb25seSB3aGVuIFBLRyBpcyBCT1RICiAgKDEpIGltcG9ydGVkIGJ5IHRoZSB1c2VyJ3MgcHJvamVjdCBzb3VyY2UgKCJ1c2VkIiksIEFORAogICgyKSBpbXBvcnRhYmxlIGluIHRoZSBidWlsZCBpbnRlcnByZXRlciAoImluc3RhbGxlZCIsIHZpYSBmaW5kX3NwZWMpLgpHYXRpbmcgb24gImluc3RhbGxlZCIgYWxvbmUgd291bGQgYnVuZGxlIGh1bmRyZWRzIG9mIE1CIG9mIGFuIHVudXNlZCBsaWJyYXJ5CmludG8gZXZlcnkgRVhFIG1lcmVseSBiZWNhdXNlIGl0IGhhcHBlbnMgdG8gc2l0IGluIGEgZmF0IGdsb2JhbC9jb25kYSBlbnYKKGEgNS1saW5lIGhlbGxvLXdvcmxkIHNob3VsZCBzdGF5IGxlYW4pLiBHYXRpbmcgb24gInVzZWQiIGFsb25lIGNvdWxkIHBhc3MgYQpmbGFnIGZvciBhIHBhY2thZ2UgdGhhdCBpcyBpbXBvcnRlZCBidXQgbm90IGFjdHVhbGx5IGluc3RhbGxlZCwgd2hpY2ggbWFrZXMKUHlJbnN0YWxsZXIgZXJyb3Igb3V0LiBSZXF1aXJpbmcgYm90aCBrZWVwcyBsZWFuIGFwcHMgbGVhbiBhbmQgYXZvaWRzIHNwdXJpb3VzCmZsYWdzLgoKVGhlIGN1cmF0ZWQgc2V0IHVzZXMgSU1QT1JUIG5hbWVzIChza2xlYXJuLCBub3Qgc2Npa2l0LWxlYXJuKSBiZWNhdXNlCi0tY29sbGVjdC1zdWJtb2R1bGVzIHRha2VzIHRoZSBpbXBvcnRhYmxlIG1vZHVsZSBuYW1lLCBhbmQgbWF0Y2hpbmcgdGhlIGltcG9ydApuYW1lIGFnYWluc3QgcHJvamVjdCBzb3VyY2UgYXZvaWRzIHRoZSBwYWNrYWdlLXZzLWltcG9ydCBuYW1pbmcgbWlzbWF0Y2guCgpVc2FnZTogcHl0aG9uIH5jb2xsZWN0X3N1Ym1vZHVsZXMucHkgW3Byb2plY3Rfcm9vdF0gICAoZGVmYXVsdDogY3dkKQoiIiIKX192ZXJzaW9uX18gPSAiY29sbGVjdF9zdWJtb2R1bGVzIHYxICgyMDI2LTA2LTI3KSIKX19hbGxfXyA9IFsiRFlOQU1JQ19QS0dTIiwgImltcG9ydGVkX3RvcF9sZXZlbHMiLCAiY29sbGVjdF9mbGFncyIsICJtYWluIl0KCmltcG9ydCBhc3QKaW1wb3J0IGltcG9ydGxpYi51dGlsCmltcG9ydCBvcwppbXBvcnQgcmUKaW1wb3J0IHN5cwoKIyBDdXJhdGVkIHNldCBvZiBwYWNrYWdlcyB3aG9zZSBzdWJtb2R1bGVzIGFyZSBsb2FkZWQgdmlhIGR5bmFtaWMgZGlzcGF0Y2gKIyAoZXN0aW1hdG9yIHJlZ2lzdHJpZXMsIGJhY2tlbmQgcGx1Z2lucywgY29tcGlsZWQtZXh0ZW5zaW9uIHN1Ym1vZHVsZXMpIHRoYXQKIyBQeUluc3RhbGxlcidzIHN0YXRpYyB0cmFjZXIgbWlzc2VzLiBJbXBvcnQtbmFtZSA9PSAtLWNvbGxlY3Qtc3VibW9kdWxlcyB0YXJnZXQuCiMgQ29uc2VydmF0aXZlIG9uIHB1cnBvc2U6IGhlYXZ5IE1MIHN0YWNrcyAodG9yY2gvdGVuc29yZmxvdy90cmFuc2Zvcm1lcnMpIGFyZQojIGV4Y2x1ZGVkIC0tIGNvbGxlY3RpbmcgdGhlaXIgc3VibW9kdWxlcyBibG9hdHMgdGhlIEVYRSBieSBnaWdhYnl0ZXMgYW5kIHRob3NlCiMgdXNlcnMgdHlwaWNhbGx5IHN1cHBseSBleHBsaWNpdCBkZXBzLgpEWU5BTUlDX1BLR1MgPSAoInNrbGVhcm4iLCAibWF0cGxvdGxpYiIsICJzY2lweSIsICJwbG90bHkiKQoKIyBEaXJlY3RvcmllcyBuZXZlciBwYXJ0IG9mIHRoZSB1c2VyJ3MgYXBwbGljYXRpb24gc291cmNlLgpfU0tJUF9ESVJTID0gZnJvemVuc2V0KFsKICAgICJkaXN0IiwgImJ1aWxkIiwgIl9fcHljYWNoZV9fIiwgIm5vZGVfbW9kdWxlcyIsCl0pCgoKZGVmIF9za2lwX2RpcihuYW1lKToKICAgICIiIlRydWUgaWYgYSBkaXJlY3Rvcnkgc2hvdWxkIG5vdCBiZSB3YWxrZWQgZm9yIHVzZXIgc291cmNlLiIiIgogICAgaWYgbmFtZS5zdGFydHN3aXRoKCIuIikgb3IgbmFtZS5zdGFydHN3aXRoKCJ+Iik6CiAgICAgICAgcmV0dXJuIFRydWUKICAgIHJldHVybiBuYW1lIGluIF9TS0lQX0RJUlMKCgpkZWYgX3JlZ2V4X3RvcF9sZXZlbHModGV4dCk6CiAgICAiIiJGYWxsYmFjayBpbXBvcnQgc2NhbiBmb3IgYSBzaW5nbGUgZmlsZSB0aGF0IGZhaWxlZCB0byBBU1QtcGFyc2UuCgogICAgTWF0Y2hlcyBvbmx5IGltcG9ydCBzdGF0ZW1lbnRzIGF0IGxpbmUgc3RhcnQgKGFmdGVyIG9wdGlvbmFsIHdoaXRlc3BhY2UpLAogICAgd2l0aCBhIHdvcmQgYm91bmRhcnkgc28gJ2ltcG9ydCBzY2lweXRob24nIGRvZXMgbm90IG1hdGNoICdzY2lweScuCiAgICAiIiIKICAgIGZvdW5kID0gc2V0KCkKICAgIGZvciBwa2cgaW4gRFlOQU1JQ19QS0dTOgogICAgICAgIHBhdHRlcm4gPSByIig/bSleXHMqKD86aW1wb3J0fGZyb20pXHMrIiArIHJlLmVzY2FwZShwa2cpICsgciJcYiIKICAgICAgICBpZiByZS5zZWFyY2gocGF0dGVybiwgdGV4dCk6CiAgICAgICAgICAgIGZvdW5kLmFkZChwa2cpCiAgICByZXR1cm4gZm91bmQKCgpkZWYgX2ZpbGVfdG9wX2xldmVscyh0ZXh0KToKICAgICIiIlJldHVybiB0aGUgc2V0IG9mIHRvcC1sZXZlbCBpbXBvcnRlZCBtb2R1bGUgbmFtZXMgaW4gb25lIHNvdXJjZSBmaWxlLgoKICAgIFVzZXMgQVNUIChzbyBjb21tZW50ZWQtb3V0IG9yIHN0cmluZy1saXRlcmFsICdpbXBvcnRzJyBkbyBub3QgY291bnQpLiBPbiBhCiAgICBTeW50YXhFcnJvciBpbiB0aGUgdXNlcidzIGNvZGUsIGZhbGxzIGJhY2sgdG8gYSBjb25zZXJ2YXRpdmUgcmVnZXggc2NhbiBvZgogICAgdGhlIHNhbWUgdGV4dCBzbyBhIHNpbmdsZSB1bi1wYXJzZWFibGUgZmlsZSBkb2VzIG5vdCBibGluZCB0aGUgd2hvbGUgc2Nhbi4KICAgICIiIgogICAgZm91bmQgPSBzZXQoKQogICAgdHJ5OgogICAgICAgIHRyZWUgPSBhc3QucGFyc2UodGV4dCkKICAgIGV4Y2VwdCAoU3ludGF4RXJyb3IsIFZhbHVlRXJyb3IpOgogICAgICAgIHJldHVybiBfcmVnZXhfdG9wX2xldmVscyh0ZXh0KQogICAgZm9yIG5vZGUgaW4gYXN0LndhbGsodHJlZSk6CiAgICAgICAgaWYgaXNpbnN0YW5jZShub2RlLCBhc3QuSW1wb3J0KToKICAgICAgICAgICAgZm9yIGFsaWFzIGluIG5vZGUubmFtZXM6CiAgICAgICAgICAgICAgICBpZiBhbGlhcy5uYW1lOgogICAgICAgICAgICAgICAgICAgIGZvdW5kLmFkZChhbGlhcy5uYW1lLnNwbGl0KCIuIilbMF0pCiAgICAgICAgZWxpZiBpc2luc3RhbmNlKG5vZGUsIGFzdC5JbXBvcnRGcm9tKToKICAgICAgICAgICAgIyBsZXZlbCA+IDAgaXMgYSByZWxhdGl2ZSBpbXBvcnQgKGZyb20gLiBpbXBvcnQgeCkgLS0gdGhlIG1vZHVsZSBpcwogICAgICAgICAgICAjIGxvY2FsIHRvIHRoZSBwcm9qZWN0LCBuZXZlciBvbmUgb2YgdGhlIGN1cmF0ZWQgdGhpcmQtcGFydHkgcGtncy4KICAgICAgICAgICAgaWYgbm9kZS5sZXZlbCA9PSAwIGFuZCBub2RlLm1vZHVsZToKICAgICAgICAgICAgICAgIGZvdW5kLmFkZChub2RlLm1vZHVsZS5zcGxpdCgiLiIpWzBdKQogICAgcmV0dXJuIGZvdW5kCgoKZGVmIGltcG9ydGVkX3RvcF9sZXZlbHMocm9vdCk6CiAgICAiIiJXYWxrIHRoZSBwcm9qZWN0IHRyZWUgYW5kIHJldHVybiB0aGUgc2V0IG9mIHRvcC1sZXZlbCBpbXBvcnRlZCBtb2R1bGVzLiIiIgogICAgZm91bmQgPSBzZXQoKQogICAgZm9yIGN1cnJlbnQsIGRpcnMsIGZpbGVzIGluIG9zLndhbGsocm9vdCk6CiAgICAgICAgZGlyc1s6XSA9IFtkIGZvciBkIGluIGRpcnMgaWYgbm90IF9za2lwX2RpcihkKV0KICAgICAgICBmb3IgbmFtZSBpbiBmaWxlczoKICAgICAgICAgICAgaWYgbm90IG5hbWUuZW5kc3dpdGgoIi5weSIpIG9yIG5hbWUuc3RhcnRzd2l0aCgifiIpOgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgcGF0aCA9IG9zLnBhdGguam9pbihjdXJyZW50LCBuYW1lKQogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4ocGF0aCwgInIiLCBlbmNvZGluZz0idXRmLTgiLCBlcnJvcnM9Imlnbm9yZSIpIGFzIGhhbmRsZToKICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gaGFuZGxlLnJlYWQoKQogICAgICAgICAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvdW5kIHw9IF9maWxlX3RvcF9sZXZlbHModGV4dCkKICAgIHJldHVybiBmb3VuZAoKCmRlZiBfaXNfaW5zdGFsbGVkKG5hbWUpOgogICAgIiIiVHJ1ZSBpZiBuYW1lIGlzIGltcG9ydGFibGUgaW4gdGhlIGN1cnJlbnQgKGJ1aWxkKSBpbnRlcnByZXRlci4iIiIKICAgIHRyeToKICAgICAgICByZXR1cm4gaW1wb3J0bGliLnV0aWwuZmluZF9zcGVjKG5hbWUpIGlzIG5vdCBOb25lCiAgICBleGNlcHQgKEltcG9ydEVycm9yLCBWYWx1ZUVycm9yLCBBdHRyaWJ1dGVFcnJvcik6CiAgICAgICAgIyBmaW5kX3NwZWMgY2FuIHJhaXNlIGZvciBuYW1lc3BhY2UvcGFydGlhbCBwYWNrYWdlczsgdHJlYXQgYXMgYWJzZW50LgogICAgICAgIHJldHVybiBGYWxzZQoKCmRlZiBjb2xsZWN0X2ZsYWdzKHJvb3QsIGluc3RhbGxlZF9jaGVjaz1Ob25lKToKICAgICIiIlJldHVybiBvcmRlcmVkIC0tY29sbGVjdC1zdWJtb2R1bGVzIGZsYWdzIGZvciB1c2VkIEFORCBpbnN0YWxsZWQgcGFja2FnZXMuCgogICAgaW5zdGFsbGVkX2NoZWNrIGlzIGluamVjdGFibGUgZm9yIHRlc3Rpbmcgc28gdGhlIGdhdGluZyBsb2dpYyBjYW4gYmUKICAgIGV4ZXJjaXNlZCB3aXRob3V0IGFjdHVhbGx5IGluc3RhbGxpbmcgaGVhdnkgcGFja2FnZXMuCiAgICAiIiIKICAgIGlmIGluc3RhbGxlZF9jaGVjayBpcyBOb25lOgogICAgICAgIGluc3RhbGxlZF9jaGVjayA9IF9pc19pbnN0YWxsZWQKICAgIHVzZWQgPSBpbXBvcnRlZF90b3BfbGV2ZWxzKHJvb3QpCiAgICBmbGFncyA9IFtdCiAgICBmb3IgcGtnIGluIERZTkFNSUNfUEtHUzoKICAgICAgICBpZiBwa2cgaW4gdXNlZCBhbmQgaW5zdGFsbGVkX2NoZWNrKHBrZyk6CiAgICAgICAgICAgIGZsYWdzLmFwcGVuZCgiLS1jb2xsZWN0LXN1Ym1vZHVsZXM9IiArIHBrZykKICAgIHJldHVybiBmbGFncwoKCmRlZiBtYWluKGFyZ3Y9Tm9uZSk6CiAgICBhcmdzID0gbGlzdChzeXMuYXJndlsxOl0gaWYgYXJndiBpcyBOb25lIGVsc2UgYXJndikKICAgICMgTm9ybWFsaXplIHRvIGFuIGFic29sdXRlIHBhdGggc28gdGhlIHdhbGsgaXMgYW5jaG9yZWQgcmVnYXJkbGVzcyBvZiB0aGUKICAgICMgY2FsbGVyJ3MgY3dkIC8gcmVsYXRpdmUtcGF0aCBkcmlmdCAoZGVmZW5zaXZlIG9uIFdpbmRvd3MgbXVsdGktZHJpdmUgcGF0aHMpLgogICAgcm9vdCA9IG9zLnBhdGguYWJzcGF0aChhcmdzWzBdIGlmIGFyZ3MgZWxzZSAiLiIpCiAgICBzeXMuc3Rkb3V0LndyaXRlKCIgIi5qb2luKGNvbGxlY3RfZmxhZ3Mocm9vdCkpKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK" rem ~hidden_import_scan.py decides the next --hidden-import target from a frozen EXE rem stderr: strict ModuleNotFoundError + the module must be installed in the build env. set "HP_HIDDEN_IMPORT_SCAN=IiIiaGlkZGVuX2ltcG9ydF9zY2FuIHYxICgyMDI2LTA2LTI4KQpEZWNpZGUgdGhlIG5leHQgLS1oaWRkZW4taW1wb3J0IHRhcmdldCBmcm9tIGEgZnJvemVuIEVYRSdzIHN0ZGVyciwgZm9yIHRoZQpTbGljZSAyIGF1dG8tcmVjb3ZlcnkgbG9vcCBpbiBydW5fc2V0dXAuYmF0LgoKU1RSSUNUIGFuZCBET1VCTEUtR0FURUQgb24gcHVycG9zZToKICAoMSkgVGhlIEVYRSBzdGRlcnIgbXVzdCBjb250YWluIGBNb2R1bGVOb3RGb3VuZEVycm9yOiBObyBtb2R1bGUgbmFtZWQgJ1gnYC4KICAgICAgVGhhdCBpcyB0aGUgZGV0ZXJtaW5pc3RpYyAiWCdzIGNvZGUgaXMgbm90IGluIHRoZSBidW5kbGUiIHNpZ25hbCAtLQogICAgICBQeUluc3RhbGxlciBsZWZ0IFggb3V0LCBhbmQgYC0taGlkZGVuLWltcG9ydD1YYCBpcyB0aGUgZXhhY3Qgc3RydWN0dXJhbCBmaXguCiAgKDIpIFggKGl0cyB0b3AtbGV2ZWwgcGFja2FnZSkgbXVzdCBiZSBpbXBvcnRhYmxlIGluIHRoZSBCVUlMRCBpbnRlcnByZXRlcgogICAgICAoZmluZF9zcGVjKS4gSWYgWCBpcyBub3QgaW5zdGFsbGVkLCB0aGlzIGlzIGEgdXNlciB0eXBvIG9yIGEgZ2VudWluZWx5CiAgICAgIG1pc3NpbmcgZGVwZW5kZW5jeSAtLSBOT1QgYSBwYWNrYWdpbmcgbWlzcyAtLSBzbyB3ZSBlbWl0IG5vdGhpbmcgYW5kIGxldAogICAgICB0aGUgcG9zdC1mbGlnaHQgaGludHMgc3VyZmFjZSB0aGUgc3RhY2sgdHJhY2UuIFRoaXMgaXMgd2hhdCBtYWtlcyBhIHR5cG8KICAgICAgbGlrZSBgaW1wb3J0IG5vbmV4aXN0YW50YCBjb3N0IFpFUk8gcmVidWlsZHMuCiAgKDMpIFggbXVzdCBub3QgYWxyZWFkeSBiZSBpbiB0aGUgYWxyZWFkeS10cmllZCBsaXN0IChubyBsb29wcykuCiAgKDQpIFggbXVzdCBub3QgYmUgYSBwbGF0Zm9ybS9zdGRsaWIgc2hpbSBsZWdpdGltYXRlbHkgYWJzZW50IG9uIFdpbmRvd3MuCgpEZWxpYmVyYXRlbHkgTk9UIGhhbmRsZWQ6IGBJbXBvcnRFcnJvcjogY2Fubm90IGltcG9ydCBuYW1lICdZJyBmcm9tICdaJ2AuIFogaXMKYWxyZWFkeSBidW5kbGVkIGFuZCBZIGlzIGFuIGF0dHJpYnV0ZSwgbm90IGEgbW9kdWxlLCBzbyBubyAtLWhpZGRlbi1pbXBvcnQKdGFyZ2V0IGlzIGRlcml2YWJsZSBhbmQgYSByZWJ1aWxkIGNhbm5vdCBmaXggaXQuIFN1Y2ggZXJyb3JzIGFyZSB1c2VyIGNvZGUKKHR5cG9zLCBjaXJjdWxhciBpbXBvcnRzLCB2ZXJzaW9uIGRyaWZ0KSBvciBkeW5hbWljLXN1Ym1vZHVsZSBnYXBzIGJldHRlciBmaXhlZApieSAtLWNvbGxlY3Qtc3VibW9kdWxlcyAoaGFuZGxlZCBzZXBhcmF0ZWx5KS4gVGhleSByb3V0ZSB0byBoaW50cyB1bmNoYW5nZWQuCgpVc2FnZTogcHl0aG9uIH5oaWRkZW5faW1wb3J0X3NjYW4ucHkgPHN0ZGVycl9maWxlPiBbYWxyZWFkeV90cmllZCAuLi5dClByaW50cyB0aGUgbmV4dCBoaWRkZW4taW1wb3J0IG1vZHVsZSBuYW1lIChvciBub3RoaW5nKSB0byBzdGRvdXQuCiIiIgpfX3ZlcnNpb25fXyA9ICJoaWRkZW5faW1wb3J0X3NjYW4gdjEgKDIwMjYtMDYtMjgpIgpfX2FsbF9fID0gWyJTS0lQIiwgIm5leHRfaGlkZGVuX2ltcG9ydCIsICJtYWluIl0KCmltcG9ydCBpbXBvcnRsaWIudXRpbAppbXBvcnQgcmUKaW1wb3J0IHN5cwoKIyBQbGF0Zm9ybS9zdGRsaWIgbW9kdWxlcyBsZWdpdGltYXRlbHkgYWJzZW50IG9uIFdpbmRvd3MgLS0gbmV2ZXIgYSBwYWNrYWdpbmcKIyBtaXNzLCBzbyBuZXZlciBoaWRkZW4taW1wb3J0IHRoZW0gKG1pcnJvciBvZiB0aGUgcGFyc2Vfd2FybiB1bml4LW9ubHkgc2V0KS4KU0tJUCA9IGZyb3plbnNldChbCiAgICAiZ3JwIiwgInB3ZCIsICJwb3NpeCIsICJyZXNvdXJjZSIsICJmY250bCIsICJyZWFkbGluZSIsICJ0ZXJtaW9zIiwgInR0eSIsCiAgICAicHR5IiwgImNyeXB0IiwgInNwd2QiLCAibmlzIiwgInN5c2xvZyIsICJvc3NhdWRpb2RldiIsCiAgICAiX3Bvc2l4c3VicHJvY2VzcyIsICJfc2Nwcm94eSIsICJfZnJvemVuX2ltcG9ydGxpYl9leHRlcm5hbCIsCl0pCgojIE9ubHkgTW9kdWxlTm90Rm91bmRFcnJvciAtLSBOT1QgYSBiYXJlIEltcG9ydEVycm9yIChzZWUgbW9kdWxlIGRvY3N0cmluZykuCl9QQVRURVJOID0gcmUuY29tcGlsZSgKICAgIHIiTW9kdWxlTm90Rm91bmRFcnJvcjogTm8gbW9kdWxlIG5hbWVkIFsnXCJdKFteJ1wiXSspWydcIl0iCikKCgpkZWYgX2lzX2luc3RhbGxlZChuYW1lKToKICAgICIiIlRydWUgaWYgbmFtZSBpcyBpbXBvcnRhYmxlIGluIHRoZSBjdXJyZW50IChidWlsZCkgaW50ZXJwcmV0ZXIuIiIiCiAgICB0cnk6CiAgICAgICAgcmV0dXJuIGltcG9ydGxpYi51dGlsLmZpbmRfc3BlYyhuYW1lKSBpcyBub3QgTm9uZQogICAgZXhjZXB0IChJbXBvcnRFcnJvciwgVmFsdWVFcnJvciwgQXR0cmlidXRlRXJyb3IpOgogICAgICAgIHJldHVybiBGYWxzZQoKCmRlZiBuZXh0X2hpZGRlbl9pbXBvcnQoc3RkZXJyX3RleHQsIGFscmVhZHlfdHJpZWQ9KCksIGluc3RhbGxlZF9jaGVjaz1Ob25lKToKICAgICIiIlJldHVybiB0aGUgbmV4dCAtLWhpZGRlbi1pbXBvcnQgbW9kdWxlIG5hbWUsIG9yICIiIGlmIG5vbmUgaXMgZml4YWJsZS4KCiAgICBpbnN0YWxsZWRfY2hlY2sgaXMgaW5qZWN0YWJsZSBmb3IgdGVzdGluZyBzbyB0aGUgZmluZF9zcGVjIGdhdGUgY2FuIGJlCiAgICBleGVyY2lzZWQgd2l0aG91dCBpbnN0YWxsaW5nIHBhY2thZ2VzLiBJdCBpcyBjYWxsZWQgd2l0aCB0aGUgVE9QLUxFVkVMCiAgICBwYWNrYWdlIG5hbWUgKG5vdCB0aGUgZG90dGVkIHN1Ym1vZHVsZSkgdG8gYXZvaWQgaW1wb3J0aW5nIHRoZSBwYXJlbnQKICAgIHBhY2thZ2UncyBzaWRlIGVmZmVjdHMgZHVyaW5nIGRldGVjdGlvbi4KICAgICIiIgogICAgaWYgaW5zdGFsbGVkX2NoZWNrIGlzIE5vbmU6CiAgICAgICAgaW5zdGFsbGVkX2NoZWNrID0gX2lzX2luc3RhbGxlZAogICAgdHJpZWQgPSBzZXQoYWxyZWFkeV90cmllZCkKICAgIGZvciBtYXRjaCBpbiBfUEFUVEVSTi5maW5kaXRlcihzdGRlcnJfdGV4dCk6CiAgICAgICAgbW9kID0gbWF0Y2guZ3JvdXAoMSkuc3RyaXAoKQogICAgICAgIGlmIG5vdCBtb2Qgb3IgbW9kIGluIHRyaWVkOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgIHRvcCA9IG1vZC5zcGxpdCgiLiIpWzBdCiAgICAgICAgaWYgdG9wIGluIFNLSVAgb3IgbW9kIGluIFNLSVAgb3IgdG9wLnN0YXJ0c3dpdGgoIl8iKToKICAgICAgICAgICAgY29udGludWUKICAgICAgICAjIEdhdGUgKDIpOiB0aGUgdG9wLWxldmVsIHBhY2thZ2UgbXVzdCBiZSBpbnN0YWxsZWQgaW4gdGhlIGJ1aWxkIGludGVycC4KICAgICAgICAjIFdlIGVtaXQgdGhlIEZVTEwgZG90dGVkIG5hbWUgYXMgdGhlIGhpZGRlbi1pbXBvcnQgdGFyZ2V0IGJ1dCBnYXRlIG9uCiAgICAgICAgIyB0aGUgdG9wLWxldmVsIHBhY2thZ2Ugc28gZGV0ZWN0aW9uIG5ldmVyIGltcG9ydHMgYSBoZWF2eSBzdWJtb2R1bGUuCiAgICAgICAgaWYgaW5zdGFsbGVkX2NoZWNrKHRvcCk6CiAgICAgICAgICAgIHJldHVybiBtb2QKICAgIHJldHVybiAiIgoKCmRlZiBtYWluKGFyZ3Y9Tm9uZSk6CiAgICBhcmdzID0gbGlzdChzeXMuYXJndlsxOl0gaWYgYXJndiBpcyBOb25lIGVsc2UgYXJndikKICAgIGlmIG5vdCBhcmdzOgogICAgICAgIHJldHVybgogICAgc3RkZXJyX2ZpbGUgPSBhcmdzWzBdCiAgICBhbHJlYWR5ID0gYXJnc1sxOl0KICAgIHRyeToKICAgICAgICB3aXRoIG9wZW4oc3RkZXJyX2ZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICAgICAgdGV4dCA9IGZoLnJlYWQoKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgcmV0dXJuCiAgICBzeXMuc3Rkb3V0LndyaXRlKG5leHRfaGlkZGVuX2ltcG9ydCh0ZXh0LCBhbHJlYWR5KSkKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbWFpbigpCg==" rem ~dll_pct_sanitize.ps1 strips %/^ from env var values for :log's UNQUOTED-echo safety -- rem emitted as a real .ps1 file (not inline -Command text) so cmd.exe never parses its content. set "HP_DLL_PCT_SANITIZE=IyBTdHJpcHMgJS9eIGZyb20gb25lIG9yIG1vcmUgZW52IHZhciB2YWx1ZXMsIGZvciA6bG9nJ3MgVU5RVU9URUQtZWNobyBzYWZldHkgKHNlZQojIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kJ3MgIjpsb2cgZWNob2VzIFVOUVVPVEVEIiBlbnRyeSkuIEEgbGl0ZXJhbCAlIG9uIHRoZSBzYW1lIGNtZC5leGUKIyBsb2dpY2FsIGxpbmUgYXMgYSByZWFsICVWQVIlIHJlZmVyZW5jZSAoZS5nLiAlTE9HJSkgY2FuIHNpbGVudGx5IGNvcnJ1cHQgY21kLmV4ZSdzIG93bgojIGxlZnQtdG8tcmlnaHQgJS1wYWlyaW5nIHNjYW4gLS0gdGhpcyBleGFjdCBoYXphcmQgYnJva2UgdGhlIG9sZCBpbmxpbmUgYC1Db21tYW5kYCB2ZXJzaW9uIG9mCiMgdGhpcyBsb2dpYyBUSFJFRSB0aW1lcyBpbiByZWFsIENJIGJlZm9yZSBsYW5kaW5nIGhlcmUuIEVtaXR0ZWQgYXMgYSByZWFsIC5wczEgZmlsZSBzcGVjaWZpY2FsbHkKIyBzbyB0aGlzIHRleHQgaXMgTkVWRVIgcGFyc2VkIGJ5IGNtZC5leGUncyBvd24gdG9rZW5pemVyIC0tIG9ubHkgdGhlIG91dGVyIGAtRmlsZSAicGF0aCIgYXJnLi4uYAojIGxpbmUgaXMsIGFuZCBwbGFpbiBhcmd2IGhhcyBubyAlL14tcGFpcmluZyBoYXphcmQuICYvfC88Lz4gYXJlIGhhbmRsZWQgYnkgdGhlIENBTExFUiB2aWEgcGxhaW4KIyBjbWQuZXhlIDpzZWFyY2g9cmVwbGFjZSBzdWJzdGl0dXRpb24gYmVmb3JlIHRoaXMgcnVucyAodGhhdCBwYXJ0IHdhcyBuZXZlciBicm9rZW4pLgojCiMgVXNhZ2U6IHBvd2Vyc2hlbGwgLUZpbGUgZGxsX3BjdF9zYW5pdGl6ZS5wczEgPGVudlZhck5hbWU+IDxvdXRGaWxlPiBbPGVudlZhck5hbWU+IDxvdXRGaWxlPiAuLi5dCiMgRm9yIGVhY2ggcGFpciwgcmVhZHMgW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZShuYW1lKSwgc3RyaXBzICUvXiwgYW5kIHdyaXRlcyB0aGUKIyByZXN1bHQgKG5vIHRyYWlsaW5nIG5ld2xpbmUpIHRvIG91dEZpbGUgLS0gcmVhZCBiYWNrIGJ5IHRoZSBjYWxsZXIgdmlhIGEgcGxhaW4KIyAiZm9yIC9mICJ1c2ViYWNrcSBkZWxpbXM9IiAlJVggaW4gKG91dEZpbGUpIGRvIHNldCIuCiMKIyBDYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRExMX1BDVF9TQU5JVElaRSBwYXlsb2FkIGluIHJ1bl9zZXR1cC5iYXQuIEFmdGVyIGVkaXRpbmc6CiMgYHB5dGhvbiB0b29scy9zeW5jX3BheWxvYWQucHkgSFBfRExMX1BDVF9TQU5JVElaRSB0b29scy9kbGxfcGN0X3Nhbml0aXplLnBzMWAuCiMgdGVzdHMvdGVzdF9kbGxfcGN0X3Nhbml0aXplLnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyAoQ1JMRi9MRiBub3JtYWxpemVkKS4KJHBjdCA9IFtjaGFyXTM3CmZvciAoJGkgPSAwOyAkaSAtbHQgJGFyZ3MuQ291bnQ7ICRpICs9IDIpIHsKICAgICRuYW1lID0gJGFyZ3NbJGldCiAgICAkb3V0UGF0aCA9ICRhcmdzWyRpICsgMV0KICAgICR2ID0gW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZSgkbmFtZSkKICAgIGlmICgtbm90ICR2KSB7ICR2ID0gJycgfQogICAgJHYgPSAkdiAtcmVwbGFjZSAkcGN0LCAnXycgLXJlcGxhY2UgJ1xeJywgJ18nCiAgICBbU3lzdGVtLklPLkZpbGVdOjpXcml0ZUFsbFRleHQoJG91dFBhdGgsICR2LCBbU3lzdGVtLlRleHQuRW5jb2RpbmddOjpBU0NJSSkKfQo=" set "HP_MIGRATE_GITATTRIBUTES=IyBBU0NJSSBvbmx5LiBSRVEtMDE1IEl0ZW0gNjA6IG1pZ3JhdGUgYSBwcmUtZXhpc3RpbmcgLmdpdGF0dHJpYnV0ZXMgc3RpbGwgY2FycnlpbmcgdGhlDQojIGRpc3Byb3ZlbiAiKi5iYXQgZW9sPWNybGYiLyIqLmNtZCBlb2w9Y3JsZiIgcnVsZSAod3JpdHRlbiBieSBhbiBvbGRlciBjb3B5IG9mIHRoaXMNCiMgYm9vdHN0cmFwcGVyLCBiZWZvcmUgdGhlIGVvbD1jcmxmIC0+IC10ZXh0IGZpeCkgdG8gIi10ZXh0IiwgaW4gcGxhY2UuIFJlcGxhY2VzIE9OTFkgdGhlDQojIFRFWFQgb2YgbGluZXMgdGhhdCBleGFjdGx5IG1hdGNoIG9uZSBvZiB0aGUgdHdvIGtub3duLXN0YWxlIHN0cmluZ3MgLS0gZXZlcnkgb3RoZXIgbGluZSdzDQojIGNvbnRlbnQsIEFORCBldmVyeSBsaW5lIHRlcm1pbmF0b3IgaW4gdGhlIGZpbGUgKGluY2x1ZGluZyB0aGUgdGVybWluYXRvcnMgaW1tZWRpYXRlbHkNCiMgYXJvdW5kIHRoZSB0d28gcmVwbGFjZWQgbGluZXMpLCBwYXNzIHRocm91Z2ggYnl0ZS1pZGVudGljYWwuIFNlZQ0KIyBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCdzICIuYmF0IGZpbGVzOiAtdGV4dCwgbm90IGVvbD1jcmxmIiBlbnRyeSBmb3Igd2h5IC10ZXh0IGlzDQojIGNvcnJlY3QgYW5kIGVvbD1jcmxmIGlzIG5vdC4gUHJpbnRzIGEgcGxhaW4gcmVzdWx0IG1hcmtlciB0byBzdGRvdXQgZm9yIHRoZSBjYWxsZXIgdG8gbG9nLg0KIw0KIyBkZXJpdmVkIHJlcXVpcmVtZW50IChDb2RlUmFiYml0IHJldmlldywgUFIgIzQ1NSk6IGFuIGVhcmxpZXIgZHJhZnQgdXNlZA0KIyBbU3lzdGVtLklPLkZpbGVdOjpSZWFkQWxsTGluZXMvV3JpdGVBbGxMaW5lcywgd2hpY2ggc3RyaXBzIGV2ZXJ5IGxpbmUncyBvd24gdGVybWluYXRvciBhbmQNCiMgcmVpbXBvc2VzIGEgc2luZ2xlIHVuaWZvcm0gb25lIChFbnZpcm9ubWVudC5OZXdMaW5lIC0tIENSTEYgb24gcmVhbCBXaW5kb3dzLCB3aGVyZSB0aGlzDQojIHNjcmlwdCBhY3R1YWxseSBydW5zKSBvbiB3cml0ZS4gVGhhdCB3b3VsZCBoYXZlIHNpbGVudGx5IGNvbnZlcnRlZCBhbiBMRi1vbmx5DQojIC5naXRhdHRyaWJ1dGVzJyBFTlRJUkUgY29udGVudCB0byBDUkxGIGV2ZW4gdGhvdWdoIG9ubHkgdHdvIGxpbmVzIHdlcmUgZXZlciBtZWFudCB0bw0KIyBjaGFuZ2UgLS0gaW52aXNpYmxlIHRvIGxvY2FsIHRlc3Rpbmcgb24gYSBMaW51eCBzYW5kYm94LCB3aGVyZSBFbnZpcm9ubWVudC5OZXdMaW5lIGlzIExGLA0KIyBzbyB0aGUgZWFybGllciB2ZXJzaW9uJ3Mgb3duIGxvY2FsIHZlcmlmaWNhdGlvbiBjb3VsZCBub3QgaGF2ZSBjYXVnaHQgdGhpcy4gRml4ZWQgYnkNCiMgd29ya2luZyBvbiB0aGUgcmF3IHRleHQgd2l0aCBhIHNjb3BlZCByZWdleCByZXBsYWNlICh0b3VjaGVzIG9ubHkgdGhlIHR3byB0YXJnZXQgbGluZXMnDQojIG93biB0ZXh0LCB1c2luZyBsb29rYXJvdW5kIHRvIHJlY29nbml6ZSBsaW5lIGJvdW5kYXJpZXMgd2l0aG91dCBjb25zdW1pbmcgb3IgYWx0ZXJpbmcgdGhlDQojIHRlcm1pbmF0b3JzIHRoZW1zZWx2ZXMpIGluc3RlYWQgb2YgZXZlciBzcGxpdHRpbmcgaW50byAvIHJlam9pbmluZyBmcm9tIGEgbGluZXMgYXJyYXkuDQpwYXJhbSgNCiAgICBbUGFyYW1ldGVyKE1hbmRhdG9yeSA9ICR0cnVlKV1bc3RyaW5nXSRQYXRoDQopDQoNCmlmICgtbm90IChUZXN0LVBhdGggLUxpdGVyYWxQYXRoICRQYXRoKSkgew0KICAgIFdyaXRlLU91dHB1dCAnTk9PUDptaXNzaW5nJw0KICAgIGV4aXQgMA0KfQ0KDQojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKHJlYWwgQ0kgZmFpbHVyZSBvbiBQUiAjNDU1J3MgZmlyc3QgcnVuLCBvbiBnZW51aW5lIFdpbmRvd3MgUG93ZXJTaGVsbA0KIyA1LjEgLS0gcm9vdCBjYXVzZSBub3QgaWRlbnRpZmllZCwgc2luY2Ugb25seSBMaW51eCBwd3NoIDcgaXMgYXZhaWxhYmxlIGZvciBsb2NhbA0KIyB2ZXJpZmljYXRpb24pOiB0aGUgd2hvbGUgYm9keSBiZWxvdyBpcyBub3cgd3JhcHBlZCBpbiB0cnkvY2F0Y2gsIGVtaXR0aW5nIGEgc2luZ2xlLWxpbmUNCiMgIkVSUk9SOjx0eXBlPjogPG1lc3NhZ2U+IiBtYXJrZXIgaW5zdGVhZCBvZiBsZXR0aW5nIGEgcmF3LCBwb3NzaWJseSBtdWx0aS1saW5lIHRlcm1pbmF0aW5nDQojIGVycm9yIHJlYWNoIHRoZSBjYWxsZXIgdW5leHBsYWluZWQuIHJ1bl9zZXR1cC5iYXQncyBvd24gOm1lcmdlX2dpdF9jb25maWcgY2FsbGVyIGFscmVhZHkNCiMgdHJlYXRzIGFueSBub24tTUlHUkFURUQvTk9PUCByZXN1bHQgYXMgYSBub24tZmF0YWwgV0FSTiAodGhpcyBmZWF0dXJlIG5ldmVyIGdhdGVzIHRoZSBsYW5lLA0KIyBzZWUgQ0xBVURFLm1kJ3MgSXRlbSA2MCBlbnRyeSkgYW5kIG5vdyB0eXBlcyB0aGlzIHNjcmlwdCdzIHJhdyBzdGRvdXQvc3RkZXJyIHRvIGJvdGggY29uc29sZQ0KIyBhbmQgJUxPRyUgb24gdGhhdCBicmFuY2ggLS0gY29sbGFwc2luZyBlbWJlZGRlZCBuZXdsaW5lcyBoZXJlIGtlZXBzIHRoYXQgZHVtcCB0byBvbmUgbGluZS4NCnRyeSB7DQogICAgIyBkZXJpdmVkIHJlcXVpcmVtZW50OiBwcmVzZXJ2ZSB0aGUgZmlsZSdzIG93biBlbmNvZGluZyBleGFjdGx5IHRvbywgbm90IGp1c3QgbGluZQ0KICAgICMgZW5kaW5ncy4gU3RyZWFtUmVhZGVyIHdpdGggZGV0ZWN0RW5jb2RpbmdGcm9tQnl0ZU9yZGVyTWFya3M9dHJ1ZSBhdXRvLWRldGVjdHMgYSByZWFsDQogICAgIyBCT00gKFVURjgvVVRGMTZMRS9VVEYxNkJFL1VURjMyKSBhbmQgcmVwb3J0cyBpdCB2aWEgQ3VycmVudEVuY29kaW5nOyB3aGVuIG5vIEJPTSBpcw0KICAgICMgcHJlc2VudCBpdCBmYWxscyBiYWNrIHRvIHRoZSBzdXBwbGllZCBkZWZhdWx0IC0tIFVURjggV0lUSE9VVCBhIEJPTSAobWF0Y2hpbmcgd2hhdCB0aGlzDQogICAgIyBib290c3RyYXBwZXIncyBvd24gcGxhaW4tQVNDSUkgYD4+IGVjaG9gIHdyaXRlcyBwcm9kdWNlKSwgbm90DQogICAgIyBbU3lzdGVtLlRleHQuRW5jb2RpbmddOjpVVEY4J3Mgb3duIHN0YXRpYyBpbnN0YW5jZSwgd2hpY2ggaGFzIEJPTSBlbWlzc2lvbiBlbmFibGVkIGFuZA0KICAgICMgd291bGQgaGF2ZSBhZGRlZCBhIEJPTSB0aGF0IHdhcyBuZXZlciB0aGVyZS4NCiAgICAjDQogICAgIyBkZXJpdmVkIHJlcXVpcmVtZW50IChDb2RlUmFiYml0LCBQUiAjNDU2KTogdGhyb3dPbkludmFsaWRCeXRlcz0kdHJ1ZSwgbm90IGltcGxpY2l0DQogICAgIyAkZmFsc2UuIEEgbm8tQk9NIGZpbGUgd2l0aCBnZW51aW5lbHkgbm9uLVVURjggYnl0ZXMgd291bGQgb3RoZXJ3aXNlIGRlY29kZSBTSUxFTlRMWQ0KICAgICMgKFUrRkZGRCBwZXIgYmFkIGJ5dGUsIGNvbmZpcm1lZCwgbm8gZXhjZXB0aW9uKSBhbmQgZ2V0IHJld3JpdHRlbiBhcyBjb3JydXB0ZWQgVVRGOCAtLQ0KICAgICMgdGhyb3dpbmcgcm91dGVzIGl0IGludG8gdGhlIHRyeS9jYXRjaCBiZWxvdyBpbnN0ZWFkLCBhIHNhZmUgRVJST1I6IG1hcmtlciwgdW50b3VjaGVkIGZpbGUuDQogICAgJG5vQm9tVXRmOCA9IE5ldy1PYmplY3QgU3lzdGVtLlRleHQuVVRGOEVuY29kaW5nKCRmYWxzZSwgJHRydWUpDQogICAgJHJlYWRlciA9IE5ldy1PYmplY3QgU3lzdGVtLklPLlN0cmVhbVJlYWRlcigkUGF0aCwgJG5vQm9tVXRmOCwgJHRydWUpDQogICAgdHJ5IHsNCiAgICAgICAgJHRleHQgPSAkcmVhZGVyLlJlYWRUb0VuZCgpDQogICAgICAgICRlbmNvZGluZyA9ICRyZWFkZXIuQ3VycmVudEVuY29kaW5nDQogICAgfSBmaW5hbGx5IHsNCiAgICAgICAgJHJlYWRlci5DbG9zZSgpDQogICAgfQ0KDQogICAgJGNoYW5nZWQgPSAkZmFsc2UNCg0KICAgICMgKD88PVxBfFxyXG58XG4pIC8gKD89XHJcbnxcbnxceik6IHRoZSBzdGFsZSB0ZXh0IG11c3QgYmUgYSBnZW51aW5lIHdob2xlIGxpbmUgLS0NCiAgICAjIHByZWNlZGVkIGJ5IHRoZSBzdGFydCBvZiB0aGUgZmlsZSBvciBhIGxpbmUgdGVybWluYXRvciwgYW5kIGZvbGxvd2VkIGJ5IGEgbGluZQ0KICAgICMgdGVybWluYXRvciBvciB0aGUgZW5kIG9mIHRoZSBmaWxlIC0tIHdpdGhvdXQgdGhlIG1hdGNoIGl0c2VsZiBjb25zdW1pbmcgYW55IHRlcm1pbmF0b3IsDQogICAgIyBzbyB3aGF0ZXZlciBtaXggb2YgQ1JMRi9MRi9uby10cmFpbGluZy1uZXdsaW5lIHRoZSBmaWxlIGFscmVhZHkgaGFzIGlzIGxlZnQgY29tcGxldGVseQ0KICAgICMgYWxvbmUuIEEgcGFydGlhbCBtYXRjaCAodGhlIHN0YWxlIHRleHQgd2l0aCBzb21ldGhpbmcgZWxzZSBhcHBlbmRlZCBvbiB0aGUgc2FtZSBsaW5lKQ0KICAgICMgbmV2ZXIgc2F0aXNmaWVzIHRoZSBsb29rYWhlYWQsIHNvIGl0IGlzIGNvcnJlY3RseSBuZXZlciB0b3VjaGVkIC0tIHRoZSBzYWZldHkgcHJvcGVydHkNCiAgICAjIHByb3RlY3RpbmcgYSB1c2VyJ3Mgb3duIGhhbmQtZWRpdGVkIGNvbnRlbnQuIFxBIGFsb25lIChub3QgYWxzbyBhIGJhcmUNCiAgICAjIGFsdGVybmF0aXZlKSBpcyBkZWxpYmVyYXRlIChDb2RlUmFiYml0IHJldmlldywgUFIgIzQ1NSk6IFN0cmVhbVJlYWRlcidzIG93bg0KICAgICMgQk9NLXN0cmlwcGluZy1vbi1yZWFkIGFscmVhZHkgY29uc3VtZXMgYSBnZW51aW5lIGxlYWRpbmcgQk9NIGFzIGVuY29kaW5nIG1ldGFkYXRhDQogICAgIyBiZWZvcmUgJHRleHQgaXMgZXZlciBzZXQsIHNvICR0ZXh0IG5ldmVyIGFjdHVhbGx5IHN0YXJ0cyB3aXRoIFUrRkVGRiBoZXJlIC0tIFxBIGFscmVhZHkNCiAgICAjIGNvdmVycyB0aGUgcmVhbCAiZmlyc3QgbGluZSIgY2FzZSB3aXRoIG5vIGV4dHJhIGFsdGVybmF0aXZlIG5lZWRlZC4gQSBiYXJlDQogICAgIyBhbHRlcm5hdGl2ZSB3b3VsZCBpbnN0ZWFkIGJlIGEgZ2VudWluZSBmYWxzZS1wb3NpdGl2ZSBoYXphcmQ6IGl0IGlzIG5vdCBpdHNlbGYgYW5jaG9yZWQNCiAgICAjIHRvIHRoZSBzdGFydCBvZiB0aGUgZmlsZSwgc28gYSBsaW5lIHdpdGggaXRzIE9XTiBwcmVmaXggdGV4dCBmb2xsb3dlZCBieSBhIGxpdGVyYWwNCiAgICAjIGVtYmVkZGVkIFUrRkVGRiBjaGFyYWN0ZXIgKGUuZy4gInVzZXIgPEJPTT4qLmJhdCBlb2w9Y3JsZiIpIHdvdWxkIHdyb25nbHkgc2F0aXNmeSB0aGUNCiAgICAjIGxvb2tiZWhpbmQgYW5kIGdldCByZXdyaXR0ZW4sIGV2ZW4gdGhvdWdoIHRoYXQgaXMgbm90IGFuIGV4YWN0IHdob2xlLWxpbmUgbWF0Y2ggYXQgYWxsLg0KICAgICRuZXcgPSBbcmVnZXhdOjpSZXBsYWNlKCR0ZXh0LCAnKD88PVxBfFxyXG58XG4pXCpcLmJhdCBlb2w9Y3JsZig/PVxyXG58XG58XHopJywgJyouYmF0IC10ZXh0JykNCiAgICBpZiAoJG5ldyAtbmUgJHRleHQpIHsgJGNoYW5nZWQgPSAkdHJ1ZTsgJHRleHQgPSAkbmV3IH0NCiAgICAkbmV3ID0gW3JlZ2V4XTo6UmVwbGFjZSgkdGV4dCwgJyg/PD1cQXxcclxufFxuKVwqXC5jbWQgZW9sPWNybGYoPz1cclxufFxufFx6KScsICcqLmNtZCAtdGV4dCcpDQogICAgaWYgKCRuZXcgLW5lICR0ZXh0KSB7ICRjaGFuZ2VkID0gJHRydWU7ICR0ZXh0ID0gJG5ldyB9DQoNCiAgICBpZiAoLW5vdCAkY2hhbmdlZCkgew0KICAgICAgICBXcml0ZS1PdXRwdXQgJ05PT1A6bm8tc3RhbGUtbGluZXMnDQogICAgICAgIGV4aXQgMA0KICAgIH0NCg0KICAgIFtTeXN0ZW0uSU8uRmlsZV06OldyaXRlQWxsVGV4dCgkUGF0aCwgJHRleHQsICRlbmNvZGluZykNCiAgICBXcml0ZS1PdXRwdXQgJ01JR1JBVEVEJw0KICAgIGV4aXQgMA0KfSBjYXRjaCB7DQogICAgJG1zZyA9ICIkKCRfLkV4Y2VwdGlvbi5HZXRUeXBlKCkuRnVsbE5hbWUpOiAkKCRfLkV4Y2VwdGlvbi5NZXNzYWdlKSIgLXJlcGxhY2UgJ1tcclxuXSsnLCAnICcNCiAgICBXcml0ZS1PdXRwdXQgIkVSUk9SOiRtc2ciDQogICAgZXhpdCAwDQp9DQo=" set "HP_DLL_BUNDLE_SCAN=IiIiZGxsX2J1bmRsZV9zY2FuIHYxICgyMDI2LTA4LTA0KQpDb25kYSBuYXRpdmUtRExMIGJ1bmRsaW5nIHJlcGFpciBsb29wJ3Mgc2NhbiBoZWxwZXIgKENMQVVERS5tZCBJdGVtIDI0IC8KZG9jcy9wcmQtY29uZGEtbmF0aXZlLWRsbC1idW5kbGluZy5tZCkuIFB5SW5zdGFsbGVyIGJ1bmRsZXMgYSBjb25kYQpleHRlbnNpb24ncyAucHlkIGJ1dCBub3QgaXRzIG5hdGl2ZSBETEwgZGVwZW5kZW5jeSB1bmRlciBjb25kYSdzCkxpYnJhcnlcXGJpbiBjb252ZW50aW9uIChlLmcuIGVjY29kZXMuZGxsIGZvciBweWdyaWIpIC0tIGFubm91bmNlZCBhdApidWlsZCB0aW1lOgogIFdBUk5JTkc6IExpYnJhcnkgbm90IGZvdW5kOiBjb3VsZCBub3QgcmVzb2x2ZSAnZWNjb2Rlcy5kbGwnLCBkZXBlbmRlbmN5CiAgb2YgJy4uLlxccHlncmliXFxfcHlncmliLmNwMzE0LXdpbl9hbWQ2NC5weWQnLgpNaXJyb3JzIH5oaWRkZW5faW1wb3J0X3NjYW4ucHk6IHJlYWN0aXZlLCBib3VuZGVkLCBkb3VibGUtZ2F0ZWQgKHRoZSBuYW1lZApETEwgbXVzdCBhbHNvIGV4aXN0IHVuZGVyIExpYnJhcnlcXGJpbiwgb3Igbm90aGluZyBpcyBlbWl0dGVkKS4gR2VuZXJhbCwKbm90IGhhcmRjb2RlZCB0byBlY2NvZGVzLmRsbCAtLSBwYXJzZXMgd2hhdGV2ZXIgbmFtZSB0aGUgd2FybmluZyBnaXZlcy4KClVzYWdlOgogIC0tZGV0ZWN0IDxsb2dfZmlsZT4gPGJ5dGVfb2Zmc2V0PgogICAgUHJvdmlkZXItYWdub3N0aWM6IHByaW50cyB0aGUgbmV4dCB1bnJlc29sdmVkIERMTCBuYW1lLCBvciBub3RoaW5nLgogICAgTm8gZGlzayBsb29rdXAgLS0gbGV0cyBhIG5vbi1jb25kYSBydW4gbG9nICJkZXRlY3RlZCwgY2FuJ3QgcmVwYWlyIgogICAgaW5zdGVhZCBvZiBub3RoaW5nLgogIDxsb2dfZmlsZT4gPGJ5dGVfb2Zmc2V0PiA8Y29uZGFfZW52X2Rpcj4gW3RyaWVkX2ZpbGVdCiAgICBQcmludHMgIjxuYW1lPnw8cGF0aD4iIChvciBub3RoaW5nKS4gdHJpZWRfZmlsZSAob25lICJuYW1lfHBhdGgiIGxpbmUKICAgIHBlciBwcmlvciBhdHRlbXB0KSByZXBsYWNlcyBhcmd2IHRyaWVkLW5hbWVzIHRvIGF2b2lkIGNtZC5leGUKICAgIG1ldGFjaGFyYWN0ZXIgY29ycnVwdGlvbiBmcm9tIGEgRExMIGJhc2VuYW1lIGNvbnRhaW5pbmcgYSBzcGFjZSBvciAmLgogICAgU2tpcHMgcGFzdCBhIG5hbWVkLWJ1dC1ub3Qtb24tZGlzayBjYW5kaWRhdGUgdG8gdGhlIG5leHQgb25lLgpieXRlX29mZnNldCBleGNsdWRlcyBhbiBlYXJsaWVyIGJ1aWxkL3J1bidzIG93biB3YXJuaW5ncyBhbHJlYWR5IGluIHRoZQpzYW1lIHBlcnNpc3RlbnQgbG9nIGZpbGUuCiIiIgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCBzeXMKCl9fdmVyc2lvbl9fID0gImRsbF9idW5kbGVfc2NhbiB2MSAoMjAyNi0wOC0wNCkiCl9fYWxsX18gPSBbImxvY2F0ZV9kbGwiLCAibWFpbiIsICJuZXh0X2RsbF90YXJnZXQiLCAicmVhZF90YWlsIiwgInJlYWRfdHJpZWRfZmlsZSJdCgpfUEFUVEVSTiA9IHJlLmNvbXBpbGUoCiAgICByIkxpYnJhcnkgbm90IGZvdW5kOiBjb3VsZCBub3QgcmVzb2x2ZSBbJ1wiXShbXidcIl0rKVsnXCJdIgopCgoKZGVmIHJlYWRfdGFpbChsb2dfZmlsZSwgb2Zmc2V0KToKICAgICIiIlJlYWQgbG9nX2ZpbGUgZnJvbSBieXRlIG9mZnNldCBvbndhcmQ7IHRvbGVyYW50IGJpbmFyeSBkZWNvZGUuIiIiCiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKGxvZ19maWxlLCAicmIiKSBhcyBmaDoKICAgICAgICAgICAgZmguc2VlayhtYXgoMCwgb2Zmc2V0KSkKICAgICAgICAgICAgcmV0dXJuIGZoLnJlYWQoKS5kZWNvZGUoInV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgcmV0dXJuICIiCgoKZGVmIG5leHRfZGxsX3RhcmdldChsb2dfdGV4dCwgYWxyZWFkeV90cmllZD0oKSk6CiAgICAiIiJSZXR1cm4gdGhlIG5leHQgdW5yZXNvbHZlZCBETEwgYmFzZW5hbWUgZnJvbSBhIGJ1aWxkIGxvZyBzbGljZSwgb3IgIiIuIiIiCiAgICB0cmllZCA9IHt0Lmxvd2VyKCkgZm9yIHQgaW4gYWxyZWFkeV90cmllZH0KICAgIGZvciBtYXRjaCBpbiBfUEFUVEVSTi5maW5kaXRlcihsb2dfdGV4dCk6CiAgICAgICAgbmFtZSA9IG1hdGNoLmdyb3VwKDEpLnN0cmlwKCkKICAgICAgICBpZiBuYW1lIGFuZCBuYW1lLmxvd2VyKCkgbm90IGluIHRyaWVkOgogICAgICAgICAgICByZXR1cm4gbmFtZQogICAgcmV0dXJuICIiCgoKZGVmIGxvY2F0ZV9kbGwobmFtZSwgY29uZGFfZW52X2Rpcik6CiAgICAiIiJTZWFyY2ggPGNvbmRhX2Vudl9kaXI+XFxMaWJyYXJ5XFxiaW4gKHJlY3Vyc2l2ZWx5KSBmb3IgbmFtZTsgcmV0dXJuCiAgICB0aGUgZmlyc3QgbWF0Y2gncyBmdWxsIHBhdGgsIG9yICIiIGlmIG5vdCBmb3VuZC4gUmVjdXJzaXZlIGJlY2F1c2UgYQogICAgaG9vayBjYW4gcGxhY2UgYSBETEwgdW5kZXIgYSBwYWNrYWdlLW5hbWVkIHN1YmZvbGRlciByYXRoZXIgdGhhbgogICAgZGlyZWN0bHkgaW4gTGlicmFyeVxcYmluIChjb25maXJtZWQgZm9yIGhvb2stZ3JpYmFwaS5weSdzIG93bgogICAgZWNjb2Rlcy5kbGwgcGxhY2VtZW50LCBkb2NzL3ByZC1jb25kYS1uYXRpdmUtZGxsLWJ1bmRsaW5nLm1kIEZpbmRpbmcgMSkuCiAgICAiIiIKICAgIGlmIG5vdCBjb25kYV9lbnZfZGlyOgogICAgICAgIHJldHVybiAiIgogICAgbGliX2JpbiA9IG9zLnBhdGguam9pbihjb25kYV9lbnZfZGlyLCAiTGlicmFyeSIsICJiaW4iKQogICAgaWYgbm90IG9zLnBhdGguaXNkaXIobGliX2Jpbik6CiAgICAgICAgcmV0dXJuICIiCiAgICB0YXJnZXQgPSBuYW1lLmxvd2VyKCkKICAgIGZvciByb290LCBfZGlycywgZmlsZXMgaW4gb3Mud2FsayhsaWJfYmluKToKICAgICAgICBmb3IgZm5hbWUgaW4gZmlsZXM6CiAgICAgICAgICAgIGlmIGZuYW1lLmxvd2VyKCkgPT0gdGFyZ2V0OgogICAgICAgICAgICAgICAgcmV0dXJuIG9zLnBhdGguam9pbihyb290LCBmbmFtZSkKICAgIHJldHVybiAiIgoKCmRlZiByZWFkX3RyaWVkX2ZpbGUocGF0aCk6CiAgICAiIiJSZWFkIGFscmVhZHktdHJpZWQgRExMIG5hbWVzIChvbmUgIm5hbWV8cGF0aCIgbGluZSBlYWNoKSBmcm9tIHBhdGguCiAgICBNaXNzaW5nL3VucmVhZGFibGUgZmlsZSAtPiBubyB0cmllZCBuYW1lcyAobm90IGFuIGVycm9yKS4iIiIKICAgIG5hbWVzID0gW10KICAgIHRyeToKICAgICAgICB3aXRoIG9wZW4ocGF0aCwgInIiLCBlbmNvZGluZz0idXRmLTgiLCBlcnJvcnM9Imlnbm9yZSIpIGFzIGZoOgogICAgICAgICAgICBmb3IgbGluZSBpbiBmaDoKICAgICAgICAgICAgICAgIGxpbmUgPSBsaW5lLnN0cmlwKCkKICAgICAgICAgICAgICAgIGlmIGxpbmU6CiAgICAgICAgICAgICAgICAgICAgbmFtZXMuYXBwZW5kKGxpbmUuc3BsaXQoInwiLCAxKVswXSkKICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgIHBhc3MKICAgIHJldHVybiBuYW1lcwoKCmRlZiBfZGV0ZWN0KGFyZ3MpOgogICAgIiIiLS1kZXRlY3QgbW9kZTogcHJvdmlkZXItYWdub3N0aWMsIGxvZy1vbmx5LiBTZWUgbW9kdWxlIGRvY3N0cmluZy4iIiIKICAgIGlmIGxlbihhcmdzKSA8IDI6CiAgICAgICAgcmV0dXJuCiAgICBsb2dfZmlsZSwgb2Zmc2V0X3JhdyA9IGFyZ3NbMF0sIGFyZ3NbMV0KICAgIHRyeToKICAgICAgICBvZmZzZXQgPSBpbnQob2Zmc2V0X3JhdykKICAgIGV4Y2VwdCBWYWx1ZUVycm9yOgogICAgICAgIG9mZnNldCA9IDAKICAgIHRleHQgPSByZWFkX3RhaWwobG9nX2ZpbGUsIG9mZnNldCkKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybgogICAgbmFtZSA9IG5leHRfZGxsX3RhcmdldCh0ZXh0KQogICAgaWYgbmFtZToKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKG5hbWUpCgoKZGVmIG1haW4oYXJndj1Ob25lKToKICAgIGFyZ3MgPSBsaXN0KHN5cy5hcmd2WzE6XSBpZiBhcmd2IGlzIE5vbmUgZWxzZSBhcmd2KQogICAgaWYgbm90IGFyZ3M6CiAgICAgICAgcmV0dXJuCiAgICBpZiBhcmdzWzBdID09ICItLWRldGVjdCI6CiAgICAgICAgX2RldGVjdChhcmdzWzE6XSkKICAgICAgICByZXR1cm4KICAgIGlmIGxlbihhcmdzKSA8IDM6CiAgICAgICAgcmV0dXJuCiAgICBsb2dfZmlsZSwgb2Zmc2V0X3JhdywgY29uZGFfZW52X2RpciA9IGFyZ3NbMF0sIGFyZ3NbMV0sIGFyZ3NbMl0KICAgIHRyaWVkX2ZpbGUgPSBhcmdzWzNdIGlmIGxlbihhcmdzKSA+IDMgZWxzZSBOb25lCiAgICBhbHJlYWR5ID0gcmVhZF90cmllZF9maWxlKHRyaWVkX2ZpbGUpIGlmIHRyaWVkX2ZpbGUgZWxzZSBbXQogICAgdHJ5OgogICAgICAgIG9mZnNldCA9IGludChvZmZzZXRfcmF3KQogICAgZXhjZXB0IFZhbHVlRXJyb3I6CiAgICAgICAgb2Zmc2V0ID0gMAogICAgdGV4dCA9IHJlYWRfdGFpbChsb2dfZmlsZSwgb2Zmc2V0KQogICAgaWYgbm90IHRleHQ6CiAgICAgICAgcmV0dXJuCiAgICAjIFNraXAgYSBzdGFsZS91bnJlbGF0ZWQgbm90LW9uLWRpc2sgY2FuZGlkYXRlOyBrZWVwIHRyeWluZyB0aGUgcmVzdC4KICAgIGNhbmRpZGF0ZXMgPSBsaXN0KGFscmVhZHkpCiAgICB3aGlsZSBUcnVlOgogICAgICAgIG5hbWUgPSBuZXh0X2RsbF90YXJnZXQodGV4dCwgY2FuZGlkYXRlcykKICAgICAgICBpZiBub3QgbmFtZToKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgcGF0aCA9IGxvY2F0ZV9kbGwobmFtZSwgY29uZGFfZW52X2RpcikKICAgICAgICBpZiBwYXRoOgogICAgICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKG5hbWUgKyAifCIgKyBwYXRoKQogICAgICAgICAgICByZXR1cm4KICAgICAgICBjYW5kaWRhdGVzLmFwcGVuZChuYW1lKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK" rem ~pep723_writeback.py promotes resolved dependencies into the entry file's rem PEP 723 header via uv add --script; see docs/plan-pep723-writeback.md Part 2.1. set "HP_PEP723_WRITEBACK=IiIicGVwNzIzX3dyaXRlYmFjayB2MSAoMjAyNi0wNy0xOCkKUHJvbW90ZXMgYSByZXNvbHZlZCBkZXBlbmRlbmN5IGxpc3QgaW50byB0aGUgZW50cnkgZmlsZSdzIFBFUCA3MjMgaGVhZGVyIHZpYQpgdXYgYWRkIC0tc2NyaXB0YC4gU2VlIGRvY3MvcGxhbi1wZXA3MjMtd3JpdGViYWNrLm1kIFBhcnQgMi4xIGZvciB0aGUgZnVsbCBkZXNpZ24uClVzYWdlOiBweXRob24gcGVwNzIzX3dyaXRlYmFjay5weSA8ZW50cnkucHk+IDx1dl9leGU+IDxweXRob25fZXhlPiA8cGFja2FnZXNfZmlsZT4KUHJpbnRzIG9uZSByZXN1bHQgbGluZSB0byBzdGRvdXQ6IE9LOjxuPiAvIFNLSVA6PHJlYXNvbj4gLyBFUlJPUjo8cmVhc29uPi4KIiIiCl9fdmVyc2lvbl9fID0gInBlcDcyM193cml0ZWJhY2sgdjEgKDIwMjYtMDctMTgpIgpfX2FsbF9fID0gWyJyZWFkX3BhY2thZ2VzIiwgInN0cmlwX3BlcDcyM19ibG9jayIsICJtYWluIl0KCmltcG9ydCBvcwppbXBvcnQgc3VicHJvY2VzcwppbXBvcnQgc3lzCgoKZGVmIHJlYWRfcGFja2FnZXMocGF0aCk6CiAgICAiIiJSZXR1cm4gYSBsaXN0IG9mIG5vbi1ibGFuaywgbm9uLWNvbW1lbnQgbGluZXMgZnJvbSBhIHBhY2thZ2VzIGZpbGUuIiIiCiAgICBwYWNrYWdlcyA9IFtdCiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKHBhdGgsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICAgICAgZm9yIGxpbmUgaW4gZmg6CiAgICAgICAgICAgICAgICBsaW5lID0gbGluZS5zdHJpcCgpCiAgICAgICAgICAgICAgICBpZiBub3QgbGluZSBvciBsaW5lLnN0YXJ0c3dpdGgoIiMiKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgIyBwaXAgZGlyZWN0aXZlcyAoLWUsIC1yLCAtLWhhc2gsIC4uLikgZXhpdCAyIGZyb20gdXYncyBjbGFwCiAgICAgICAgICAgICAgICAjIHBhcnNlciBzYW1lIGFzIG1hbGZvcm1lZCBUT01MIC0tIGZpbHRlciBvciBtYWluKCkgd3JvbmdseQogICAgICAgICAgICAgICAgIyBzdHJpcHMgYSB2YWxpZCBoZWFkZXIuIFJlYWwgc3BlY3MgbmV2ZXIgc3RhcnQgd2l0aCAnLScuCiAgICAgICAgICAgICAgICBpZiBsaW5lLnN0YXJ0c3dpdGgoIi0iKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgcGFja2FnZXMuYXBwZW5kKGxpbmUpCiAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICBwYXNzCiAgICByZXR1cm4gcGFja2FnZXMKCgpkZWYgc3RyaXBfcGVwNzIzX2Jsb2NrKHRleHQpOgogICAgIiIiUmVtb3ZlIGFuIGV4aXN0aW5nICcjIC8vLyBzY3JpcHQnIC4uLiAnIyAvLy8nIGJsb2NrLCBsaW5lIGJ5IGxpbmUuCgogICAgQSBsaW5lLWJ5LWxpbmUgc3RhdGUgbWFjaGluZSwgbm90IGEgcmVnZXg6IGEgZ3JlZWR5IHJlZ2V4IHJpc2tzIHN0cmlwcGluZwogICAgY29kZSBhZnRlciB0aGUgYmxvY2ssIGFuZCBhIGxhenkgb25lIGNhbiBsZWF2ZSBhIHN0cmF5IGZlbmNlIGxpbmUgYmVoaW5kLAogICAgd2hpY2ggbmV3ZXIgdXYgdHJlYXRzIGFzIGEgaGFyZCBlcnJvciAoYXN0cmFsLXNoL3V2IzE5NTQ0KS4gVGhlIGNsb3NpbmcKICAgIGZlbmNlIG1hdGNoIHRvbGVyYXRlcyB0cmFpbGluZyB3aGl0ZXNwYWNlIChhc3RyYWwtc2gvdXYjMTA5MTgpLgogICAgIiIiCiAgICBsaW5lcyA9IHRleHQuc3BsaXRsaW5lcyhrZWVwZW5kcz1UcnVlKQogICAgb3V0ID0gW10KICAgIGluX2Jsb2NrID0gRmFsc2UKICAgIGZvciBsaW5lIGluIGxpbmVzOgogICAgICAgIHN0cmlwcGVkID0gbGluZS5yc3RyaXAoIlxyXG4iKQogICAgICAgIGlmIG5vdCBpbl9ibG9jayBhbmQgc3RyaXBwZWQgPT0gIiMgLy8vIHNjcmlwdCI6CiAgICAgICAgICAgIGluX2Jsb2NrID0gVHJ1ZQogICAgICAgICAgICBjb250aW51ZQogICAgICAgIGlmIGluX2Jsb2NrOgogICAgICAgICAgICBpZiBzdHJpcHBlZC5yc3RyaXAoKSA9PSAiIyAvLy8iOgogICAgICAgICAgICAgICAgaW5fYmxvY2sgPSBGYWxzZQogICAgICAgICAgICBjb250aW51ZQogICAgICAgIG91dC5hcHBlbmQobGluZSkKICAgIHJldHVybiAiIi5qb2luKG91dCkKCgpkZWYgcnVuX3V2X2FkZChlbnRyeV9wYXRoLCB1dl9leGUsIHB5dGhvbl9leGUsIHBhY2thZ2VzKToKICAgICIiIkludm9rZSBgdXYgYWRkIC0tc2NyaXB0YCBvbmNlOyByZXR1cm4gKHJldHVybmNvZGUsIHN0ZGVycl90ZXh0KS4iIiIKICAgIGNtZCA9IFt1dl9leGUsICJhZGQiLCAiLS1zY3JpcHQiLCBlbnRyeV9wYXRoLCAiLXAiLCBweXRob25fZXhlXSArIHBhY2thZ2VzCiAgICB0cnk6CiAgICAgICAgIyBCb3VuZGVkIGxpa2Ugb3RoZXIgbmV0d29yay10b3VjaGluZyBoZWxwZXJzIGhlcmUgKGVtYmVkX3B5dmVyX2NoZWNrLnB5CiAgICAgICAgIyBzb2NrZXQgdGltZW91dCkgLS0gYSBzdGFsbGVkIHV2IGNhbGwgbXVzdCBub3QgaGFuZyB0aGUgYm9vdHN0cmFwLgogICAgICAgIHByb2MgPSBzdWJwcm9jZXNzLnJ1bihjbWQsIGNhcHR1cmVfb3V0cHV0PVRydWUsIHRleHQ9VHJ1ZSwgdGltZW91dD0xMjApCiAgICBleGNlcHQgc3VicHJvY2Vzcy5UaW1lb3V0RXhwaXJlZDoKICAgICAgICByZXR1cm4gMSwgInRpbWVvdXQiCiAgICAjIGFzdHJhbC1zaC91diMxNTk1NjogYSBiZW5pZ24gVklSVFVBTF9FTlYgbWlzbWF0Y2ggd2FybmluZyBjYW4gYXBwZWFyIG9uIHN0ZGVycgogICAgIyB3aXRoIGV4aXQgY29kZSAwIC0tIHN0ZGVyciB0ZXh0IGlzIG5ldmVyIGl0c2VsZiBhIGZhaWx1cmUgc2lnbmFsLCBvbmx5IHRoZQogICAgIyBwcm9jZXNzIHJldHVybiBjb2RlIGlzLgogICAgcmV0dXJuIHByb2MucmV0dXJuY29kZSwgcHJvYy5zdGRlcnIKCgpkZWYgbWFpbihhcmd2PU5vbmUpOgogICAgYXJncyA9IGxpc3Qoc3lzLmFyZ3ZbMTpdIGlmIGFyZ3YgaXMgTm9uZSBlbHNlIGFyZ3YpCiAgICBpZiBsZW4oYXJncykgPCA0OgogICAgICAgIHByaW50KCJFUlJPUjpiYWRfYXJncyIpCiAgICAgICAgcmV0dXJuIDEKICAgIGVudHJ5X3BhdGgsIHV2X2V4ZSwgcHl0aG9uX2V4ZSwgcGFja2FnZXNfZmlsZSA9IGFyZ3NbMF0sIGFyZ3NbMV0sIGFyZ3NbMl0sIGFyZ3NbM10KCiAgICBwYWNrYWdlcyA9IHJlYWRfcGFja2FnZXMocGFja2FnZXNfZmlsZSkKICAgIGlmIG5vdCBwYWNrYWdlczoKICAgICAgICBwcmludCgiU0tJUDpub19wYWNrYWdlcyIpCiAgICAgICAgcmV0dXJuIDAKCiAgICAjIEVuY29kaW5nIHByZS1jaGVjazogbmV2ZXIgaGFuZCB1diBhIGZpbGUgd2hvc2UgVVRGLTgtbmVzcyBpcyB1bmNvbmZpcm1lZC4KICAgIHRyeToKICAgICAgICB3aXRoIG9wZW4oZW50cnlfcGF0aCwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZmg6CiAgICAgICAgICAgIGZoLnJlYWQoKQogICAgZXhjZXB0IChVbmljb2RlRGVjb2RlRXJyb3IsIE9TRXJyb3IpOgogICAgICAgIHByaW50KCJTS0lQOm5vbl91dGY4IikKICAgICAgICByZXR1cm4gMAoKICAgICMgRmlsZS1sb2NrIGNhbmFyeTogZGlhZ25vc3RpYy1xdWFsaXR5IG9ubHksIGdpdmVzIGEgY2xlYXJlciByZWFzb24gdGhhbiB0aGUKICAgICMgZ2VuZXJpYyBFUlJPUjp1dl9yY188bj4gcGF0aCBiZWxvdy4gQWNjZXB0ZWQgVE9DVE9VIHJhY2UgKGxvY2sgY291bGQgYmUKICAgICMgYWNxdWlyZWQgYWZ0ZXIgdGhpcyBjaGVjaykgZmFsbHMgYmFjayB0byB0aGF0IHNhbWUgc2FmZSBnZW5lcmljIHBhdGguCiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKGVudHJ5X3BhdGgsICJyK2IiKToKICAgICAgICAgICAgcGFzcwogICAgZXhjZXB0IFBlcm1pc3Npb25FcnJvcjoKICAgICAgICBwcmludCgiU0tJUDpmaWxlX2xvY2tlZCIpCiAgICAgICAgcmV0dXJuIDAKICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgIHBhc3MKCiAgICBpZiBlbnRyeV9wYXRoLmVuZHN3aXRoKCIucHkiKSBhbmQgX2xvY2tfc2lkZWNhcl9leGlzdHMoZW50cnlfcGF0aCk6CiAgICAgICAgcHJpbnQoIlNLSVA6bG9ja2ZpbGUiKQogICAgICAgIHJldHVybiAwCgogICAgcmMsIF9zdGRlcnIgPSBydW5fdXZfYWRkKGVudHJ5X3BhdGgsIHV2X2V4ZSwgcHl0aG9uX2V4ZSwgcGFja2FnZXMpCiAgICBpZiByYyA9PSAwOgogICAgICAgIHByaW50KCJPSzolZCIgJSBsZW4ocGFja2FnZXMpKQogICAgICAgIHJldHVybiAwCiAgICBpZiByYyA9PSAyOgogICAgICAgICMgYXN0cmFsLXNoL3V2IzEwOTE4IC8gIzE5NTQ0OiBleGl0IDIgaXMgdXYncyBzaWduYWwgdGhlIGV4aXN0aW5nIGhlYWRlcgogICAgICAgICMgaXMgdW5wYXJzZWFibGUgVE9NTCAtLSB0aGUgb25lIGNhc2Ugd2hlcmUgc3RhcnRpbmcgb3ZlciBpcyBjb3JyZWN0LgogICAgICAgICMgQW55IG90aGVyIGV4aXQgY29kZSBtdXN0IG5vdCBzdHJpcCBhbnl0aGluZy4KICAgICAgICB0cnk6CiAgICAgICAgICAgICMgbmV3bGluZT0iIiBrZWVwcyBDUkxGIGludGFjdCBvbiByZWFkIC0tIHdpdGhvdXQgaXQsIGJvdGggdGhlCiAgICAgICAgICAgICMgc3RyaXBwZWQgd3JpdGUgYW5kIHRoZSByZXN0b3JlLW9uLWRvdWJsZS1mYWlsdXJlIHdyaXRlIGJlbG93CiAgICAgICAgICAgICMgd291bGQgc2lsZW50bHkgbm9ybWFsaXplIHRoZSB3aG9sZSBmaWxlJ3MgbGluZSBlbmRpbmdzIHRvIExGLgogICAgICAgICAgICB3aXRoIG9wZW4oZW50cnlfcGF0aCwgInIiLCBlbmNvZGluZz0idXRmLTgiLCBlcnJvcnM9Imlnbm9yZSIsIG5ld2xpbmU9IiIpIGFzIGZoOgogICAgICAgICAgICAgICAgb3JpZ2luYWwgPSBmaC5yZWFkKCkKICAgICAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICAgICAgcHJpbnQoIkVSUk9SOnN0cmlwX3JldHJ5X2ZhaWxlZDpyZWFkIikKICAgICAgICAgICAgcmV0dXJuIDEKICAgICAgICBzdHJpcHBlZCA9IHN0cmlwX3BlcDcyM19ibG9jayhvcmlnaW5hbCkKICAgICAgICB0cnk6CiAgICAgICAgICAgIHdpdGggb3BlbihlbnRyeV9wYXRoLCAidyIsIGVuY29kaW5nPSJ1dGYtOCIsIG5ld2xpbmU9IiIpIGFzIGZoOgogICAgICAgICAgICAgICAgZmgud3JpdGUoc3RyaXBwZWQpCiAgICAgICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgICAgIHByaW50KCJFUlJPUjpzdHJpcF9yZXRyeV9mYWlsZWQ6d3JpdGUiKQogICAgICAgICAgICByZXR1cm4gMQogICAgICAgIHJjMiwgX3N0ZGVycjIgPSBydW5fdXZfYWRkKGVudHJ5X3BhdGgsIHV2X2V4ZSwgcHl0aG9uX2V4ZSwgcGFja2FnZXMpCiAgICAgICAgaWYgcmMyID09IDA6CiAgICAgICAgICAgIHByaW50KCJPSzolZCIgJSBsZW4ocGFja2FnZXMpKQogICAgICAgICAgICByZXR1cm4gMAogICAgICAgICMgUmV0cnkgYWxzbyBmYWlsZWQgLS0gcmVzdG9yZSBvcmlnaW5hbCAoc3RpbGwtbWFsZm9ybWVkKSBjb250ZW50IHJhdGhlcgogICAgICAgICMgdGhhbiBsZWF2ZSB0aGUgZmlsZSBwZXJtYW5lbnRseSBzdHJpcHBlZCAobWlycm9ycyBQVlcgUXVpY2tTdGFydCdzIG93bgogICAgICAgICMgcmVzdG9yZS1vbi1kb3VibGUtZmFpbHVyZSBndWFyYW50ZWUpLgogICAgICAgIHRyeToKICAgICAgICAgICAgd2l0aCBvcGVuKGVudHJ5X3BhdGgsICJ3IiwgZW5jb2Rpbmc9InV0Zi04IiwgbmV3bGluZT0iIikgYXMgZmg6CiAgICAgICAgICAgICAgICBmaC53cml0ZShvcmlnaW5hbCkKICAgICAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICAgICAgcGFzcwogICAgICAgIHByaW50KCJFUlJPUjpzdHJpcF9yZXRyeV9mYWlsZWQ6JWQiICUgcmMyKQogICAgICAgIHJldHVybiAxCiAgICBwcmludCgiRVJST1I6dXZfcmNfJWQiICUgcmMpCiAgICByZXR1cm4gMQoKCmRlZiBfbG9ja19zaWRlY2FyX2V4aXN0cyhlbnRyeV9wYXRoKToKICAgIHJldHVybiBvcy5wYXRoLmV4aXN0cyhlbnRyeV9wYXRoICsgIi5sb2NrIikKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" exit /b 0 :log rem derived requirement (CLAUDE.md Item 42, lever 1): DEBUG/TRACE/INSTALL-tagged lines are rem suppressed from the LIVE console by default (opt back in via HP_VERBOSE_CONSOLE=1) -- the rem full detail is always written to LOG regardless, so this only trims what a beginner rem double-click user sees, never what a diagnostic re-read of ~setup.log can find. Detected rem via a pure substring-slice comparison (VAR colon-tilde start,length), never findstr/piping -- rem see docs/agent-lessons-learned.md's "Quote a variable before piping it into findstr" entry rem for why a subprocess pipe on MSG would be hazardous here (message text can legally contain rem "&"). HP_VERBOSE_CONSOLE is checked against the exact string "1", not merely defined, so an rem accidental HP_VERBOSE_CONSOLE=0 (or any other non-"1" value) still suppresses -- matches this rem file's own established flag-check idiom elsewhere (e.g. HP_FORCE_CONDA_ONLY). set "MSG=%~1" set "HP_LOG_SUPPRESS=" if not "%HP_VERBOSE_CONSOLE%"=="1" ( if "%MSG:~0,7%"=="[DEBUG]" set "HP_LOG_SUPPRESS=1" if "%MSG:~0,7%"=="[TRACE]" set "HP_LOG_SUPPRESS=1" if "%MSG:~0,9%"=="[INSTALL]" set "HP_LOG_SUPPRESS=1" ) if not defined HP_LOG_SUPPRESS echo %date% %time% %MSG% >> "%LOG%" echo [%date% %time%] %MSG% exit /b 0 :write_runtime_txt rem derived requirement: called from inside a parenthesized if-block so %PYVER% rem would expand at block-parse time (empty) if inlined. Subroutine body is rem re-parsed at call time, so %PYVER% correctly reflects the for/f result. rem derived requirement: guarded by HP_RUNTIME_TXT_PREEXIST so write-back only rem fires when runtime.txt did not pre-exist (Tier 2/3 promotion to Tier 1). if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" ( >"runtime.txt" echo %PYVER% if errorlevel 1 ( call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active." ) else ( call :log "[INFO] runtime.txt written: %PYVER%" set "HP_PYSPEC_ORIGINAL=%PYSPEC%" set "PYSPEC=%PYVER:python-=python=%" rem derived requirement: this PYSPEC value is an artifact of whichever provider rem happened to run first, not a genuine user requirement -- see the identical rem comment at :try_conda_create's PYSPEC decision for why this must not be rem forced onto a LATER, different provider during a REQ-009 cascade re-entry. rem HP_PYSPEC_ORIGINAL preserves whatever constraint -- a genuine pyproject.toml/ rem PEP 723 range, or nothing -- existed immediately before this overwrite. set "HP_PYSPEC_WRITEBACK=1" ) ) exit /b 0 rem :conda_binary_corrupt -- REQ-020: shows user-friendly message when conda binary fails health check. rem Called when: (a) HP_TEST_CORRUPT_CONDA=1, (b) call "%CONDA_BAT%" info returns non-zero (DLL error, etc.). rem Interactive users get a Y/N prompt to self-heal; CI exits immediately. rem HP_TEST_HEAL_ANSWER bypasses HP_CI_LANE gate so CI can test the decline path without pausing. rem PVW_CONDA_EXE overrides skip self-heal: we must not delete a user-managed conda root. :conda_binary_corrupt cls echo. echo ================================================================ echo CORRUPTED PYTHON ENVIRONMENT DETECTED echo ================================================================ echo. echo The local conda installation appears to be broken. echo This can happen after a Windows update or OS migration echo ^(example: DLL load error 0xc000007b^). echo. echo Affected path: %MINICONDA_ROOT% echo. call :log "[ERROR] Corrupt conda binary detected at: %CONDA_BAT%" if defined PVW_CONDA_EXE goto :corrupt_override_exit if defined HP_TEST_HEAL_ANSWER goto :heal_prompt if defined HP_CI_LANE goto :corrupt_ci_exit :heal_prompt set "HP_HEAL_RAW=" if defined HP_TEST_HEAL_ANSWER ( set "HP_HEAL_RAW=%HP_TEST_HEAL_ANSWER%" ) else ( set /p "HP_HEAL_RAW= Would you like to delete it and rebuild? [Y/N] " ) set "HP_HEAL_CHOICE=%HP_HEAL_RAW:~0,1%" if /I "%HP_HEAL_CHOICE%"=="Y" goto :evict_and_rebuild echo. echo Exiting without changes. Delete the folder above manually, echo then run this setup again. echo. call :die "[ERROR] Corrupt conda env; user declined rebuild." 2 exit /b 2 :corrupt_override_exit echo. echo This binary was specified via PVW_CONDA_EXE: echo %PVW_CONDA_EXE% echo. echo Automatic self-healing is not available for user-managed conda. echo Please fix or replace the binary at the path above, then re-run. echo. call :die "[ERROR] Corrupt user-managed conda (PVW_CONDA_EXE); fix manually." 2 exit /b 2 :corrupt_ci_exit call :die "[ERROR] Corrupt conda binary in CI; cache must be cleared." 2 exit /b 2 :evict_and_rebuild echo. echo [INFO] Removing corrupt Miniconda installation... rem HP_TEST_SKIP_EVICT: CI branch-coverage flag -- skip actual deletion and re-download so rem the test does not destroy the CI Miniconda installation. The eviction log line fires to rem prove the accept branch was reached. Must not be set in production. if not defined HP_TEST_SKIP_EVICT ( rmdir /s /q "%MINICONDA_ROOT%" 2>nul if exist "%MINICONDA_ROOT%" ( echo. echo [WARN] Could not fully remove %MINICONDA_ROOT%. echo Some files may be locked. Close any Python/conda windows and try again. echo. call :die "[ERROR] Could not delete corrupt conda dir; files may be locked." 3 exit /b 3 ) echo [INFO] Corrupt installation removed. Downloading fresh copy... ) call :log "[INFO] Self-healing: corrupt conda evicted from %MINICONDA_ROOT%." set "CONDA_BAT=" set "HP_CONDA_JUST_INSTALLED=" set "HP_ENV_STATE_RESULT=" if not defined HP_TEST_SKIP_EVICT ( call :download_miniconda_exe if exist "%TEMP%\miniconda.exe" ( call :try_conda_install ) if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1 ) call :select_conda_bat if not defined CONDA_BAT ( call :die "[ERROR] Fresh Miniconda install failed after self-healing eviction." 4 exit /b 4 ) set "HP_CONDA_JUST_INSTALLED=1" goto :after_conda_bat_validation :conda_bulk_install rem Run conda bulk install; retry once if output indicates a transient network failure. rem derived requirement: ~conda_bulk.tmp is tilde-prefixed so it is gitignored and cleaned here. if exist "~conda_bulk.tmp" del "~conda_bulk.tmp" >nul 2>&1 rem [TEST] HP_TEST_FORCE_CONDA_BULK_FAIL: force a non-transient bulk failure (no retry keyword) rem so the caller takes the REQ-005.3 per-package fallback. No real conda call is made here. if "%HP_TEST_FORCE_CONDA_BULK_FAIL%"=="1" ( call :log "[TEST] HP_TEST_FORCE_CONDA_BULK_FAIL: forcing non-transient bulk failure for REQ-005.3 per-pkg fallback." exit /b 1 ) if not "%HP_TEST_FORCE_CONDA_NETWORK_FAIL%"=="1" goto :conda_bulk_real_call rem [TEST] HP_TEST_FORCE_CONDA_NETWORK_FAIL: simulate a transient CondaHTTPError on first attempt. echo CondaHTTPError: HTTP 000 CONNECTION FAILED (simulated) > "~conda_bulk.tmp" set "HP_TEST_FORCE_CONDA_NETWORK_FAIL=" type "~conda_bulk.tmp" >> "%LOG%" set "HP_CBULK_RC=1" goto :conda_bulk_have_rc :conda_bulk_real_call call "%CONDA_BAT%" install -y -n "%ENVNAME%" --file "~reqs_conda.txt" --override-channels -c conda-forge > "~conda_bulk.tmp" 2>&1 set "HP_CBULK_RC=%ERRORLEVEL%" type "~conda_bulk.tmp" >> "%LOG%" :conda_bulk_have_rc if "%HP_CBULK_RC%"=="0" ( del "~conda_bulk.tmp" >nul 2>&1 exit /b 0 ) findstr /i /c:"CondaHTTPError" /c:"Failed to fetch" /c:"timed out" /c:"ConnectionError" "~conda_bulk.tmp" >nul 2>&1 if errorlevel 1 ( del "~conda_bulk.tmp" >nul 2>&1 exit /b 1 ) del "~conda_bulk.tmp" >nul 2>&1 echo Conda install failed -- possible network or repository issue. Retrying once... call :log "[INSTALL] conda bulk: transient failure detected; retrying after 15s." timeout /t 15 /nobreak >nul 2>&1 echo Retrying package installation... call "%CONDA_BAT%" install -y -n "%ENVNAME%" --file "~reqs_conda.txt" --override-channels -c conda-forge >> "%LOG%" 2>&1 if not errorlevel 1 exit /b 0 echo *** Package installation could not complete. This may be a temporary network issue. echo *** See log file for details: ~setup.log call :log "[WARN] conda bulk: retry after transient failure also failed." exit /b 1 :try_nuitka_tier_a rem AV-Safe Build Path (docs/prd-av-safe-build-path.md) requirements 2-4, Tier A only: when the rem PyInstaller build fails for any reason (build error, or output missing/vanished -- both rem paths above converge here, per requirement 3's "single trigger category" design), attempt a rem fallback build via Nuitka directly inside the SAME environment already used for the rem PyInstaller attempt -- same Python, same installed packages, no reprovisioning. Nuitka does rem its own internal compiler discovery (MSVC first, then MinGW64 auto-download); per requirement rem 4's own explicit instruction, this subroutine does NOT probe for a compiler itself -- that rem kind of fingerprinting is exactly what research Finding 2 already argued against. Tier B rem (reprovisioned pinned-3.12 environment for the no-compiler-found case) is NOT implemented rem yet -- this is Tier A only; a Tier A failure currently falls through to the caller's own rem :warn_build_incomplete site (CLAUDE.md Item 46 Bucket B migrated these callers off :die; rem see that subroutine's own header comment for why it is not a :die call anymore). rem Called via `call` (never `goto`) from inside the PyInstaller-build if/else nesting above, so rem it is safe regardless of block depth -- see the "Nested if/else (no goto)" comment there. call :log "[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two)." if defined HP_TEST_FORCE_NUITKA_FAIL ( call :log "[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure." exit /b 1 ) if "%HP_ENV_MODE%"=="uv" ( "%HP_UV_EXE%" pip install --python "%HP_PY%" -q nuitka >> "%LOG%" 2>&1 ) else ( "%HP_PY%" -m pip install -q nuitka >> "%LOG%" 2>&1 ) if errorlevel 1 ( call :log "[WARN] Fallback build: could not install the fallback build tool; fallback unavailable." exit /b 1 ) if exist "dist\%ENVNAME%.exe" del "dist\%ENVNAME%.exe" >nul 2>&1 rem --assume-yes-for-downloads: Nuitka can otherwise prompt interactively to confirm its own rem dependency downloads (e.g. the MinGW64 compiler) -- fatal for both CI and a real rem non-interactive double-click user; the Prime Directive tolerates zero prompts here. "%HP_PY%" -m nuitka --onefile --assume-yes-for-downloads --remove-output --output-dir=dist -o "%ENVNAME%.exe" "%HP_ENTRY%" >> "%LOG%" 2>&1 if errorlevel 1 ( call :log "[WARN] Fallback build did not complete successfully." call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this fallback should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help this fallback succeed." exit /b 1 ) if not exist "dist\%ENVNAME%.exe" ( call :log "[WARN] Fallback build finished but did not produce dist\%ENVNAME%.exe." call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this fallback should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help this fallback succeed." exit /b 1 ) call :log "[INFO] Fallback build succeeded: dist\%ENVNAME%.exe was produced using the fallback build system." exit /b 0 :die set "MSG=%~1" set "RC=%~2" if "%RC%"=="" set "RC=1" echo %date% %time% %MSG% >> "%LOG%" echo [%date% %time%] %MSG% rem derived requirement: exit /b here only returns from THIS call frame -- a caller with no rem halt/goto after "call :die" simply continues (see docs/agent-lessons-learned.md's ":die" rem entry). If execution later reaches :after_cascade_decision/:after_env_skip with rem HP_BOOTSTRAP_STATE still "ok", the final status write silently reverts this error back to rem success. Setting it here, once, covers every call site (already-fixed and future) instead rem of requiring each one to remember its own companion set. set "HP_BOOTSTRAP_STATE=error" call :write_status "error" %RC% %PYCOUNT% call :release_lock rem REQ-016: retain terminal window on error so user can read the message. if not defined HP_CI_LANE ( pause ) exit /b %RC% rem CLAUDE.md Active Backlog Item 46 (Bucket B): a non-pausing sibling of :die for a call site rem that has failed at ONE task (e.g. every build tool tried) but the run itself is NOT doomed -- rem real, useful work still follows (e.g. the interpreter-fallback verification) that will itself rem determine the honest final outcome. Deliberately does NOT pause (the run isn't over yet, so rem stopping here would misleadingly look like the terminal state), does NOT call :release_lock rem (a concurrent second instance must not be able to start while this run still has real rem verification work ahead of it), and does NOT call :write_status (the eventual :success/ rem :print_no_exe_briefing dispatch writes the real final status once HP_BOOTSTRAP_STATE is rem settled). Still sets HP_BOOTSTRAP_STATE=error, same as :die, so the final report is honest. :warn_build_incomplete set "MSG=%~1" call :log "%MSG%" set "HP_BOOTSTRAP_STATE=error" exit /b 0 :rotate_log powershell -NoProfile -ExecutionPolicy Bypass -Command ^ "if (Test-Path '%LOG%') { if ((Get-Item '%LOG%').Length -gt 10485760) { Move-Item -Force '%LOG%' '%LOGPREV%' } }" exit /b 0 :acquire_lock rem REQ-024: concurrent-instance (double-click race) protection. mkdir is atomic on NTFS, rem giving a race-free acquire primitive; CMD has no native mutex. Staleness (age-based, not rem PID-liveness -- PIDs get reused) is the correctness backstop for the crash/kill/power-loss rem case, since CMD has no finally/trap to guarantee release on every exit path. See rem docs/agent-lessons-learned.md "Concurrent-instance lock" for the full design rationale. set "HP_LOCK_DIR=%HP_SCRIPT_ROOT%~bootstrap.lock" set "HP_LOCK_OWNED=" if defined HP_TEST_DISABLE_LOCK exit /b 0 mkdir "%HP_LOCK_DIR%" 2>nul if not errorlevel 1 goto :lock_acquired call :lock_is_stale if not errorlevel 1 goto :lock_stale_evict if errorlevel 2 goto :lock_indeterminate echo *** echo *** Another instance of this setup appears to be running in this folder already. echo *** If you are sure that is NOT the case ^(for example, a previous run crashed^), echo *** delete the "~bootstrap.lock" folder next to this script and run it again. echo *** if exist "%HP_LOCK_DIR%\owner.txt" type "%HP_LOCK_DIR%\owner.txt" call :log "[WARN] REQ-024: setup already running (lock held, not stale); this instance is exiting." if not defined HP_CI_LANE pause exit /b 1 :lock_indeterminate rem derived requirement: CLAUDE.md Item 49 -- an indeterminate staleness result (neither rem 'stale' nor 'fresh' came back from the PowerShell probe, e.g. a transient hiccup) must not rem be treated as "fresh" the way it silently was before this fix, since that produced a false rem "another instance is running" hard block for a condition that has nothing to do with a rem concurrent run. Also must not be treated as "stale" (silently evicting a lock that might rem genuinely still be held by a live instance would be equally wrong) -- so this is a THIRD, rem distinct outcome: continue without the lock, same graceful shape as the already-existing rem "could not acquire lock after evicting" path below. call :log "[WARN] REQ-024: could not determine whether the existing lock is stale (indeterminate result); continuing without it." exit /b 0 :lock_stale_evict call :log "[INFO] REQ-024: stale lock evicted (older than the staleness threshold); proceeding." rd /s /q "%HP_LOCK_DIR%" 2>nul mkdir "%HP_LOCK_DIR%" 2>nul if errorlevel 1 ( call :log "[WARN] REQ-024: could not acquire lock after evicting stale lock; continuing without it." exit /b 0 ) :lock_acquired set "HP_LOCK_OWNED=1" set "HP_LOCK_PID=" for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$PID" 2^>nul`) do set "HP_LOCK_PID=%%P" >"%HP_LOCK_DIR%\owner.txt" echo pid=%HP_LOCK_PID% >>"%HP_LOCK_DIR%\owner.txt" echo started=%date% %time% exit /b 0 :lock_is_stale rem exit/b 0 = stale (caller should evict); exit/b 1 = fresh (still held by a live instance); rem exit/b 2 = indeterminate (CLAUDE.md Item 49 -- neither recognized value came back from the rem probe below). derived requirement: HP_TEST_FORCE_LOCK_STALE gives CI a deterministic way to rem exercise the eviction path without waiting out the real ~2 hour threshold; rem HP_TEST_FORCE_LOCK_INDETERMINATE similarly exercises the indeterminate path deterministically, rem without needing to actually simulate a PowerShell hiccup. if defined HP_TEST_FORCE_LOCK_STALE exit /b 0 if defined HP_TEST_FORCE_LOCK_INDETERMINATE exit /b 2 set "HP_LOCK_STALE_RESULT=" for /f "usebackq delims=" %%R in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "if (Test-Path '%HP_LOCK_DIR%') { try { $d = (Get-Item '%HP_LOCK_DIR%').LastWriteTime; if (((Get-Date)-$d).TotalHours -ge 2) { 'stale' } else { 'fresh' } } catch { 'indeterminate' } } else { 'stale' }" 2^>nul`) do set "HP_LOCK_STALE_RESULT=%%R" if "%HP_LOCK_STALE_RESULT%"=="stale" exit /b 0 if "%HP_LOCK_STALE_RESULT%"=="fresh" exit /b 1 exit /b 2 :release_lock if defined HP_LOCK_OWNED ( rd /s /q "%HP_LOCK_DIR%" 2>nul set "HP_LOCK_OWNED=" ) exit /b 0 :run_installer_timeout rem [Active Backlog item 14] Bounds a "start "" /wait" external-installer launch with a rem generous, configurable timeout ceiling instead of an unbounded wait -- see rem tools/run_installer_with_timeout.ps1's own header comment for the full design rationale -- rem UseShellExecute=$true for UAC-elevation-via-manifest parity with plain "start /wait"; rem taskkill /F /T instead of $p.Kill for real process-tree termination, since Windows rem PowerShell 5.1 / .NET Framework's Process.Kill has no tree-kill overload. This is a rem safety CEILING against a genuinely hung installer, not a responsiveness check -- killing rem an installer too early while it is still legitimately writing files/registry keys risks a rem WORSE outcome (a corrupted half-installed target) than a slow-but-succeeding install, so rem callers must pick a generous value. See the per-call-site comments for the real-world rem research behind each timeout value. rem rem Params: %1=exe path, %2=argument string (single token; caller's responsibility to already rem have it as one properly-tokenized string, matching HP_PROBE_ARGS's own "caller provides a rem ready string" contract), %3=timeout in milliseconds, %4=short label for logging. rem Returns the installer's real exit code (or 1 on timeout) via errorlevel, exactly like the rem old bare "start "" /wait" did -- callers' existing "if errorlevel 1 goto ..." need no change. rem rem Falls back to the OLD untimed "start "" /wait" behavior if the helper payload cannot be rem emitted to disk (extremely rare -- disk/permission failure), so a helper-emission problem rem can never make an installer unreachable outright; it only removes the timeout ceiling for rem that one run. set "HP_INSTALLER_LABEL=%~4" set "HP_INSTALLER_EXE=%~1" set "HP_INSTALLER_ARGS=%~2" set "HP_INSTALLER_TIMEOUT_MS=%~3" set "HP_INSTALLER_RESULT=~installer_result.txt" set "HP_INSTALLER_RC=1" set "HP_INSTALLER_TIMEDOUT=0" if exist "~run_installer_with_timeout.ps1" del "~run_installer_with_timeout.ps1" >nul 2>&1 if exist "%HP_INSTALLER_RESULT%" del "%HP_INSTALLER_RESULT%" >nul 2>&1 call :emit_from_base64 "~run_installer_with_timeout.ps1" HP_INSTALLER_TIMEOUT if errorlevel 1 goto :installer_timeout_fallback call :log "[INFO] Launching %HP_INSTALLER_LABEL% installer (timeout ceiling: %HP_INSTALLER_TIMEOUT_MS% ms)." powershell -NoProfile -ExecutionPolicy Bypass -File "~run_installer_with_timeout.ps1" if exist "%HP_INSTALLER_RESULT%" ( for /f "usebackq tokens=1,2 delims=|" %%A in ("%HP_INSTALLER_RESULT%") do ( set "HP_INSTALLER_RC=%%A" set "HP_INSTALLER_TIMEDOUT=%%B" ) ) goto :installer_timeout_cleanup :installer_timeout_fallback call :log "[WARN] Installer timeout helper could not be emitted; running %HP_INSTALLER_LABEL% without a timeout ceiling." start "" /wait "%HP_INSTALLER_EXE%" %HP_INSTALLER_ARGS% set "HP_INSTALLER_RC=%ERRORLEVEL%" goto :installer_timeout_done :installer_timeout_cleanup if "%HP_INSTALLER_TIMEDOUT%"=="1" call :log "[WARN] %HP_INSTALLER_LABEL% installer exceeded its %HP_INSTALLER_TIMEOUT_MS% ms timeout ceiling; terminated." if exist "~run_installer_with_timeout.ps1" del "~run_installer_with_timeout.ps1" >nul 2>&1 if exist "%HP_INSTALLER_RESULT%" del "%HP_INSTALLER_RESULT%" >nul 2>&1 :installer_timeout_done set "HP_INSTALLER_EXE=" set "HP_INSTALLER_ARGS=" set "HP_INSTALLER_TIMEOUT_MS=" set "HP_INSTALLER_RESULT=" set "HP_INSTALLER_LABEL=" rem derived requirement: HP_INSTALLER_TIMEDOUT is deliberately NOT cleared here -- it is the rem only way a caller can tell a timeout (HP_INSTALLER_RC hardcoded to 1, not a real installer rem exit code) apart from a genuine installer failure that happens to also exit 1. Each call rem freshly re-sets it at entry (either "0" or from the result file), so leaving it live across rem return is safe: no caller can ever read a stale value from an earlier, different call. exit /b %HP_INSTALLER_RC% :try_conda_install rem derived requirement: AllUsers install can fail when UAC rejects elevation even for admin accounts. rem JustMe is the non-admin fallback that installs under the user profile instead. rem Both attempts reuse the already-downloaded installer at %TEMP%\miniconda.exe (no re-download). rem derived requirement: [Active Backlog item 16, renumbered from 11 -- see docs/agent-closed-backlog.md] rem track whether AllUsers was actually launched vs. rem only skipped, so :tci_justme's own log line can tell the two apart instead of unconditionally rem claiming AllUsers "failed" even when it was never attempted. Reset defensively at entry in case rem a future caller invokes this subroutine more than once in the same process. set "HP_CONDA_ALLUSERS_ATTEMPTED=" set "HP_CONDA_ALLUSERS_TIMEDOUT=" rem derived requirement: non-admin machines produce a UAC prompt when AllUsers install is attempted; rem skip directly to JustMe when the process is not elevated. rem HP_TEST_NOT_ELEVATED=1 simulates a non-admin environment for CI coverage of this branch. if "%HP_TEST_NOT_ELEVATED%"=="1" ( call :log "[INFO] Not elevated; skipping AllUsers Miniconda install." goto :tci_justme ) fsutil dirty query %systemdrive% >nul 2>&1 if errorlevel 1 ( call :log "[INFO] Not elevated; skipping AllUsers Miniconda install." goto :tci_justme ) rem derived requirement: [Active Backlog item 14] bound the installer launch with a generous rem timeout ceiling instead of an unbounded wait. Real-world reports (Travis CI Community forum: rem "Installing Anaconda/Miniconda times out after 40 minutes"; multiple conda/conda and rem ContinuumIO/anaconda-issues GitHub issues reporting the silent installer hanging indefinitely rem at extraction or the post-install script) confirm this is not a theoretical risk. 60 minutes rem is a generous ceiling above the documented ~40 min real-world duration. set "HP_CONDA_ALLUSERS_ATTEMPTED=1" set "HP_CONDA_ALLUSERS_RC=" call :run_installer_timeout "%TEMP%\miniconda.exe" "/InstallationType=AllUsers /AddToPath=0 /RegisterPython=0 /S /D=%MINICONDA_ROOT%" 3600000 "Miniconda AllUsers" set "HP_CONDA_ALLUSERS_RC=%ERRORLEVEL%" set "HP_CONDA_ALLUSERS_TIMEDOUT=%HP_INSTALLER_TIMEDOUT%" if not "%HP_CONDA_ALLUSERS_RC%"=="0" goto :tci_justme set "HP_CONDA_INSTALL_MODE=AllUsers" call :log "[INFO] Miniconda installed successfully." goto :eof :tci_justme rem derived requirement: a genuine timeout (HP_CONDA_ALLUSERS_TIMEDOUT=1, see rem :run_installer_timeout's own header comment) hardcodes HP_CONDA_ALLUSERS_RC to 1 -- that is rem a sentinel, not the installer's real exit code, so it must not be presented as one; report rem reason=timeout instead of a fabricated exitCode. if defined HP_CONDA_ALLUSERS_ATTEMPTED ( if "%HP_CONDA_ALLUSERS_TIMEDOUT%"=="1" ( call :log "[WARN] Miniconda AllUsers install failed (reason=timeout); retrying with JustMe." ) else ( call :log "[WARN] Miniconda AllUsers install failed (exitCode=%HP_CONDA_ALLUSERS_RC%, reason=installer_failed); retrying with JustMe." ) ) else ( call :log "[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead." ) if exist "%MINICONDA_ROOT%" rd /s /q "%MINICONDA_ROOT%" >nul 2>&1 rem derived requirement: [Active Backlog item 10] HP_TEST_FORCE_JUSTME_FAIL=1 deterministically rem forces the JustMe install to fail WITHOUT launching the real installer, so CI can exercise rem :tci_both_failed (both AllUsers and JustMe failing) without depending on a genuinely broken rem installer/ACL environment. HP_TEST_NOT_ELEVATED=1 alone already reaches this label by rem skipping straight past the AllUsers attempt; this flag makes the JustMe attempt fail too. if "%HP_TEST_FORCE_JUSTME_FAIL%"=="1" ( call :log "[INFO] HP_TEST_FORCE_JUSTME_FAIL=1; simulating JustMe install failure without launching the real installer." goto :tci_both_failed ) call :run_installer_timeout "%TEMP%\miniconda.exe" "/InstallationType=JustMe /AddToPath=0 /RegisterPython=0 /S /D=%MINICONDA_ROOT%" 3600000 "Miniconda JustMe" if errorlevel 1 goto :tci_both_failed set "HP_CONDA_INSTALL_MODE=JustMe" call :log "[INFO] Miniconda installed (JustMe fallback)." goto :eof :tci_both_failed rem derived requirement: [Active Backlog item 16] the terminal message must not imply AllUsers was rem genuinely attempted-then-failed when it was only ever skipped (not elevated) -- mirrors the rem same HP_CONDA_ALLUSERS_ATTEMPTED distinction :tci_justme already makes above. if defined HP_CONDA_ALLUSERS_ATTEMPTED ( call :die "[ERROR] Miniconda install failed (both AllUsers and JustMe)." ) else ( call :die "[ERROR] Miniconda install failed (AllUsers skipped -- not elevated; JustMe also failed)." ) goto :eof :conda_base_update if /i not "%HP_ENV_MODE%"=="conda" goto :eof if "%HP_TEST_CONDA_UPDATE%"=="1" ( if exist "%TEMP%\~conda.update.done" ( call :log "[INFO] Conda base update: skipped (already ran this session)." goto :eof ) goto :cbu_run ) if defined HP_CONDA_JUST_INSTALLED goto :cbu_firstinstall set "HP_CONDA_UPDATE_RESULT=update" rem derived requirement: raw .NET File/DateTime APIs, not Get-Content/Get-Date/Test-Path -- rem see docs/agent-lessons-learned.md "Prefer raw .NET types over Utility-module cmdlets" for rem the confirmed Windows PowerShell 5.1 module-autoload gap this avoids (Get-FileHash failed rem to autoload in the identical for-/f-backtick-subshell topology; Get-Date/Get-Content share rem its Microsoft.PowerShell.Utility module, so both are equally exposed, not just the one that rem was actually caught failing). for /f "delims=" %%R in ('powershell -NoProfile -ExecutionPolicy Bypass -Command "if ([System.IO.File]::Exists('%MINICONDA_ROOT%\~conda.lastupdate')) { try { $d = [datetime]([System.IO.File]::ReadAllText('%MINICONDA_ROOT%\~conda.lastupdate', [System.Text.Encoding]::ASCII)); if (([datetime]::Now - $d).TotalDays -ge 30) { 'update' } else { 'skip' } } catch { 'update' } } else { 'update' }"') do set "HP_CONDA_UPDATE_RESULT=%%R" if "%HP_CONDA_UPDATE_RESULT%"=="update" goto :cbu_run call :log "[INFO] Conda base update: skipped (last update < 30 days ago)." goto :eof :cbu_firstinstall call :log "[INFO] Conda base update: skipped (first install)." powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.IO.File]::WriteAllText('%MINICONDA_ROOT%\~conda.lastupdate', [datetime]::Now.ToString('yyyy-MM-ddTHH:mm:ss'), [System.Text.Encoding]::ASCII)" >nul 2>&1 goto :eof :cbu_run call :log "[INFO] Conda base update: running (>=30 days since last update or no record)." call "%CONDA_BAT%" update -n base --all --override-channels -c conda-forge -y >> "%LOG%" 2>&1 if not errorlevel 1 ( powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.IO.File]::WriteAllText('%MINICONDA_ROOT%\~conda.lastupdate', [datetime]::Now.ToString('yyyy-MM-ddTHH:mm:ss'), [System.Text.Encoding]::ASCII)" >nul 2>&1 call :log "[INFO] Conda base update complete." ) else ( call :log "[WARN] Conda base update failed; continuing." ) type nul > "%TEMP%\~conda.update.done" 2>nul goto :eof :merge_git_config rem REQ-015: idempotently append standard .gitignore and .gitattributes entries. rem Uses findstr errorlevel: 0=found (skip), 1=not found, 2=file missing; both 1 and 2 trigger append. set "HP_GI_SIG=# Automated Python Bootstrapper Standard Ignores" set "HP_GA_SIG=# Automated Python Bootstrapper Attributes" findstr /C:"%HP_GI_SIG%" ".gitignore" >nul 2>&1 if not errorlevel 1 goto :mgc_gi_done call :log "[INFO] REQ-015: Appending standard ignores to .gitignore." >> ".gitignore" echo. >> ".gitignore" echo %HP_GI_SIG% >> ".gitignore" echo .*_env/ >> ".gitignore" echo .venv/ >> ".gitignore" echo .uv/ >> ".gitignore" echo .cache/ >> ".gitignore" echo .conda/ >> ".gitignore" echo dist/ >> ".gitignore" echo build/ >> ".gitignore" echo *~ >> ".gitignore" echo ~* :mgc_gi_done findstr /C:"%HP_GA_SIG%" ".gitattributes" >nul 2>&1 if not errorlevel 1 goto :mgc_ga_done call :log "[INFO] REQ-015: Appending standard attributes to .gitattributes." rem derived requirement: "-text" (not "eol=crlf") for *.bat/*.cmd -- eol=crlf only affects rem checkout, never the blob a raw/GitHub-served download returns, so a user's own .bat files rem would still be corrupted for anyone downloading them raw from THEIR repo. This repo's own rem docs/agent-lessons-learned.md ".bat files: -text, not eol=crlf" entry is the full writeup rem of why; propagate the fix this bootstrapper itself needed, not the pattern proven insufficient. >> ".gitattributes" echo. >> ".gitattributes" echo %HP_GA_SIG% >> ".gitattributes" echo *.bat -text >> ".gitattributes" echo *.cmd -text >> ".gitattributes" echo *.exe binary :mgc_ga_done rem CLAUDE.md Item 60: a user who already ran an OLDER copy of this bootstrapper has the rem HP_GA_SIG signature present, so the findstr check above skips the whole append block on a rem newer run -- their .gitattributes stays on the disproven *.bat/*.cmd eol=crlf rule rem indefinitely with no path to the fix. This label is reached both when the signature was rem already found AND right after a fresh append, so .gitattributes always exists here. rem tools/migrate_gitattributes.ps1 replaces ONLY lines that exactly match one of the two rem known-stale strings, in place -- every other line (including any user hand-edits rem elsewhere in the file) passes through byte-identical. call :emit_from_base64 "~migrate_gitattributes.ps1" HP_MIGRATE_GITATTRIBUTES powershell -NoProfile -ExecutionPolicy Bypass -File "~migrate_gitattributes.ps1" -Path ".gitattributes" > "~ga_migrate_result.txt" 2>&1 if exist "~migrate_gitattributes.ps1" del "~migrate_gitattributes.ps1" >nul 2>&1 set "HP_GA_MIGRATE_RESULT=" rem derived requirement: read ONLY the first line of the result file into HP_GA_MIGRATE_RESULT rem -- the "if not defined" guard skips every later iteration once the first non-blank line has rem been captured. A stray extra line (e.g. a multi-line terminating-error dump the script's own rem try/catch is meant to prevent, but defense in depth costs nothing here) must never silently rem overwrite the real marker with unrelated trailing text -- the prior unguarded loop kept only rem the LAST line instead, which is the wrong end of the file for this purpose. for /f "usebackq delims=" %%X in ("~ga_migrate_result.txt") do if not defined HP_GA_MIGRATE_RESULT set "HP_GA_MIGRATE_RESULT=%%X" if "%HP_GA_MIGRATE_RESULT%"=="MIGRATED" goto :mgc_ga_migrated if "%HP_GA_MIGRATE_RESULT%"=="NOOP:no-stale-lines" goto :mgc_ga_migrate_end if "%HP_GA_MIGRATE_RESULT%"=="NOOP:missing" goto :mgc_ga_migrate_end rem derived requirement: this is a best-effort, non-gating hygiene check (mirrors this repo's rem own established "never gates the lane" convention for similar helpers, e.g. autopep_merge rem -- see docs/agent-interconnect.md) -- an unexpected result (script threw, or produced text rem outside its own 3-value contract) is logged for visibility but never treated as fatal. call :log "[WARN] REQ-015: .gitattributes migration check produced an unexpected result (non-fatal, continuing)." rem derived requirement: dump the script's own raw captured output, stdout and stderr both, via rem a plain byte copy, never through %VAR% substitution -- an unanticipated failure message rem could legally contain shell metacharacters such as less-than, greater-than, ampersand, rem pipe, percent, or caret, the exact hazard :log's own UNQUOTED echo is not safe against -- rem see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry. "type" reads file bytes rem only; it never re-parses them as command text. if exist "~ga_migrate_result.txt" type "~ga_migrate_result.txt" 2>nul if exist "~ga_migrate_result.txt" type "~ga_migrate_result.txt" >> "%LOG%" 2>nul goto :mgc_ga_migrate_end :mgc_ga_migrated call :log "[INFO] REQ-015: Migrated stale *.bat/*.cmd eol=crlf rule to -text in .gitattributes." :mgc_ga_migrate_end if exist "~ga_migrate_result.txt" del "~ga_migrate_result.txt" >nul 2>&1 set "HP_GA_MIGRATE_RESULT=" set "HP_GI_SIG=" set "HP_GA_SIG=" exit /b 0 :print_postflight_briefing rem REQ-016: print a scannable summary panel after a successful full EXE build. echo. echo ============================================================ if defined HP_EXE_VERIFY_FAILED goto :pfb_caveat echo SETUP COMPLETE echo ============================================================ echo Your standalone application is ready: echo dist\%ENVNAME%.exe if defined HP_EXE_SKIPPED echo Note: EXE verification was skipped by request (HP_SKIP_EXE_SMOKERUN). goto :pfb_runapp :pfb_caveat echo SETUP COMPLETE -- WITH A CAVEAT echo ============================================================ echo We packaged your app, but couldn't fully verify it runs as a echo standalone program. Your Python environment was set up and the echo packaging step completed without a fatal error. if defined HP_EXE_TIMEDOUT_SILENT call :pfb_gui_hint if defined HP_DLL_HINT_STATE call :pfb_dll_hint :pfb_runapp echo. echo RUNNING YOUR APP echo Double-click dist\%ENVNAME%.exe to run it. rem derived requirement: defense in depth alongside the HP_CASCADE_SAVED_PY restore above -- rem never print an empty "" "%HP_ENTRY%" interpreter command. HP_PY should always be defined rem here (the EXE this panel describes could not have been built without a working interpreter, rem and :after_cascade_decision now restores HP_PY if a declined/exhausted cascade tier cleared rem it), but the .exe itself remains correct and runnable either way, so silently omitting this rem one line is strictly safer than a guaranteed-broken command. if not defined HP_PY goto :pfb_runapp_noninterp echo You can also run it directly via the interpreter at any time: echo "%HP_PY%" "%HP_ENTRY%" :pfb_runapp_noninterp echo. echo STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it echo starts, so allow 10-15 seconds (longer for big libraries like echo numpy/scipy/matplotlib, or when extra packages were bundled to fix echo missing imports) before assuming it has hung. echo. echo If the window flashes and closes instantly: that's normal if echo your program finished quickly or hit an error before printing echo anything. To see what happened, open Command Prompt, cd to echo this folder, and run: echo dist\%ENVNAME%.exe echo This keeps the window open so you can read any messages. echo. echo A progress indicator that updates in place may appear all at echo once instead of live when run as the .exe -- that is a stdout echo buffering difference between the .exe and the script, not an error. echo. echo Does your program need launch arguments (e.g. --input file.csv)? Run echo this bootstrapper again with them added after the entry file, e.g. echo run_setup.bat "%HP_ENTRY%" --input file.csv echo and they will be forwarded to your program during THIS setup run echo (up to 8 extra arguments). This does not change how a plain echo double-click of dist\%ENVNAME%.exe launches it afterward -- for that, echo make a Windows shortcut to the .exe and add the arguments to its echo Target field, or launch it yourself from a Command Prompt. echo. echo KEEP these files with your project: echo requirements.txt -- packages your app depends on echo runtime.txt -- Python version pin echo. echo SAFE TO DELETE to reclaim disk space: echo .*_env\ folders -- environment directories echo ~* files -- tilde-prefix work files (e.g. ~setup.log) echo build\ -- PyInstaller build cache echo ============================================================ echo. if defined HP_EXE_VERIFY_FAILED goto :pfb_log_caveat call :log "[INFO] REQ-016: Post-flight briefing printed." exit /b 0 :pfb_log_caveat call :log "[WARN] REQ-016: Post-flight briefing printed; EXE unverified, advised direct run." exit /b 0 :pfb_dll_hint rem docs/open-questions.md item 1 (answered yes, implemented): a real, already-computed fact rem (:dll_bundle_recover's own outcome -- CLAUDE.md Item 24/25/28/29) surfacing in the caveat rem panel above, instead of staying purely generic. Three wording buckets, not one -- a review rem pass on PR #414 (see docs/agent-interconnect.md's DLL-bundling section) found detection and rem repair are NOT the same event, so the wording must not claim more than each state actually rem means: "skipped" states never attempted a fix; "failed"/"exhausted" states attempted but did rem not fully resolve it; "repaired" means the DLL itself was fixed (this caveat firing anyway rem means something ELSE is still wrong -- HP_DLL_REPAIRED can trigger a bounded follow-up rem :hidden_import_recover pass that still leaves HP_EXE_EXIT non-zero for an unrelated reason, rem so this is rare but real, not dead code). goto-based dispatch (not a parenthesized block) so rem each %VAR% reads its runtime value -- see docs/agent-lessons-learned.md's "Provider-cascade rem dispatch is goto-based on purpose". HP_DLL_DETECTED_SAFE was already sanitized (ampersand, rem pipe, angle brackets, percent, and caret all stripped) for :log's own unquoted echo, so it rem is equally safe on this panel's plain echo. if "%HP_DLL_HINT_STATE%"=="repaired" goto :pfb_dll_hint_repaired if "%HP_DLL_HINT_STATE%"=="failed_rebuild" goto :pfb_dll_hint_failed if "%HP_DLL_HINT_STATE%"=="failed_missing_exe" goto :pfb_dll_hint_failed if "%HP_DLL_HINT_STATE%"=="exhausted" goto :pfb_dll_hint_failed goto :pfb_dll_hint_skipped :pfb_dll_hint_skipped rem Covers skipped_nuitka / skipped_non_conda / unlocatable: detection happened, but no repair rem was ever attempted for it -- must NOT claim "we tried to fix it automatically." echo. echo NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while echo packaging your app, but no automatic repair for it ran during this build. echo This may or may not be related to the caveat above. goto :pfb_dll_hint_done :pfb_dll_hint_failed echo. echo NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while echo packaging your app, and an automatic repair was attempted but did not fully echo resolve it. This may be related to the caveat above. goto :pfb_dll_hint_done :pfb_dll_hint_repaired echo. echo NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while echo packaging your app and was automatically repaired; it is likely not the echo cause of the caveat above. :pfb_dll_hint_done exit /b 0 :pfb_gui_hint rem CLAUDE.md Active Backlog Item 41: the ~30s verification kill only fires when ZERO rem stdout/stderr bytes were observed before the deadline (~exe_smokerun.ps1's own $sawOutput rem gate, see docs/agent-interconnect.md "Activity-aware EXE-smoke kill") -- exactly the shape rem of a correctly-behaving GUI app (tkinter, PyQt) with a mainloop and no console output, not rem just a genuinely hung program. Distinguish the two in the panel text itself, not just the rem pre-launch warning (:warn_user_code_launch already discloses the kill beforehand, but that rem disclosure and this panel are both worded for a console-program mental model otherwise). echo. echo NOTE: this can happen for a GUI app, e.g. tkinter or PyQt, that opens its echo own window and prints nothing to the console -- that produces the exact echo same silence as a genuinely hung program, so we cannot tell them apart echo automatically. If a window appeared and worked normally, this caveat echo does not necessarily mean anything is wrong. exit /b 0 :print_no_exe_briefing rem docs/open-questions.md item 1: when PyInstaller AND the Nuitka fallback both fail outright rem (no dist\%ENVNAME%.exe at all), the run still succeeds via the interpreter fallback and the rem final [STATUS] line reads identically to a real EXE success, with the only prior signal rem being one [ERROR] line several seconds earlier. This panel closes that gap -- purely rem additive, does not touch success/failure semantics (~bootstrap.status.json already rem correctly records state=error via :die's own HP_BOOTSTRAP_STATE=error). rem [REQ-027] P2 honest messaging: the header text below used to unconditionally claim "your rem code ran successfully" regardless of whether the interpreter run (:verify_no_exe_interpreter, rem which always runs immediately before this panel on the no-EXE path) actually exited 0 -- a rem real, pre-existing dishonest claim, confirmed by tracing that HP_NOEXE_VERIFY_FAILED is the rem ONLY signal this panel previously ignored. Branches on it now, mirroring rem :print_postflight_briefing's own :pfb_caveat dispatch shape. echo. echo ============================================================ if defined HP_NOEXE_VERIFY_FAILED goto :noexe_caveat echo YOUR CODE RAN -- BUT NO STANDALONE .EXE WAS PRODUCED echo ============================================================ echo We could not package your app into a double-clickable .exe echo (see the ERROR message above for why), but your code ran echo successfully just now using the prepared Python environment. echo Your environment and dependencies ARE installed correctly. goto :noexe_runapp :noexe_caveat echo NO STANDALONE .EXE -- AND WE CAN'T CONFIRM YOUR CODE RAN CLEANLY echo ============================================================ echo We could not package your app into a double-clickable .exe echo (see the ERROR message above for why). We also just ran it echo directly via the prepared Python environment, and it exited echo with an error (see the [STATUS] line above) -- so we can't echo tell whether that's a bug in the Python code we tried to run echo or something this bootstrapper missed. Your environment and echo dependencies ARE still installed correctly; run it yourself echo below to see the full output. :noexe_runapp echo. echo RUNNING YOUR APP (without an .exe) -- the most direct option echo "%HP_PY%" "%HP_ENTRY%" echo Need launch arguments? Add them directly after that command, e.g. echo "%HP_PY%" "%HP_ENTRY%" --input file.csv echo. if defined HP_NOEXE_VERIFY_FAILED ( echo Want to try different arguments through the bootstrapper itself echo instead? Your already-installed environment is reused either echo way; it will just attempt the .exe build again too: echo run_setup.bat "%HP_ENTRY%" arg1 arg2 echo. ) echo KEEP these files with your project: echo requirements.txt -- packages your app depends on echo runtime.txt -- Python version pin echo. echo SAFE TO DELETE to reclaim disk space: echo .*_env\ folders -- environment directories echo ~* files -- tilde-prefix work files (e.g. ~setup.log) echo ============================================================ echo. if defined HP_NOEXE_VERIFY_FAILED ( call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, interpreter run also unverified." ) else ( call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, advised direct interpreter run." ) exit /b 0 :print_fastpath_ambiguous_note rem [REQ-027] P2 honest messaging (docs/plan-cli-interactive-verification.md): the cached-EXE rem fast path is deliberately zero-friction for PROMPTS (never a consent gate, per rem docs/agent-interconnect.md's "Fast path = ZERO friction" design requirement) -- this is a rem plain informational print, not a question, so it does not violate that requirement. Fires rem only when the fail-fast probe classified the reused EXE as alive/healthy (so it was kept, rem not discarded+rebuilt) and it later exited non-zero -- see :try_fast_exe. Whether that rem non-zero exit means a real bug in the user's own code, an unresolved dependency, or rem something else entirely is NOT something this bootstrapper can determine (Open Question 3, rem same plan doc) -- this note is deliberately honest about that limit, not a diagnosis. echo. echo ============================================================ echo SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED echo ============================================================ echo Your existing standalone application was reused (dist\%ENVNAME%.exe), echo and it exited with an error just now (see the [STATUS] line echo above) -- so we can't tell whether that's a bug in the Python echo code we tried to run, or something else. Your environment and echo dependencies ARE still installed correctly. echo. echo RUNNING YOUR APP echo Double-click dist\%ENVNAME%.exe to run it, or run it from a echo Command Prompt to see the full output. echo. echo WANT TO TRY AGAIN? You do not have to start over from scratch -- echo just run this bootstrapper again the same way you did before; echo your already-installed environment and built .exe are reused. echo. echo WANT A FRESH BUILD instead (re-checks all dependencies from scratch)? echo Delete dist\%ENVNAME%.exe and run this bootstrapper again. echo ============================================================ echo. call :log "[WARN] REQ-016: Post-flight note printed; cached EXE kept despite a non-zero exit after the fail-fast probe." exit /b 0 :check_net_after_dl_fail rem REQ-013: called after a primary download fails. Pings 8.8.8.8 to distinguish rem no-internet (Scenario A) from specific-URL-failed (Scenario B). rem HP_TEST_OFFLINE=1 simulates ping failure for CI branch coverage. rem derived requirement: both the ping and curl reachability checks retry once (2 total rem attempts each) before concluding "no internet" -- a single dropped ICMP echo or a rem momentarily-contended curl connect on a busy shared CI runner is enough to misclassify a rem genuinely-online host as offline (root-caused from a real self.ux.connectivity.online CI rem flake). Mirrors the REQ-022 detect-transient-then-retry-once idiom used elsewhere in this rem file (:try_conda_create/:conda_bulk_install). The counter-based loop below is safe because rem these lines are top-level (not nested in a parenthesized block); the "Y" retry branch rem further down uses literal duplication instead of a counter, since it IS nested in a block rem and a counter set+read inside the same block would hit CMD's parse-time %VAR% expansion rem trap (see docs/agent-lessons-learned.md). if "%HP_TEST_OFFLINE%"=="1" ( call :log "[TEST] HP_TEST_OFFLINE: simulating ping failure for REQ-013." goto :cndf_ping_failed ) set "HP_CONN_PING_ATTEMPT=0" :cndf_ping_retry set /a HP_CONN_PING_ATTEMPT+=1 ping -n 1 8.8.8.8 >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity check: internet reachable. Cascading to fallback." exit /b 0 ) if %HP_CONN_PING_ATTEMPT% LSS 2 goto :cndf_ping_retry rem ICMP may be blocked on corporate networks; try HTTPS as secondary reachability check. set "HP_CONN_CURL_ATTEMPT=0" :cndf_curl_retry set /a HP_CONN_CURL_ATTEMPT+=1 curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity check: internet reachable via HTTPS (ICMP blocked). Cascading to fallback." exit /b 0 ) if %HP_CONN_CURL_ATTEMPT% LSS 2 goto :cndf_curl_retry :cndf_ping_failed call :log "[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed)." :cndf_prompt_loop rem CLAUDE.md Active Backlog Item 50: this prompt previously had no CI-safe auto-decline at rem all, unlike every sibling consent gate -- against a genuinely open, interactive console rem with nobody present to answer, it would block on set /p indefinitely. Fixed with rem HP_TEST_CNDF_ANSWER (deterministic test override) and NOINPUT/HP_NONINTERACTIVE rem auto-decline (the same authoritative "no human will answer" signals :run_postexec_ rem checkpoint already treats this way -- see docs/agent-interconnect.md). rem rem Deliberately does NOT auto-decline on bare HP_CI_LANE, unlike most sibling gates: two rem existing regression tests (self.ux.connectivity.offline.n/.prompt.shown/.retry in rem selfapps_ux_hardening.ps1) already run this exact gate under HP_CI_LANE=test while rem piping real Y/N answers via stdin, specifically to exercise the interactive Y-retry rem loop -- a static HP_CI_LANE auto-decline would silently bypass set /p and break that rem coverage. A genuinely detached CI job (closed/EOF stdin) already resolves safely via the rem pre-existing empty-input default below regardless of HP_CI_LANE; NOINPUT/HP_NONINTERACTIVE rem are the correct signal for "deliberately unattended," matching this repo's own convention rem that those two flags -- not HP_CI_LANE -- represent an explicit non-interactivity rem declaration (see docs/agent-lessons-learned.md's "Env-var flags are scaffolding" entry). echo WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (N): set "HP_CONN_CHOICE=" if defined HP_TEST_CNDF_ANSWER ( set "HP_CONN_CHOICE=%HP_TEST_CNDF_ANSWER%" ) else if defined NOINPUT ( set "HP_CONN_CHOICE=n" ) else if defined HP_NONINTERACTIVE ( set "HP_CONN_CHOICE=n" ) else ( set /p "HP_CONN_CHOICE=Your choice [y/n]: " ) if "%HP_CONN_CHOICE:~0,1%"=="" ( call :log "[INFO] REQ-013: Connectivity prompt: empty input; defaulting offline." set "HP_OFFLINE_MODE=1" exit /b 1 ) if /I "%HP_CONN_CHOICE:~0,1%"=="y" ( if "%HP_TEST_OFFLINE%"=="1" ( call :log "[TEST] HP_TEST_OFFLINE: Y selected; still simulating offline." goto :cndf_ping_failed ) rem derived requirement: 2 literal attempts each, not a counter var -- this whole branch is rem nested inside the "y" parenthesized block, and a counter set+read inside the same block rem would be frozen at the block's pre-execution value by CMD's parse-time %VAR% expansion, rem see docs/agent-lessons-learned.md; literal duplication has no such variable to freeze. ping -n 1 8.8.8.8 >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity restored after retry." exit /b 0 ) ping -n 1 8.8.8.8 >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity restored after retry." exit /b 0 ) curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)." exit /b 0 ) curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1 if not errorlevel 1 ( call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)." exit /b 0 ) call :log "[INFO] REQ-013: Still offline after Y; re-prompting." goto :cndf_prompt_loop ) call :log "[INFO] REQ-013: Connectivity prompt: user chose offline (N)." set "HP_OFFLINE_MODE=1" exit /b 1 :system_python_consent_gate rem REQ-014: halt and require explicit consent before using global system Python. rem CI-safe (mirrors :cascade_consent_gate): HP_TEST_SYSCON_ANSWER (Y/N) overrides; otherwise rem HP_CI_LANE auto-declines with no prompt (no set /p hang in CI); interactive users get a rem y/n prompt. The prompt string is echoed unconditionally so it appears even on the rem auto-decline path. exit 0 = accepted, exit 1 = declined. echo. echo *** WARNING: System Python Execution *** echo *** Using global system Python may pollute shared packages. *** echo. echo Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline. set "HP_SYSCON_CHOICE=" if defined HP_TEST_SYSCON_ANSWER ( set "HP_SYSCON_CHOICE=%HP_TEST_SYSCON_ANSWER%" ) else if defined HP_CI_LANE ( set "HP_SYSCON_CHOICE=n" ) else ( set /p "HP_SYSCON_CHOICE=Your choice [y/n]: " ) if "%HP_SYSCON_CHOICE:~0,1%"=="" ( call :log "[INFO] REQ-014: System Python consent: empty input; declining." exit /b 1 ) if /I "%HP_SYSCON_CHOICE:~0,1%"=="y" ( call :log "[INFO] REQ-014: System Python consent: user accepted." exit /b 0 ) call :log "[INFO] REQ-014: System Python consent: user declined." exit /b 1 :hp_test_conda_fail call :log "[TEST] HP_TEST_FORCE_CONDA_FAIL: simulating conda env creation failure." set "HP_ENV_READY=" call :handle_conda_failure "[TEST] conda env create forced to fail." if defined HP_ENV_READY goto :after_env_mode_selection call :die "[ERROR] conda env create failed." goto :after_env_mode_selection