ABOUT ME

-

Total
-
  • Rust: cargo something 만들기
    컴퓨터/Rust 2023. 3. 28. 10:06
    728x90
    반응형

    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)
    }

     

    참고

    @Cargo 공식 문서

     

    728x90

    '컴퓨터 > Rust' 카테고리의 다른 글

    Rust: 한글 종성 유니코드 변경하기  (0) 2023.04.17
    Rust: 카카오 Karlo API wrapper  (0) 2023.03.24
    Rust: AsMut, AsRef, Deref, DerefMut 정리  (1) 2022.12.31

    댓글