본문 바로가기

Cargo 커스텀 명령어

cargo 이름 규칙을 따르는 실행 파일로 사용자 정의 하위 명령을 만들고 팀 자동화 도구의 배포·호환성·오류 처리를 검증합니다.

프로젝트 규모가 커지면 반복 작업을 줄이는 자동화가 생산성에 큰 차이를 만듭니다.

Cargo의 확장 명령어 구조는 이 자동화를 러스트 생태계에 자연스럽게 붙일 수 있게 해 줍니다.

처음에는 cargo-something 규칙이 단순 별칭처럼 보일 수 있지만, 실제로는 팀 표준 작업 절차를 고정하는 데 매우 유용합니다.

이 절은 Cargo 확장을 편의 기능으로만 보지 않고, 개발 워크플로를 일관되게 만드는 실전 수단으로 이해하도록 돕습니다.

Cargo는 직접 Cargo를 수정하지 않고도 새로운 보조 명령어로 확장할 수 있게끔 설계되어 있습니다.

만약 $PATH에 있는 어떤 바이너리의 이름이 cargo-something이라면, cargo something이라는 명령어로 마치 Cargo의 보조 명령어인 것처럼 실행할 수 있습니다.

이와 같은 커스텀 명령어들은 cargo --list를 실행할 때의 목록에도 포함됩니다.

cargo install을 이용해 확장 모듈을 설치한 다음 Cargo의 기본 제공 도구처럼 이용할 수 있다는 점은 Cargo 설계에서 무척 편리한 장점입니다!

Cargo 확장은 실행 파일 이름, 셸의 검색 경로, Cargo의 하위 명령 변환 규칙이 맞물릴 때 자연스럽게 동작합니다.

작은 도움말 명령 만들기

cargo foo --help와 실행 파일을 직접 부르는 경우에 어떤 인수가 다른지 확인해 보겠습니다. 작업 중인 프로젝트와 분리한 빈 예제 폴더를 준비하고, 다음 전체 소스를 cargo-foo.rs로 저장하세요. 이미 rustc와 cargo를 사용할 수 있는 환경을 전제로 하며, 이 소스는 Rust 1.61 이상과 edition 2021을 기준으로 합니다.

11장에서 배운 실행 인수를 읽고, 도움말은 stdout과 성공 종료로, 요청한 실패는 stderr와 종료값 2로 표현합니다. ExitCode는 main이 부모 프로세스에 돌려줄 정상 종료 상태입니다.

cargo-foo.rs
use std::env;
use std::process::ExitCode;

fn main() -> ExitCode {
    let mut args: Vec<String> = env::args().skip(1).collect();
    println!("args={:?}", args);
    if args.first().map(String::as_str) == Some("foo") {
        args.remove(0);
    }
    match args.as_slice() {
        [option] if option == "--help" => {
            println!("usage: cargo foo --help | --fail");
            ExitCode::SUCCESS
        }
        [option] if option == "--fail" => {
            eprintln!("error: requested failure");
            ExitCode::from(2)
        }
        _ => {
            eprintln!("error: use --help or --fail");
            ExitCode::from(2)
        }
    }
}

args()의 첫 항목은 실행 파일 이름이므로 skip(1)로 제외합니다. 나머지 인수를 먼저 출력한 뒤 첫 값이 foo라면 제거합니다. 이 제거는 Cargo 호출과 직접 호출을 함께 받으려는 이 도구의 입력 정책입니다. --help는 사용법을 출력하고, --fail은 실패 메시지를 출력한 뒤 2를 반환합니다.

Unix에서 준비하고 호출하기

예제 폴더에서 다음 명령으로 실행 파일을 만듭니다. --crate-name cargo_foo는 Rust 크레이트 이름이고, Cargo가 찾을 실행 파일 이름은 하이픈이 들어간 cargo-foo입니다.

mkdir bin
rustc --edition=2021 --crate-name cargo_foo cargo-foo.rs -o bin/cargo-foo
echo "$?"

rustc 바로 다음의 echo "$?"가 0이고 bin/cargo-foo가 만들어졌을 때만 아래 호출로 진행하세요. 컴파일이 실패하면 먼저 진단을 고치고 다시 컴파일합니다. 이전에 만든 실행 파일의 결과로 새 소스가 반영됐다고 판단하지 않습니다.

