"신원을 봐, 시간 말고. 수정 시각으로 보면 엉뚱한 바이너리가 더 새 거였어."
모든 실행 뒤에 있는 데이터베이스
LaunchServices는 "com.example.spark가 어떤 앱이야", "spark://는 누가 받아", ".md 파일은 뭘로 열어" 같은 질문에 대답하는 macOS 데이터베이스야. Finder 더블클릭, open -a, open -b, 브라우저에서 누른 URL, NSWorkspace.openApplication이 전부 여길 거쳐. 그래서 가족 빌드 스크립트는 전부 설치한 번들을 lsregister -f로 등록하는 걸로 마무리해. 이 도구는 PATH가 아니라 LaunchServices.framework/Support 안에 있어. 데이터베이스가 새 빌드를 바로 알아보게 하려는 거야. 앞 트랙에서 open으로 띄우라고 고집한 이유도 이거야. LaunchServices로 띄운 앱은 자기가 책임 프로세스고, 열기 이벤트를 받고, 앱답게 활성화돼. 셸에서 실행 파일을 바로 돌리면 그냥 그 셸의 자식 프로세스야.
open엔 가끔 발목을 잡는 세부 사항이 두 가지 있어. open --env는 프로세스를 새로 띄울 때만 환경 변수를 넣어. 그래서 이미 돌고 있는 앱에 대고 쓰면 "already running and so the additional environment variables could not be set"이라며 거절해. 새 인스턴스를 띄우려면 -n을 붙이거나, 먼저 옛 걸 종료해. 그리고 프로세스 이름은 번들 이름이 아니라 CFBundleExecutable을 따라. 번들 이름으로 pkill -x를 부른 스모크 스크립트는 아무것도 못 맞혔어. 안에 든 실행 파일 이름이 달랐거든. 이름은 Info.plist에서 읽어.
/Applications는 모두가 같이 쓰는 칸 하나야
한 레포를 여러 벌 체크아웃해 두고 세션 여럿이 거기서 일하는 Mac에선, /Applications/Spark.app은 모두가 덮어쓰는 칸 하나야. 가족 클라이언트의 설치 앱 관문이 세 번 연속 실패했어. 설치된 바이너리에 테스트하려던 코드가 없었거든. 실행 도중에 다른 세션이 다른 체크아웃에서 빌드해서 설치해버린 거야. 있던 검사는 전부 통과했어. 번들 식별자, 제품 버전, 서명, designated requirement, CFBundleVersion까지. 근데 그건 그 앱의 어떤 빌드에도 다 맞는 사실이야. 환경 변수 전달이며 인자 확장이며 한참 헤매다가, 설치된 바이너리에 strings를 돌려보고서야 끝났어. 새로 넣은 표시 문자열이 아예 없었거든.
고치려면 신원이 실제로 드러나는 곳을 봐야 해. 체크아웃에 빌드 산출물이 있으면 설치된 실행 파일의 SHA-256이 그거랑 같아야 해. 이 검사는 더 흔한 실수도 잡아. 다시 빌드해놓고 설치를 까먹는 거. 여기선 시각이 아무것도 증명 못 해. 엉뚱한 바이너리가 더 새 파일이었으니까.
통하는 진단 순서
설치된 앱이 네가 고친 게 반영 안 된 것처럼 굴면, 먼저 설치된 바이너리에 strings -a를 돌려서 방금 넣은 문자열을 찾아(Swift 보간 문자열은 글자 조각으로 나뉘어 들어가니까 앞부분으로 찾아). 그러고 나서 설치된 실행 파일 해시를 기계에 있는 빌드 산출물 전부와 비교해서 어느 트리에서 왔는지 알아내. 돌고 있는 프로세스가 받은 환경은 맨 마지막에, 그 pid에 ps eww를 돌려서 확인해.