Crates.io에 크레이트 배포하기
지금까지 우리는 crates.io의 패키지를 프로젝트 의존성으로 사용해 왔습니다. 하지만 여러분 자신의 패키지를 배포해 다른 사람과 코드를 공유할 수도 있습니다. crates.io 에 있는 크레이트 레지스트리는 패키지의 소스 코드를 배포하므로, 주로 오픈 소스 코드를 호스팅합니다.
러스트와 Cargo는 여러분이 배포한 패키지를 다른 사람들이 더 쉽게 찾고 사용할 수 있도록 해 주는 기능을 갖추고 있습니다. 이제 그런 기능들을 몇 가지 살펴본 뒤, 실제로 패키지를 배포하는 방법을 설명하겠습니다.
유용한 문서 주석 달기
패키지에 대해 정확히 문서화해 두면 다른 사용자가 언제 어떻게 써야 하는지 이해하는 데
큰 도움이 되므로, 문서를 작성하는 데 시간을 투자할 가치가 있습니다. 3장에서는 //
를 사용해 러스트 코드에 주석을 다는 방법을 다뤘습니다. 러스트에는 문서를 위한
특별한 종류의 주석도 있는데, 이름 그대로 문서 주석(documentation comment) 입니다.
이 주석은 HTML 문서를 생성합니다. 그 HTML 은 크레이트가 어떻게 구현되었는지 보다는
크레이트를 어떻게 사용해야 하는지 를 알고 싶은 프로그래머를 위한 공개 API 항목의
문서 내용을 보여 줍니다.
문서 주석은 슬래시 두 개가 아니라 세 개, /// 를 사용하고, 텍스트 형식을 위해
Markdown 문법을 지원합니다. 문서화하려는 항목 바로 위에 문서 주석을 둡니다.
목록 14-1은 my_crate 라는 크레이트 안 add_one 함수에 대한 문서 주석 예입니다.
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
여기서는 add_one 함수가 무엇을 하는지 설명하고, Examples 라는 제목의 섹션을
시작한 뒤, add_one 함수를 어떻게 사용하는지 보여 주는 코드 예제를 제공합니다.
이 문서 주석으로부터 HTML 문서를 만들려면 cargo doc 을 실행하면 됩니다. 이 명령은
러스트와 함께 배포되는 rustdoc 도구를 실행하고, 생성한 HTML 문서를 target/doc
디렉터리에 넣습니다.
편의를 위해 cargo doc --open 을 실행하면 현재 크레이트 문서(그리고 의존성 문서도
함께)를 HTML 로 빌드한 뒤, 결과를 웹 브라우저로 열어 줍니다. add_one 함수로
이동해 보면, 문서 주석의 텍스트가 그림 14-1처럼 렌더링된 것을 확인할 수 있습니다.
그림 14-1: add_one 함수에 대한 HTML 문서
자주 쓰는 섹션들
목록 14-1에서는 # Examples 라는 Markdown 제목을 사용해 HTML 안에 “Examples”
라는 섹션을 만들었습니다. 크레이트 작성자들이 문서에 자주 넣는 다른 섹션은 다음과
같습니다.
- Panics: 문서화하는 함수가 어떤 상황에서 패닉할 수 있는지를 설명합니다. 자신의 프로그램이 패닉하지 않기를 원하는 호출자는 이런 상황에서 그 함수를 호출하지 않도록 주의해야 합니다.
- Errors: 함수가
Result를 반환한다면, 어떤 종류의 에러가 일어날 수 있고 어떤 조건에서 그런 에러가 반환되는지 설명해 주면 호출자가 상황별로 다르게 대응하는 코드를 작성하는 데 도움이 됩니다. - Safety: 함수가
unsafe하다면(이는 20장에서 다룹니다), 왜 unsafe 한지와, 호출자가 반드시 지켜야 하는 불변 조건을 설명하는 섹션이 있어야 합니다.
대부분의 문서 주석이 이 모든 섹션을 필요로 하지는 않지만, 사용자 입장에서 어떤 정보를 궁금해할지 떠올리는 체크리스트로는 아주 좋습니다.
문서 주석을 테스트로 사용하기
문서 주석 안에 예제 코드 블록을 넣으면 라이브러리 사용법을 보여 주는 데 도움이 될 뿐
아니라, 추가 보너스도 있습니다. cargo test 를 실행하면 문서 안의 코드 예제를
테스트로 함께 실행합니다! 예제가 포함된 문서는 가장 좋습니다. 그러나 문서가 작성된 뒤
코드가 바뀌어서 더 이상 동작하지 않는 예제는 가장 나쁩니다. 목록 14-1의 add_one
함수 문서를 가진 상태에서 cargo test 를 실행하면, 테스트 결과 안에 다음과 같은
섹션이 보일 것입니다.
Doc-tests my_crate
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
이제 함수나 예제를 바꿔서 예제 안의 assert_eq! 가 패닉하도록 만든 뒤
cargo test 를 다시 실행하면, 문서 테스트가 예제와 코드가 더 이상 맞지 않는다는
사실을 바로 잡아냅니다.
포함한 항목 문서화하기
//! 스타일의 문서 주석은 주석 뒤에 오는 항목 이 아니라, 그 주석을 포함하고 있는
항목 을 문서화합니다. 이 문서 주석은 보통 크레이트 루트 파일(관례적으로 src/lib.rs)
이나 모듈 파일 안에서, 크레이트 전체 또는 모듈 전체를 설명할 때 사용합니다.
예를 들어 add_one 함수를 담고 있는 my_crate 크레이트의 목적을 설명하는 문서를
추가하고 싶다면, 목록 14-2처럼 src/lib.rs 파일 맨 앞에 //! 로 시작하는 문서
주석을 적습니다.
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
my_crate 크레이트 전체에 대한 문서마지막 //! 로 시작하는 줄 뒤에는 어떤 코드도 없다는 점에 주의하세요. /// 가
아니라 //! 를 사용했기 때문에, 그 뒤에 오는 항목이 아니라 이 주석을 포함하고 있는
항목 을 문서화하는 것입니다. 여기서는 그 항목이 src/lib.rs 파일, 즉 크레이트
루트입니다. 따라서 이 주석은 크레이트 전체를 설명합니다.
cargo doc --open 을 실행하면, 이 주석들은 그림 14-2처럼 크레이트 공개 항목 목록
위쪽의 문서 첫 화면에 표시됩니다.
크레이트나 모듈을 설명할 때는 이런 “포함한 항목 문서 주석” 이 특히 유용합니다. 이 주석은 컨테이너 전체의 목적을 설명하는 데 쓰면, 사용자가 크레이트의 구조를 더 쉽게 이해할 수 있습니다.
그림 14-2: 크레이트 전체를 설명하는 주석을 포함한 my_crate
문서
편리한 공개 API 내보내기
크레이트를 배포할 때 공개 API 구조는 매우 중요한 고려사항입니다. 여러분은 크레이트 구조를 잘 알고 있겠지만, 사용하는 사람은 그렇지 않습니다. 특히 크레이트 안에 큰 모듈 계층이 있을 경우, 사용자는 자신이 쓰고 싶은 타입이 어디 있는지 찾기 어려울 수 있습니다.
7장에서는 pub 키워드로 항목을 공개하는 방법과, use 키워드로 항목을 스코프
안으로 가져오는 방법을 설명했습니다. 하지만 여러분이 개발하는 동안 편하게 느껴지는
구조가, 사용자에게도 편한 구조라는 보장은 없습니다. 예를 들어 구조체를 여러 단계의
계층 안에 정리해 두고 싶을 수 있지만, 사용자는 그 깊은 계층 안에 있는 타입이
존재하는지 알아내기조차 어려울 수 있습니다. 또
use my_crate::some_module::another_module::UsefulType; 같은 긴 경로를 써야
하는 것에도 짜증을 느낄 수 있습니다. use my_crate::UsefulType; 가 훨씬 더
편하기 때문입니다.
좋은 소식은, 외부에서 사용하기에 구조가 불편하다면 내부 구조를 반드시 뜯어고칠 필요는
없다는 점입니다. 대신 pub use 를 사용해 내부 구조와는 다른 공개 구조를 만들 수
있습니다. 재수출(re-exporting) 이란 어떤 위치의 public 항목을 다른 위치에서도
public 하게 만들어, 마치 원래 그 다른 위치에서 정의된 것처럼 보이게 하는 것입니다.
예를 들어 예술 개념을 모델링하는 art 라는 라이브러리를 만들었다고 해 봅시다.
이 라이브러리 안에는 PrimaryColor 와 SecondaryColor 라는 두 enum을 담은
kinds 모듈과, mix 함수를 담은 utils 모듈이 있다고 합시다. 목록 14-3이
그 구조를 보여 줍니다.
//! # Art
//!
//! A library for modeling artistic concepts.
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
unimplemented!();
}
}
kinds 와 utils 모듈로 나누어 정리한 art 라이브러리그림 14-3은 cargo doc 으로 생성한 이 크레이트의 문서 첫 화면이 어떤 모습일지를
보여 줍니다.
그림 14-3: kinds 와 utils 모듈만 보이는 art 크레이트
문서 첫 화면
여기서는 PrimaryColor, SecondaryColor, mix 가 첫 화면에 보이지 않습니다.
사용자는 그것들을 보려면 kinds 와 utils 를 직접 클릭해 들어가야 합니다.
이 라이브러리에 의존하는 다른 크레이트는, 현재 정의된 모듈 구조를 그대로 따라
use 문을 써야만 art 의 항목을 스코프로 가져올 수 있습니다. 목록 14-4는 art
크레이트의 PrimaryColor 와 mix 를 사용하는 예입니다.
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art 크레이트 항목 사용하기목록 14-4의 코드를 작성한 사람은 PrimaryColor 가 kinds 모듈 안에 있고, mix 가
utils 모듈 안에 있다는 사실을 알아내야 했습니다. 그런데 art 크레이트의 모듈
구조는, art 를 사용하는 사람보다 art 를 개발하는 사람에게 더 중요한 정보일 수
있습니다. 이 내부 구조는 사용자가 art 를 어떻게 써야 하는지 이해하는 데 큰 도움은
되지 않지만, 대신 어디를 찾아야 하는지 먼저 파악해야 하고 use 문에도 모듈 이름을
모두 적어야 하므로 오히려 혼란을 줍니다.
이 내부 조직을 공개 API에서 제거하려면, 목록 14-3의 art 크레이트 코드에 pub use
문을 추가해 항목을 최상위 레벨로 재수출할 수 있습니다. 목록 14-5를 보세요.
//! # Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
// --snip--
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
// --snip--
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Orange
}
}
pub use 문 추가하기이제 cargo doc 이 생성하는 API 문서는 그림 14-4처럼 문서 첫 화면에 재수출된
항목들을 직접 보여 주고 링크도 걸어 줍니다. 덕분에 PrimaryColor,
SecondaryColor, mix 를 훨씬 쉽게 찾을 수 있습니다.
그림 14-4: 첫 화면에 재수출 항목이 나타나는 art 크레이트
문서
art 크레이트 사용자는 여전히 목록 14-3의 내부 구조를 따라 목록 14-4 같은 방식으로
코드를 쓸 수도 있고, 목록 14-5에서 만든 더 편한 구조를 사용해 목록 14-6처럼 쓸
수도 있습니다.
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art 크레이트에서 재수출된 항목을 사용하는 프로그램중첩 모듈이 많을 때는, 타입을 최상위 레벨로 pub use 재수출하는 것이 크레이트를
사용하는 사람의 경험에 큰 차이를 만들 수 있습니다. pub use 의 또 다른 흔한
사용처는, 현재 크레이트에서 어떤 의존성의 정의를 재수출해 그 의존성의 정의가 마치
여러분 크레이트 공개 API 일부인 것처럼 보이게 만드는 것입니다.
유용한 공개 API 구조를 만드는 일은 과학이라기보다는 예술에 가깝고, 사용자에게 가장
잘 맞는 API를 찾기 위해 계속 다듬어 갈 수 있습니다. pub use 를 선택지로 가지면,
크레이트 내부 구조를 어떻게 잡을지에 훨씬 유연해지고, 그 내부 구조와 사용자에게
보여 주는 구조를 분리할 수 있습니다. 여러분이 설치해 본 크레이트들의 코드를 열어,
내부 구조와 공개 API가 서로 다른지 직접 살펴보는 것도 좋은 연습입니다.
Crates.io 계정 만들기
크레이트를 배포하려면 먼저 crates.io
계정을 만들고 API 토큰을 받아야 합니다. 이를 위해 crates.io
홈페이지에 가서 GitHub 계정으로 로그인하세요. (현재는 GitHub 계정이 필요하지만,
나중에는 다른 계정 생성 방식도 지원될 수 있습니다.) 로그인한 뒤
https://crates.io/me/ 의 계정 설정 페이지로
가서 API 키를 확인합니다. 그런 다음 cargo login 명령을 실행하고, 프롬프트가 뜨면
API 키를 붙여 넣습니다.
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
이 명령은 Cargo에게 API 토큰을 알려 주고, 그 값을 로컬의 ~/.cargo/credentials.toml 에 저장합니다. 이 토큰은 비밀 값이라는 점에 주의하세요. 절대 다른 사람과 공유하지 마세요. 어떤 이유로든 토큰을 공유했다면, crates.io 에서 그 토큰을 폐기(revoke)하고 새 토큰을 발급받아야 합니다.
새 크레이트에 메타데이터 추가하기
배포하고 싶은 크레이트가 있다고 합시다. 배포하기 전에, 그 크레이트의 Cargo.toml
파일 [package] 섹션 안에 메타데이터를 몇 가지 추가해야 합니다.
먼저 크레이트는 고유한 이름을 가져야 합니다. 로컬에서 작업하는 동안에는 아무 이름이나
붙일 수 있지만, crates.io 에서 크레이트 이름은
선착순으로 할당됩니다. 한 번 사용된 이름은 다른 누구도 같은 이름으로 배포할 수 없습니다.
배포를 시도하기 전에 원하는 이름을 검색해 보세요. 이미 쓰인 이름이라면 다른 이름을
찾아야 하고, Cargo.toml 의 [package] 섹션 아래 name 필드를 그 새 이름으로
바꿔야 합니다. 예를 들면 다음과 같습니다.
파일명: Cargo.toml
[package]
name = "guessing_game"
이렇게 고유한 이름을 골랐더라도, 지금 상태에서 cargo publish 를 실행하면 경고와
에러를 보게 됩니다.
$ cargo publish
Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip--
error: failed to publish to registry at https://crates.io
Caused by:
the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields
에러가 나는 이유는 중요한 정보 몇 가지가 빠져 있기 때문입니다. 설명(description)과
라이선스는 필수입니다. 그래야 다른 사람이 여러분의 크레이트가 무엇을 하는지, 어떤
조건으로 사용할 수 있는지 알 수 있습니다. Cargo.toml 에는 검색 결과에 표시될 한두
문장 정도의 설명을 넣어야 합니다. license 필드에는 라이선스 식별자 값 을 넣어야
합니다. 이 값으로 어떤 식별자를 사용할 수 있는지는 [Linux Foundation의 SPDX]
spdx 목록에서 확인할 수 있습니다. 예를 들어 MIT 라이선스로 크레이트를 배포한다면
MIT 식별자를 추가하면 됩니다.
파일명: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
SPDX 목록에 없는 라이선스를 쓰고 싶다면, 그 라이선스 전문을 프로젝트 안 파일로
넣고, license 대신 license-file 키로 그 파일 이름을 지정해야 합니다.
어떤 라이선스가 여러분 프로젝트에 적절한지는 이 책의 범위를 벗어납니다. 다만 러스트
커뮤니티의 많은 사람들은 러스트 자체와 같은 방식으로 MIT OR Apache-2.0 이라는
이중 라이선스를 사용합니다. 이 관례는, OR 로 구분해 여러 라이선스 식별자를 함께
지정할 수 있다는 점도 보여 줍니다.
고유한 이름과 버전, 설명, 라이선스를 추가한 뒤, 배포 준비가 된 프로젝트의 Cargo.toml 은 다음과 비슷하게 생길 수 있습니다.
파일명: Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
Cargo 문서에는 다른 사람이 여러분의 크레이트를 더 쉽게 찾고 사용할 수 있도록 추가로 지정할 수 있는 메타데이터가 더 많이 설명되어 있습니다.
Crates.io에 배포하기
이제 계정도 만들고, API 토큰도 저장했고, 크레이트 이름과 필요한 메타데이터도 정했다면, 배포할 준비가 끝났습니다! 크레이트를 배포한다는 것은 특정 버전을 crates.io 에 올려 다른 사람이 쓸 수 있게 하는 것입니다.
주의해야 할 점은, 한 번 배포는 영구적 이라는 것입니다. 어떤 버전도 덮어쓸 수 없고, 특수한 상황이 아닌 이상 코드를 삭제할 수도 없습니다. Crates.io의 중요한 목표 중 하나는 영구적인 코드 아카이브 역할을 하는 것입니다. 그래야 crates.io 의 크레이트에 의존하는 프로젝트가 언제든 같은 빌드를 재현할 수 있습니다. 버전 삭제를 허용하면 그 목표를 달성할 수 없게 됩니다. 대신 배포할 수 있는 버전 수 자체에는 제한이 없습니다.
이제 cargo publish 명령을 다시 실행해 봅시다. 이제는 성공해야 합니다.
$ cargo publish
Updating crates.io index
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Packaged 6 files, 1.2KiB (895.0B compressed)
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
Uploaded guessing_game v0.1.0 to registry `crates-io`
note: waiting for `guessing_game v0.1.0` to be available at registry
`crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
Published guessing_game v0.1.0 at registry `crates-io`
축하합니다! 이제 여러분의 코드는 러스트 커뮤니티와 공유되었고, 누구든 자신의 프로젝트 의존성에 이 크레이트를 쉽게 추가할 수 있게 되었습니다.
기존 크레이트의 새 버전 배포하기
크레이트를 수정한 뒤 새 버전을 공개할 준비가 되면, Cargo.toml 안의 version
값을 바꾸고 다시 배포하면 됩니다. 어떤 종류의 변경을 했는지에 따라 다음 버전 번호를
어떻게 정할지는 시맨틱 버저닝 규칙을 따르세요. 그다음 cargo publish
를 실행해 새 버전을 업로드하면 됩니다.
Crates.io 버전 폐기하기
크레이트의 이전 버전을 삭제할 수는 없지만, 앞으로 새 프로젝트가 그 버전을 새 의존성으로 추가하지 못하게 막을 수는 있습니다. 어떤 이유로든 특정 버전이 깨져 버렸을 때 유용한 기능입니다. 이런 상황에서 Cargo는 크레이트 버전을 yank 하는 기능을 제공합니다.
버전을 yank 하면, 새로운 프로젝트가 그 버전에 의존하지 못하게 되지만, 이미 그 버전에 의존하고 있는 기존 프로젝트는 계속 그대로 동작합니다. 요컨대 yank는 Cargo.lock 을 가진 기존 프로젝트를 깨뜨리지 않으면서, 앞으로 생성될 새로운 Cargo.lock 이 그 버전을 더 이상 사용하지 않게 만드는 동작입니다.
크레이트 버전을 yank 하려면, 예전에 배포했던 그 크레이트 디렉터리에서 cargo yank
를 실행하고 어떤 버전을 yank 할지 지정하면 됩니다. 예를 들어 guessing_game
크레이트의 1.0.1 버전을 배포했는데, 그것을 yank 하고 싶다면 프로젝트 디렉터리에서
다음처럼 실행합니다.
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
명령에 --undo 를 붙이면 yank를 되돌려, 그 버전에 다시 의존할 수 있게 만들 수도
있습니다.
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
yank는 코드를 삭제하는 것이 아닙니다. 예를 들어 실수로 비밀 값을 업로드했다면, yank 로는 그 비밀 값을 지울 수 없습니다. 그런 경우에는 즉시 그 비밀 값을 교체해야 합니다.