cargo-bazel 0.18.0

A collection of tools which use Cargo to generate build targets for Bazel
Documentation
//! Common utilities

pub(crate) mod starlark;
pub(crate) mod symlink;
pub(crate) mod target_triple;

pub(crate) const CRATES_IO_INDEX_URL: &str = "https://github.com/rust-lang/crates.io-index";

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

/// Convert a string into a valid crate module name by applying transforms to invalid characters
pub(crate) fn sanitize_module_name(name: &str) -> String {
    name.replace('-', "_")
}

/// Some character which may be present in version IDs are not valid
/// in Bazel repository names. This converts invalid characters. See
/// [RepositoryName.java](https://github.com/bazelbuild/bazel/blob/4.0.0/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java#L42)
pub(crate) fn sanitize_repository_name(name: &str) -> String {
    name.replace('+', "-")
}

/// Vendored crates are generated by cargo itself in `src/metadata.rs` in the
/// `VendorGenerator::generate()` method.  This means that the semver metadata will
/// always contain a (+) symbol, which is not compatible with bazel's labels.
/// This function will rename the cargo vendor generated file paths to be compatible with bazel
/// labels by simply replacing the (+) with a (-). If this file is called by any other cli mod,
/// it just simply joins the out dir to the path
pub(crate) fn normalize_cargo_file_paths(
    outputs: BTreeMap<PathBuf, String>,
    out_dir: &Path,
) -> BTreeMap<PathBuf, String> {
    outputs
        .into_iter()
        .map(|(path, content)| {
            // Get Path Str and Parent Path Str so we can rename the root file
            let original_path_str = path.to_str().expect("All file paths should be strings");
            let original_parent_path_str = path
                .parent()
                .expect("Should have parent")
                .to_str()
                .expect("All file paths should be strings");

            let path = if original_parent_path_str.contains('+') {
                let new_parent_file_path = sanitize_repository_name(original_parent_path_str);
                std::fs::rename(
                    out_dir.join(original_parent_path_str),
                    out_dir.join(new_parent_file_path),
                )
                .expect("Could not rename paths");
                PathBuf::from(&original_path_str.replace('+', "-"))
            } else {
                path
            };

            // In recent versions of Bazel, canonical repository paths may contain (+)
            // symbols so it is important to apply the transformation only to `outputs`
            // and leave `out_dir` untouched.
            let path = out_dir.join(path);
            (path, content)
        })
        .collect()
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_sanitize_repository_name() {
        let name = "anyhow-1.0.0+semver_meta";
        let got = sanitize_repository_name(name);
        assert_eq!(got, String::from("anyhow-1.0.0-semver_meta"));
    }

    #[test]
    fn test_sanitize_repository_name_no_change() {
        let name = "tokio-1.20.0";
        let got = sanitize_repository_name(name);
        assert_eq!(got, String::from("tokio-1.20.0"));
    }

    #[test]
    fn test_normalize_cargo_file_paths() {
        let mut outputs = BTreeMap::new();
        outputs.insert(
            PathBuf::from("libbpf-sys-1.3.0+v1.3.0/BUILD.bazel"),
            "contents".into(),
        );

        let outdir = tempfile::tempdir().unwrap();
        // create dir to mimic cargo vendor
        std::fs::create_dir_all(outdir.path().join("libbpf-sys-1.3.0+v1.3.0")).unwrap();

        let got = normalize_cargo_file_paths(outputs, outdir.path());
        for output in got.into_keys() {
            assert!(!output.to_str().unwrap().contains('+'));
        }
    }

    #[test]
    fn test_normalize_cargo_file_paths_no_rename() {
        let mut outputs = BTreeMap::new();
        outputs.insert(PathBuf::from("tokio-1.30.0/BUILD.bazel"), "contents".into());

        let outdir = tempfile::tempdir().unwrap();

        let got = normalize_cargo_file_paths(outputs, outdir.path());
        for output in got.into_keys() {
            assert!(!output.to_str().unwrap().contains('+'));
        }
    }
}