Cargo 커스텀 명령어
cargo 이름 규칙을 따르는 실행 파일로 사용자 정의 하위 명령을 만들고 팀 자동화 도구의 배포·호환성·오류 처리를 검증합니다.
Cargo는 Cargo 자체를 고치지 않고도 새 하위 명령(subcommand)으로 확장할 수 있게 설계되어 있습니다. $PATH에 있는 어떤 바이너리의 이름이 cargo-something이라면, cargo something이라고 실행해 그것을 Cargo의 하위 명령처럼 실행할 수 있습니다. 이런 커스텀 명령은 cargo --list를 실행했을 때의 목록에도 나옵니다. cargo install로 확장을 설치한 뒤 Cargo에 들어 있는 도구처럼 실행할 수 있다는 것은 Cargo 설계의 아주 편리한 장점입니다. 아래 그림은 cargo foo --help를 실행했을 때 Cargo가 무엇을 찾아 어떻게 실행하는지를 단계별로 보여 줍니다.
Cargo 외부 하위 명령의 탐색·인수·종료 계약
cargo foo --help→cargo-foo 검색→foo/--help 전달→외부 반환 코드의 구체 호출 경계이다. metadata는 선택적 별도 호출이다.
RUST · EXTERNAL SUBCOMMAND
cargo foo는 cargo-foo에 foo와 나머지 인수를 전달한다
외부 하위 명령은 발견·인수 전달·반환 계약으로 이어집니다. 본문의 cargo-something 명명 규칙을 foo라는 구체적인 명령에 적용합니다.
입력 · cargo foo --help
Cargo가 모르는 foo는 외부 cargo-foo 실행 파일 탐색으로 바뀝니다.
탐색 · 이름과 경로
기본적으로 Cargo home/bin의 외부 도구를 PATH보다 우선합니다. Cargo home/bin을 PATH에 넣으면 그 순서로 우선순위를 정할 수 있습니다.
실행 · foo와 나머지 인수
argv[0]은 실행 파일 이름, argv[1]은 foo, argv[2]는 --help입니다. 출력과 종료 상태는 호출한 셸/CI에 전달됩니다.
정확한 변환 · 발견과 도움말
cargo --list
cargo foo --help
# 자식: cargo-foo foo --helpcargo help foo도 같은 도움말 인수를 전달합니다. 도구는 Rust나 crates.io 패키지일 필요가 없습니다.
선택적 통합 · 프로젝트 정보
cargo metadata --format-version 1외부 도구가 프로젝트 정보를 필요로 할 때 선택해서 호출합니다. 모든 하위 명령이 자동으로 metadata를 호출하는 것은 아닙니다.
반환 · 실패 기준
stdout·stderr와 종료 상태는 실행한 도구의 계약입니다. 잘못된 인수·빈 작업공간·환경 누락도 일관된 결과로 정하고 실제 셸/CI에서 확인합니다.
배포 · 버전과 이름 충돌
cargo install은 같은 이름의 실행 target을 가진 패키지를 설치하는 한 방법입니다. 실행 파일 이름 충돌, 설치 버전, 입력 계약과 사용자 권한을 함께 관리합니다.
Cargo 내부 라이브러리 API는 안정된 통합 계약이 아닙니다. 필요하면 CARGO 환경변수로 Cargo CLI를 호출하고, 구조화된 metadata의 format-version을 명시합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | cargo foo --help에서 cargo-foo 외부 실행 파일 호출로 변환 |
| 관계·조건 설명 | Cargo가 모르는 foo 명령을 cargo-foo 파일로 탐색하고 foo와 --help 인수를 전달한다. metadata 호출은 도구가 원할 때 사용하는 별도 통합이다. |
| 관계·조건 설명 | 원래 가로 도표와 같은 세 노드와 두 진행 간선이다. cargo foo --help 입력→이름 cargo-foo와 실행 경로 탐색→발견한 프로그램 실행 및 foo/--help 전달이다. 실제 argv0의 철자는 고정하지 않는다. cargo --list의 발견은 실행이 아니고 metadata 통합은 선택 사항이다. 별칭/내장 명령/동명 이름/PATH/CARGOHOME의 우선순위와 Unicode/종료 조건은 본문의 보조 설명을 따른다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| Cargo 호출 cargo foo --help로 외부 명령을 요청한다. |
|
|
| 외부 실행 찾은 cargo-foo에 인수를 전달한다. |
|
|
| 결과 반환 외부 명령의 종료 결과가 돌아온다. |
|
|
Cargo 외부 하위 명령의 탐색·인수·종료 계약
cargo foo --help→cargo-foo 검색→foo/--help 전달→외부 반환 코드의 구체 호출 경계이다. metadata는 선택적 별도 호출이다.
RUST · EXTERNAL SUBCOMMAND
cargo foo는 cargo-foo에 foo와 나머지 인수를 전달한다
외부 하위 명령은 발견·인수 전달·반환 계약으로 이어집니다. 본문의 cargo-something 명명 규칙을 foo라는 구체적인 명령에 적용합니다.
입력 · cargo foo --help
Cargo가 모르는 foo는 외부 cargo-foo 실행 파일 탐색으로 바뀝니다.
탐색 · 이름과 경로
기본적으로 Cargo home/bin의 외부 도구를 PATH보다 우선합니다. Cargo home/bin을 PATH에 넣으면 그 순서로 우선순위를 정할 수 있습니다.
실행 · foo와 나머지 인수
argv[0]은 실행 파일 이름, argv[1]은 foo, argv[2]는 --help입니다. 출력과 종료 상태는 호출한 셸/CI에 전달됩니다.
정확한 변환 · 발견과 도움말
cargo --list
cargo foo --help
# 자식: cargo-foo foo --helpcargo help foo도 같은 도움말 인수를 전달합니다. 도구는 Rust나 crates.io 패키지일 필요가 없습니다.
선택적 통합 · 프로젝트 정보
cargo metadata --format-version 1외부 도구가 프로젝트 정보를 필요로 할 때 선택해서 호출합니다. 모든 하위 명령이 자동으로 metadata를 호출하는 것은 아닙니다.
반환 · 실패 기준
stdout·stderr와 종료 상태는 실행한 도구의 계약입니다. 잘못된 인수·빈 작업공간·환경 누락도 일관된 결과로 정하고 실제 셸/CI에서 확인합니다.
배포 · 버전과 이름 충돌
cargo install은 같은 이름의 실행 target을 가진 패키지를 설치하는 한 방법입니다. 실행 파일 이름 충돌, 설치 버전, 입력 계약과 사용자 권한을 함께 관리합니다.
Cargo 내부 라이브러리 API는 안정된 통합 계약이 아닙니다. 필요하면 CARGO 환경변수로 Cargo CLI를 호출하고, 구조화된 metadata의 format-version을 명시합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | cargo foo --help에서 cargo-foo 외부 실행 파일 호출로 변환 |
| 관계·조건 설명 | Cargo가 모르는 foo 명령을 cargo-foo 파일로 탐색하고 foo와 --help 인수를 전달한다. metadata 호출은 도구가 원할 때 사용하는 별도 통합이다. |
| 관계·조건 설명 | 원래 가로 도표와 같은 세 노드와 두 진행 간선이다. cargo foo --help 입력→이름 cargo-foo와 실행 경로 탐색→발견한 프로그램 실행 및 foo/--help 전달이다. 실제 argv0의 철자는 고정하지 않는다. cargo --list의 발견은 실행이 아니고 metadata 통합은 선택 사항이다. 별칭/내장 명령/동명 이름/PATH/CARGOHOME의 우선순위와 Unicode/종료 조건은 본문의 보조 설명을 따른다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| Cargo 호출 cargo foo --help로 외부 명령을 요청한다. |
|
|
| 외부 실행 찾은 cargo-foo에 인수를 전달한다. |
|
|
| 결과 반환 외부 명령의 종료 결과가 돌아온다. |
|
|
Cargo는 자신이 모르는 하위 명령 foo를 받으면, 기본 명령이나 별칭(alias)이 아닌지 확인한 뒤 cargo-foo라는 실행 파일을 찾습니다. 기본으로 Cargo는 $CARGO_HOME/bin에 있는 외부 도구를 PATH의 다른 폴더보다 먼저 고르며, $CARGO_HOME/bin을 PATH에 직접 넣으면 그 순서로 우선순위를 정할 수 있습니다. 찾은 프로그램에는 하위 명령 이름 foo와 나머지 인수 --help를 그대로 넘깁니다. 프로그램의 첫 인수 argv[0]은 실행 파일 자신이고, argv[1]이 foo, argv[2]가 --help입니다. 프로그램의 출력과 종료 상태는 그대로 호출한 셸이나 CI에 전달됩니다. cargo --list가 목록에 보여 주는 것은 찾았다는 뜻일 뿐 실행한 것이 아닙니다.
작은 하위 명령 만들기
cargo foo로 실행할 하위 명령을 직접 만들어 봅니다. 하위 명령의 이름이 foo이므로 패키지와 실행 파일의 이름은 cargo-foo로 짓습니다. 작업 중인 프로젝트와 떨어진 폴더에서 새 바이너리 패키지를 만듭니다.
cargo new cargo-foo
cd cargo-foosrc/main.rs에 다음 코드를 씁니다. 받은 인수를 먼저 출력하고, --help를 받으면 사용법을 출력하고 성공으로, 그 밖의 인수를 받으면 표준 에러에 오류를 출력하고 종료 상태 2로 끝납니다.
use std::env;
use std::process::ExitCode;
fn main() -> ExitCode {
let args: Vec<String> = env::args().skip(1).collect();
println!("args={args:?}");
let rest = match args.first().map(String::as_str) {
Some("foo") => &args[1..],
_ => &args[..],
};
match rest {
[option] if option == "--help" => {
println!("usage: cargo foo --help");
ExitCode::SUCCESS
}
_ => {
eprintln!("error: use --help");
ExitCode::from(2)
}
}
}env::args()의 첫 값은 실행 파일의 이름이므로 skip(1)로 건너뜁니다. Cargo를 거쳐 실행되면 그다음 값이 하위 명령 이름 foo이므로, 첫 값이 foo라면 그것을 빼고 나머지 인수만 봅니다. 이렇게 하면 cargo foo --help로 실행하든 cargo-foo --help로 직접 실행하든 같은 방식으로 인수를 해석합니다. main이 돌려주는 ExitCode는 프로그램의 종료 상태가 됩니다. ExitCode::SUCCESS는 0이고, ExitCode::from(2)는 2입니다.
이 패키지를 설치합니다. --path로 로컬 폴더의 패키지를 설치하면, 실행 파일 cargo-foo가 설치 루트의 bin 폴더에 들어갑니다.
cargo install --path .설치 폴더가 PATH에 있다면, 이제 cargo --list의 목록에 foo가 나오고, cargo foo로 이 프로그램을 실행할 수 있습니다. Cargo는 foo라는 내장 명령이 없으면 PATH에서 cargo-foo라는 실행 파일을 찾아 실행하기 때문입니다.
cargo foo --helpargs=["foo", "--help"]
usage: cargo foo --help첫 줄에 foo가 남아 있는 것은 Cargo가 하위 명령 이름까지 인수로 넘기기 때문입니다. 둘째 줄은 코드가 그 foo를 건너뛴 뒤 나머지가 --help 하나뿐인 것을 알아보고 출력한 사용법입니다.
같은 도구의 세 입력 비교
같은 프로그램을 세 가지 방식으로 실행하면, 코드를 읽어 다음과 같은 결과를 예상할 수 있습니다.
| 실행 | 표준 출력 | 표준 에러 | 종료 상태 |
|---|---|---|---|
cargo foo --help | args=["foo", "--help"], usage: cargo foo --help | 없음 | 0 |
cargo-foo --help | args=["--help"], usage: cargo foo --help | 없음 | 0 |
cargo foo --bad | args=["foo", "--bad"] | error: use --help | 2 |
Cargo를 거쳐 실행하면 인수에 하위 명령 이름 foo가 하나 더 들어 있습니다. cargo help foo도 foo와 --help를 넘기므로 cargo foo --help와 같은 인수를 받습니다. 종료 상태는 bash에서 echo $?로, PowerShell에서 $LASTEXITCODE로 확인할 수 있습니다. 결과가 예상과 다르면 실행 파일의 이름과 실제로 고른 경로, 같은 이름의 별칭이나 다른 cargo-foo가 있는지 먼저 확인합니다.
하위 명령이 꼭 러스트로 쓰였거나 crates.io의 패키지일 필요는 없습니다. 이름 규칙을 따르는 실행 파일이면 됩니다. 하위 명령이 프로젝트의 정보가 필요하다면 cargo metadata --format-version 1을 불러 구조화된 정보를 받을 수 있으며, 모든 하위 명령이 자동으로 이 정보를 받는 것은 아닙니다. Cargo의 내부 라이브러리 API는 안정적인 연동 방법이 아니므로, Cargo를 다시 불러야 한다면 Cargo가 하위 명령에 넘겨주는 CARGO 환경 변수의 경로로 Cargo 명령을 실행합니다. 잘못된 인수나 빈 작업공간 같은 경우에 무엇을 출력하고 어떤 종료 상태로 끝낼지도 정해 두고, 실제 셸과 CI에서 확인합니다. 이름과 전달 인수, 탐색 경로의 규칙은 Cargo의 커스텀 하위 명령 문서에 있습니다.
정리
Cargo와 crates.io로 코드를 나누는 것은 러스트 생태계가 여러 작업에 쓸모 있는 까닭 가운데 하나입니다. 러스트의 표준 라이브러리는 작고 안정적이지만, 크레이트는 언어와 다른 일정으로 쉽게 공유하고 쓰고 개선할 수 있습니다. 쓸모 있는 코드를 crates.io에 나누는 것을 망설이지 마세요. 다른 누군가에게도 쓸모 있을 가능성이 큽니다.
PATH에 있는cargo-something실행 파일은cargo something으로 실행할 수 있고,cargo --list의 목록에도 나옵니다.- Cargo는 기본 명령이나 별칭이 아닌 하위 명령을 받으면
cargo-이름실행 파일을 찾으며, 기본으로$CARGO_HOME/bin을 먼저 봅니다. - 하위 명령은
argv[1]로 하위 명령 이름을, 그 뒤로 나머지 인수를 그대로 받고, 출력과 종료 상태는 호출한 쪽에 전달됩니다. cargo install로 하위 명령을 설치해 Cargo의 기본 도구처럼 쓸 수 있습니다.- 프로젝트 정보가 필요하면
cargo metadata를 부르고, Cargo를 다시 부를 때는CARGO환경 변수를 씁니다.
연습 문제
1. 무엇이 실행될까
~/.cargo/bin에 cargo-lint가 설치되어 있습니다. cargo lint --fix src를 실행하면 Cargo는 어떤 프로그램을 어떤 인수로 실행하나요? cargo help lint는 어떤가요?
해설 보기
Cargo는 lint가 기본 명령이나 별칭이 아니므로 cargo-lint를 찾아 실행하며, 실행 파일 이름 다음에 lint, --fix, src를 차례로 넘깁니다. 곧 프로그램이 받는 인수는 argv[1]이 lint, argv[2]가 --fix, argv[3]이 src입니다. cargo help lint는 cargo-lint에 lint와 --help를 넘깁니다.
2. CI에서 실패를 알아채려면
CI에서 cargo foo --bad를 실행했는데 화면에는 오류 메시지가 보였지만 CI 단계는 성공으로 표시되었습니다. 코드의 어떤 부분이 이렇게 만들 수 있나요?
해설 보기
오류 메시지를 출력한 뒤 main이 ExitCode::SUCCESS나 ()를 돌려주어 종료 상태가 0이 되었을 가능성이 큽니다. CI는 출력 내용이 아니라 종료 상태로 성공과 실패를 판단하므로, 오류일 때는 이 글의 코드처럼 ExitCode::from(2) 같은 0이 아닌 값을 돌려주어야 합니다. 메시지를 출력하는 일과 종료 상태를 정하는 일은 따로 해야 합니다.
이것으로 13장을 마칩니다. 다음 장의 Box<T>와 힙 데이터부터는 힙의 데이터를 가리키는 스마트 포인터를 살펴봅니다.