비욘드 JS: 러스트 - cargo / crate.io advanced

dante Yoon·2023년 1월 2일
0

beyond js

목록 보기
15/20
post-thumbnail

글을 시작하며

안녕하세요, 단테입니다. 오늘은 Cargo와 crate.io의 좀 더 advance한 내용에 대해 이야기해보겠습니다.

Customizing Builds with Release Profiles

러스트에서 release profile은 코들르 컴파일 할때 커스터마이징한 옵션을 줄 수 있는
설정입니다.

카고는 두 가지의 메인 프로필을 가지고 있습니다.
dev profile 카고는 cargo build 할 때 사용되며 release profilecargo build --release를 할 때 사용되는 프로필입니다. dev profile은 개발용 옵션들이 기본적으로 적용되어 있으며 release profile은 배포용 옵션들이 적용되어 있습니다.

$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
$ cargo build --release
    Finished release [optimized] target(s) in 0.0s

컴파일러는 빌드 옵션에 따라 개발/배포용 프로필을 선택하여 사용합니다.

카고는 [profile.*]를 Cargo.toml 파일에 설정하지 않았을 때는 기본 설정을 사용합니다.
커스터마이징을 위해 [profile.*] 를 작성하면 기본 설정에 대해 오버라이딩 됩니다 예를들어 다음은 dev, release profile을 위한 opt-level 세팅들입니다.ㄴ

// Cargo.toml
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

opt-level 세팅은 몇 개의 옵티마이즈를 러스트 컴파일러가 코드에 적용할지를 정하는 것으로 0 부터 3 중에 설정할 수 있습니다. 숫자가 커질수록 더 긴 컴파일 시간이 소요되므로 개발모드에서 컴파일을 자주하는 경우에는 낮은 수의 optimization로 설정할 수 있습니다. 이 경우 런타임의 코드는 더 느려질 수 있습니다.

기본 설정의 opt-level 값은 dev 프로필에서는 0입니다. 배포단계에서는 컴파일에 시간이 오래걸리더라도 실제 코드가 더 빠르게 실행되는게 중요하므로 배포 모드는 컴파일 시간과 실제 코드가 실행되는 속도 간의 트레이드 오프 관계를 가지고 있습니다. 기본 세팅된 release 프로필의 opt-level은 3입니다.

이 세팅 값들을 Cargo.toml 파일에 오버라이딩할 수 있습니다. 예를 들어서 개발용 프로필에 1레벨의 optimization 값을 주고 싶다면, 다음처럼 설정하면 됩니다.

[profile.dev]
opt-level = 1

이 코드는 기본 설정된 0을 오버라이딩 합니다. 이제 cargo build를 입력하면, 카고는 dev profile에 기본 설정된 여러 값들에 더해서 opt-level에 대해서는 커스터마이징된 값을 사용하게 될 것입니다. opt-level 값을 1로 설정했기 때문에 카고는 좀 더 많은 최적화 작업을 적용할 것 입니다.

Publishing a Crate to Crates.io

node환경에서 npm registry에서 제공하는 패키지를 사용하듯이 러스트에서는 crates.io에서 제공하는 의존성 패키지들을 사용합니다. 반대로 우리가 작성한 코드들을 사람들과 공유할 수도 있습니다. crates.io의 crate registry가 우리가 만든 코드와 패키지들을 사람들과 공유하여 이것을 오픈소스화 시킬 수 있습니다.

러스트와 카고는 사람들이 배포한 패키지를 쉽게 사용할 수 있게 도와줍니다.

먼저 만드는 것부터 해봅시다.

Making Useful Documentation Comments

정확한 문서화는 사람들이 라이브러리를 사용하기 더욱 용이하게 도와줍니다. 따라서 문서화에 어느정도 시간을 쏟는 것은 그만한 가치가 있습니다. 러스트에서 커멘트를 작성할 때는 두개의 슬래쉬 //를 사용하는데요, 이것 말고도 러스트에서는 documentation comment라고 하는 코멘트 표기 방법이있습니다. 이것은 html 문서를 만들어줄 것입니다.

문서 코멘트는 세 개의 스래쉬로 사용됩니다. ///

/// Adds one to the number given.
///
/// # Examples
/// ```
/// let answer = my_create::add_one(arg)
/// 
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
  x + 1 
}

