문제 해결
실제로 겪은 실패와 그것을 알아보는 법
이 페이지의 거의 모든 문제는 옷만 바꿔 입은 세 가지입니다. 렌더러 라이브러리가 링커가 기대하는 자리에 없거나, JDK가 다른 것이거나, 건드리면 안 되는 텍스트 경로를 건드린 것입니다.
링커가 렌더러를 찾지 못함
증상: cargo run … --features native-renderer가
library not found for -ldioxus_compose_renderer로 실패하거나, 링크는 됐는데
실행 직후 libdioxus_compose_renderer.dylib을 언급하는 동적 로더 오류로
죽습니다.
native-renderer 기능은 Rust 빌드가 워크스페이스 기준 고정된 위치
dioxus-compose-renderer/build/native-image/dist/lib에서 라이브러리를 찾게
하고, 그 경로를 rpath로도 심습니다. 그 디렉터리가 없거나 비어 있다면 렌더러를 아직 빌드하지
않은 것입니다.
ls dioxus-compose-renderer/build/native-image/dist/lib
# 기대: libdioxus_compose_renderer.dylib, libskiko-macos-*.dylib,
# libjawt.dylib, libawt_lwawt.dylib
./dioxus-compose-renderer/desktop/scripts/build-native.sh
네 파일을 따로 떼어 옮기지 마세요. 렌더러는 dladdr로 자기 위치를 알아낸 다음
그 디렉터리에서 java.home, skiko.library.path,
skiko.data.path를 끌어냅니다. 혼자 다른 곳에 복사된 라이브러리는 로드는 되고
Skia나 AWT를 못 찾습니다.
빌드도 실행도 되는데 창이 안 뜸
--features native-renderer 없이 빌드했다면 정상입니다. Host가 아무 일도 하지
않는 렌더러로 물러나므로 launch가 곧바로 반환하고 프로세스가 끝납니다. 고장이
아니라 테스트와 벤치마크용 구성입니다.
기능이 켜져 있는데도 그렇다면, 양쪽을 분리해 보세요. 스모크 테스트는 최소한의 C 호스트를
라이브러리에 링크해 run을 직접 부릅니다.
./dioxus-compose-renderer/desktop/scripts/smoke-test.sh
여기서는 창이 뜨는데 Rust에서는 안 뜬다면 바이너리 링크 문제입니다. 여기서도 안 뜨면 렌더러 라이브러리 자체가 잘못된 것이고, 보통 다음 문제입니다.
잘못된 JDK: Darwin AWT 함정
증상: 빌드 스크립트가
error: set GRAALVM_HOME to a Liberica NIK 25 Full installation로 멈춥니다.
이건 좋은 경우입니다. 문제를 대신 잡아 준 것이니까요.
나쁜 경우는 PATH에 upstream GraalVM의 native-image가 있어서
빌드는 되는데, 실행 시 AWT 클래스나 심볼이 없다고 죽거나 창이 아예 뜨지 않는 것입니다.
upstream GraalVM은 Darwin에서 AWT를 건너뜁니다
(oracle/graal#13272). Liberica
NIK Full은 정적으로 링크합니다. 실제로 무엇을 가리키고 있는지 확인하세요.
echo "$GRAALVM_HOME"
"$GRAALVM_HOME"/bin/native-image --version # Liberica NIK 25 빌드여야 합니다
비슷한 변형이 하나 더 있습니다. 메타데이터를 수집하는 JVM 실행과 네이티브 빌드는 같은
JDK여야 합니다. 스크립트는 GRAALVM_HOME으로 JAVA_HOME을 설정해서
이를 강제합니다. 다른 JVM에서 렌더러를 돌린 뒤 이미지를 빌드하면, 컴파일 대상이 아닌 JDK를
묘사한 reachability 메타데이터를 쓰게 됩니다.
시작하자마자 끝나거나, 끝나지 않음
dioxus_compose_renderer_run은 프로세스 메인 스레드에서 불러야 합니다. macOS에서
다른 스레드에서 부르면 아무 일도 하지 않고 RUN_NOT_MAIN_THREAD(-4)를
반환합니다. 같은 진입점의 다른 음수 코드도 알아 두면 좋습니다.
| 코드 | 의미 |
|---|---|
-1 | GraalVM isolate를 만들지 못했습니다. |
-2 | run이 두 번 호출됐습니다. |
-3 | 라이브러리가 자기 경로를 알아내지 못해 런타임 레이아웃을 모릅니다. |
-4 | 메인 스레드가 아닙니다. |
-5 | 렌더러 스레드를 시작하지 못했습니다. |
반대로 창은 닫혔는데 프로세스가 끝나지 않는다면 AppKit 재진입 문제입니다. AWT가 이벤트
루프를 소유해 [NSApp run]에 다시 들어갔고, 그래서 제어가 Host로 돌아오지
않습니다. 메인 스레드가 NSApplication을 직접 돌리고 렌더러를 보조 스레드에 두는
설계가 바로 이걸 피하기 위한 것입니다.
IME 문제
헷갈리기 쉬운 세 가지 실패입니다.
Enter가 조합 중이던 글자를 삼키고 전송됨
한글을 치다가 글자를 확정하려고 Enter를 눌렀는데 그 글자가 빠진 채 메시지가 전송됩니다. 조합 중에 키 이벤트가 Host까지 올라왔다는 뜻입니다. 규칙은 조합 중에는 렌더러가 키 이벤트를 아예 전달하지 않는 것이고, 조합 중의 Enter는 제출이 아니라 확정입니다. 이 증상을 보면 버그는 핸들러가 아니라 렌더러 쪽 키 경로에 있습니다.
입력 중에 조합이 리셋됨
글자를 만드는 도중 글자가 사라지거나 순서가 흐트러집니다. 텍스트가 Host를 왕복한 전형적인
증상으로, 조합 중에 Rust가 필드 값을 설정한 것입니다. TextField가 비제어인 이유가
이것이고, SetText 명령도 조합이 끝날 때까지 기다리도록 정의돼 있습니다.
네이티브 이미지에서만 조합이 안 됨
JVM에서 렌더러를 돌리면 한글이 되는데 네이티브 이미지에서는 안 됩니다. 이것은 설계상
설정 문제로 다룹니다. AOT 컴파일은 어떤 코드 경로가 도는지를 바꾸지 않기 때문입니다.
순서대로 확인하세요. InputMethodDescriptor ServiceLoader 리소스, JNI/리플렉션
메타데이터, 로케일과 문자셋 리소스, 그리고 CJK 폰트 폴백입니다.
tracing agent는 실제로 실행된 코드 경로만 기록합니다. 앱을 띄웠다가 그냥 닫으면 IME에 대해
아무것도 기록되지 않습니다. collect-metadata.sh를 돌린 다음 창에 한글을 입력하고,
이것저것 눌러 보고, 창이 스스로 닫히게 두세요. 그렇게 하지 않고 모은 메타데이터는 정확히 이
증상으로 텍스트 입력이 깨진 네이티브 이미지를 만듭니다.
전체 수동 체크리스트: "안녕하세요" 조합, 자모 단위 백스페이스, 화살표 이동 시 조합 확정, 문장 중간 삽입, 멀티라인에서의 Enter 처리, 한글 섞인 텍스트 붙여넣기, 일본어·중국어 후보창, 두부 문자 없음, 은 SPEC §6에 있고 JVM 셸이 아니라 네이티브 이미지에서 확인합니다.
ProtocolError 이벤트
렌더러가 모르는 위젯 타입이나 속성, Modifier가 와도 아무것도 죽지 않습니다. 렌더러가 코드와
메시지를 담은 ProtocolError 이벤트를 보냅니다. 이 이벤트가 보인다면 양쪽이
스키마에 대해 다른 생각을 하고 있다는 뜻이고, 보통은 지금 돌리는 Rust 크레이트와 다른
소스에서 빌드된 렌더러 라이브러리입니다. 렌더러를 다시 빌드하세요.
완전한 불일치는 더 일찍 걸립니다. 핸드셰이크가 스키마 해시와 프로토콜 버전을 비교해 다르면 초기화를 실패시킵니다.
생성된 Kotlin 때문에 테스트가 실패함
생성된 Kotlin과 저장소의 파일을 비교하는 테스트가 실패했다면, Rust 스키마를 고치고 재생성하지 않은 것입니다. 의도된 경보입니다. 스키마가 단일 소스이므로, 갱신되지 않은 Kotlin 인터프리터는 런타임에 어긋나는 대신 빌드에서 막혀야 합니다.