1주차 과제는 Windows 11 + MSVC로 ThorVG를 네이티브 빌드했었습니다. 윈도우가 불편한 감이 있어서 우분투를 따로 깔았었는데, 굳이 그럴 필요 없이 WSL로 해도 될 것 같아서, 그냥 WSL2(Ubuntu)에서 했습니다.
지난번에 MSYS2랑 MSVC 헤더가 충돌해서 꽤 오래 헤맸는데, 윈도우에서 유닉스 도구를 섞는 구성은 그만하는 게 낫겠다고 생각했습니다. thorvg.web 빌드가 어차피 셸 스크립트 기반이기도 하고요.
헤더 충돌 같은 건 없었는데, 대신 환경을 새로 만든 탓에 다른 문제를 조금 만났습니다.
환경
| 항목 | 값 |
|---|---|
| OS | Windows 11 + WSL2 (Ubuntu) |
| 셸 | bash |
| Node.js | v24.18.1 |
| pnpm | 11.18.0 |
| Emscripten | 6.0.5 |
| 에디터 | VS Code + WSL 확장 |
왜 WSL2로 갔나
1주차 때 겪은 게 이유가 됐습니다. MSYS2로 SDL2를 받았더니 그 include 경로에 MinGW 표준 헤더까지 같이 들어 있어서, MSVC가 자기 stdio.h 대신 MinGW 걸 읽고 에러를 100개 넘게 뱉었습니다. 결국 도구 체인 두 개가 한 시스템에 섞여서 생긴 문제였습니다.
thorvg.web은 더 명확했습니다. 빌드가 wasm_player_build.sh, wasm_wcanvas_setup.sh 같은 셸 스크립트로 돌아가고 package.json 스크립트도 POSIX 기반입니다. 윈도우 네이티브로 하려면 Git Bash 같은 걸 끼워야 하는데, 그러면 또 비슷한 일이 생길 것 같았습니다.
WSL2는 실제 리눅스 커널을 돌리는 거라 프로젝트가 상정한 환경 그대로 쓸 수 있습니다. 솔직히 우분투를 조금 써보니 윈도우가 그립기도 했습니다.
개발 가이드 요약
Development Guide 정리입니다.
구성
thorvg.web은 모노레포고 패키지 두 개로 되어 있습니다.
@thorvg/lottie-player— Lottie 재생 전용@thorvg/webcanvas— 범용 캔버스 API 둘 다 같은 ThorVG WASM 코어를 공유하고 여러 렌더 백엔드를 지원합니다.
사전 준비
- Node.js 20+, pnpm 10+
- Emscripten SDK (WASM 컴파일)
- Meson & Ninja (ThorVG 코어 빌드)
EMSDK에 emsdk 절대경로를 넣고, 필요하면 PATH에 추가합니다.
export EMSDK={absolute path}/emsdk
export PATH=$EMSDK/upstream/emscripten:$PATH
빌드 결과물이 서로 다릅니다
lottie-player는 dist/ 아래가 프리셋별로 나뉩니다.
- 표준:
sw/,gl/,wg/ - 라이트:
sw-lite/,gl-lite/,wg-lite/webcanvas는 WASM 하나에 모든 백엔드(sw/gl/wg)가 들어갑니다. 그래서dist/가 평평합니다.
빌드 순서 (WebCanvas 기준)
thorvg/서브모듈에서 코어 라이브러리 빌드wasm/webcanvas/에서 WASM 바인딩 빌드wasm/wasm32*.txt의 크로스컴파일 설정 사용build_wasm_wcanvas/에thorvg.{wasm,js,d.ts}출력
그 외
- 루트 package.json에
"build": "pnpm -r build"가 있어서 최상위에서 한 번에 빌드할 수 있습니다. - Playground(3001), perf-test(3000)는 yarn을 씁니다.
- 로컬 빌드를 다른 프로젝트에서 테스트하려면
npm install /path/to/packages/webcanvas로 교체합니다.
설치
1. 빌드 도구
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git curl
sudo apt install -y python3 cmake meson ninja-build
Emscripten이 python3랑 cmake를 씁니다.
2. Node.js, pnpm
nvm으로 깔았습니다. 나중에 버전 바꿀 일이 있을 것 같아서요.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
corepack enable
corepack prepare pnpm@latest --activate
3. Emscripten SDK
cd ~
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
4. 환경 변수
매번 치기 싫어서 .bashrc에 넣었습니다. 1주차 때 윈도우에서 .bat 파일 만들어 쓴 거랑 같은 이유인데 이쪽이 훨씬 편합니다.
echo 'export EMSDK=$HOME/emsdk' >> ~/.bashrc
echo 'export PATH=$EMSDK/upstream/emscripten:$PATH' >> ~/.bashrc
source ~/.bashrc
echo $EMSDK
emcc --version
5. 클론
cd ~/projects
git clone --recursive https://github.com/thorvg/thorvg.web.git
cd thorvg.web
--recursive 필수입니다. thorvg/ 서브모듈의 C++ 코어를 먼저 빌드하기 때문에 비어 있으면 진행이 안 됩니다.
여기서 첫 번째로 막혔습니다. (트러블슈팅 #1)
6. 의존성 설치
pnpm install
여기서 두 번째입니다. (트러블슈팅 #2)
빌드
루트에서 한 번에 돌렸습니다.
time pnpm build
webcanvas는 4분 37초에 끝났는데 lottie-player가 10분 29초 만에 실패했습니다. (트러블슈팅 #3)
└─ Done in 4m 37.7s (webcanvas)
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @thorvg/lottie-player@1.1.0 build
Exit status 1
real 10m31.081s
--closure=1 빼고 다시 하니까 둘 다 됐습니다.
ls packages/webcanvas/dist/
ls packages/lottie-player/dist/
예제 실행
WASM은 보안 정책상 file://로 못 여는것 같았습니다. HTML 더블클릭하면 안 되고 로컬 서버를 띄워야 했습니다.
가이드는 packages/webcanvas에서 서버를 띄우라는데 거기엔 examples/가 없었습니다.
cd ~/projects/thorvg.web
npx http-server . -p 8080
live-editor.html (WebCanvas)
http://localhost:8080/examples/live-editor.html
왼쪽이 캔버스, 오른쪽이 코드 에디터입니다. 렌더러 WebGL로 두고 Basic Shapes 예제를 돌리니 사각형, 원, 삼각형이 잘 나왔습니다.
appendRect, appendCircle, moveTo/lineTo/close 같은 게 바로 보여서 WebCanvas가 도형을 어떻게 다루는지 감 잡기 좋았습니다.
lottie.html (Lottie Player)
http://localhost:8080/examples/lottie.html
Lottie 100개 넘게 동시에 재생되는 벤치마크 페이지입니다. 좌상단에 FPS, MS, 메모리가 실시간으로 찍힙니다.
examples/index.html
http://localhost:8080/examples/
(index.html이 자동으로 열립니다)
Play, Pause, Stop, Reverse, Loop, Speed, Frame 컨트롤이 있는 단일 재생 예제입니다. Frame 슬라이더로 특정 프레임에 직접 갈 수 있는데, ThorVG가 Lottie를 런타임에 프레임 단위로 제어한다는 말이 무슨 뜻인지 여기서 처음 눈으로 봤습니다.
save2gif, save2png로 현재 프레임을 이미지로 뽑을 수도 있습니다.
트러블슈팅
#1 서브모듈 클론이 안 됩니다
Submodule 'thorvg' (git@github.com:thorvg/thorvg.git) registered for path 'thorvg'
Cloning into '/home/youngb0/projects/thorvg.web/thorvg'...
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
fatal: clone of 'git@github.com:thorvg/thorvg.git' into submodule path ... failed
Failed to clone 'thorvg' a second time, aborting
메인 레포는 받아졌는데 서브모듈만 실패했습니다.
메시지의 git@github.com:이 단서였습니다. .gitmodules에 서브모듈 URL이 SSH로 되어 있는데, SSH는 키를 미리 등록해둬야 쓸 수 있습니다. WSL을 방금 깐 상태라 키가 없어서 인증에 실패한 거였습니다.
thorvg는 공개 저장소니까 HTTPS로도 받을 수 있어서 URL 치환으로 해결했습니다.
git config --global url."https://github.com/".insteadOf "git@github.com:"
git submodule update --init --recursive
Cloning into '/home/youngb0/projects/thorvg.web/thorvg'...
Submodule path 'thorvg': checked out '1b3aed2c188571f49ef4d87062b9f92ff1426c57'
#2 빌드 스크립트가 차단됩니다
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @swc/core@1.15.46, esbuild@0.28.1
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
pnpm install은 됐는데 pnpm build가 2.6초 만에 실패했습니다.
pnpm 10부터 의존성의 postinstall 스크립트를 기본으로 막는다고 합니다. 설치할 때 자동 실행되는 스크립트가 공급망 공격 통로로 쓰인 사례가 있어서 사용자가 직접 승인해야 하도록 바뀐 것 같습니다. pnpm install 로그 첫 줄에도 Lockfile passes supply-chain policies가 찍힙니다.
@swc/core랑 esbuild는 네이티브 바이너리 준비에 이 스크립트가 필요해서, 승인 안 하면 빌드가 시작조차 안 됩니다.
pnpm approve-builds
스페이스로 둘 다 고르고 엔터, y입니다.
✓ The next packages will now be built: @swc/core, esbuild.
Do you approve? Yes
node_modules/.pnpm/@swc+core@1.15.46/node_modules/@swc/core: Running postinstall script, done in 82ms
node_modules/.pnpm/esbuild@0.28.1/node_modules/esbuild: Running postinstall script, done in 95ms
참고로 처음에 pnpm approve-build라고 s를 빼고 쳐서 한 번 더 헤맸습니다.
#3 lottie-player WASM 빌드 실패
webcanvas는 4분 37초에 끝났는데 lottie-player가 10분 29초 만에 죽었습니다.
/tmp/emscripten_temp_.../thorvg.jso3.js:1:0: ERROR - [JSC_ILLEGAL_MODULE_RENAMING]
Original definition: {1}
1| // include: shell.js
...
880| wasmExports=EMSCRIPTEN$AWAIT(createWasm());EMSCRIPTEN$AWAIT(run());
1 error(s), 21 warning(s), 61.2% typed
em++: error: closure compiler failed (rc: 1)
ninja: build stopped: subcommand failed.
Emscripten이 만든 JS를 Closure Compiler가 한 번 더 최적화하는 단계에서 실패했습니다. 에러가 가리킨 EMSCRIPTEN$AWAIT는 Emscripten이 비동기 초기화에 쓰는 이름인데, Closure가 이걸 모듈 참조로 착각해서 ILLEGAL_MODULE_RENAMING이 난 것 같습니다.
옵션이 어디 있는지 몰라서 레포를 뒤졌습니다.
grep -rn "closure" --include="*.sh" --include="*.txt" --include="*.build" --include="*.json" . | grep -v node_modules
./packages/webcanvas/wasm_wcanvas_build.sh:39:# 3. Remove --closure=1 and -sEXPORTED_RUNTIME_METHODS from cpp_link_args
./packages/webcanvas/wasm_wcanvas_build.sh:47: sed "s|, '--closure=1'||g" | \
./wasm/wasm32.txt:17:cpp_link_args = [..., '--closure=1', ...]
./wasm/wasm32_sw.txt:17:cpp_link_args = [..., '--closure=1', ...]
./wasm/wasm32_gl.txt:17:cpp_link_args = [..., '--closure=1', ...]
./wasm/wasm32_wg.txt:17:cpp_link_args = [..., '--closure=1', ...]
--closure=1은 wasm/wasm32*.txt의 cpp_link_args에 있었고, webcanvas는 빌드 스크립트에서 이걸 빼고 진행하고 있었습니다. lottie-player엔 그런 처리가 없어서 설정을 그대로 씁니다.
설정 파일에서 빼고 다시 빌드했습니다.
sed -i "s|, '--closure=1'||g" wasm/wasm32.txt wasm/wasm32_sw.txt wasm/wasm32_gl.txt wasm/wasm32_wg.txt
rm -rf packages/lottie-player/build_wasm_player thorvg/build_wasm_player
pnpm --filter @thorvg/lottie-player build
meson이 이전 설정을 캐시하고 있어서 빌드 디렉토리를 지우고 해야 했습니다.
Closure는 결과물 크기를 줄이는 최적화라 끄면 파일이 커지는데, 동작에는 문제가 없었습니다.
#4 예제 경로가 가이드랑 다릅니다
가이드엔 이렇게 나와 있습니다.
cd packages/webcanvas
npx http-server . -p 8080
# Visit http://localhost:8080/examples/
근데 packages/webcanvas 아래엔 examples/가 없어서 404가 났습니다.
find ~/projects/thorvg.web -name "live-editor.html" -not -path "*/node_modules/*"
/home/youngb0/projects/thorvg.web/examples/live-editor.html
레포 루트에 있었습니다. 루트에서 서버를 띄우니까 예제가 참조하는 packages/*/dist/ 경로도 같이 잡혀서 잘 됐습니다.
cd ~/projects/thorvg.web
npx http-server . -p 8080
아직 모르는 것
- Closure를 끄면 파일이 얼마나 커지는지 확인을 안 해봤습니다. 실제로 문제가 될 정도인지 궁금합니다.
- webcanvas랑 lottie-player의 closure 처리 차이가 의도된 건지 아닌지 판단이 안 섭니다.
- lottie-player는 프리셋을 6종으로 나누는데 webcanvas는 단일 바이너리입니다. 용도 차이(재생 전용 vs 범용 API) 때문인 것 같긴 한데 정확히는 모르겠습니다.
- 빌드 로그에 순환 의존성 경고(
src/index.ts -> src/core/Canvas.ts -> src/index.ts)가 있었습니다. 빌드는 통과했는데 왜 저런 구조인지는 아직 안 봤습니다.
마치며
1주차 네이티브 빌드 때랑 막힌 지점의 성격이 달랐습니다. 그때는 도구 체인이 섞여서 난 문제였고, 이번엔 아무것도 없는 새 환경이라 생긴 문제들이었습니다.
네 개 다 검색으로는 잘 안 나왔는데, 에러 메시지를 그냥 읽으면 대부분 답이 그 안에 있었습니다. git@github.com:이라는 접두사, Ignored build scripts라는 문구, closure compiler failed라는 마지막 줄이 각각 원인을 가리켰었습니다.
댓글
Discussion 원문