PATH="$PWD/bin:$PATH" cargo foo --help
echo "$?"
./bin/cargo-foo --help
echo "$?"
PATH="$PWD/bin:$PATH" cargo foo --fail
echo "$?"

앞에 쓴 PATH 값은 해당 Cargo 명령에만 적용됩니다. 각 호출 바로 뒤의 echo "$?"는 그 호출의 종료값을 보여 줍니다.

Windows PowerShell에서 준비하고 호출하기

Windows에서는 실행 파일 이름을 cargo-foo.exe로 만듭니다.

New-Item -ItemType Directory -Path bin
rustc --edition=2021 --crate-name cargo_foo cargo-foo.rs -o bin\cargo-foo.exe
if ($LASTEXITCODE -ne 0) { throw 'cargo-foo build failed' }

컴파일이 성공해 bin\cargo-foo.exe가 생긴 뒤 다음 블록을 실행하세요. PATH는 현재 PowerShell 프로세스에서만 바꾸고, 호출이 끝나거나 오류가 나면 finally에서 원래 값으로 되돌립니다.

$fooPreviousPath = $env:PATH
try {
    $fooBinPath = Join-Path (Get-Location) 'bin'
    $env:PATH = $fooBinPath + [IO.Path]::PathSeparator + $fooPreviousPath
    cargo foo --help
    $LASTEXITCODE
    & .\bin\cargo-foo.exe --help
    $LASTEXITCODE
    cargo foo --fail
    $LASTEXITCODE
} finally {
    $env:PATH = $fooPreviousPath
}

$LASTEXITCODE는 바로 앞의 외부 프로그램이 반환한 종료값입니다. 위 두 운영체제의 예제는 foo라는 기본 명령·별칭·다른 cargo-foo와 충돌하지 않는 환경을 전제로 합니다. Cargo는 기본적으로 $CARGO_HOME/bin을 PATH보다 먼저 찾으며, 이 디렉터리를 PATH에 넣으면 PATH 순서를 따릅니다. 따라서 예제의 bin을 앞에 넣었다는 사실만으로 모든 환경에서 같은 실행 파일이 선택된다고 보장할 수는 없습니다.

같은 도구의 세 입력 비교

아래는 위 소스를 읽어 예상할 수 있는 결과입니다. 인수 목록은 코드의 println!이 stdout에 먼저 출력합니다.

호출stdout·stderr의 내용종료값
cargo foo --helpstdout: args=["foo", "--help"], 이어서 usage: cargo foo --help | --fail; stderr: 없음0
직접 cargo-foo --helpstdout: args=["--help"], 이어서 usage: cargo foo --help | --fail; stderr: 없음0
cargo foo --failstdout: args=["foo", "--fail"]; stderr: error: requested failure2

Cargo 호출에는 foo가 한 항목 더 있습니다. 실행 파일 이름인 첫 인수의 실제 경로나 철자는 이 표에서 고정하지 않습니다. stdout과 stderr를 한 화면에 합쳐 볼 때 두 채널의 표시 시간 순서도 가정하지 않습니다.

