← 과제 목록

[ThorVG Web] 1주차) 프로젝트 작업환경 구축 조정민

@YoungB0
  • #과제
목차

1주차 과제는 Windows 11 + MSVC로 ThorVG를 네이티브 빌드했었습니다. 윈도우가 불편한 감이 있어서 우분투를 따로 깔았었는데, 굳이 그럴 필요 없이 WSL로 해도 될 것 같아서, 그냥 WSL2(Ubuntu)에서 했습니다.

지난번에 MSYS2랑 MSVC 헤더가 충돌해서 꽤 오래 헤맸는데, 윈도우에서 유닉스 도구를 섞는 구성은 그만하는 게 낫겠다고 생각했습니다. thorvg.web 빌드가 어차피 셸 스크립트 기반이기도 하고요.

헤더 충돌 같은 건 없었는데, 대신 환경을 새로 만든 탓에 다른 문제를 조금 만났습니다.

환경

항목
OSWindows 11 + WSL2 (Ubuntu)
bash
Node.jsv24.18.1
pnpm11.18.0
Emscripten6.0.5
에디터VS Code + WSL 확장
image

왜 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-playerdist/ 아래가 프리셋별로 나뉩니다.

  • 표준: sw/, gl/, wg/
  • 라이트: sw-lite/, gl-lite/, wg-lite/ webcanvas는 WASM 하나에 모든 백엔드(sw/gl/wg)가 들어갑니다. 그래서 dist/가 평평합니다.

빌드 순서 (WebCanvas 기준)

  1. thorvg/ 서브모듈에서 코어 라이브러리 빌드
  2. wasm/webcanvas/에서 WASM 바인딩 빌드
  3. wasm/wasm32*.txt의 크로스컴파일 설정 사용
  4. 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
image

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/
image

예제 실행

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가 도형을 어떻게 다루는지 감 잡기 좋았습니다.

image

lottie.html (Lottie Player)

http://localhost:8080/examples/lottie.html

Lottie 100개 넘게 동시에 재생되는 벤치마크 페이지입니다. 좌상단에 FPS, MS, 메모리가 실시간으로 찍힙니다.

image

examples/index.html

http://localhost:8080/examples/

(index.html이 자동으로 열립니다)

Play, Pause, Stop, Reverse, Loop, Speed, Frame 컨트롤이 있는 단일 재생 예제입니다. Frame 슬라이더로 특정 프레임에 직접 갈 수 있는데, ThorVG가 Lottie를 런타임에 프레임 단위로 제어한다는 말이 무슨 뜻인지 여기서 처음 눈으로 봤습니다.

save2gif, save2png로 현재 프레임을 이미지로 뽑을 수도 있습니다.

image

트러블슈팅

#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'
image image 전역 설정이라 다른 프로젝트에서도 같은 일이 안 생깁니다. SSH 키를 등록하는 방법도 있는데, 지금은 clone만 하면 되는 상황이라 이쪽으로 갔습니다.

#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/coreesbuild는 네이티브 바이너리 준비에 이 스크립트가 필요해서, 승인 안 하면 빌드가 시작조차 안 됩니다.

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
image 그다음 `pnpm install`을 다시 돌려서 차단됐던 스크립트를 반영했습니다.

참고로 처음에 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=1wasm/wasm32*.txtcpp_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는 결과물 크기를 줄이는 최적화라 끄면 파일이 커지는데, 동작에는 문제가 없었습니다.

image 다만 이게 잘못된 설정이라고 단정하긴 어려울 것 같습니다. webcanvas 쪽은 `-sEXPORTED_RUNTIME_METHODS`도 같이 빼는 걸 보면 다른 이유가 있을 수도 있고, lottie-player는 프리셋을 6종이나 만드니까 파일 크기가 더 중요해서 일부러 유지하는 걸 수도 있습니다. Emscripten 6.0.5에서만 나는 문제일 가능성도 있고요. 혹시 아시는 분 있으면 알려주시면 감사하겠습니다.

#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 원문