add_one 함수에 대한 설명과 예제 코드를 작성했습니다. Examples라는 Heading 을 작성헀고 이제 cargo doc을 통해 documentation을 만들 수 있습니다. 이 명령어는 rustdoc 도구를 사용해서 target/doc 디렉토리에 html 문서를 만들어줍니다.

문서 코멘트가 테스트 대상이 된다고?

cargo test는 문서화에 작성된 코드 예제들을 대상으로 테스트를 수행해줍니다. 예제 코드가 잘못 유저들에게 전달되는 것을 방지하기 위해 cargo test를 수행해서 add_one 함수에 대한 테스트를 수행할 수 있습니다.

   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

Commenting Contained Items

//! 코멘트가 있는데 보통 create root 파일 내부에 작성하거나 모듈 내부에 작성해서 crate나 모듈에 대해 이야기 합니다.

add_one 함수를 가지고 있는 my_crate crate를 설명하기 위한 문서화를 추가한다고 하면 //!를 documentation comments 서두에 붙일 수 있습니다.

//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--

//!가 마지막으로 작성된 이후 한 줄이 비었습니다. ///말고 //!를 사용했기 때문에 뒤에 따라오는 함수를 설명하는 코멘트가 아니라 이 crate 전체를 설명하는 코멘트가 됩니다.

cargo doc --open 명령어를 수행하면 my_crate라고 불리는 crate에 대한 설명이 public item에 대한 설명 리스트 위에 만들어집니다.

pub use 키워드를 사용한 퍼블릭 api 공개

crate를 공개할 때 public apis의 형태를 심사숙고해야 합니다. 사용하는 사람들이 큰 모듈 상하계층에서 어떻게 필요한 부분을 골라사용해야 할지 어려움을 겪기 때문입니다.
예전 강의에서 외부 모듈을 스코프로 사용할 때는 use 키워드를 사용한다고 했었는데요, use mycrate::some_module::another_module::UsefulType 이런식으로 내부 깊숙한 곳에 있는 타입을 가져다 사용하는 것은 유쾌하지 않을 것입니다.

만약 라이브러리 구조가 복잡하게 얽혀있다고 하더라도 다시 이 구조를 헤집어 놓을 필요는 없습니다. re-export 구문을 사용하여 public api들만 한 장소에 모은 다음에 공개할 수 있습니다.

예시 - art 라이브러리

//! # 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--
 }
}

위 예제를 cargo doc으로 만들게 되면 다음과 같이 됩니다.

PrimaryColorSecondaryColor 타입에 대한 정보들은 kinds, util를 클릭해야 찾을 수 있습니다.

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

메인 함수에서 art crate를 사용했습니다. PrimaryColor와 mix가 각각 kinds, mix 모듈에 있다는 사실을 알아야 합니다.

re export를 사용해보겠습니다.

//! # 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
    }
}

cargo doc을 통해 생성되는 문서에는 PrimaryColor, SecondaryColor 타입에 대한 정보가 이전 문서보다 보기 쉽게 나타나있습니다.

이제 mix, PrimaryColor를 사용하기 위해서는 다음과 같이 art 모듈에서 사용하면 됩니다.

use art::{mix, Primarycolor};

fn main() {
  // --snip--
}

pub use를 사용해 사용자에게 친화적인 퍼블릭 api 모듈 구조를 제공할 수 있습니다.

Crates.io 계정 만들기

터미널에서 cargo login을 통해 crate.io로 이동한 다음, 깃허브 인증과정을 거친 후 token을 생성해 다시 터미널로 돌아와 해당 토큰을 입력한다면 정상적으로 crate.io에 로그인할 수 있을 것입니다.


create에 메타뎅터 추가하기

crate를 만들어 작업한 후에 최종적으로 배포하기 전에 Cargo.toml에 메타데이터를 적을 수 있습니다.

cates.io에 이미 존재하고 있지 않은 crate이름을 선점해보세요. 먼저 중복 패키지 이름이없는지 확인한 이후에 다음 처럼 패키지 이름을 명명합니다.

[package]
name = "guessing_game"
$ 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: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata

description과 license가 누락되었기 때문에 에러가 발생했습니다.

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]
$ cargo publish
    Updating crates.io index
   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
   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 [unoptimized + debuginfo] target(s) in 0.19s
   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)

Cargo workspaces