인수 목록이나 메시지가 예상과 다르면 실행 파일 이름과 선택된 경로, 같은 이름의 별칭·도구가 있는지 먼저 확인하세요. 도구가 정상적으로 2를 반환한 경우와 Cargo가 실행 파일을 찾거나 시작하지 못해 101로 끝난 경우는 실패 위치가 다릅니다. 신호에 의한 종료나 panic은 위 정상 반환 표의 범위 밖입니다. env::args()는 비Unicode 인수에서 panic할 수 있으므로 그런 입력을 받을 도구라면 11장의 args_os() 구분도 적용해야 합니다.

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-foo 파일로 탐색하고 foo와 help 인수를 전달한다. metadata 호출은 도구가 원할 때 사용하는 별도 통합이다.
cargo foo --help사용자의 하위 명령
연결 관계cargo foo --help · 사용자의 하위 명령 → cargo-foo 탐색 · Cargo home/bin · PATH
cargo-foo 탐색Cargo home/bin · PATH
연결 관계cargo-foo 탐색 · Cargo home/bin · PATH → 외부 실행 파일 · foo · --help 전달
외부 실행 파일foo · --help 전달
입력 명령에서 발견한 실행 파일의 인수로원 desktop과같은세노드두진행간선이다. cargo foo --help 입력→이름cargo-foo와실행경로탐색→발견한프로그램실행및foo/--help전달이다. 실제argv0철자는고정하지않는다. cargo --list의발견은실행이아니고 metadata통합은선택사항이다. 별칭/내장명령/동명이름/PATH/CARGOHOME우선순위와Unicode/종료조건은본문보조설명을따른다.
입력cargo foo --help
연결 관계입력 · cargo foo --help → 이름 · 경로 탐색 · cargo-foo · 기본 명령·별칭 구별
이름 · 경로 탐색cargo-foo기본 명령·별칭 구별
연결 관계이름 · 경로 탐색 · cargo-foo · 기본 명령·별칭 구별 → 발견한 프로그램 실행 · 인수: foo / --help · 나머지 인수 그대로
발견한 프로그램 실행인수: foo / --help나머지 인수 그대로

입력 · 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 --help

cargo 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로 외부 명령을 요청한다.

  • 입력: foo / --help
  • 탐색 이름: cargo-foo
  • Cargo가 외부 명령 이름과 실행 파일을 연결한다.
외부 실행

찾은 cargo-foo에 인수를 전달한다.

  • 실행 대상: cargo-foo
  • 전달: foo, --help
  • 외부 subcommand의 인수 경계를 기존 Cargo 내부 명령과 구분한다.
결과 반환

외부 명령의 종료 결과가 돌아온다.

  • 산출물: 반환 코드
  • metadata: 선택적 별도 호출
  • metadata 호출을 모든 외부 명령의 필수 단계로 만들지 않는다.

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-foo 파일로 탐색하고 foo와 help 인수를 전달한다. metadata 호출은 도구가 원할 때 사용하는 별도 통합이다.
cargo foo --help사용자의 하위 명령
연결 관계cargo foo --help · 사용자의 하위 명령 → cargo-foo 탐색 · Cargo home/bin · PATH
cargo-foo 탐색Cargo home/bin · PATH
연결 관계cargo-foo 탐색 · Cargo home/bin · PATH → 외부 실행 파일 · foo · --help 전달
외부 실행 파일foo · --help 전달
입력 명령에서 발견한 실행 파일의 인수로원 desktop과같은세노드두진행간선이다. cargo foo --help 입력→이름cargo-foo와실행경로탐색→발견한프로그램실행및foo/--help전달이다. 실제argv0철자는고정하지않는다. cargo --list의발견은실행이아니고 metadata통합은선택사항이다. 별칭/내장명령/동명이름/PATH/CARGOHOME우선순위와Unicode/종료조건은본문보조설명을따른다.
입력cargo foo --help
연결 관계입력 · cargo foo --help → 이름 · 경로 탐색 · cargo-foo · 기본 명령·별칭 구별
이름 · 경로 탐색cargo-foo기본 명령·별칭 구별
연결 관계이름 · 경로 탐색 · cargo-foo · 기본 명령·별칭 구별 → 발견한 프로그램 실행 · 인수: foo / --help · 나머지 인수 그대로
발견한 프로그램 실행인수: foo / --help나머지 인수 그대로

입력 · 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 --help

cargo 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로 외부 명령을 요청한다.

  • 입력: foo / --help
  • 탐색 이름: cargo-foo
  • Cargo가 외부 명령 이름과 실행 파일을 연결한다.
외부 실행

찾은 cargo-foo에 인수를 전달한다.

  • 실행 대상: cargo-foo
  • 전달: foo, --help
  • 외부 subcommand의 인수 경계를 기존 Cargo 내부 명령과 구분한다.
결과 반환

외부 명령의 종료 결과가 돌아온다.

  • 산출물: 반환 코드
  • metadata: 선택적 별도 호출
  • metadata 호출을 모든 외부 명령의 필수 단계로 만들지 않는다.

실무 적용 체크포인트

