Files
FESADev/docs/mitc4-shell-feast-eigen/msvc-build-access-error.md
T
2026-09-06 22:40:43 +09:00

161 lines
17 KiB
Markdown

# MSVC 빌드 접근 오류와 Ninja 전환
- 작성 목적: MITC4 질량행렬·FEAST 구현 준비 중 발생한 Windows 빌드 환경 오류와 조치 기록
- Feature ID: `mitc4-shell-feast-eigen`
- Owner: `Root` (사용자 요청 설명문서)
- 입력 계약: `r2`, `E8` 실행 결과, `E9` 미실행 검증 계획과 `E10` 로컬 설정 결과
- 작성일: 2026-09-06
- 상태: MSVC 컴파일·링크 및 후속 CTest 통과 확인. 최초 GoogleTest discovery 오류와 wrapper 비정상 종료로 전체 준비 검증 미완료
MSVC 컴파일러와 x64/Debug 조건을 유지하면서 CMake의 빌드 generator를 Visual Studio에서 프로젝트가 이미 지원하는 Ninja로 바꿨다. 기본 `workspace-write` 권한에서 MSVC 컴파일·링크까지 진행했고, 이후 별도 GoogleTest discovery 문제를 확인했다. 최초 manual 빌드가 실패했으므로 전체 준비 검증은 아직 완료되지 않았다.
## 관찰된 오류
Visual Studio generator의 다음 명령이 native `workspace-write` 실행에서 실패했다.
```powershell
cmake --build .harness/build --config Debug
```
오류는 C++ 컴파일 전에 `ZERO_CHECK`의 `GetOutOfDateItems` 작업에서 발생했다.
```text
MSB4018: GetOutOfDateItems
System.TypeInitializationException: Microsoft.Build.Utilities.FileTracker
System.UnauthorizedAccessException: 0x80070005 (E_ACCESSDENIED)
Microsoft.Build.Framework.NativeMethods.GetLongFilePath
Microsoft.Build.Utilities.FileTracker..cctor()
```
같은 실행에서 configure는 성공했지만 manual Debug build는 exit 1이었다. 별도로 등록된 Stop의 build와 CTest는 성공했다. 따라서 자동 검증의 성공으로 native manual build 실패를 통과 처리하지 않았다.
확인된 범위는 MSBuild의 FileTracker 초기화에서 발생하는 접근 오류다. 어떤 Windows 접근 규칙과 정확한 내부 경로가 거부됐는지까지 확정한 것은 아니다.
## 앞선 조치와 구분해야 할 문제
1. 최초 configure의 `FESA_GTEST_SOURCE_DIR` 누락은 사용자 지정 `C:/git/googletest`와 설치된 MKL/TBB/HDF5 경로로 해결했다.
2. 긴 TEMP 경로에서 기존 pressure 테스트의 결과 잠금 파일 생성이 실패했다. 동일 binary의 TEMP 경로만 바꾼 비교에서 긴 경로는 실패하고 짧은 경로는 통과했다. 이후 TEMP/TMP는 새 `.harness/build/t-<8hex>`, pytest basetemp는 새 `.harness/build/p-<8hex>`를 사용하고 각각 80자 이하를 확인한다.
3. 짧은 TEMP/TMP를 build와 같은 native shell에 직접 설정해도 FileTracker 접근 오류가 재발했다. 따라서 TEMP 대입 누락이나 경로 길이 문제만으로 이 빌드 오류를 설명하지 않는다.
## 적용한 Ninja 구성
- 기존 `windows-msvc-ninja-debug` configure/build/test preset을 함께 사용한다.
- `.harness/config.example.json`의 설정을 ignored `.harness/config.json`으로 사용한다. 예상 밖 기존 설정은 덮어쓰지 않는다.
- 새 binary directory는 `.harness/build-ninja-msvc-debug`다. 기존 VS `.harness/build` cache와 실행 기록을 보존한다.
- Harness를 시작하는 부모 shell에서 설치된 `Launch-VsDevShell.ps1`로 MSVC host/target x64 환경을 설정하고 Ninja 및 승인된 dependency 경로를 적용한다. 이번 실행의 자식 tool shell에서는 해당 스크립트 호출이 PowerShell 실행 정책으로 거부됐다. 실행 정책을 우회하지 않고 부모에서 상속된 `VSINSTALLDIR`, `VSCMD_ARG_TGT_ARCH=x64`, `INCLUDE`, `LIB`, `cl.exe`, `ninja.exe`를 확인한 뒤 컴파일·링크를 진행했다.
- 기존 reference helper가 실제 library의 수정 시각을 확인할 수 있도록 `CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG`를 새 build의 `src/fesa/Debug`로 지정한다. Library를 복사하거나 수정 시각을 조작하지 않는다.
- 환경 변수는 해당 process에서만 설정하고 `finally`에서 복원한다. 정확한 설정·복원과 실행 순서는 [구현 환경 문서 E8](implementation-environment.md#e8-attempt3-후의-정상-backend-대안)에 있다.
아래는 `E8`에서 사용한 핵심 검증 명령이다. 후속 재검증은 아직 실행하지 않은 [E9 계획](implementation-environment.md#e9-attempt4-후의-직렬-ninja-검증)의 병렬도 설정과 진입 조건을 함께 따른다.
```powershell
$mfeArchive = (Join-Path (Get-Location).Path '.harness/build-ninja-msvc-debug/src/fesa/Debug').Replace('\', '/')
cmake --preset windows-msvc-ninja-debug "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG=$mfeArchive"
cmake --build --preset windows-msvc-ninja-debug
ctest --preset windows-msvc-ninja-debug --show-only=json-v1
ctest --preset windows-msvc-ninja-debug --output-on-failure
```
실제 실행에서는 각 명령의 종료 코드를 즉시 확인하고 실패 시 다음 검증으로 진행하지 않는다. Registered Stop도 같은 preset과 binary directory를 사용한다. Test selection, assertion, source, 수치 계약, reference 원본과 검증 기준은 이 환경 전환에서 변경하지 않는다.
## 검증 결과
| 확인 항목 | 결과 |
| --- | --- |
| 짧은 TEMP를 직접 적용한 기존 native MSBuild | configure exit 0, build exit 1 (`E_ACCESSDENIED`) |
| 위 실행의 별도 registered Stop/CTest | exit 0, 362/362 통과 |
| Root의 새 Ninja 준비 configure | exit 0, 7.446초; MSVC Hostx64/x64, Debug, Ninja 및 dependency 경로 확인 |
| Root configure 전후 보존 확인 | tracked 변경 0, 기존 VS cache 동일, Git clean |
| 새 Ninja native Debug build | 컴파일 경고 0개; 컴파일·링크 이후 integration GoogleTest discovery의 JSON parsing 오류로 exit 1 |
| 새 Ninja manual CTest/full pytest | 최초 manual build 실패 뒤 미실행 |
| 새 Ninja registered 검증의 CMake/CTest | build exit 0, CTest exit 0, 362/362 통과 |
| 위 registered 검증을 감싼 프로세스 | Python 두 프로세스 exit 0, 외부 PowerShell wrapper exit 1; 전체 Hook 성공으로 합치지 않음 |
| E8.2 Harness invocation | native PowerShell exit 1; manual build 실패를 보존 |
Root의 configure 성공이나 후속 CTest 통과는 최초 manual build 실패를 대체하지 않는다. 이 결과만으로 전체 준비 단계의 통과를 주장하지 않는다. 세부 구현 담당 보고서는 [implementation-prep-evidence.md](implementation-prep-evidence.md)를 참조한다.
## Ninja에서 추가로 발견된 검증 장애
새 Ninja 빌드에서는 기존 FileTracker 접근 오류 대신, 컴파일·링크 후 CMake의 GoogleTest 목록 생성에서 `ParseTestList.cmake:96` JSON parsing 오류가 발생했다. 현재 설치된 모듈 조합을 읽어 다음을 확인했다.
- `GoogleTestAddTests.cmake`는 `TEST_TARGET`의 SHA-256 앞 10자를 JSON 파일명으로 사용한다.
- `GoogleTest.cmake`와 실제 생성된 세 discovery 스크립트에는 `TEST_TARGET` 인수가 없다.
- 세 스크립트가 같은 디렉터리의 `cmake_test_discovery_e3b0c44298.json`을 삭제·생성·읽는 구조다.
- 동일 integration binary의 단독 목록 생성은 34개 테스트를 반환했고, 생성된 discovery 스크립트도 단독 실행에서는 통과했다.
이 관찰은 병렬 POST_BUILD discovery의 공유 파일 경쟁을 뒷받침한다. 정확한 최초 경쟁 순서를 재현한 것은 아니며, 단독 진단의 성공으로 최초 전체 빌드 실패를 통과 처리하지 않았다. 후속 `E9` 계획은 `CMAKE_BUILD_PARALLEL_LEVEL=1`과 한 번의 `--clean-first --verbose` 빌드로 세 discovery의 실제 직렬 실행을 검증한다. 설치된 모듈·생성된 JSON·테스트 소스와 discovery guard는 그대로이며, 이 후속 계획은 아직 미실행이다.
## 자동 검증의 별도 제한 시간 문제와 적용 결과
설치된 Codex CLI `0.153.2`의 `hooks/list`·`config/read`를 모델 실행 없이 조회했다. 현재 worktree의 project config는 읽지만, 실제 enabled/trusted Stop Hook은 원본 checkout의 `C:\git\FESADev\.codex\hooks.json`에서 `1800`초로 선택됐다. 현재 worktree 파일의 `7500`초는 실제 Hook 선택에 반영되지 않았다. 공식 지원 `project_root_markers`를 해당 조회에만 지정해도 결과는 같았다.
이번 native raw에는 Hook 설정 출처 이벤트가 없으므로, 현재 resolver 결과와 당시 외부 PowerShell wrapper가 약 1802초 뒤 exit 1인 관찰을 대조한 진단이다. Python/CTest의 후속 exit 0과 wrapper exit 1은 계속 별도로 보존한다.
사용자가 공유 설정 변경과 신뢰 검토를 설명받은 뒤 작업 계속을 지시한 범위에 따라 다음 한 값을 적용했다.
```text
대상: C:\git\FESADev\.codex\hooks.json
필드: hooks.Stop[0].hooks[0].timeout
변경: 1800 -> 7500 (초)
```
- 변경 전 파일 SHA-256: `0ab23dad4643f45238f4a60402083e32d8801f5c41fd6ba12b68ef6a9fc5fc28`
- 나머지 bytes를 보존하고 한 번 치환한 적용 파일 SHA-256: `c8065e7b60882da5db3f90d57b265da25b3c25980bf669d0b1da863e621871ca`; 원본 checkout의 로컬 커밋 `42a4847`
- 영향: 이 원본 Hook을 선택하는 다른 worktree에도 제한 시간 변경이 적용된다. Hook 명령, 검증 범위, assertion과 sandbox 권한은 그대로다.
- 승인 경계: 현재 feature worktree 밖의 공유 설정에 대한 이번 지시로 처리했다. 기존 단일 `E6` 실행 승인과는 별개이며, 새 sandbox 우회 또는 원격 작업을 허용하지 않는다.
공식 문서는 여러 Hook source를 함께 로드하며 상위 layer가 기존 Hook을 대체하지 않는다고 설명한다. 또한 정의가 바뀌면 새 hash에 대한 신뢰 검토가 필요하다. 따라서 inline Hook 추가나 trust 상태의 임의 수정으로 해결하지 않는다. [공식 Hook 문서](https://learn.chatgpt.com/docs/hooks)
최초 조회 근거는 [기본 설정 해석](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-hook-config-resolution.json), [정상 root marker 지정 조회](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-hook-root-marker-observation.json), [두 파일의 읽기 전용 감사](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-hook-source-audit.json)에 있다.
대화형 CLI는 Windows ConPTY 프로세스 생성 오류로 시작하지 못했다. 공식 app-server `config/value/write`를 사용해 검토한 Stop 정의의 `trusted_hash` 한 필드만 등록했다. `expectedVersion`으로 동시 변경을 검사했고, 설정 전체의 의미를 메모리에서 비교해 다른 필드가 같음을 확인했다. 최초 API 조회에서는 해시의 `sha256:` 접두사를 예상하지 못해 write 전에 중단했으며 그 기록도 보존했다.
후속 `hooks/list`에서 canonical source, `timeoutSec=7500`, `enabled=true`, `trustStatus=trusted` 및 현재 정의 해시 `sha256:a6bfcf3553d81e134065da4e66e8124e245d302c11e90b09ecdf07ff79b034a8`을 확인했다. [공식 API 적용·확인 기록](../../.harness/build/mitc4-eigen-evidence/prep/hook-timeout-activation/trust-review-official-api.json)은 exit 0이다. 이 조회·설정 과정에서는 Hook을 실행하지 않았고, 실제 전체 검증 통과 여부는 후속 prep 실행에서 판정한다.
## 근거 기록
- [동일 native build shell의 TEMP/TMP 설정과 FileTracker 실패](../../.harness/build/mitc4-eigen-evidence/prep/attempt3/root-manual-build-environment-observation.json)
- [새 Ninja 환경 준비 결과](../../.harness/build/mitc4-eigen-evidence/prep/attempt3/e8-root-setup/result.json), [실제 선택된 compiler·dependency·archive 경로](../../.harness/build/mitc4-eigen-evidence/prep/attempt3/e8-root-setup/configuration-observation.json)
- [최초 Ninja manual build 원시 로그](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/manual-cmake-d2d41bcf6bb242f4b484ea02850d762e/build.log): UTF-16LE, SHA-256 `47484e659a73977626c549fb56783ad5416ff14260e48e559b37b6b93f358f96`
- [설치된 CMake 모듈과 생성된 discovery 경로의 읽기 전용 감사](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-gtest-discovery-path-observation.json): SHA-256 `9d383f04a347df95b742403ceca4732b331ddf4ec96437993f0ceffaa060bf59`
- [실제 프로세스 종료 관찰](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-process-observations.json): CMake PID `1916`, CTest `21912`, Python `3492`/`24968`, 외부 wrapper `23560`, Harness 부모 `21996`을 command·parent와 함께 대조했다. 파일의 `role`은 탐색용 분류다. `rg` 소스 조회 shell인 PID `10528`은 Hook 실행에서 제외한다.
이 원시 로그는 해당 workspace의 ignored 검증 artifact다. 장기 workflow 상태와 후속 실행 판정은 [coordination.md](coordination.md)를 기준으로 한다.
## E10: 자동 Stop의 의존성 경로 누락 해결
초기 `E8` 설정은 process 단위였다. 설명문서 커밋 뒤 별도 자동 Stop에서는 부모 환경의 의존성 변수 네 개가 없었고, 공통 preset의 `$env{...}`가 cache를 빈 값으로 덮어써 `FESA_GTEST_SOURCE_DIR` 오류가 재발했다. [당시 cache와 관찰 기록](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-stop-after-decision-docs/observation.json)을 보존했다.
이를 해결하기 위해 workspace 전용 `CMakeUserPresets.json`에 `mfe-workspace-msvc-ninja-debug`를 만들었다.
- 공통 preset을 상속하면서 GoogleTest `C:/git/googletest`와 MKL/TBB/HDF5 설치 경로를 cache 값으로 직접 지정한다.
- 정상 MSVC x64 개발 환경에서 얻은 INCLUDE/LIB 및 cl/rc/mt/Ninja 실행 경로를 담고, build/test가 configure 환경을 상속하도록 한다.
- CMake child의 TEMP/TMP는 설정 수명 동안 전용 70자 경로를 사용하고 빌드 병렬도는 `1`로 지정한다. Python/pytest의 새 basetemp 규칙은 유지한다.
- ignored `.harness/config.json`의 configure/build/test preset을 모두 같은 로컬 이름으로 맞춘다. `/CMakeUserPresets.json` 한 줄을 `.gitignore`에 추가해 `3cfbefb`로 커밋했다.
이제 해당 workspace에서는 부모 환경에 의존성 변수 네 개가 없어도 다음 명령이 설정을 읽는다. 전체 명령 대응과 복원 절차는 [E10 환경 문서](implementation-environment.md#e10-부모-환경과-독립적인-workspace-user-preset)에 있다.
```powershell
cmake --preset mfe-workspace-msvc-ninja-debug
```
| 확인 항목 | 실제 결과 |
| --- | --- |
| 새 preset 목록·local config 해석 | 통과 |
| 최초 E10 configure | exit 1. 의존성은 찾았지만 compiler 경로의 `Hostx64`/`HostX64` 표기 차이로 CMake가 cache를 재초기화했고, 내부 재configure에서 의존성 값이 사라짐 |
| compiler 문자열 일치 확인 뒤 동일 preset의 configure 한 번 | exit 0, 1.656초, stderr 0 bytes; 부모의 의존성 변수 네 개는 미설정 |
| 후속 cache·adapter 대조 | 의존성 네 경로, MSVC 19.51/x64, pointer 8 bytes, Ninja/Debug, Debug archive 경로 및 네 검증 명령의 local preset 일치 확인 |
| E10의 production build/CTest | 미실행; configure 성공을 전체 구현 준비 통과로 대체하지 않음 |
근거는 [최초 실행 결과](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-e10-setup/result.json), [후속 configure 결과](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-e10-setup/configure-followup/result.json), [최종 설정·명령 감사](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-e10-setup/configuration-observation.json)에 있다. 기존 solver library hash, source, phase history와 원본 checkout의 Hook은 보존했다.
로컬 preset·설정·전용 TEMP는 workspace에 유지한다. 설치 경로 변경이나 feature 종료 때 설정 수명을 검토한다. E10 의존성 설정과 위 공유 Hook의 제한 시간·신뢰 수정은 별도로 적용·검증했다.
## E11: 실제 자동 검증 결과
후속 실행의 등록 Stop은 `mfe-workspace-msvc-ninja-debug`와 Ninja `-j 1`을 사용했고, CTest **362개 통과·실패 0개**로 끝났다. CTest PID `8112`, Stop Python `20788`/`25272`, 외부 wrapper `24308`의 실제 exit 0을 관찰했다. Wrapper는 13:04:39Z부터 13:37:13Z까지 약 1,954초 실행돼 이전 1,800초 제한을 넘겨 정상 종료했다. 근거는 [보존된 CTest 로그](../../.harness/build/mitc4-eigen-evidence/prep/attempt5/registered-LastTest.log), [실제 종료 관찰](../../.harness/build/mitc4-eigen-evidence/prep/attempt5/root-process-observations-resumed.jsonl), [Coordinator terminal 감사](../../.harness/build/mitc4-eigen-evidence/prep/attempt5/coordinator-terminal-audit.json)에 있다.
이때 수동 검증 담당은 이전 VS 명령을 사용해 FileTracker 오류를 다시 만났으므로, 수동 검증과 prep 전체는 통과하지 않았다. Native Executor exit 2와 `blocked` 결과도 보존했다. 다음 조치는 prep/main Step 문서에 현재 preset 명령을 직접 명시하는 계획 정합이며, 자동 검증 성공을 mass/FEAST 구현 완료로 해석하지 않는다. 첫 관찰기는 로그 파일 공유 충돌로 중단돼 재연결했으므로, 그 공백의 build exit는 직접 관찰했다고 주장하지 않는다. PID 재사용 또는 handle 확보 실패 행도 성공 종료 근거에서 제외한다.
기존 Executor가 실제 argv에 `--dangerously-bypass-hook-trust`를 넣는 사실도 확인했다. 이번 수정에서 추가한 옵션은 아니며, 별도 read-only [전체 Hook 조회](../../.harness/build/mitc4-eigen-evidence/prep/attempt5/root-all-hook-trust-observation.json)에서는 적용 대상 10개 모두 `enabled/trusted`였다. 따라서 옵션이 없었다고 보고하지 않으며, 검토한 정의의 정상 신뢰 등록 근거를 별도로 보존한다.