yarn, pnpm workspace를 사용하여 모노레포를 구성하는 것처럼 cargo workspace를 사용해 점점 커지는 패키지를 파편화할 수 있습니다.

create workspace

먼저 우리가 workspace를 실습해볼 폴더를 만듭시다. 프로젝트 루트가 되는 디렉토리 입니다.

$ mkdir add
$ cd add

디렉토리를 만들었으면 이제 Cargo.toml 파일을 해당 디렉토리에 듭니다.

// Cargo.toml
[workspace]

members = [
    "adder",
]

그 다음 adder package를 만듭니다.

$ cargo new adder
     Created binary (application) `adder` package

이 시점에서 우리는 cargo build를 할 수 있습니다.
이제 전체 폴더 구조는 이렇게 됩니다.

add
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target

add workspace는 오직 하나의 타멧 디렉토리를 최상위 레벨에 가지고 있습니다. adder 패키지는 타겟 디렉토리가 없습니다. adder 디렉토리 내부에서 cargo build 명령어를 실행한다고 하더라도 별 다른 점이 발생하지 않습니다. 카고는 workspace 내부에 타겟 디렉토리를 만듭니다. 워크스페이스 내부에 있는 create이 다른 crate와 의존관계가 있을 수 있기 때문입니다. 만약 각 crate가 각기 다른 타겟 디렉토리를 갖고 있다면 각 crate는 자기 자신의 타겟디렉토리에 근거해 다시 한번 빌드하는 과정을 거쳐야 할 것입니다. 오직 타겟 디렉토리를 하나만 공유하기 때문에 crate가 빌드될 때 불필요한 빌드를 피할 수 있습니다.

두번째 패키지를 만들자.

add_one 패키지를 workspace의 새로운 패키지로 만들겠습니다. 최상단 Cargo.toml에 add_one 경로를 members 리스트에 포함시킵시다.

[workspace]

members = [
    "adder",
    "add_one",
]

다음 명령어를 통해 새로운 라이브러리 crate를 생성합니다.

$ cargo new add_one --lib
      Created library `add_one` package
├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

add_one/src/lib.rs 파일에 add_one 함수를 생성합니다.

pub fn add_one(x: i32) -> i32 {
    x + 1
}

adder패키지에서 add_one 패키지를 디펜던시로 갖게 합니다.
Cargo는 workspace 내부에 있는 crate들끼리 의존 관계를 갖는다고 생각하지 않으므로 명시적으로 [dependencies]를 설정해주어야 합니다.

// adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
// adder/src/main.rs
use add_one;
fn main() {
  let num = 10;
  println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

이제 빌드합니다.

$ cargo build
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.68s

add 디렉토리 내부에 있는 binary crate를 실행시키고 싶으면 패키지 이름을 명시해서 -p 옵션과 함께 실행시킵니다.

$ cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!

외부 라이브러리 사용할 때는 어떻게 의존 관계를 설정해야 하는가

Cargo.lock파일은 워크 스페이스 내부 가장 상위 레벨에만 존재하는 것을 볼 수 있습니다. 이것은 모든 crate가 같은 버전의 의존성을 사용하는 것을 보장합니다. adder/Cargo.toml. add_one/Cargo.toml 파일에 rand 의존성을 추가하면 Cargo.lock에 명시된 같은 버전을 사용합니다.

add_one crate에서 rand를 사용해야 한다고 가정합시다.

// add_one/Cargo.toml
[dependencies]
rand = "0.8.5"

add_one/src/lib.rs 파일에 use rand; statement를 선언했습니다. 하지만 이후 빌드 과정에서 경고 문구가 나오게 됩니다.

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   --snip--
   Compiling rand v0.8.5
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
 --> add_one/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `add_one` (lib) generated 1 warning
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 10.18s

최상단 Cargo.lock 파일에서 rand에 대한 add_one의 종속성 정보를 가지고 있습니다.
add 패키지에서 add_one을 사용하므로 이 패키지를 사용하는 다른 crate에도 해당 rand의 종속성 정보를 명시해야 합니다. 두 crate에 동일 종속성을 명시하더라도 카고는 종속성을 중복해서 다운받지 않습니다.

글을 마치며

오늘은 cargo / crate.io에 대한 고급 개념을 알아보았습니다.
공부하시느라 수고 많으셨습니다.

감사합니다.!

profile
성장을 향한 작은 몸부림의 흔적들

0개의 댓글