지난 글에서 stoke init → stoke build → stoke run 세 명령으로 C++ 프로젝트 하나를 처음부터 만들어봤어요. 이번 글은 그 세 명령 뒤에, 또는 그 옆에 있는 실제 기능들을 정리한 거예요.
1. 프레임워크 스캐폴딩 — stoke init <framework>
언어만 고르는 게 아니라 프레임워크까지 바로 지정할 수 있어요. stoke init fastapi, stoke init gin처럼요. 빈 껍데기가 아니라 실행 가능한 최소 예제(라우트, 핸들러 구조)까지 만들어주고, 의존성 다운로드까지 끝낸 상태로 넘겨줘요.


언어 | 프레임워크 |
|---|---|
Python | FastAPI, Flask, Django |
Java | Spring Boot |
Go | Gin, Echo, Fiber, Chi, Bubble Tea(TUI) |
Rust | Actix Web, Axum, Rocket |
Kotlin | Ktor, Spring Boot |
C# | ASP.NET core |
Ruby | Sinatra |
PHP | Slim |
JavaScript | Express, Fastify |
TypeScript | Next.js, NestJS, Vite, Nuxt,SVelteKi, Hono |
웹 프레임워크가 대부분인데, 최근에 Bubble Tea(Go TUI 라이브러리)를 추가했어요. CLI/TUI 도구를 만들고 싶은데 옵션이 웹 프레임워크뿐이라 아쉬웠던 부분이에요.
2. 기존 프로젝트에 얹기 — CMake/Meson 위임

이미 CMakeLists.txt나 meson.build가 있다면 stoke가 밀어내지 않아요. stoke.toml에 build_system = "cmake" 한 줄만 추가하면 stoke build/run/watch/hot-reload가 내부적으로 cmake --build/meson compile을 그대로 호출해요. 통일된 명령어는 그대로 쓰면서 실제 컴파일은 익숙한 도구가 하게 두는 거예요.
3. 의존성 관리 — stoke add / stoke remove

stoke.toml이 실제 매니페스트인 Python/Java는 stoke add requests로 바로 설치까지 돼요.
JS/TS는 package.json이 매니페스트라 stoke.toml은 안 건드리고 대신 npm install을 실행해주는데, 알려진 npm 버그가 있는 버전을 감지하면 전역 npm은 안 건드리고 npx로 고쳐진 버전을 그 자리에서만 우회 실행해요. Go/Rust/C#/Ruby/PHP처럼 이미 자기 도구(go get, cargo add)가 있는 언어는 손대지 않고 뭘 써야 하는지만 안내해요.
4. 재현 가능한 빌드 — Lock 파일

stoke.lock이 컴파일러/의존성 버전을 기록해두면, 팀원이 같은 프로젝트를 체크아웃했을 때 stoke가 그 버전에 맞춰줘요. commit 모드(git에 커밋, 팀 공유)와 local 모드(개인 전용, gitignore) 중 선택 가능해요.
5. 원격/공유 빌드 캐시
STOKE_REMOTE_CACHE_DIR로 네트워크 드라이브나 NAS를 지정하면 팀 전체가 컴파일 결과물을 공유해요. 현재는 C/C++와 Java에서만 지원하지만, 다른 언어로도 넓히는 걸 고려하고 있어요. 한 사람이 컴파일한 오브젝트 파일을 다른 머신이 그대로 받아 써요. 캐시 히트 판단은 파일 mtime이 아니라 내용 해시로 하는데, 같은 git 커밋이어도 mtime은 머신마다 다르기 때문이에요.


B 디렉토리는 .stoke/ 캐시가 하나도 없는 완전히 새 프로젝트인데도 compiled가 아니라 remote cache hit이 찍혀요 — A에서 컴파일한 결과물을 공유 디렉토리를 통해 그대로 받아 썼다는 뜻이에요. 실제 팀/CI 환경에서는 ~/shared-cache 자리에 NAS나 네트워크 드라이브 경로가 들어가는 거죠.
6. 프로젝트 로컬 툴체인 + stoke exec

