-
Rust: cargo something 만들기컴퓨터/Rust 2023. 3. 28. 10:06728x90반응형
cargo-subcommand
소개
cargo inspect cargo depsize cargo korea
cargo 기본 옵션에 없는 위와 같은 옵션은 어떻게 만들까?
cargo-subcommand라고도 불리며 Cargo를 수정하지 않고 새 하위 명령어를 추가할 수 있도록 설계되었다.
$PATH에 있는 바이너리 파일의 이름이 cargo-something으로 지정되면 cargo something처럼 실행하여
마치 Cargo 하위 명령어인 것처럼 사용할 수 있다.
이러한 사용자 정의 명령어는 cargo --list를 실행할 때 나열된다.
이 글에서는 실제로 만들어보고 사용하는 과정을 적었다. @코드 바로 보기
만들 cargo-depsize는 Cargo 패키지 매니저를 사용하여 현재 Rust workspace에서 각 의존성 패키지의 크기와 모든 의존성의 총 크기를 계산하고 표시하고 tokio::fs 라이브러리를 비롯한 Rust 언어 구조와 cargo 및 std 라이브러리의 특성, 구조체, 함수를 사용하여 비동기 프로그래밍을 구현해 봤다.
메인 함수는 Config 객체를 초기화하고 Cargo.toml 파일을 찾아 Workspace 객체를 생성한 다음 의존성 크기를 계산하는 함수를 호출하고, 이 함수는 ignore 라이브러리를 사용하여 파일 시스템을 탐색하여 각 패키지의 크기를 계산하며 크기 값을 인간이 이해할 수 있는 문자열로 포맷하는 사용자 지정 함수이다.
마지막으로, 계산된 패키지 크기와 모든 의존성의 총크기가 표시된다.
cargo-depsize
일단 새 프로젝트를 만들 때 binary이며 cargo-로 시작하는 이름으로 만들어야 한다.
cargo new cargo-depsize --bin
Async로 하고 싶어서 tokio를 이용했다. cargo depsize를 하면 main 함수가 실행된다.
#[tokio::main] async fn main() { let result = run().await; if let Err(err) = result { eprintln!("Error: {:?}", err); process::exit(1); } } async fn run() -> Result<()> { let config = Config::default()?; // Locate the Cargo.toml let manifest_path = find_root_manifest_for_wd(&env::current_dir()?)?; // Create a workspace from the Cargo.toml let workspace = Workspace::new(&manifest_path, &config)?; // Calculate and display the total size of each dependency calculate_and_display_depsize(&workspace).await?; Ok(()) }
핵심 부분인 dependencies 들의 크기를 계산하는 코드이다.
이 함수는 주어진 Rust 작업공간의 모든 종속성의 크기를 비동기적으로 계산하고, 각 종속성의 크기와 종속성의 총크기를 출력한다.
- RustcTargetData와 CliFeatures를 생성하여 작업공간과의 종속성을 확인
- cargo::ops::resolve_ws_with_opts를 사용하여 작업공간의 종속성 그래프 얻기
- 작업공간의 패키지 목록에서 각 패키지의 크기를 계산하고, package_sizes 해시맵에 저장
- 현재 작업공간의 루트 패키지와 그 종속성을 불러오기
- 루트 종속성 목록에서 각 종속성의 최신 버전의 패키지 ID를 찾고, 패키지 크기를 계산한 후, 총합에 추가
- 각 종속성의 이름과 버전, 그리고 패키지 크기를 출력
- 모든 종속성의 총 크기를 출력
async fn calculate_and_display_depsize(workspace: &Workspace<'_>) -> Result<()> { // Obtain dependency graph // let requested_targets: Vec<CompileKind> = vec![]; let target_data = RustcTargetData::new(workspace, &[])?; let cli_features = CliFeatures::new_all(true); //let specs: Vec<cargo::core::PackageIdSpec> = vec![]; let has_dev_units = HasDevUnits::Yes; let force_all_targets = ForceAllTargets::Yes; let workspace_resolve = cargo::ops::resolve_ws_with_opts( workspace, &target_data, &[], // requested_targets &cli_features, &[], // specs has_dev_units, force_all_targets, )?; let packages = workspace_resolve.pkg_set.packages(); // let resolve = workspace_resolve.workspace_resolve; let mut package_sizes = HashMap::<PackageId, u64>::new(); for package in packages.into_iter() { let size = calculate_package_size(package).await?; package_sizes.insert(package.package_id().clone(), size); } let root_package = workspace.current()?; let root_deps = root_package .dependencies() .iter() .filter(|dep| dep.kind() == DepKind::Normal); let package_set = &workspace_resolve.pkg_set; let mut sum = 0; for dep in root_deps { let package_name = dep.package_name(); let mut latest_package_id: Option<PackageId> = None; for package_id in package_set .package_ids() .filter(|id| id.name() == package_name) { latest_package_id = match latest_package_id { Some(current_latest) => Some(if package_id.version() > current_latest.version() { package_id } else { current_latest }), None => Some(package_id), }; } let package_id = latest_package_id.unwrap(); let package = package_set.get_one(package_id)?; let size = calculate_package_size(package).await?; sum += size; let name_ver = format!("{} (v{})", dep.name_in_toml(), package_id.version()); println!("{: <25} : {}", name_ver, format_size(size)); } println!("> Total size: {}", format_size(sum)); Ok(()) }
I/O 작업은 ignore + tokio::fs를 사용해서 async로 처리해 보았다.
async fn calculate_package_size(package: &cargo::core::Package) -> Result<u64> { let package_path = package.root(); let walker = ignore::WalkBuilder::new(package_path).build(); let mut total_size = 0; for entry in walker { match entry { Ok(entry) => { if entry.file_type().unwrap().is_file() { let metadata = fs::metadata(entry.path()).await?; total_size += metadata.len(); } } Err(err) => eprintln!("Error: {}", err), } } Ok(total_size) }
참고
728x90'컴퓨터 > Rust' 카테고리의 다른 글
Rust: 한글 종성 유니코드 변경하기 (0) 2023.04.17 Rust: 카카오 Karlo API wrapper (0) 2023.03.24 Rust: AsMut, AsRef, Deref, DerefMut 정리 (1) 2022.12.31