[Rust] cargo 프로젝트 내 Cargo.toml과 Cargo.lock 그리고 incremental compilation
오늘은 첫 글에서 hello Rust 프로젝트를 생성하고, 이를 컴파일 및 실행하면서 자동으로 생긴 리소스들 중 관심이 가는 것들을 살펴본다.
Cargo.toml
Copilot에게 Cargo.toml에 대해 물으니 아래와 같은 답변을 받을 수 있었다.
Cargo.toml 파일은 Rust 프로젝트의 설정 파일로, 프로젝트의 메타데이터와 의존성을 관리하는 중요한 역할을 합니다. 이 파일은 TOML (Tom’s Obvious, Minimal Language) 형식으로 작성되며, 다음과 같은 주요 섹션으로 구성됩니다
[package]
name = "my_project"
version = "0.1.0"
authors = ["Your Name <your.email@example.com>"]
edition = "2021"
[dependencies]
my_other_project = "0.2.0"
serde = "1.0"
[dev-dependencies]
anyhow = "1.0" # 에러 핸들링을 위한 라이브러리
serde_json = "1.0" # JSON 파싱을 위한 라이브러리
그러니까.. Rust는 cargo라는 패키지 매니저이자 빌드 시스템을 활용해 "cargo build", "cargo publish" 등 빌드와 배포를 진행할 수 있다. 이때 Cargo.toml 파일은 해당 프로젝트의 버전, 타 프로젝트와의 의존성, 개발 중에만 필요한 라이브러리의 의존성을 명시할 수 있다. [dev-dependencies] 섹션에 명시된 라이브러리들은, 개발 중에만 사용되고 배포 빌드에는 포함되지 않는다고 한다. 또한 명시된 프로젝트 버전 정보가 빌드된 파일의 메타데이터에 포함돼, 디버깅이나 배포에 편리하다. 버전 및 의존성 관리를 강제하는..
Cargo.lock
Cargo.lock 파일은 프로젝트의 의존성 트리를 고정(freeze)하는 역할을 한다. Cargo.toml 파일에 명시된 의존성뿐만 아니라, 그 의존성들이 의존하는 모든 하위 의존성들의 정확한 버전을 기록하여, 동일한 빌드가 재현 가능하도록 한다.
아니 근데 Cargo.toml에서 이미 고정했는데...? 라고 생각했으나.. 두 파일의 역할은 조금 다르고, 상호 보완적이다.
Cargo.toml의 역할
- 의존성 선언: 프로젝트에서 필요한 라이브러리와 그 버전 범위를 선언합니다.
- 사양: 어떤 라이브러리가 필요한지, 그리고 그 라이브러리의 최소 버전 요구 사항을 명시합니다.
Cargo.lock의 역할
- 의존성 고정: 실제로 사용된 라이브러리의 정확한 버전을 기록합니다.
- 재현 가능성: 동일한 프로젝트를 다른 환경에서 빌드할 때, 동일한 의존성 버전을 사용하도록 보장합니다.
즉, Cargo.toml에는 필수 및 최소 의존성을 표기한다면, Cargo.lock은 언제든 특정 빌드를 재현할 수 있는 안전장치다. 실제 여러 명의 개발자가 협업하는 상황에서는 환경이 꼬이는 일이 허다하기 때문에..
예를 들어 위 Cargo.toml 파일 예시의 serde = "1.0"를 보면, 개발자는 1.0.x 버전 중 어떤 것이든 사용할 수 있다. 다만 특정 시점에 빌드를 하고 배포할 때 1.0.130 버전이 사용됐다면, 이는 Cargo.lock에 기록하여 오류 및 버그 가능성을 줄인다.
cargo update
위 명령어를 통해, Cargo.toml 파일에 기록된 의존성의 범위 내에서 가능한 최신 버전으로 Cargo.lock을 업데이트 할 수 있다.
증분 컴파일(Incremental Compilation)
프로젝트 내 target/debug/incremental 디렉토리를 보면, 소스코드 변경 후 빌드를 반복할 때마다 무언가 파일이 쌓인다.
그 안에는 다시 .lock 파일과 bin 파일들이 생성된 것을 확인할 수 있었다.
해당 디렉토리는 증분 컴파일(incremental compilation)과 관련된 데이터를 저장하는 곳이다.
증분 컴파일은 Rust 컴파일러가 이전 빌드의 결과를 재사용하여 컴파일 시간을 단축시키는 기능입니다.
이 디렉토리에는 컴파일러가 중간 결과를 캐시하여, 코드의 일부만 변경되었을 때 전체를 다시 컴파일하지 않고 변경된 부분만 다시 컴파일할 수 있도록 합니다
docker가 이미지를 layer로 관리하며 빌드 과정을 캐싱하고 불필요한 메모리 사용과 빌드 과정을 줄이는 것과 유사한 개념인 것 같다. 의존성 그래프를 활용해 변경되지 않은 부분은 재사용하고, 변경된 부분만 다시 컴파일한다.
내부적으로 알아서 증분 컴파일 캐시를 제거하여 너무 커지지 않도록 관리하며, 소스 코드가 크게 변경되거나 프로젝트 설정이 변경되면, 컴파일러는 캐시를 무효화하고 필요한 부분을 다시 컴파일하여 오래된 데이터가 남아 있지 않도록 한다.
cargo clean
또한 위 명령어로 아티팩트와 증분 컴파일 캐시를 모두 삭제할 수 있다.
다음 글에는 기본 문법과 Rust 만의 특별한 문법이 있다면 공부해보자.