무엇을 입력으로 보는가
3. 입력을 무엇으로 판정하는가
2단 판정입니다.
- 크기와 수정 시각. 전부 그대로면 내용도 그대로로 봅니다. 파일을 열지 않습니다.
- 하나라도 다르면 그 파일만 해시해서 인장의 해시와 비교합니다. 같으면 내용은 바뀌지 않은 것이고, 인장의 수정 시각만 갱신합니다.
2단이 없으면 캐시가 거의 동작하지 않습니다. git checkout은 받은 파일 전부의 수정 시각을
지금으로 바꿉니다. 엑셀에서 워크북을 열었다 저장만 해도 같은 일이 일어납니다. 1단만 있으면
체크아웃 한 번이 항상 전체 실행이 되고, 캐시가 있는 자리는 「내가 방금 고친 것」 하나로
좁아집니다.
남는 구멍 하나를 적어 둡니다. 크기가 같고 수정 시각이 같은데 내용이 다른 파일은 검출하지
못합니다. 수정 시각을 의도적으로 되돌려야 만들 수 있는 상태이고, make 이래 모든 빌드 도구가
같은 자리에 같은 구멍을 두고 있습니다. --full이 그 자리의 답입니다.
3.1 파일 목록으로는 부족한 입력
| 입력 | 왜 파일 해시로 부족한가 | 무엇을 인장에 기록하는가 |
|---|---|---|
| 디렉터리의 워크북 목록 | 워크북을 하나 추가한 것은 기존 어느 파일도 바꾸지 않습니다. 새 테이블이 산출에 나타나야 하는데 인장은 「전부 그대로」로 판정합니다 | 검색된 파일 이름의 정렬된 목록의 해시. include·exclude 적용 전의 목록이어야 합니다 — 제외 목록을 고친 것도 여기서 검출됩니다 |
| 애셋 트리 | asset 컬럼의 검사 대상입니다(AssetRoots). 파일이 하나 생기면 오류였던 셀이 통과하고, 없어지면 반대가 됩니다 | 이름 목록의 해시. 존재만 확인하므로 내용은 해시하지 않습니다 — 게임의 콘텐츠 트리 전체를 해시하는 것과 이름만 해시하는 것은 비용이 다릅니다 |
| 검증 규칙 파일 | 규칙만 고친 실행이 검증을 건너뛰면 고친 규칙이 실행되지 않습니다 | rules/와 shared/의 파일 내용. 검증 파이프라인 §10이 컴파일 캐시의 조건으로 적어 둔 것과 같은 자리입니다 |
| 암호화 키 | 키가 바뀌면 .tcb의 바이트가 전부 달라집니다 | 키의 해시. 키 자체는 기록하지 않습니다 — 인장은 평문 파일입니다 |
| 구글 시트 문서 | 파일이 아니므로 크기와 수정 시각이 없습니다 | Drive의 version — 단조 증가 카운터. 스코프 drive.metadata.readonly를 추가합니다(§6.5) |
4. CLI 옵션 — 무엇이 전체 실행을 강제하는가
원칙: 분류되지 않은 옵션은 전체 실행입니다. 새 옵션을 추가하는 사람이 캐시를 생각하지 않았을 때의 결과가 「조용히 낡은 산출」이 아니라 「느린 실행」이어야 합니다.
구현도 그 원칙을 따릅니다. Options의 프로퍼티마다 분류를 어트리뷰트로 선언하고,
분류가 없는 프로퍼티가 하나라도 있으면 테스트가 실패합니다. 목록을 별도 파일에 두면
옵션이 추가될 때 어긋나고, 어긋난 것을 검출하는 것이 없습니다 — 소스·타깃 레지스트리가
어트리뷰트 스캔인 것과 같은 이유입니다.
| 분류 | 옵션 | 근거 |
|---|---|---|
| 대상 | --recipe | 어느 인장을 확인할 것인지 정합니다 |
| 모델 키에 포함 | --target-side · --time-zone · --env | 산출의 내용이 달라집니다. --target-side는 어느 타깃이 실행되는지까지 달라집니다 |
| 검증 키에 포함 | --skip-runtime-validation | 건너뛴 실행의 인장으로 건너뛰지 않는 실행을 통과시키면 안 됩니다 |
| 타깃에 따라 모델 키에 포함 | --commit · --branch · --commit-author · --commit-date · --repository | §5 |
| 무관 | --verbose · --silent · --debug | 산출이 한 바이트도 달라지지 않습니다. 여기에 넣으면 --verbose 한 번이 캐시를 버립니다 — 문제를 확인하려고 켠 플래그가 확인하려던 실행을 지워 버리는 것이 됩니다 |
| 캐시를 사용하지 않음 | --validate-only | 게이트입니다. 「지난번에 통과하였다」로 통과시키면 게이트가 아닙니다(§6.3) |
| 전체 강제 | --full — 신규 | 캐시를 확인하지 않고 전부 실행합니다. 인장은 새로 기록합니다 |
| 산출 강제 | --force-output — 신규 | 입력 판정은 캐시를 쓰고 타깃은 전부 실행합니다(§7.1) |
| 변환이 아님 | --history · --stats · --serve · --prune · --dump-schema · --new-recipe · --new-validator · --list-validators · --new-encryption-key | 지금처럼 동작합니다. 인장을 읽지도 쓰지도 않습니다 |
4.1 레시피 변경이 무효화하는 범위
| 고친 곳 | 다시 실행되는 것 |
|---|---|
Sources.* | 전부 |
전역 설정 — DefaultDelimiter · TimeZone · Naming · Assets · OnFormulaError · TrueWords · FalseWords · BuiltinBoolWords | 전부 |
Validation.* — KnownProblems 포함 | 검증 |
Targets[i]의 설정 | 그 타깃 |
Targets에 항목 추가 | 그 타깃만 |
Targets에서 항목 삭제 | 아무것도. 단, 그 타깃이 쓴 파일을 정리해야 합니다 — 인장에 그 타깃의 출력 목록이 있으므로 지울 대상을 알 수 있습니다 |
| 주석 · 공백 · 속성 순서 | 아무것도(§2) |
5. 건너뛰면 안 되는 타깃
history와 summary는 데이터가 같아도 남은 일이 있습니다. 커밋이 다르면 그 커밋의
스냅샷이 아직 없고, 히스토리의 목적이 「어느 커밋에서 무엇이 바뀌었는가」이므로 그 스냅샷이
없는 것은 데이터가 빠진 것입니다.
그래서 규칙은 이렇습니다 — TargetKind.Description 타깃이 레시피에 있으면 커밋 식별자가
모델 키에 들어갑니다. 같은 커밋의 재변환은 건너뛰고, 새 커밋은 전체 실행이 됩니다. 이
판정이 타깃 목록에서 나오므로 코어가 history라는 이름을 아는 자리가 생기지 않습니다.
데이터베이스 익스포터는 다릅니다. 파일 타깃의 산출은 해시로 확인할 수 있지만 데이터베이스의
상태는 확인할 수 없습니다 — 누가 스키마를 지웠는지 인장에는 적혀 있지 않습니다. 그래서
데이터베이스 타깃은 건너뛰지 않습니다. 건너뛰려면 그 store가 지난 실행의 것임을 그쪽에
기록해 두고 확인해야 하고, 그것은 SchemaBaseline의 역할에 가깝습니다 — 그 확인이 붙을
때까지는 매 실행 동작합니다.
이 판정에는 규칙이 필요하지 않았습니다. 「데이터베이스 타깃과 히스토리 타깃은 제외」라고 목록을 적을 자리에서, 파일을 하나도 쓰지 않은 항목은 확인할 것이 없으므로 실행한다는 한 문장이 같은 답을 냅니다. 목록은 타깃이 추가될 때 어긋나고, 이 문장은 어긋나지 않습니다 — 그리고 「확인할 수 있는 것만 건너뛴다」는 원래 하려던 말 그 자체입니다.
결정적이지 않은 산출도 갈라야 합니다. html 타깃은 생성 시각을 파일에 기록합니다
(HtmlCodeGenerator의 view.CreatedAt). 그러면 그 타깃의 출력 해시가 매 실행 달라지고,
출력 검증이 항상 실패합니다. 타깃이 자기 산출의 결정성을 어트리뷰트로 선언하고, 결정적이지
않은 타깃은 출력 검증에서 제외합니다. 더 나은 답은 그 시각을 산출에서 제거하는 것입니다 —
그러면 html도 캐시 대상이 되고, 이 예외 자체가 사라집니다.