프로젝트에 다른 버전을 설치할 때 존재하지 않는 버전을 넣으면, 이렇게 [version] not found라고 나와요.
이때 그냥 실패로 끝나는 게 아니라 지금 설치 가능한 버전 목록을 바로 보여줘서, 어떤 버전을 입력해야 할지 헤맬 필요가 없어요.
목록에 있는 버전으로 다시 설치하면


프롬프트 가운데에 있는 1.25.0은 세 값 중 어느 것도 아니에요
버전 | 어디서 왔나? | |
|---|---|---|
전역 go | 1.26.0 | /usr/bin/go에 설치된 것 |
go.mod에 적힌 값 | 1.25.0 | go mod init을 실행한 go 배포판이 새 모듈에 기본으로 써넣는 언어 버전 |
프로젝트 로컬 go | 1.26.5 | stoke install --language=go --version=1.26.5로 이 프로젝트에만 설치한 것 |
stoke exec -- go version은 첫 번째, 두 번째 값은 다 무시하고 세 번째(1.26.5)를 써요
stoke install --language=go 같은 언어 설치는 시스템 전역이 아니라
프로젝트 폴더 안(.stoke/toolchains/)에만 들어가요.
함정 하나 : stoke build는 이 로컬 설치를 알아서 찾아 쓰지만, 사용자가 직접 쉘에서 치는 go mod tidy 같은 명령이나 에디터 플러그인(Neovim Mason의 gopls 설치 등)은 시스템 PATH만 보다가 실패해요.
저는 go를 전역으로도 깔아놔서 go mod tidy가 그냥 되긴 하는데, 시스템에 go가 아예 없었더마녀 여기서 "command not found"가 났을 거에요.
stoke exec -- go mod tidy는 그 한 번의 명령에만 프로젝트 로컬 툴체인을 PATH에 얹어서 실행해줘요.
7. 그 밖의 기능들
stoke test — pytest, go test, cargo test, JUnit 5, RSpec, PHPUnit 등 언어별 표준 테스트 도구로 통일된 진입점
Pre/post-build 훅 — stoke.toml에 pre_build/post_build로 임의 셸 명령 등록
플러그인 시스템 — stoke 소스 수정 없이 외부 pip 패키지로 새 언어/스캐폴드 추가
stoke ide-sync — 여러 stoke 프로젝트를 스캔해서 VSCode 멀티루트 워크스페이스 생성
여기까지 정리하면서 새삼 느낀 게, 언어가 12개라는 게 생각보다 훨씬 무거운 숫자였다는 거예요. 언어 하나하나는 별거 아닌데, 다 합치면 이야기가 완전히 달라져요.
같은 "버전 설치"라는 기능 하나만 봐도 언어마다 방식이 다 달라요. Go/Java/Node.js는 tarball 하나 받아서 풀면 끝이지만, Rust는 rustup이라는 자기만의 설치 프로그램을 거쳐야 하고, Python은 embeddable zip에 pip조차 안 들어있어서 따로 심어줘야 해요. "언어 설치"라는 한 문장 뒤에 이렇게 다른 구현이 5~6개씩 숨어있는 거죠.
빌드도 마찬가지예요. C/C++는 파일 단위로 캐시하고 병렬 컴파일해야 하는데, Java는 javac가 변경분을 알아서 몰아서 컴파일하는 구조라 캐시 전략 자체를 다르게 짜야 했어요. 이번 글 쓰면서 직접 겪은 것처럼, go.mod에 적히는 버전이 실제 실행 중인 go 버전이랑 다를 수 있다는 것처럼 언어마다 저마다의 관례와 함정이 있어서, "12개 언어를 하나로 통일한다"는 목표 자체는 간단해 보여도 그 뒤에서 처리해야 할 예외 케이스는 끝이 없더라고요.
그래도 이런 디테일들을 stoke 안에 다 흡수해두면, 쓰는 사람 입장에서는 여전히 stoke init → build → run 세 줄이면 끝나는 거니까 — 복잡함을 없앤 게 아니라 어딘가로 옮겨둔 거긴 하지만, 최소한 매번 새로 겪지 않아도 되게는 만든 것 같아요.
아직 댓글이 없어요. 첫 댓글을 남겨보세요.