6~9단계 — 접근자와 출력
6. 접근자의 객체화 — 인스턴스와 전역 헬퍼
접근자를 인스턴스화하고(내부 Snapshot의 공개 승격), 기존 정적 표면은 「현재 인스턴스」로
위임하는 전역 헬퍼로 유지하며, 검증 규칙은 context.Tables로 접근합니다. 싱글턴처럼 쓰는
프로젝트는 지금과 같은 코드를 쓰고, 필요한 프로젝트는 인스턴스를 여럿 만듭니다 — 선택권을
주는 설계입니다. 상세 설계·미결·게이트는 접근자 객체화로
분리했습니다.
7단계가 접근자의 공개 표면을 어셈블리로 고정하므로 이 단계가 선행합니다. 템플릿 변경이므로 골든 절차를 탑니다.
7. C# 코드 생성의 .dll 출력 — 구현 완료
7-1. 구현 (2026-08-15)
| 무엇 | 어떻게 |
|---|---|
| 옵션 | CSharpRecipe.Output — "source"(기본) 또는 "assembly". 이름은 AssemblyName이고, 비우면 Namespace, 그것도 비우면 접근자 이름을 씁니다 |
| 만드는 법 | 소스를 임시 폴더에 스테이징을 거치지 않고 만들고 그것을 컴파일합니다. 그 파일들은 소비자가 볼 산출물이 아니라 컴파일러가 읽고 지울 것이라 스테이징의 대상이 아닙니다 |
| 컴파일 대상 | netstandard2.1. 4단계에서 미뤄둔 세트를 여기서 담았습니다(121개, 압축 2.8 MB) |
| 심볼 | 어셈블리 안에 넣습니다(embedded). .dll 옆의 .pdb는 없어지는 파일입니다 |
| 결정성 | deterministic: true. 저장소에 커밋해 두어도 아무것도 바뀌지 않은 실행이 변경으로 보이지 않습니다 |
| 함께 나가는 것 | 요약 문서 .xml, 그리고 엔진이 직접 컴파일해야 하는 소스 — TabbitUnityAdapter.cs와 (켰다면) TabbitUpdater.cs |
| 스윕 | EmitBytes가 익스포터와 같은 매니페스트 경 로로 등록합니다. 텍스트 마커를 가질 수 없는 파일이라 스윕이 「찾은 것」이 아니라 「쓴 것」으로 압니다 |
3단계의 어댑터 분리가 여기서 값을 했습니다. 유니티 분기가 액세서 안에 남아 있었다면
netstandard2.1 컴파일이 UnityEngine을 찾다가 실패했을 것입니다. 지금은 그 파일 하나만 소스로
빠져나갑니다.
7-2. 작업 중 드러난 회귀
생성 C#이 netstandard2.1로 컴파일되지 않고 있었습니다. MAC 검증이 들어오면서 리더가
IncrementalHash.GetHashAndReset(Span<byte>)을 쓰게 되었는데, 그 오버로드는 netstandard2.1에
없습니다 — 유니티 프로젝트는 이 리더를 컴파일할 수 없는 상태였습니다. 같은 일을 하는
TryGetHashAndReset으로 바꿨습니다.
게이트는 있었습니다. test/EmittedCodeLanguageCheck가 바로 그 검사인데, 테스트 스위트가 그
프로젝트를 빌드하지 않아 걸리지 않았습니다 — 솔루션 전체를 빌드하는 CI에서만 드러납니다.
스위트에서 ReferenceOutputAssembly="false"로 참조해 빌드되게 했습니다. 타입을 쓰려는 것이
아니라 그 프로젝트의 빌드 자체가 검사이기 때문입니다.
7-3. 빌드 산출물이 소스 트리로 샌 일
참조 세트를 묶는 경로를 $(IntermediateOutputPath)로 적었는데, 그 속성은 이 PropertyGroup이
평가되는 시점에 아직 비어 있습니다. 결과로 스테이징 폴더와 zip 두 개가 src/ 밑에 떨어졌고,
.gitignore가 그 이름을 모르므로 추적도 무시도 되지 않는 상태로 남았습니다 — 참조 어셈블리
170개가 코드 옆에서 git add .를 기다리고 있었습니다.
$(BaseIntermediateOutputPath)로 바꿨습니다. 이쪽은 SDK의 앞쪽 import에서 정해지므로 평가
시점에 이미 obj\이고, 그 아래는 이미 무시 대상입니다.
gitignore에 이름을 더하지 않은 것이 판단입니다. 무시 목록에 적는 것은 「나오면 안 되는 자리에 나오는 것을 덮는 일」이고, 고쳐야 할 것은 나오는 자리입니다.
7-4. 원래 설계
현재 구조
emit 인프라의 대부분이 존재하며 매 실행 돌고 있습니다 — 검증 접근자 빌드
(RuleAccessor.cs:79-211)가 「CsCodeGenerator 구동 → 생성 .cs 전체 수집 → Roslyn으로 dll
emit → 로드 → 실데이터 주입」을 수행합니다. 본질은 이 내부 동작을 공식 출력 옵션으로
승격 하는 것입니다.
설계
| 작업 | 내용 |
|---|---|
| 레시피 옵션 | CSharpRecipe에 출력 형식(배타: 소스 / 어셈블리)과 어셈블리 이름 추가. 옵션 형태(bool 대 enum 문자열)은 WriteCargoToml·WriteGoMod·WriteUpdater 선례와의 일관성 기준으로 확정. --new-recipe 스켈레톤은 리플렉션 기반이라 자동 반영 |
| emit 경로 일반화 | 접근자 빌드의 경로를 코드젠 출력용으로 승격. netstandard2.1 세트(4단계)로 컴파일, 결정적 emit, embedded pdb, XML 문서 |
| 스윕 전환 | 현재 스윕은 「Generated by Tabbit」 텍스트 마커 판별이라 dll을 인식하지 못합니다(GeneratedFileMarker.cs). 바이너리 익스포터의 매니페스트 prune 경로(StagingFiles.cs:102-108)를 재사용합니다 |
| Unity 소비 | 3단계 완료가 전제 — 중립 dll + 어댑터 소스 1장. dll 모드에서도 어댑터는 소스로 동봉합니다. 업데이터(TabbitUpdater.cs, UnityEngine 참조 포함)도 dll에 넣을 수 없으므로 같은 소스 동봉 축입니다 |
배타 옵션입니다. 목적이 노이즈 제거이고, 코드 확인은 IDE 디컴파일로, 디버깅은 embedded pdb로 충분합니다.
dll의 판정 방식. 소스 골든과 달리 dll 바이트는 Roslyn 버전에 결속되어 골든 비교가 취약합니다. 게이트는 「로드해서 공개 표면과 데이터 왕복을 검사」하는 방식으로 합니다 — 검증 접근자가 이미 수행하는 검사와 같은 종류입니다.
게이트 신설 — netstandard2.1 컴파일 검사. 기존 cs-check류는 net10 기준입니다. 생성
코드가 netstandard2.1로 실제 컴파일되는지가 dll 모드의 전제 검사입니다(TcbReader의 암호화
구현이 자체 구현이라 성립 가능성은 높으나 실측이 필요합니다).
스펙에서 정의할 의미 변화. dll 모드에서의 Sweep(매니페스트로 대체)·WriteUpdater
(소스 동봉으로 이동)·출력 폴더 구조.
8. 규칙 우선순위 — 티어와 차단점 — 구현 완료
순차 스테이지(pre/ · global/ · runtime/)의 규칙에 우선순위를 지정합니다. 같은 값이 한
티어이고, 티어 경계가 차단점입니다 — 앞 티어가 오류를 냈으면 뒤 티어를 실행하지 않고, 건너뛴
규칙 수를 보고합니다. tables/는 병렬 실행이고 규칙이 테이블 단위이므로 제외합니다. 지정은
어트리뷰트로 하고(계약 어셈블리에 타입을 두며, 코어의 [TabbitTarget(…, Order = 20)]이
선례입니다), 전체 순서는 조회 명령으로 확인합니다.
순서를 도입하는 것이 아니라 차단점을 도입하는 것입니다 — 파일 수집이 이미 경로 기준 정렬이라 순서는 결정적이고, 없는 것은 「앞의 것이 실패하면 뒤를 실행하지 않는다」는 의미론입니다. 상세 설계·미결·게이트는 규칙 우선순위로 분리했습니다.
어트리뷰트가 없으면 현재와 같은 동작이므로 기존 샘플·픽스처는 수정 대상이 아닙니다.
9. 생성 코드의 이름 체계
액세서 이름과 네임스페이스류 이름을 하나의 정본에서 파생시키고, AccessorName이 실제로
액세서 타입의 이름을 정하게 합니다 — 지금은 모든 언어 중 5개에서만 그렇게 동작하고, 7개는
파일 이름만 바꾸며, TypeScript는 아무 일도 하지 않습니다. 옵션 키 이름이 언어마다 다른 것
(Namespace · PackageName · CrateName · ModuleName)은 언어 관용이므로 바꾸지 않고,
통일의 대상은 의미와 기본값입니다. 상세 설계·미결·게이트는
생성 코드의 이름 체계로 분리했습니다.
6단계와 같은 템플릿의 같은 자리를 만지므로 한 골든 주기에 묶고, 7단계의 dll이 이 이름들을 어셈블리 경계로 굳히므로 그보다 앞섭니다. 이 체계가 정해지면 아래 미결 표의 이름 결정 3건이 함께 해소됩니다.