Cargo 확장을 팀 워크플로에 적용할 때는 아래 항목을 함께 관리하는 것이 좋습니다.

  • cargo-<name> 명명 규칙과 목적(빌드/검증/배포)을 문서에 명시하기
  • CI 환경과 로컬 개발 환경에서 동일하게 실행되는지 확인하기
  • 실패 시 표준 cargo 명령으로 우회 가능한 절차를 준비하기
  • 도구 버전 고정 전략(Cargo.lock, 설치 버전 정책)까지 함께 관리하기

팀 표준 도입 순서

커스텀 명령어를 팀에 도입할 때는 기능 추가보다 운영 안정성을 먼저 고정하는 것이 좋습니다.

  1. 반복되는 수동 명령(예: 빌드+검증+포맷)을 먼저 한 줄 워크플로로 정리합니다.
  2. cargo-<name> 명령의 입력/출력 규칙을 README에 문서화합니다.
  3. 로컬과 CI에서 동일한 결과가 나오는지 최소 시나리오를 먼저 검증합니다.
  4. 실패 시 표준 Cargo 명령으로 즉시 우회 가능한 경로를 유지합니다.

최소 운영 검증 시나리오

위 예제의 도움말·실패 입력처럼 stdout, stderr, 종료값을 나눠 기록하고, 실제 팀 명령에서도 입력마다 기대 결과를 정해 보세요.

도입 직후에는 아래 시나리오를 통과시키면 회귀를 빠르게 줄일 수 있습니다.

  • 빈 작업 공간에서 명령 실행 시 의도한 안내 메시지를 출력하는가
  • 잘못된 인자 입력 시 종료 코드와 오류 메시지가 일관적인가
  • CI 환경에서 동일 명령이 성공/실패 기준을 동일하게 따르는가
  • 새 팀원이 문서만 보고 5분 내에 실행 경로를 재현할 수 있는가

적용 범위 구분

Cargo 확장을 도입할 때는 모든 작업을 한 번에 감싸기보다, 영향도가 큰 반복 작업부터 범위를 좁혀 적용하는 편이 안전합니다.

  • 빌드/테스트/포맷처럼 공통 작업은 확장 명령으로 고정
  • 프로젝트별 특수 배포 로직은 일반 cargo 명령과 분리
  • 팀 합의가 끝나기 전에는 기존 경로를 병행 유지

배포 전 검증 메모

커스텀 명령어를 외부 팀에 공유할 때는 실행 편의보다 실패 동작의 일관성을 먼저 점검해야 합니다.

  • 잘못된 인자 입력 시 종료 코드와 오류 메시지가 표준 규칙을 따르는지 확인합니다.
  • 필수 환경 변수 누락 시 즉시 실패하고 복구 방법을 안내하는지 점검합니다.
  • CI 로그에서 실패 원인을 추적할 수 있도록 단계별 출력 형식을 고정합니다.

이 검증 항목을 먼저 고정해 두면, 커스텀 명령어 수가 늘어나더라도 운영 복잡도를 예측 가능한 범위로 관리할 수 있습니다.

특히 장애 대응 시 명령어별 실패 원인을 빠르게 분리하는 데 도움이 됩니다.


Cargo 확장 정리

Cargo와 crates.io를 통해 도구와 코드를 공유하는 흐름은 러스트 생태계를 실무 친화적으로 확장하는 핵심 기반입니다.

러스트의 기본 라이브러리는 작고 고정되어 있지만, 크레이트들은 쉽게 공유될 수 있고, 쉽게 사용될 수 있으며 러스트 언어 자체보다 훨씬 빠른 속도로 발전합니다.

여러분에게 유용한 코드가 있다면 주저 말고 crates.io에 공유하세요.

분명 다른 누군가에게도 도움이 될 테니까요!

Cargo 커스텀 명령어는 이름 규칙, PATH 배치, 입력 계약, 팀 표준, 검증 시나리오를 함께 정해야 자동화 도구로 안정화됩니다.


커스텀 명령어로 Cargo 확장하기 학습 판단 기준표

커스텀 명령어로 Cargo를 확장할 때 컴파일러가 막는 지점과 테스트로 확인할 지점을 분리해 봅니다.