From 0e657e7ee91ea8ec7a642be2cf458fdf6c1e1b7b Mon Sep 17 00:00:00 2001 From: John Date: Mon, 15 Jun 2026 13:12:48 +0800 Subject: [PATCH 1/4] Fix release build dependency verification --- .github/workflows/build-and-release.yml | 82 +++++++++++-- build.bat | 22 +++- history.md | 148 ++++++++++++++++++++++++ p27/SHA256SUMS.txt | 16 +++ p27/SOURCES.md | 49 ++++++++ p27/test_verify_sha256.ps1 | 62 ++++++++++ p27/verify_sha256.ps1 | 82 +++++++++++++ 7 files changed, 450 insertions(+), 11 deletions(-) create mode 100644 history.md create mode 100644 p27/SHA256SUMS.txt create mode 100644 p27/SOURCES.md create mode 100644 p27/test_verify_sha256.ps1 create mode 100644 p27/verify_sha256.ps1 diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index c7b385f..f650b02 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -21,6 +21,12 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + + - name: Verify vendored Python dependencies + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File p27\verify_sha256.ps1 - name: Setup MSBuild uses: microsoft/setup-msbuild@v2 @@ -28,60 +34,116 @@ jobs: - name: Install Python 2.7 shell: pwsh run: | + $ErrorActionPreference = "Stop" echo "[INFO] Installing Python 2.7.13 MSI..." - Start-Process msiexec.exe -ArgumentList "/i p27\python-2.7.13.msi /qn ALLUSERS=1 ADDLOCAL=ALL LIMITUI=1" -Wait + $pythonMsi = Resolve-Path "p27\python-2.7.13.msi" + $args = "/i `"$pythonMsi`" /qn ALLUSERS=1 ADDLOCAL=ALL LIMITUI=1" + $proc = Start-Process msiexec.exe -ArgumentList $args -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "Python 2.7 MSI install failed with exit code $($proc.ExitCode)." + } + if (-not (Test-Path "C:\Python27\python.exe")) { + throw "Python 2.7 install completed but C:\Python27\python.exe was not found." + } + & C:\Python27\python.exe --version echo "C:\Python27" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo "C:\Python27\Scripts" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Install PyGTK shell: pwsh run: | + $ErrorActionPreference = "Stop" echo "[INFO] Installing PyGTK MSI..." - Start-Process msiexec.exe -ArgumentList "/i p27\pygtk-all-in-one-2.24.1.win32-py2.7.msi /qn" -Wait + $pygtkMsi = Resolve-Path "p27\pygtk-all-in-one-2.24.1.win32-py2.7.msi" + $pygtkLog = Join-Path $env:RUNNER_TEMP "pygtk-install.log" + # PyGTK all-in-one skips Python auto-detection in quiet mode, so TARGETDIR is required. + $args = "/i `"$pygtkMsi`" /qn ALLUSERS=1 TARGETDIR=C:\Python27 /L*V `"$pygtkLog`"" + $proc = Start-Process msiexec.exe -ArgumentList $args -Wait -PassThru + if ($proc.ExitCode -ne 0) { + if (Test-Path $pygtkLog) { + Get-Content $pygtkLog -Tail 80 + } + throw "PyGTK MSI install failed with exit code $($proc.ExitCode)." + } + & C:\Python27\python.exe -c "import gtk, gobject, pango; print('PyGTK import OK')" - name: Install pywin32 & pyHook shell: pwsh run: | + $ErrorActionPreference = "Stop" echo "[INFO] Extracting pywin32..." 7z x p27\pywin32-221.win32-py2.7.exe -opywin32_tmp + if ($LASTEXITCODE -ne 0) { throw "Failed to extract pywin32." } Copy-Item -Path pywin32_tmp\PLATLIB\* -Destination C:\Python27\Lib\site-packages\ -Recurse -Force Copy-Item -Path pywin32_tmp\SCRIPTS\* -Destination C:\Python27\Scripts\ -Recurse -Force echo "[INFO] Extracting pyHook..." 7z x p27\pyHook-1.5.1.win32-py2.7.exe -opyhook_tmp + if ($LASTEXITCODE -ne 0) { throw "Failed to extract pyHook." } Copy-Item -Path pyhook_tmp\PLATLIB\* -Destination C:\Python27\Lib\site-packages\ -Recurse -Force + & C:\Python27\python.exe -c "import win32api, win32gui, pythoncom, pyHook; print('pywin32/pyHook import OK')" - name: Bootstrap pip and install dependencies shell: pwsh run: | - python p27\get-pip.py + $ErrorActionPreference = "Stop" + python -m ensurepip --default-pip + if ($LASTEXITCODE -ne 0) { throw "ensurepip failed." } + python -m pip install --no-index p27\pip-20.3b1-py2.py3-none-any.whl + if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed." } # Install whl packages - pip install p27\altgraph-0.17.4-py2.py3-none-any.whl - pip install p27\macholib-1.16.3-py2.py3-none-any.whl - pip install p27\configparser-4.0.2-py2.py3-none-any.whl - pip install p27\future-0.18.0-cp27-none-any.whl - pip install p27\psutil-6.1.1-cp27-none-win32.whl - pip install p27\PyAudio-0.2.11-cp27-cp27m-win32.whl - pip install p27\pywin32_ctypes-0.2.0-py2.py3-none-any.whl + python -m pip install --no-index p27\altgraph-0.17.4-py2.py3-none-any.whl + python -m pip install --no-index p27\macholib-1.16.3-py2.py3-none-any.whl + python -m pip install --no-index p27\configparser-4.0.2-py2.py3-none-any.whl + python -m pip install --no-index p27\future-0.18.0-cp27-none-any.whl + python -m pip install --no-index p27\psutil-6.1.1-cp27-none-win32.whl + python -m pip install --no-index p27\PyAudio-0.2.11-cp27-cp27m-win32.whl + python -m pip install --no-index p27\pywin32_ctypes-0.2.0-py2.py3-none-any.whl # Install pefile 7z x p27\pefile-2017.8.1.zip -opefile_tmp + if ($LASTEXITCODE -ne 0) { throw "Failed to extract pefile." } pushd pefile_tmp\pefile-2017.8.1 python setup.py install + if ($LASTEXITCODE -ne 0) { throw "pefile install failed." } popd # Install PyInstaller 3.4 tar -xzf p27\PyInstaller-3.4.tar.gz + if ($LASTEXITCODE -ne 0) { throw "Failed to extract PyInstaller." } pushd PyInstaller-3.4 python setup.py install + if ($LASTEXITCODE -ne 0) { throw "PyInstaller install failed." } popd + python -c "import gtk, gobject, pango, win32api, pythoncom, pyHook, pyaudio, psutil; print('Build imports OK')" - name: Build UCL_LIU shell: cmd run: | build.bat + - name: Verify packaged executable contents + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $exe = "dist\uclliu.exe" + if (-not (Test-Path $exe)) { + throw "Packaged executable not found: $exe" + } + $bytes = [System.IO.File]::ReadAllBytes($exe) + $text = [System.Text.Encoding]::GetEncoding(28591).GetString($bytes) + foreach ($needle in @("gtk", "gobject", "pango", "pyHook", "win32api", "pythoncom")) { + if (-not $text.Contains($needle)) { + throw "Packaged executable is missing expected PyInstaller entry: $needle" + } + } + Get-Item $exe | Select-Object FullName, Length, LastWriteTime + if (Test-Path "build\uclliu\warn-uclliu.txt") { + echo "[INFO] PyInstaller warnings:" + Get-Content "build\uclliu\warn-uclliu.txt" + } + - name: Package Release Assets shell: pwsh run: | diff --git a/build.bat b/build.bat index c7b30c0..bbf7f56 100644 --- a/build.bat +++ b/build.bat @@ -1,11 +1,31 @@ +@echo off +setlocal chcp 950 -rd /S build +if exist build rd /S /Q build SET VS90COMNTOOLS=%VS140COMNTOOLS% :: Build TSF Bridge first call build_tsf.bat +if errorlevel 1 ( + echo [ERROR] TSF Bridge build failed. + exit /b 1 +) :: Build Main EXE +if not exist c:\python27\scripts\pyinstaller.exe ( + echo [ERROR] PyInstaller not found: c:\python27\scripts\pyinstaller.exe + exit /b 1 +) c:\python27\scripts\pyinstaller -F -w --onefile --clean --icon="pic\uclliu_logo.ico" --version-file=metadata.txt --exclude-module=_ssl --exclude-module=_bz2 --exclude-module=_lzma --exclude-module=pyconfig uclliu.pyw +if errorlevel 1 ( + echo [ERROR] PyInstaller build failed. + exit /b 1 +) +if not exist dist\uclliu.exe ( + echo [ERROR] dist\uclliu.exe was not created. + exit /b 1 +) echo [OK] All components built. +endlocal +exit /b 0 diff --git a/history.md b/history.md new file mode 100644 index 0000000..566de31 --- /dev/null +++ b/history.md @@ -0,0 +1,148 @@ +# Chat History + +## 2026-05-07 + +- 使用者回報 `Explorer.EXE` 很容易 crash。 +- Windows 事件內容指出 faulting module 是 `D:\GD\UCL_LIU\dist\tsf_bridge\x64\UclTsfBridge.dll`,例外狀況代碼 `0xc0000005`,錯誤位移 `0x0000000000002c8c`。 +- 初步方向:檢查 TSF bridge 的 C++ COM/TSF lifecycle、pipe thread 停止邏輯、hidden window `SendMessage` race、以及 DLL unload/Deactivate 行為。 +- 已修正 `tsf_bridge/UclTsfBridge/TextService.cpp`:`Deactivate()` 會停止 pipe server、避免透過裸全域 active pointer 呼叫可能失效的 service、補上 hidden window message 參數檢查,並整理 pipe thread handle 生命週期。 +- 已修正 `build_tsf.bat`:copy DLL 或 bat 失敗時會回報錯誤並停止,不再顯示誤導性的 `[OK]`。 +- 建置 `Release|x64` 與 `Release|Win32` 皆成功且 0 warning/0 error。`dist\tsf_bridge\x64\UclTsfBridge.dll` 因被多個程序載入而無法覆蓋;新的 x64 DLL 在 `tsf_bridge\UclTsfBridge\x64\Release\UclTsfBridge.dll`。 +- 使用者後續回報 `LINE.exe` crash,faulting module 仍是 `dist\tsf_bridge\x64\UclTsfBridge.dll`,錯誤位移仍是 `0x0000000000002c8c`,例外代碼 `0xc000041d`。判斷與 Explorer crash 是同一顆舊版 TSF DLL 的同一類問題。 + +## 2026-05-07(續) + +- 使用者回報新的 `LINE.exe` crash:faulting module `dist\tsf_bridge\x64\UclTsfBridge.dll`,例外代碼 `0xc000041d`,錯誤**新位移** `0x0000000000002e9c`(比上次 `0x2c8c` 多約 0x210 bytes)。 +- 觸發情境:LINE 開啟選檔對話框(IFileOpenDialog)時,於搜尋欄用 UCL LIU 輸入法打字出字。 +- 根本原因分析: + 1. **主要**:file picker dialog 的 ITfContext 不支援 `TF_ES_READWRITE | TF_ES_SYNC`,`CommitText` 退回 async `TF_ES_READWRITE`。使用者選擇檔案後 dialog 關閉,TSF 釋放 context,但 async `DoEditSession` 仍被 dispatch → 存取已釋放的 context → AV → 例外從 COM callback 逃出 → `0xc000041d`。 + 2. **次要**:`StopPipeServer` 用 `WaitForSingleObject`,若 pipe thread 同時在 `SendMessageW` 等 UI thread 抽訊息則死結。 +- 已修正 `tsf_bridge/UclTsfBridge/TextService.cpp`: + - `DoEditSession` 加 `__try/__except(EXCEPTION_EXECUTE_HANDLER)`(主要修正) + - `WndProc` 加 `__try/__except(EXCEPTION_EXECUTE_HANDLER)` + - `CommitText` async fallback 移除謊報的 `sessionResult = S_OK` + - `StopPipeServer` 改用 `MsgWaitForMultipleObjects` + `PeekMessage` 迴圈(防止死結) + - `Deactivate` 中 `DestroyWindow` 移到 `StopPipeServer` 之後(確保 pipe thread 停止後再摧毀視窗) +- 建置 Release|x64 與 Release|Win32 皆成功,0 warning / 0 error。新 DLL 在 `tsf_bridge\UclTsfBridge\x64\Release\UclTsfBridge.dll` 與 `Win32\Release\`。`dist\tsf_bridge\x64\UclTsfBridge.dll` 因被程序載入仍無法覆蓋,需關閉 LINE 後手動複製。 + +## 2026-05-07(再續) + +- 使用者指出 TSF DLL 在 Explorer、檔案選擇對話框、Explorer 搜尋框/路徑欄等 shell TSF host 中,`pipe thread + SendMessageW + TF_ES_SYNC edit session` 仍有 crash/deadlock 風險。 +- 已修正 `tsf_bridge/UclTsfBridge/TextService.cpp`: + - 新增高風險 host process 排除:`explorer.exe`、`OpenWith.exe`、`PickerHost.exe`、`FilePicker.exe`、`FileOpenPicker.exe`、`SearchHost.exe`、`ShellExperienceHost.exe`、`StartMenuExperienceHost.exe` 不啟動 PipeServer。 + - 新增 foreground/input guard:commit 前確認前景視窗屬於目前 process,且不是 Explorer/file dialog shell window;LINE 內的 IFileOpenDialog 這類 `#32770` + shell child window 也會被擋下。 + - `commit_text` 從 `SendMessageW` 改為 `PostMessageW`,pipe thread 不再同步等待 TSF/UI thread;回應改為 `{"ok":true,"queued":true}`。 + - `WM_UCL_COMMIT` 改為接收 heap 配置的 `std::wstring*`,處理後釋放,實際 `CommitText()` 仍在 TSF thread 執行。 +- 原樣建置因本機缺少專案指定的 `v145` toolset 失敗於 MSB8020;用命令列臨時覆寫 `/p:PlatformToolset=v143` 驗證 Release|x64 與 Release|Win32 皆成功,0 warning / 0 error。 + +## 2026-05-07(解除 DLL file lock) + +- 使用者希望 unregister 後不用每次登出才能覆蓋 `UclTsfBridge.dll`。 +- 已新增 `tsf_bridge\unlock_tsf_bridge.ps1` 與 `dist\tsf_bridge\unlock_tsf_bridge.ps1`: + - 掃描目前載入 `UclTsfBridge.dll` 的程序,列出 process name / PID / module path。 + - 對非核心程序逐一詢問是否關閉;先嘗試 graceful close,必要時再二次詢問是否 force terminate。 + - `explorer.exe` 走確認後重啟 Explorer 的流程。 + - 核心程序、helper/regsvr32/uclliu/python 類程序會跳過,不主動終止。 +- 已更新 `tsf_bridge\unregister_tsf_bridge.bat` 與 dist 版: + - `regsvr32 /u` 會檢查錯誤碼。 + - unregister 成功後呼叫 `unlock_tsf_bridge.ps1`,協助釋放 file lock。 +- 已更新 `build_tsf.bat`:會把 `unlock_tsf_bridge.ps1` 複製到 `dist\tsf_bridge`。 +- 已更新 `uclliu.pyw`: + - GUI unregister 改成同步執行 `regsvr32 /u /s`,能知道真正成敗。 + - unregister 成功後啟動 PowerShell helper,逐一處理鎖定 DLL 的程序。 + - `tsf_bridge_get_dll_path()` 會跳過 x86/Win32 DLL,避免 64-bit `regsvr32` 拿錯檔。 +- 驗證: + - PowerShell parser 檢查 source/dist helper 皆通過。 + - 本機沒有可用 Python 2 runtime,因此未能對 `uclliu.pyw` 做 Python 2 語法編譯檢查。 + - 目前仍載入舊 dist x64 DLL 的程序包含 `Code`、`explorer`、`LINE`、`msedgewebview2`、`Widgets`。 + +## 2026-05-07(修正 TSF build 工具鏈混版) + +- 使用者回報 Win32 link 階段失敗:`LINK : fatal error LNK1101: MSPDB140.DLL 版本不正確`。 +- 原因方向:`build_tsf.bat` 原本優先找 `%ProgramFiles%\Microsoft Visual Studio\18\Community\MSBuild...`,本機同時有 VS 18、VS 2022、VS 2019/2017 Build Tools,容易讓 MSBuild/toolset/PDB server 混版。 +- 已更新 `build_tsf.bat`: + - 移除 VS 18 優先路徑。 + - 優先使用 VS 2022,再 fallback VS 2019。 + - `vswhere` 搜尋限制為 `[16.0,18.0)`,避免抓到 VS 18。 + - 建置時覆寫 `/p:PlatformToolset=v143` 與 `/p:PreferredToolArchitecture=x64`,並加 `/nr:false`。 + - 建置前 `taskkill /IM mspdbsrv.exe /F`,避免舊 PDB server 造成 LNK1101。 +- 驗證:使用 VS2022 MSBuild + `PlatformToolset=v143` 分別建置 `Release|Win32` 與 `Release|x64` 皆成功,0 warning / 0 error。 + +## 2026-05-07(build copy 時自動解除 DLL lock) + +- 使用者回報 build 成功後 copy 到 `dist\tsf_bridge\x64\UclTsfBridge.dll` 失敗:檔案正由另一個程序使用。 +- 已更新 `build_tsf.bat`: + - 新增 `:CopyWithUnlock` subroutine。 + - x64/x86 DLL copy 第一次失敗時,會呼叫 `tsf_bridge\unlock_tsf_bridge.ps1`。 + - helper 會列出載入 `UclTsfBridge.dll` 的程序並逐一詢問是否關閉/重啟;完成後 build script 會自動 retry copy。 + - 若 retry 仍失敗,才停止並提示關閉剩餘程序後重跑。 +- 未直接執行完整 `build_tsf.bat`,避免互動式 helper 在目前工作階段中關閉使用者的 Code/LINE/Explorer。 +- 使用者執行 unregister 時遇到 `'""' is not recognized as an internal or external command`: + - 原因是 `unregister_tsf_bridge.bat` 在 `if exist "%UNLOCK_SCRIPT%" (...)` 區塊內才設定 `PS_EXE`,但 `%PS_EXE%` 會在整個區塊解析前展開,導致命令變成空字串。 + - 已修正 source/dist 兩份 `unregister_tsf_bridge.bat`:將 `PS_EXE` 設定移到 `if` 區塊外。 + +## 2026-05-07(TSF 修正確認) + +- 使用者回覆「看來可以了」,目前 TSF build / unregister / DLL lock 相關修正看起來已可接受。 + +## 2026-05-08(補上 VS 2026 TSF build) + +- 使用者要求檢查 `.bat`,並加上 VS 2026 的編譯方式。 +- 已更新 `build_tsf.bat`: + - 優先偵測 VS 2026 / VS 18 的 MSBuild。 + - VS 2026 使用 `/p:PlatformToolset=v145`。 + - 找不到 VS 2026 時再 fallback 到 VS 2022/2019,維持 `/p:PlatformToolset=v143`。 + - `vswhere` 先掃 `[18.0,19.0)`,再掃舊的 `[16.0,18.0)`。 +- 驗證:本機抓到 `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\amd64\MSBuild.exe`,以 `v145` 成功建置 TSF Bridge Release x64/Win32,0 warning / 0 error。 + +## 2026-06-15(最近開發現況盤點) + +- 目前 `master` / `origin/master` 停在 `531f967`、tag `v1.67`:新增 GitHub Actions 自動建置/發布流程,並更新 README release automation 說明。 +- 近期核心開發脈絡仍是 TSF Bridge:修 Explorer/LINE/file picker crash、避免 shell host 中啟動 pipe server、改用 `PostMessageW` 排入 TSF thread commit、改善 unregister 後 DLL file lock 解除,以及 `build_tsf.bat` 的 VS 2026 / VS 18 `v145` 與 VS2022/2019 `v143` fallback。 +- 工作區目前有未提交輸出檔:`dist\uclliu.exe`、`dist\tsf_bridge\x64\UclTsfBridge.dll`、`dist\tsf_bridge\x86\UclTsfBridge.dll`;`dist\pinyi.txt` 雖被 `git status` 標成 modified,但 blob hash 與 `HEAD` 相同,內容未變。 +- 未追蹤檔包含 `.claude\settings.local.json`、`Web.config`、`dist\README.md`、`uclliu.zip`、`p27\PyInstaller-3.2.zip` 與解開的 `p27\PyInstaller-3.2\`。 +- 潛在 CI 風險:`.github\workflows\build-and-release.yml` 會執行 `python p27\get-pip.py`,但 `p27\get-pip.py` 目前是未追蹤檔;若遠端 repo 沒有此檔,GitHub Actions 會在 bootstrap pip 階段失敗。 + +## 2026-06-15(修正 GitHub Actions release exe 啟動失敗) + +- 使用者回報 GitHub Actions 發布的 `v1.67` release exe 啟動後跳出 `Fatal error detected: Failed to execute script uclliu`。 +- 檢查 GitHub Actions run `27523920946`:job 顯示 success,但 log 中 `python p27\get-pip.py` 實際失敗,因 PowerShell 未 stop-on-error 被吞掉;PyInstaller 仍繼續產出 `dist\uclliu.exe`。 +- 下載/檢查 release 產物:`uclliu.exe` 只有 5,463,548 bytes,且二進位字串可找到 `pyHook` / `win32api` / `pythoncom`,但找不到 `gtk` / `gobject` / `pango`;判斷 PyGTK 沒有進入 PyInstaller bundle。 +- 根因:workflow 用 `msiexec /qn` 安裝 `pygtk-all-in-one-2.24.1.win32-py2.7.msi`,但 PyGTK all-in-one 在 quiet mode 會跳過 Python 自動偵測,必須指定 `TARGETDIR=C:\Python27`;原 workflow 也沒有檢查 MSI exit code 或 import 結果。 +- 已更新 `.github\workflows\build-and-release.yml`: + - Python / PyGTK MSI 安裝改用 `-PassThru` 檢查 exit code。 + - PyGTK 安裝指定 `TARGETDIR=C:\Python27`,並立即驗證 `import gtk, gobject, pango`。 + - 移除對未追蹤 `p27\get-pip.py` 的依賴,改用 `ensurepip` 與 repo 內的 `pip-20.3b1` wheel。 + - 依賴安裝後驗證 `gtk/gobject/pango/win32api/pythoncom/pyHook/pyaudio/psutil`。 + - 打包後檢查 exe 內含 `gtk/gobject/pango/pyHook/win32api/pythoncom` 字串,避免缺 PyGTK 的 exe 被發布。 +- 已更新 `build.bat`:補上 `setlocal`、安全清理 build 目錄、TSF build / PyInstaller build / `dist\uclliu.exe` 產出檢查,避免 CI 繼續吞錯。 +- 本機未完整重跑 build:目前本機沒有完整 `C:\Python27\python.exe`;已用 release exe 二進位檢查確認壞版缺 `gtk/gobject/pango`。 + +## 2026-06-15(GitHub Actions p27 依賴 SHA256 gate) + +- 使用者決定走「SHA256 安全來源方案」,不在 CI 內重新從原始碼編譯所有 Python 2.7 legacy dependencies。 +- 已新增 `p27\SHA256SUMS.txt`:只列 `.github\workflows\build-and-release.yml` 實際 release build 會用到的 14 個檔案。 +- 已新增 `p27\verify_sha256.ps1`: + - 預設驗證 `p27\SHA256SUMS.txt` 內的檔案。 + - 缺檔、hash 不合、manifest 格式錯誤、或路徑逃出 base directory 都會 fail。 +- 已新增 `p27\test_verify_sha256.ps1`:用暫存檔測試 valid manifest pass、hash mismatch fail、missing file fail。 +- 已新增 `p27\SOURCES.md`:記錄 release build gate 內每個 vendored dependency 的 expected source、workflow 用途,以及是否會執行 MSI/installer。 +- 已更新 `.github\workflows\build-and-release.yml`:在 `Checkout repository` 後立即執行 `p27\verify_sha256.ps1`,所有 MSI/EXE/whl/tar/zip 被使用前先驗證 hash。 +- 驗證: + - `p27\test_verify_sha256.ps1` 先在 verifier 不存在時失敗,再完成實作後通過。 + - `p27\verify_sha256.ps1` 對目前 `p27` 內 14 個 release build dependency 全部通過。 + +## 2026-06-15(p27 原始收集檔來源複核) + +- 使用者詢問原本收集的 MSI/EXE/whl/zip/tar/gz 是否安全。 +- 已重新比對 release build gate 內檔案: + - `python-2.7.13.msi` 對上 python.org 官方下載,且 Authenticode 簽章有效(Python Software Foundation)。 + - `pygtk-all-in-one-2.24.1.win32-py2.7.msi` 對上 GNOME 下載站;檔案本身未簽章。 + - `pywin32-221.win32-py2.7.exe` 對上 SourceForge pywin32 Build 221;檔案本身未簽章,但 workflow 只用 7-Zip 解包、不執行 installer。 + - `pyHook-1.5.1.win32-py2.7.exe` 對上 SourceForge pyHook 1.5.1;檔案本身未簽章,但 workflow 只用 7-Zip 解包、不執行 installer。 + - PyPI 發布物皆對上 PyPI JSON 內的 SHA256 digest。 +- 已額外複核未納入 release build gate 的本機收集檔: + - `get-pip.py` 對上 PyPA `get-pip` 固定 commit `049c52c665e8c5fd1751f942316e0a5c777d304f`。 + - `PyInstaller-3.2.zip`、`dis3-0.1.3-py2-none-any.whl`、`psutil-5.8.0-cp27-none-win32.whl`、`setuptools-44.1.1-py2.py3-none-any.whl` 對上 PyPI digest。 + - `VCForPython27.msi` Authenticode 簽章有效(Microsoft Corporation)。 +- 判斷:原本收集的主要檔案來源可信度足夠;但未簽章的 legacy MSI/EXE 仍須維持固定 SHA256 gate,不應在 CI 內任意更新或改抓 latest。 diff --git a/p27/SHA256SUMS.txt b/p27/SHA256SUMS.txt new file mode 100644 index 0000000..d90e9c0 --- /dev/null +++ b/p27/SHA256SUMS.txt @@ -0,0 +1,16 @@ +# SHA256 manifest for GitHub Actions release-build dependencies. +# Format: +44ea95356365195b18a5058796285789b0bfc94da1ee2ec1cb8e0a1c2ff6017a python-2.7.13.msi +db7668807a6b13d42996af079099de590307165813e68af5dab230b8e067edc8 pygtk-all-in-one-2.24.1.win32-py2.7.msi +db5891307c325c643f863321b61a6a3e8d8afe84ba65e65dcefe8ab6bf762183 pywin32-221.win32-py2.7.exe +3db9fa9adc45703589b93df05aab77bdabe985a17565b465a9e550585f85322a pyHook-1.5.1.win32-py2.7.exe +122fcd82deac1153c1699f29815bfab3f876e5bbe018cc2df1297f9802572a97 pip-20.3b1-py2.py3-none-any.whl +642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff altgraph-0.17.4-py2.py3-none-any.whl +0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c macholib-1.16.3-py2.py3-none-any.whl +254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c configparser-4.0.2-py2.py3-none-any.whl +2bc7f66fcde8d42e956143fadda385bd022d81940adf3be2085cdbd5fc42d833 future-0.18.0-cp27-none-any.whl +6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac psutil-6.1.1-cp27-none-win32.whl +f78d543a98b730e64621ebf7f3e2868a79ade0a373882ef51c0293455ffa8e6e PyAudio-0.2.11-cp27-cp27m-win32.whl +9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 pywin32_ctypes-0.2.0-py2.py3-none-any.whl +cf914a82f5ee5e73cc2cd2ad5602b2b78dd45589c04ca4fe31516dfd9436c96c pefile-2017.8.1.zip +a5a6e04a66abfcf8761e89a2ebad937919c6be33a7b8963e1a961b55cb35986b PyInstaller-3.4.tar.gz diff --git a/p27/SOURCES.md b/p27/SOURCES.md new file mode 100644 index 0000000..e16f955 --- /dev/null +++ b/p27/SOURCES.md @@ -0,0 +1,49 @@ +# Vendored Python 2.7 Dependency Sources + +This directory contains legacy Windows build dependencies used by GitHub Actions. +The release workflow verifies the files listed in `SHA256SUMS.txt` before running +any MSI/EXE installer or extracting any archive. + +To rotate a dependency: + +1. Download it from the expected upstream source. +2. Verify the vendor/project page and file name manually. +3. Replace the file in `p27`. +4. Recompute SHA256 and update `SHA256SUMS.txt`. +5. Run `pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File p27\test_verify_sha256.ps1`. +6. Run `pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File p27\verify_sha256.ps1`. + +## Release Build Gate + +Only files in this table are used by `.github/workflows/build-and-release.yml` +and enforced by `SHA256SUMS.txt`. + +| File | Expected source | Workflow use | Runs code | +| --- | --- | --- | --- | +| `python-2.7.13.msi` | `https://www.python.org/ftp/python/2.7.13/python-2.7.13.msi` | Installs Python 2.7 x86 to `C:\Python27`. | Yes, MSI | +| `pygtk-all-in-one-2.24.1.win32-py2.7.msi` | `https://download.gnome.org/binaries/win32/pygtk/2.24/pygtk-all-in-one-2.24.1.win32-py2.7.msi` | Installs GTK/PyGTK/PyGObject/PyCairo into `C:\Python27`. | Yes, MSI | +| `pywin32-221.win32-py2.7.exe` | `https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/pywin32-221.win32-py2.7.exe/download` | Extracted with 7-Zip; `PLATLIB` and `SCRIPTS` are copied into `C:\Python27`. | No installer execution | +| `pyHook-1.5.1.win32-py2.7.exe` | `https://sourceforge.net/projects/pyhook/files/pyhook/1.5.1/pyHook-1.5.1.win32-py2.7.exe/download` | Extracted with 7-Zip; `PLATLIB` is copied into `C:\Python27`. | No installer execution | +| `pip-20.3b1-py2.py3-none-any.whl` | PyPI `pip` 20.3 beta 1 | Installed with `python -m pip install --no-index`. | Python package install | +| `altgraph-0.17.4-py2.py3-none-any.whl` | PyPI `altgraph` 0.17.4 | Installed with `python -m pip install --no-index`. | Python package install | +| `macholib-1.16.3-py2.py3-none-any.whl` | PyPI `macholib` 1.16.3 | Installed with `python -m pip install --no-index`. | Python package install | +| `configparser-4.0.2-py2.py3-none-any.whl` | PyPI `configparser` 4.0.2 | Installed with `python -m pip install --no-index`. | Python package install | +| `future-0.18.0-cp27-none-any.whl` | PyPI `future` 0.18.0 | Installed with `python -m pip install --no-index`. | Python package install | +| `psutil-6.1.1-cp27-none-win32.whl` | PyPI `psutil` 6.1.1 | Installed with `python -m pip install --no-index`. | Python package install | +| `PyAudio-0.2.11-cp27-cp27m-win32.whl` | PyPI `PyAudio` 0.2.11 | Installed with `python -m pip install --no-index`. | Python package install | +| `pywin32_ctypes-0.2.0-py2.py3-none-any.whl` | PyPI `pywin32-ctypes` 0.2.0 | Installed with `python -m pip install --no-index`. | Python package install | +| `pefile-2017.8.1.zip` | PyPI `pefile` 2017.8.1 source archive | Extracted with 7-Zip, then installed with `python setup.py install`. | Python setup install | +| `PyInstaller-3.4.tar.gz` | PyPI `PyInstaller` 3.4 source archive | Extracted with tar, then installed with `python setup.py install`. | Python setup install | + +## Not Part Of The Release Build Gate + +The workflow does not currently use these files, so they are intentionally not +listed in `SHA256SUMS.txt`: + +- `get-pip.py`: matches PyPA `get-pip` commit `049c52c665e8c5fd1751f942316e0a5c777d304f`. +- `PyInstaller-3.2.zip`: matches PyPI `PyInstaller` 3.2. +- `dis3-0.1.3-py2-none-any.whl`: matches PyPI `dis3` 0.1.3. +- `psutil-5.8.0-cp27-none-win32.whl`: matches PyPI `psutil` 5.8.0. +- `setuptools-44.1.1-py2.py3-none-any.whl`: matches PyPI `setuptools` 44.1.1. +- `VCForPython27.msi`: has a valid Microsoft Authenticode signature. +- unpacked source/build helper directories under `p27` diff --git a/p27/test_verify_sha256.ps1 b/p27/test_verify_sha256.ps1 new file mode 100644 index 0000000..7c4299a --- /dev/null +++ b/p27/test_verify_sha256.ps1 @@ -0,0 +1,62 @@ +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent (Split-Path -Parent $PSCommandPath) +$verifier = Join-Path $repoRoot "p27\verify_sha256.ps1" +$caseRoot = Join-Path $env:TEMP ("uclliu-sha256-test-" + [guid]::NewGuid().ToString("N")) + +function New-TestFile { + param( + [Parameter(Mandatory = $true)][string]$Path, + [Parameter(Mandatory = $true)][string]$Content + ) + $dir = Split-Path -Parent $Path + if (-not (Test-Path $dir)) { + New-Item -ItemType Directory -Path $dir | Out-Null + } + [System.IO.File]::WriteAllText($Path, $Content, [System.Text.Encoding]::ASCII) +} + +function Invoke-Verifier { + param( + [Parameter(Mandatory = $true)][string]$ManifestPath, + [Parameter(Mandatory = $true)][string]$BasePath + ) + & pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File $verifier -ManifestPath $ManifestPath -BasePath $BasePath *> $null + return $LASTEXITCODE +} + +function Assert-Equal { + param( + [Parameter(Mandatory = $true)]$Actual, + [Parameter(Mandatory = $true)]$Expected, + [Parameter(Mandatory = $true)][string]$Message + ) + if ($Actual -ne $Expected) { + throw "$Message Expected <$Expected>, got <$Actual>." + } +} + +try { + New-Item -ItemType Directory -Path $caseRoot | Out-Null + + $validBase = Join-Path $caseRoot "valid" + $validFile = Join-Path $validBase "nested\ok.txt" + New-TestFile -Path $validFile -Content "known content" + $validHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $validFile).Hash.ToLowerInvariant() + $validManifest = Join-Path $caseRoot "valid.sha256" + New-TestFile -Path $validManifest -Content "$validHash nested/ok.txt`r`n" + Assert-Equal -Actual (Invoke-Verifier -ManifestPath $validManifest -BasePath $validBase) -Expected 0 -Message "Valid manifest should pass." + + $badHashManifest = Join-Path $caseRoot "bad-hash.sha256" + New-TestFile -Path $badHashManifest -Content ("{0} nested/ok.txt`r`n" -f ("0" * 64)) + Assert-Equal -Actual (Invoke-Verifier -ManifestPath $badHashManifest -BasePath $validBase) -Expected 1 -Message "Hash mismatch should fail." + + $missingManifest = Join-Path $caseRoot "missing.sha256" + New-TestFile -Path $missingManifest -Content "$validHash nested/missing.txt`r`n" + Assert-Equal -Actual (Invoke-Verifier -ManifestPath $missingManifest -BasePath $validBase) -Expected 1 -Message "Missing file should fail." + + Write-Host "[OK] verify_sha256 tests passed." +} +finally { + Remove-Item -LiteralPath $caseRoot -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/p27/verify_sha256.ps1 b/p27/verify_sha256.ps1 new file mode 100644 index 0000000..0b1a8fe --- /dev/null +++ b/p27/verify_sha256.ps1 @@ -0,0 +1,82 @@ +param( + [string]$ManifestPath = (Join-Path $PSScriptRoot "SHA256SUMS.txt"), + [string]$BasePath = $PSScriptRoot +) + +$ErrorActionPreference = "Stop" +$hadFailure = $false +$checkedCount = 0 + +function Report-Failure { + param([Parameter(Mandatory = $true)][string]$Message) + Write-Host "[ERROR] $Message" + $script:hadFailure = $true +} + +function Resolve-BasePath { + param([Parameter(Mandatory = $true)][string]$Path) + if (-not (Test-Path -LiteralPath $Path -PathType Container)) { + throw "Base path does not exist: $Path" + } + return [System.IO.Path]::GetFullPath((Resolve-Path -LiteralPath $Path).ProviderPath).TrimEnd("\") +} + +if (-not (Test-Path -LiteralPath $ManifestPath -PathType Leaf)) { + Write-Host "[ERROR] SHA256 manifest not found: $ManifestPath" + exit 1 +} + +$baseFullPath = Resolve-BasePath -Path $BasePath +$basePrefix = $baseFullPath + "\" +$manifestLines = @(Get-Content -LiteralPath $ManifestPath) + +for ($i = 0; $i -lt $manifestLines.Count; $i++) { + $lineNumber = $i + 1 + $line = $manifestLines[$i].Trim() + + if ($line -eq "" -or $line.StartsWith("#")) { + continue + } + + $match = [regex]::Match($line, "^(?[A-Fa-f0-9]{64})\s+\*?(?.+?)\s*$") + if (-not $match.Success) { + Report-Failure "Invalid manifest line $lineNumber`: $line" + continue + } + + $expectedHash = $match.Groups["hash"].Value.ToLowerInvariant() + $relativePath = $match.Groups["path"].Value.Replace("/", "\") + + if ([System.IO.Path]::IsPathRooted($relativePath)) { + Report-Failure "Manifest line $lineNumber uses an absolute path: $relativePath" + continue + } + + $fullPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($baseFullPath, $relativePath)) + if (-not $fullPath.StartsWith($basePrefix, [System.StringComparison]::OrdinalIgnoreCase)) { + Report-Failure "Manifest line $lineNumber escapes the base path: $relativePath" + continue + } + + if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { + Report-Failure "Missing file listed in manifest line $lineNumber`: $relativePath" + continue + } + + $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $fullPath).Hash.ToLowerInvariant() + if ($actualHash -ne $expectedHash) { + Report-Failure "SHA256 mismatch for $relativePath. Expected $expectedHash, got $actualHash." + continue + } + + $checkedCount++ + Write-Host "[OK] $relativePath" +} + +if ($hadFailure) { + Write-Host "[ERROR] SHA256 verification failed." + exit 1 +} + +Write-Host "[OK] Verified $checkedCount files from $ManifestPath." +exit 0 From 7e0a513d57f3b50d157cf6dae3cc5d5198c72cf1 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 15 Jun 2026 13:21:26 +0800 Subject: [PATCH 2/4] Install VC++ runtime for pyHook in CI --- .github/workflows/build-and-release.yml | 35 ++++++++++++++++++++++++- history.md | 17 ++++++++++++ p27/SOURCES.md | 10 +++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index f650b02..ff173d1 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -66,6 +66,34 @@ jobs: throw "PyGTK MSI install failed with exit code $($proc.ExitCode)." } & C:\Python27\python.exe -c "import gtk, gobject, pango; print('PyGTK import OK')" + + - name: Install VC++ 2013 x86 runtime + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $redistUrl = "https://aka.ms/highdpimfc2013x86enu" + $expectedHash = "53b605d1100ab0a88b867447bbf9274b5938125024ba01f5105a9e178a3dcdbd" + $redistPath = Join-Path $env:RUNNER_TEMP "vcredist2013-x86.exe" + + Invoke-WebRequest -Uri $redistUrl -OutFile $redistPath + $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $redistPath).Hash.ToLowerInvariant() + if ($actualHash -ne $expectedHash) { + throw "VC++ 2013 redist SHA256 mismatch. Expected $expectedHash, got $actualHash." + } + + $signature = Get-AuthenticodeSignature -LiteralPath $redistPath + if ($signature.Status -ne "Valid") { + throw "VC++ 2013 redist signature is not valid: $($signature.Status)." + } + + $proc = Start-Process -FilePath $redistPath -ArgumentList "/install /quiet /norestart" -Wait -PassThru + if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { + throw "VC++ 2013 redist install failed with exit code $($proc.ExitCode)." + } + if (-not (Test-Path "C:\Windows\SysWOW64\msvcr120.dll")) { + throw "VC++ 2013 redist completed but C:\Windows\SysWOW64\msvcr120.dll was not found." + } + Get-Item "C:\Windows\SysWOW64\msvcr120.dll" | Select-Object FullName, Length, VersionInfo - name: Install pywin32 & pyHook shell: pwsh @@ -81,7 +109,12 @@ jobs: 7z x p27\pyHook-1.5.1.win32-py2.7.exe -opyhook_tmp if ($LASTEXITCODE -ne 0) { throw "Failed to extract pyHook." } Copy-Item -Path pyhook_tmp\PLATLIB\* -Destination C:\Python27\Lib\site-packages\ -Recurse -Force - & C:\Python27\python.exe -c "import win32api, win32gui, pythoncom, pyHook; print('pywin32/pyHook import OK')" + foreach ($module in @("win32api", "win32gui", "pythoncom", "pyHook")) { + & C:\Python27\python.exe -c "import $module; print('$module import OK')" + if ($LASTEXITCODE -ne 0) { + throw "$module import failed." + } + } - name: Bootstrap pip and install dependencies shell: pwsh diff --git a/history.md b/history.md index 566de31..dbee34c 100644 --- a/history.md +++ b/history.md @@ -146,3 +146,20 @@ - `PyInstaller-3.2.zip`、`dis3-0.1.3-py2-none-any.whl`、`psutil-5.8.0-cp27-none-win32.whl`、`setuptools-44.1.1-py2.py3-none-any.whl` 對上 PyPI digest。 - `VCForPython27.msi` Authenticode 簽章有效(Microsoft Corporation)。 - 判斷:原本收集的主要檔案來源可信度足夠;但未簽章的 legacy MSI/EXE 仍須維持固定 SHA256 gate,不應在 CI 內任意更新或改抓 latest。 + +## 2026-06-15(修正 GitHub Actions pyHook runtime 缺 DLL) + +- Draft PR `#69` 觸發 GitHub Actions run `27525522152`。 +- 已確認 checkout、`p27\verify_sha256.ps1`、MSBuild setup、Python 2.7 MSI、PyGTK MSI 都通過;新的 fail-fast 機制生效。 +- 失敗點在 `Install pywin32 & pyHook` 的 import 驗證:7-Zip 解包 `pywin32` 與 `pyHook` 都成功,但 `python -c "import win32api, win32gui, pythoncom, pyHook"` 回報 `ImportError: DLL load failed: The specified module could not be found.` +- 本機以 `dumpbin /DEPENDENTS` 檢查 `p27\pyHook-1.5.1\build\lib.win32-2.7\pyHook\_cpyHook.pyd`,依賴: + - `USER32.dll` + - `python27.dll` + - `MSVCR120.dll` + - `KERNEL32.dll` +- 根因:GitHub `windows-2022` runner 沒有預裝 VS2013 x86 runtime 的 `MSVCR120.dll`,導致 `pyHook._cpyHook.pyd` 載入失敗。 +- 已更新 workflow: + - 在 PyGTK 後、pywin32/pyHook import 前,下載 Microsoft 官方 VS2013 x86 runtime `https://aka.ms/highdpimfc2013x86enu`。 + - 驗證 SHA256 `53b605d1100ab0a88b867447bbf9274b5938125024ba01f5105a9e178a3dcdbd` 與 Authenticode 簽章後才安裝。 + - 安裝後確認 `C:\Windows\SysWOW64\msvcr120.dll` 存在。 + - 將 `win32api`、`win32gui`、`pythoncom`、`pyHook` 改成逐一 import 驗證,之後失敗會更容易定位。 diff --git a/p27/SOURCES.md b/p27/SOURCES.md index e16f955..ddc31f7 100644 --- a/p27/SOURCES.md +++ b/p27/SOURCES.md @@ -35,6 +35,16 @@ and enforced by `SHA256SUMS.txt`. | `pefile-2017.8.1.zip` | PyPI `pefile` 2017.8.1 source archive | Extracted with 7-Zip, then installed with `python setup.py install`. | Python setup install | | `PyInstaller-3.4.tar.gz` | PyPI `PyInstaller` 3.4 source archive | Extracted with tar, then installed with `python setup.py install`. | Python setup install | +## CI-Downloaded Runtime + +The workflow downloads this Microsoft runtime at build time instead of vendoring +it in `p27`. The workflow pins the expected SHA256 before running the installer +and also verifies the Authenticode signature. + +| File | Expected source | SHA256 | Workflow use | +| --- | --- | --- | --- | +| `vcredist2013-x86.exe` | `https://aka.ms/highdpimfc2013x86enu` | `53b605d1100ab0a88b867447bbf9274b5938125024ba01f5105a9e178a3dcdbd` | Installs the VC++ 2013 x86 runtime required by `pyHook._cpyHook.pyd` (`MSVCR120.dll`). | + ## Not Part Of The Release Build Gate The workflow does not currently use these files, so they are intentionally not From 2809c40c0bb1f2df2241545756e719dabc17bb08 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 15 Jun 2026 13:25:53 +0800 Subject: [PATCH 3/4] Run pywin32 postinstall in CI --- .github/workflows/build-and-release.yml | 2 ++ history.md | 11 +++++++++++ p27/SOURCES.md | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index ff173d1..7712719 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -104,6 +104,8 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "Failed to extract pywin32." } Copy-Item -Path pywin32_tmp\PLATLIB\* -Destination C:\Python27\Lib\site-packages\ -Recurse -Force Copy-Item -Path pywin32_tmp\SCRIPTS\* -Destination C:\Python27\Scripts\ -Recurse -Force + & C:\Python27\python.exe C:\Python27\Scripts\pywin32_postinstall.py -install + if ($LASTEXITCODE -ne 0) { throw "pywin32 postinstall failed." } echo "[INFO] Extracting pyHook..." 7z x p27\pyHook-1.5.1.win32-py2.7.exe -opyhook_tmp diff --git a/history.md b/history.md index dbee34c..d03fa1b 100644 --- a/history.md +++ b/history.md @@ -163,3 +163,14 @@ - 驗證 SHA256 `53b605d1100ab0a88b867447bbf9274b5938125024ba01f5105a9e178a3dcdbd` 與 Authenticode 簽章後才安裝。 - 安裝後確認 `C:\Windows\SysWOW64\msvcr120.dll` 存在。 - 將 `win32api`、`win32gui`、`pythoncom`、`pyHook` 改成逐一 import 驗證,之後失敗會更容易定位。 + +## 2026-06-15(修正 pywin32 postinstall 缺漏) + +- GitHub Actions run `27525782043` 顯示 VS2013 runtime 安裝成功,但仍在 `Install pywin32 & pyHook` 失敗。 +- 逐一 import 驗證指出第一個失敗的是 `win32api`,錯誤仍是 `ImportError: DLL load failed: The specified module could not be found.` +- 讀取 `pywin32-221.win32-py2.7.exe` 內部檔案後確認: + - `PLATLIB\pywin32_system32\pywintypes27.dll` + - `PLATLIB\pywin32_system32\pythoncom27.dll` + - `SCRIPTS\pywin32_postinstall.py` +- 根因:workflow 只用 7-Zip 解包並 copy `PLATLIB` / `SCRIPTS`,但沒有執行 `pywin32_postinstall.py -install`;因此 `pywintypes27.dll` / `pythoncom27.dll` 沒被放到 Windows DLL 搜尋路徑,`win32api.pyd` 載入失敗。 +- 已更新 workflow:copy `pywin32` 後執行 `C:\Python27\python.exe C:\Python27\Scripts\pywin32_postinstall.py -install`,失敗時立即中止。 diff --git a/p27/SOURCES.md b/p27/SOURCES.md index ddc31f7..cd1e95a 100644 --- a/p27/SOURCES.md +++ b/p27/SOURCES.md @@ -22,7 +22,7 @@ and enforced by `SHA256SUMS.txt`. | --- | --- | --- | --- | | `python-2.7.13.msi` | `https://www.python.org/ftp/python/2.7.13/python-2.7.13.msi` | Installs Python 2.7 x86 to `C:\Python27`. | Yes, MSI | | `pygtk-all-in-one-2.24.1.win32-py2.7.msi` | `https://download.gnome.org/binaries/win32/pygtk/2.24/pygtk-all-in-one-2.24.1.win32-py2.7.msi` | Installs GTK/PyGTK/PyGObject/PyCairo into `C:\Python27`. | Yes, MSI | -| `pywin32-221.win32-py2.7.exe` | `https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/pywin32-221.win32-py2.7.exe/download` | Extracted with 7-Zip; `PLATLIB` and `SCRIPTS` are copied into `C:\Python27`. | No installer execution | +| `pywin32-221.win32-py2.7.exe` | `https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/pywin32-221.win32-py2.7.exe/download` | Extracted with 7-Zip; `PLATLIB` and `SCRIPTS` are copied into `C:\Python27`, then `pywin32_postinstall.py -install` is run. | Python postinstall | | `pyHook-1.5.1.win32-py2.7.exe` | `https://sourceforge.net/projects/pyhook/files/pyhook/1.5.1/pyHook-1.5.1.win32-py2.7.exe/download` | Extracted with 7-Zip; `PLATLIB` is copied into `C:\Python27`. | No installer execution | | `pip-20.3b1-py2.py3-none-any.whl` | PyPI `pip` 20.3 beta 1 | Installed with `python -m pip install --no-index`. | Python package install | | `altgraph-0.17.4-py2.py3-none-any.whl` | PyPI `altgraph` 0.17.4 | Installed with `python -m pip install --no-index`. | Python package install | From ce6e385540995c9f79e626c45cbb782c267805d2 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 15 Jun 2026 13:30:34 +0800 Subject: [PATCH 4/4] Record successful release build verification --- history.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/history.md b/history.md index d03fa1b..953d7a5 100644 --- a/history.md +++ b/history.md @@ -174,3 +174,8 @@ - `SCRIPTS\pywin32_postinstall.py` - 根因:workflow 只用 7-Zip 解包並 copy `PLATLIB` / `SCRIPTS`,但沒有執行 `pywin32_postinstall.py -install`;因此 `pywintypes27.dll` / `pythoncom27.dll` 沒被放到 Windows DLL 搜尋路徑,`win32api.pyd` 載入失敗。 - 已更新 workflow:copy `pywin32` 後執行 `C:\Python27\python.exe C:\Python27\Scripts\pywin32_postinstall.py -install`,失敗時立即中止。 +- GitHub Actions run `27525931068` 已通過: + - SHA256 gate、Python 2.7、PyGTK、VS2013 x86 runtime、pywin32/pyHook、pip dependencies、`build.bat`、packaged executable content check、artifact upload 全部成功。 + - `Publish GitHub Release` 在 PR run 中正確 skipped。 + - 下載 artifact 後確認 `uclliu.exe` 含 `gtk/gobject/pango/pyHook/win32api/pythoncom` 字串。 + - artifact 內 `uclliu.exe` SHA256:`D04D600555183F81B9ED0FF5D21501D40E3703BC6C57EB6466A1D0FDC1A660E7`。