크레이트를 Crates.io에 공개하기
크레이트를 Crates.io에 공개하기 (Publishing a Crate to Crates.io)
우리는 지금까지 crates.io의 패키지를 프로젝트의 의존성으로 사용해 왔어요. 그런데 자신의 패키지를 공개해서 코드를 다른 사람들과 공유할 수도 있어요. crates.io의 크레이트 레지스트리는 여러분 패키지의 소스 코드를 배포하므로, 주로 오픈 소스 코드를 호스팅해요.
러스트와 Cargo에는 공개한 패키지를 사람들이 더 쉽게 찾고 사용하게 해 주는 기능이 있어요. 그 기능 몇 가지를 다루고 나서 패키지를 공개하는 방법을 설명할게요.
출처: The Rust Book
유용한 문서 주석 작성하기 (Making Useful Documentation Comments)
패키지를 정확하게 문서화하면 다른 사용자들이 언제 어떻게 써야 할지 알 수 있어요. 문서를 쓰는 데 시간을 투자할 가치가 있죠. 3장에서 두 개의 슬래시 //로 러스트 코드에 주석을 다는 방법을 다뤘어요. 러스트에는 또 **문서 주석(documentation comment)**이라고 불리는 특별한 종류의 주석이 있는데, 이 주석은 HTML 문서를 생성해요. 이 HTML은 프로그래머를 위해 공개 API 항목의 문서 주석 내용을 보여줘요. 여러분의 크레이트가 어떻게 구현되어 있는지보다는 어떻게 사용해야 하는지를 알고 싶어 하는 프로그래머를 위한 거죠.
문서 주석은 두 개 대신 세 개의 슬래시 ///를 사용하고, 텍스트 서식에 Markdown 표기를 지원해요. 문서 주석은 문서화할 항목 바로 앞에 배치해요. Listing 14-1은 my_crate라는 크레이트의 add_one 함수에 대한 문서 주석을 보여줘요.
Filename: src/lib.rs
/// 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
}
Listing 14-1: 함수에 대한 문서 주석
여기서 add_one 함수가 무엇을 하는지 설명하고, Examples라는 제목으로 섹션을 시작한 다음 add_one 함수를 어떻게 사용하는지 보여주는 코드를 제공해요. cargo doc을 실행하면 이 문서 주석에서 HTML 문서를 생성할 수 있어요. 이 명령은 러스트와 함께 배포되는 rustdoc 도구를 실행하고 생성된 HTML 문서를 target/doc 디렉터리에 넣어요.
편의를 위해 cargo doc --open을 실행하면 현재 크레이트 문서(그리고 크레이트의 모든 의존성 문서)의 HTML을 빌드하고 결과를 웹 브라우저에서 열어줘요. add_one 함수로 이동하면 Figure 14-1처럼 문서 주석의 텍스트가 어떻게 렌더링되는지 볼 수 있어요.
Figure 14-1: add_one 함수에 대한 HTML 문서
자주 쓰는 섹션
Listing 14-1에서 # Examples Markdown 제목을 사용해 HTML에 "Examples"라는 제목의 섹션을 만들었어요. 크레이트 작성자들이 문서에서 자주 쓰는 다른 섹션은 이렇습니다:
-
Panics: 문서화 중인 함수가 panic할 수 있는 시나리오를 설명해요. 프로그램이 panic하지 않길 원하는 함수 호출자들은 이런 상황에서 함수를 호출하지 않도록 해야 해요.
-
Errors: 함수가
Result를 반환한다면, 발생할 수 있는 오류의 종류와 그 오류가 반환되게 하는 조건을 설명하면 호출자들이 서로 다른 오류를 다르게 처리하는 코드를 작성하는 데 도움이 돼요. -
Safety: 함수를 호출하는 것이
unsafe하다면(unsafety는 20장에서 다뤄요), 함수가 왜 unsafe한지, 그리고 함수가 호출자들이 지켜주길 기대하는 불변식(invariants)을 설명하는 섹션이 있어야 해요.
대부분의 문서 주석이 이 모든 섹션을 필요로 하지는 않지만, 사용자들이 알고 싶어할 코드 측면을 잊지 않도록 해 주는 좋은 체크리스트예요.
문서 주석을 테스트로 사용하기
문서 주석에 예제 코드 블록을 추가하면 라이브러리를 어떻게 사용하는지 보여주는 데 도움이 되고, 추가 보너스가 있어요. cargo test를 실행하면 문서의 코드 예제를 테스트로 실행한다는 거예요! 예제가 있는 문서만큼 좋은 건 없죠. 하지만 문서가 작성된 후 코드가 바뀌어서 동작하지 않는 예제보다 나쁜 것도 없어요. Listing 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!가 panic하게 만든 다음 cargo test를 다시 실행하면, doc 테스트가 예제와 코드가 서로 어긋났다는 걸 잡아내는 걸 볼 수 있어요!
항목을 포함하는 주석 (Contained Item Comments)
//! 스타일의 문서 주석은 주석 다음에 오는 항목이 아니라 주석을 포함하는 항목에 문서를 추가해요. 이런 문서 주석은 보통 크레이트 루트 파일(관례상 src/lib.rs)이나 모듈 안쪽에서 크레이트나 모듈 전체를 문서화할 때 사용해요.
예를 들어 add_one 함수를 담고 있는 my_crate 크레이트의 목적을 설명하는 문서를 추가하려면, Listing 14-2처럼 src/lib.rs 파일의 맨 앞에 //!로 시작하는 문서 주석을 추가하면 돼요.
Filename: 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
}
Listing 14-2: my_crate 크레이트 전체에 대한 문서
//!로 시작하는 마지막 줄 뒤에는 코드가 없다는 점을 주목하세요. /// 대신 //!로 주석을 시작했으므로, 이 주석 다음에 오는 항목이 아니라 이 주석을 포함하는 항목을 문서화하는 거예요. 이 경우 그 항목은 크레이트 루트인 src/lib.rs 파일이고, 이 주석들은 크레이트 전체를 설명해요.
cargo doc --open을 실행하면 이 주석들이 Figure 14-2처럼 my_crate 문서의 첫 페이지, 크레이트의 공개 항목 목록 위에 표시돼요.
항목 안의 문서 주석은 특히 크레이트와 모듈을 설명하는 데 유용해요. 컨테이너의 전반적인 목적을 설명하는 데 사용해서, 사용자들이 크레이트의 구성을 이해할 수 있게 도와주세요.
Figure 14-2: 크레이트 전체를 설명하는 주석을 포함해 렌더링된 my_crate 문서
편리한 공개 API 내보내기 (Exporting a Convenient Public API)
공개 API의 구조는 크레이트를 공개할 때 중요한 고려 사항이에요. 여러분의 크레이트를 사용하는 사람들은 여러분보다 그 구조에 덜 익숙하고, 크레이트의 모듈 계층이 크다면 원하는 부분을 찾는 데 어려움을 겪을 수 있어요.
7장에서 pub 키워드로 항목을 공개하고 use 키워드로 항목을 스코프로 가져오는 방법을 다뤘어요. 하지만 크레이트를 개발하는 동안 여러분에게는 타당해 보이는 구조가 사용자에게는 그리 편리하지 않을 수 있어요. 구조체를 여러 단계를 포함하는 계층으로 정리하고 싶을 텐데, 그러면 계층 깊숙이 정의한 타입을 쓰려는 사람들이 그 타입이 존재한다는 사실을 찾기 어려워할 수 있어요. 또 use my_crate::UsefulType; 대신 use my_crate::some_module::another_module::UsefulType;라고 입력해야 한다는 점에 짜증이 나기도 하죠.
좋은 소식은, 그 구조가 다른 라이브러리에서 사용하기에 편리하지 않더라도 내부 구성을 재배치할 필요가 없다는 거예요. 대신 pub use로 항목을 **재수출(re-export)**해서, 비공개 구조와는 다른 공개 구조를 만들 수 있어요. 재수출은 한 위치의 공개 항목을 마치 다른 위치에서 정의된 것처럼 그 위치에서도 공개되게 하는 거예요.
예를 들어 예술적 개념을 모델링하는 art라는 라이브러리를 만들었다고 해 볼게요. 이 라이브러리에는 PrimaryColor와 SecondaryColor라는 두 enum을 담은 kinds 모듈과 mix 함수를 담은 utils 모듈이 있어요. Listing 14-3과 같죠.
Filename: src/lib.rs
//! # 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!();
}
}
Listing 14-3: kinds와 utils 모듈로 항목을 정리한 art 라이브러리
Figure 14-3은 cargo doc이 생성한 이 크레이트 문서의 첫 페이지가 어떤 모습인지 보여줘요.
Figure 14-3: kinds와 utils 모듈을 나열한 art 문서의 첫 페이지
PrimaryColor와 SecondaryColor 타입이 첫 페이지에 나열되지 않고 mix 함수도 마찬가지라는 점을 주목하세요. 그것들을 보려면 kinds와 utils를 클릭해야 해요.
이 라이브러리를 의존하는 다른 크레이트는 현재 정의된 모듈 구조를 지정해 art의 항목을 스코프로 가져오는 use 문이 필요해요. Listing 14-4는 art 크레이트의 PrimaryColor와 mix 항목을 사용하는 크레이트의 예시예요.
Filename: src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
Listing 14-4: 내부 구조를 그대로 내보낸 상태로 art 크레이트의 항목을 사용하는 크레이트
Listing 14-4의 코드 작성자는 PrimaryColor가 kinds 모듈에 있고 mix가 utils 모듈에 있다는 걸 알아내야 했어요. art 크레이트의 모듈 구조는 사용하는 사람들보다 art 크레이트에서 작업하는 개발자에게 더 관련이 있어요. 내부 구조는 art 크레이트를 어떻게 사용하는지 이해하려는 사람에게 유용한 정보를 담고 있지 않고, 오히려 사용하는 개발자들이 어디를 봐야 할지 알아내고 use 문에 모듈 이름을 지정해야 하므로 혼란을 일으켜요.
내부 구성을 공개 API에서 제거하려면 Listing 14-3의 art 크레이트 코드를 수정해 pub use 문을 추가해서 최상위 수준에서 항목을 재수출하면 돼요. Listing 14-5와 같죠.
Filename: src/lib.rs
//! # 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
}
}
Listing 14-5: 항목을 재수출하기 위해 pub use 문 추가하기
cargo doc이 이 크레이트에 대해 생성하는 API 문서는 이제 Figure 14-4처럼 첫 페이지에 재수출을 나열하고 링크해서, PrimaryColor와 SecondaryColor 타입과 mix 함수를 더 쉽게 찾을 수 있게 해줘요.
Figure 14-4: 재수출을 나열한 art 문서의 첫 페이지
art 크레이트 사용자는 Listing 14-4에서 보여준 것처럼 Listing 14-3의 내부 구조를 여전히 보고 사용할 수 있고, Listing 14-6에서처럼 Listing 14-5의 더 편리한 구조를 사용할 수도 있어요.
Filename: src/main.rs
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
Listing 14-6: art 크레이트에서 재수출된 항목을 사용하는 프로그램
중첩된 모듈이 많은 경우 최상위 수준에서 pub use로 타입을 재수출하는 것은 크레이트 사용자들의 경험에 큰 차이를 만들어요. pub use의 또 다른 흔한 용법은 의존성의 정의를 현재 크레이트에 재수출해서 그 크레이트의 정의를 여러분 크레이트의 공개 API 일부로 만드는 거예요.
유용한 공개 API 구조를 만드는 것은 과학이라기보다 예술에 가깝고, 사용자에게 가장 잘 맞는 API를 찾기 위해 반복할 수 있어요. pub use를 선택하면 크레이트를 내부적으로 어떻게 구성할지에 유연성을 얻고, 그 내부 구조를 사용자에게 보여주는 것과 분리할 수 있어요. 설치한 크레이트들의 코드 중 일부를 살펴보면서 내부 구조가 공개 API와 다른지 확인해 보세요.
Crates.io 계정 설정하기 (Setting Up a Crates.io Account)
크레이트를 공개하려면 먼저 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에서 토큰을 폐기하고 새 토큰을 생성해야 해요.
새 크레이트에 메타데이터 추가하기 (Adding Metadata to a New Crate)
공개하고 싶은 크레이트가 있다고 해 봅시다. 공개하기 전에 크레이트의 Cargo.toml 파일의 [package] 섹션에 몇 가지 메타데이터를 추가해야 해요.
크레이트에는 고유한 이름이 필요해요. 크레이트를 로컬에서 작업할 때는 원하는 이름을 붙일 수 있어요. 하지만 crates.io의 크레이트 이름은 선착순으로 할당돼요. 한 번 이름이 사용되면 다른 누구도 그 이름으로 크레이트를 공개할 수 없어요. 크레이트를 공개하려 시도하기 전에 사용하려는 이름을 검색해 보세요. 이미 사용 중이라면 다른 이름을 찾아서 Cargo.toml 파일의 [package] 섹션 아래 name 필드를 수정해 공개할 새 이름을 써야 해요:
Filename: 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과 license는 필수예요. Cargo.toml에 description을 한두 문장으로 추가하세요. 검색 결과에서 크레이트와 함께 표시되거든요. license 필드에는 *라이선스 식별자 값(license identifier value)*을 기입해야 해요. Linux 재단의 Software Package Data Exchange(SPDX)가 이 값에 쓸 수 있는 식별자 목록을 제공해요. 예를 들어 크레이트를 MIT License로 라이선스했다는 걸 지정하려면 MIT 식별자를 추가하면 돼요:
Filename: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
SPDX에 없는 라이선스를 사용하고 싶다면 그 라이선스의 텍스트를 파일에 넣고 프로젝트에 포함한 다음, license 키 대신 license-file로 그 파일의 이름을 지정하면 돼요.
어떤 라이선스가 프로젝트에 적절한지에 대한 지침은 이 책의 범위를 벗어나요. 러스트 커뮤니티의 많은 사람들이 러스트처럼 MIT OR Apache-2.0의 이중 라이선스를 사용해 프로젝트를 라이선스해요. 이 관행은 OR로 구분된 여러 라이선스 식별자를 지정해 프로젝트에 여러 라이선스를 가질 수 있음을 보여줘요.
고유한 이름, 버전, description, 라이선스를 추가했다면, 공개할 준비가 된 프로젝트의 Cargo.toml 파일은 이렇게 생겼을 거예요:
Filename: 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에 공개하기 (Publishing to 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`
축하해요! 이제 여러분의 코드를 러스트 커뮤니티와 공유했고, 누구나 여러분의 크레이트를 프로젝트의 의존성으로 쉽게 추가할 수 있어요.
기존 크레이트의 새 버전 공개하기 (Publishing a New Version of an Existing Crate)
크레이트를 변경하고 새 버전을 릴리스할 준비가 되면, Cargo.toml 파일에 지정된 version 값을 바꾸고 다시 공개해요. 어떤 종류의 변경을 했는지에 따라 적절한 다음 버전 번호를 정하려면 시맨틱 버저닝(Semantic Versioning) 규칙을 사용하세요. 그런 다음 cargo publish를 실행해 새 버전을 업로드해요.
Crates.io에서 버전 폐기하기 (Deprecating Versions from Crates.io)
이전 버전의 크레이트를 제거할 수는 없지만, 앞으로 생길 프로젝트가 그 버전을 새 의존성으로 추가하는 것은 막을 수 있어요. 크레이트 버전이 어떤 이유로든 망가졌을 때 유용하죠. 그런 상황에서 Cargo는 크레이트 버전을 yank하는 것을 지원해요.
버전을 yank하면 새 프로젝트가 그 버전에 의존하는 것을 막으면서, 이미 그 버전에 의존하는 모든 기존 프로젝트는 계속 동작하게 해요. 본질적으로 yank는 Cargo.lock을 가진 모든 프로젝트는 깨지지 않고, 앞으로 생성되는 Cargo.lock 파일은 yank된 버전을 사용하지 않는다는 뜻이에요.
크레이트의 한 버전을 yank하려면 이전에 공개한 크레이트의 디렉터리에서 cargo yank를 실행하고 yank할 버전을 지정해요. 예를 들어 guessing_game 1.0.1 버전을 공개했고 yank하고 싶다면, guessing_game 프로젝트 디렉터리에서 다음을 실행해요:
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank [email protected]
명령에 --undo를 추가하면 yank를 되돌려 프로젝트가 다시 그 버전에 의존할 수 있게 할 수도 있어요:
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank [email protected]
yank는 어떤 코드도 삭제하지 않아요. 예를 들어 실수로 업로드된 비밀을 삭제할 수 없어요. 그런 일이 생기면 즉시 그 비밀을 재설정해야 해요.