Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/crate_universe/src/context/crate_context.rs b/crate_universe/src/context/crate_context.rs
new file mode 100644
index 0000000..0278ebe
--- /dev/null
+++ b/crate_universe/src/context/crate_context.rs
@@ -0,0 +1,838 @@
+//! Crate specific information embedded into [crate::context::Context] objects.
+
+use std::collections::{BTreeMap, BTreeSet};
+
+use cargo_metadata::{Node, Package, PackageId};
+use serde::{Deserialize, Serialize};
+
+use crate::config::CrateId;
+use crate::metadata::{CrateAnnotation, Dependency, PairredExtras, SourceAnnotation};
+use crate::utils::sanitize_module_name;
+use crate::utils::starlark::{Glob, SelectList, SelectMap, SelectStringDict, SelectStringList};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+pub struct CrateDependency {
+    /// The [CrateId] of the dependency
+    pub id: CrateId,
+
+    /// The target name of the dependency. Note this may differ from the
+    /// dependency's package name in cases such as build scripts.
+    pub target: String,
+
+    /// Some dependencies are assigned aliases. This is tracked here
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub alias: Option<String>,
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+#[serde(default)]
+pub struct TargetAttributes {
+    /// The module name of the crate (notably, not the package name).
+    pub crate_name: String,
+
+    /// The path to the crate's root source file, relative to the manifest.
+    pub crate_root: Option<String>,
+
+    /// A glob pattern of all source files required by the target
+    pub srcs: Glob,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+pub enum Rule {
+    /// `cargo_build_script`
+    BuildScript(TargetAttributes),
+
+    /// `rust_proc_macro`
+    ProcMacro(TargetAttributes),
+
+    /// `rust_library`
+    Library(TargetAttributes),
+
+    /// `rust_binary`
+    Binary(TargetAttributes),
+}
+
+/// A set of attributes common to most `rust_library`, `rust_proc_macro`, and other
+/// [core rules of `rules_rust`](https://bazelbuild.github.io/rules_rust/defs.html).
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+#[serde(default)]
+pub struct CommonAttributes {
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub compile_data: SelectStringList,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub compile_data_glob: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub crate_features: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub data: SelectStringList,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub data_glob: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub deps: SelectList<CrateDependency>,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub extra_deps: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub deps_dev: SelectList<CrateDependency>,
+
+    pub edition: String,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub linker_script: Option<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub proc_macro_deps: SelectList<CrateDependency>,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub extra_proc_macro_deps: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub proc_macro_deps_dev: SelectList<CrateDependency>,
+
+    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    pub rustc_env: SelectStringDict,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub rustc_env_files: SelectStringList,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub rustc_flags: SelectStringList,
+
+    pub version: String,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub tags: Vec<String>,
+}
+
+impl Default for CommonAttributes {
+    fn default() -> Self {
+        Self {
+            compile_data: Default::default(),
+            // Generated targets include all files in their package by default
+            compile_data_glob: BTreeSet::from(["**".to_owned()]),
+            crate_features: Default::default(),
+            data: Default::default(),
+            data_glob: Default::default(),
+            deps: Default::default(),
+            extra_deps: Default::default(),
+            deps_dev: Default::default(),
+            edition: Default::default(),
+            linker_script: Default::default(),
+            proc_macro_deps: Default::default(),
+            extra_proc_macro_deps: Default::default(),
+            proc_macro_deps_dev: Default::default(),
+            rustc_env: Default::default(),
+            rustc_env_files: Default::default(),
+            rustc_flags: Default::default(),
+            version: Default::default(),
+            tags: Default::default(),
+        }
+    }
+}
+
+// Build script attributes. See
+// https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+#[serde(default)]
+pub struct BuildScriptAttributes {
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub compile_data: SelectStringList,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub data: SelectStringList,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub data_glob: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub deps: SelectList<CrateDependency>,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub extra_deps: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    pub build_script_env: SelectStringDict,
+
+    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+    pub extra_proc_macro_deps: BTreeSet<String>,
+
+    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    pub proc_macro_deps: SelectList<CrateDependency>,
+
+    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    pub rustc_env: SelectStringDict,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub rustc_flags: SelectStringList,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub rustc_env_files: SelectStringList,
+
+    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    pub tools: SelectStringList,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub links: Option<String>,
+}
+
+impl Default for BuildScriptAttributes {
+    fn default() -> Self {
+        Self {
+            compile_data: Default::default(),
+            data: Default::default(),
+            // Build scripts include all sources by default
+            data_glob: BTreeSet::from(["**".to_owned()]),
+            deps: Default::default(),
+            extra_deps: Default::default(),
+            build_script_env: Default::default(),
+            extra_proc_macro_deps: Default::default(),
+            proc_macro_deps: Default::default(),
+            rustc_env: Default::default(),
+            rustc_flags: Default::default(),
+            rustc_env_files: Default::default(),
+            tools: Default::default(),
+            links: Default::default(),
+        }
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+#[serde(default)]
+pub struct CrateContext {
+    /// The package name of the current crate
+    pub name: String,
+
+    /// The full version of the current crate
+    pub version: String,
+
+    /// Optional source annotations if they were discoverable in the
+    /// lockfile. Workspace Members will not have source annotations and
+    /// potentially others.
+    pub repository: Option<SourceAnnotation>,
+
+    /// A list of all targets (lib, proc-macro, bin) associated with this package
+    pub targets: Vec<Rule>,
+
+    /// The name of the crate's root library target. This is the target that a dependent
+    /// would get if they were to depend on `{crate_name}`.
+    pub library_target_name: Option<String>,
+
+    /// A set of attributes common to most [Rule] types or target types.
+    pub common_attrs: CommonAttributes,
+
+    /// Optional attributes for build scripts. This field is only populated if
+    /// a build script (`custom-build`) target is defined for the crate.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub build_script_attrs: Option<BuildScriptAttributes>,
+
+    /// The license used by the crate
+    pub license: Option<String>,
+
+    /// Additional text to add to the generated BUILD file.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub additive_build_file_content: Option<String>,
+}
+
+impl CrateContext {
+    pub fn new(
+        annotation: &CrateAnnotation,
+        packages: &BTreeMap<PackageId, Package>,
+        source_annotations: &BTreeMap<PackageId, SourceAnnotation>,
+        extras: &BTreeMap<CrateId, PairredExtras>,
+        include_build_scripts: bool,
+    ) -> Self {
+        let package: &Package = &packages[&annotation.node.id];
+        let current_crate_id = CrateId::new(package.name.clone(), package.version.to_string());
+
+        let new_crate_dep = |dep: Dependency| -> CrateDependency {
+            let pkg = &packages[&dep.package_id];
+
+            // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
+            // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
+            // content to align when rendering, the dependency target needs to be explicitly sanitized.
+            let target = sanitize_module_name(&dep.target_name);
+
+            CrateDependency {
+                id: CrateId::new(pkg.name.clone(), pkg.version.to_string()),
+                target,
+                alias: dep.alias,
+            }
+        };
+
+        // Convert the dependencies into renderable strings
+        let deps = annotation.deps.normal_deps.clone().map(new_crate_dep);
+        let deps_dev = annotation.deps.normal_dev_deps.clone().map(new_crate_dep);
+        let proc_macro_deps = annotation.deps.proc_macro_deps.clone().map(new_crate_dep);
+        let proc_macro_deps_dev = annotation
+            .deps
+            .proc_macro_dev_deps
+            .clone()
+            .map(new_crate_dep);
+
+        // Gather all "common" attributes
+        let mut common_attrs = CommonAttributes {
+            crate_features: annotation.node.features.iter().cloned().collect(),
+            deps,
+            deps_dev,
+            edition: package.edition.clone(),
+            proc_macro_deps,
+            proc_macro_deps_dev,
+            version: package.version.to_string(),
+            ..Default::default()
+        };
+
+        let include_build_scripts =
+            Self::crate_includes_build_script(package, extras, include_build_scripts);
+
+        // Iterate over each target and produce a Bazel target for all supported "kinds"
+        let targets = Self::collect_targets(&annotation.node, packages, include_build_scripts);
+
+        // Parse the library crate name from the set of included targets
+        let library_target_name = {
+            let lib_targets: Vec<&TargetAttributes> = targets
+                .iter()
+                .filter_map(|t| match t {
+                    Rule::ProcMacro(attrs) => Some(attrs),
+                    Rule::Library(attrs) => Some(attrs),
+                    _ => None,
+                })
+                .collect();
+
+            // TODO: There should only be at most 1 library target. This case
+            // should be handled in a more intelligent way.
+            assert!(lib_targets.len() <= 1);
+            lib_targets
+                .iter()
+                .last()
+                .map(|attr| attr.crate_name.clone())
+        };
+
+        // Gather any build-script related attributes
+        let build_script_target = targets.iter().find_map(|r| match r {
+            Rule::BuildScript(attr) => Some(attr),
+            _ => None,
+        });
+
+        let build_script_attrs = if let Some(target) = build_script_target {
+            // Track the build script dependency
+            common_attrs.deps.insert(
+                CrateDependency {
+                    id: current_crate_id,
+                    target: target.crate_name.clone(),
+                    alias: None,
+                },
+                None,
+            );
+
+            let build_deps = annotation.deps.build_deps.clone().map(new_crate_dep);
+            let build_proc_macro_deps = annotation
+                .deps
+                .build_proc_macro_deps
+                .clone()
+                .map(new_crate_dep);
+
+            Some(BuildScriptAttributes {
+                deps: build_deps,
+                proc_macro_deps: build_proc_macro_deps,
+                links: package.links.clone(),
+                ..Default::default()
+            })
+        } else {
+            None
+        };
+
+        // Save the repository information for the current crate
+        let repository = source_annotations.get(&package.id).cloned();
+
+        // Identify the license type
+        let license = package.license.clone();
+
+        // Create the crate's context and apply extra settings
+        CrateContext {
+            name: package.name.clone(),
+            version: package.version.to_string(),
+            repository,
+            targets,
+            library_target_name,
+            common_attrs,
+            build_script_attrs,
+            license,
+            additive_build_file_content: None,
+        }
+        .with_overrides(extras)
+    }
+
+    fn with_overrides(mut self, extras: &BTreeMap<CrateId, PairredExtras>) -> Self {
+        let id = CrateId::new(self.name.clone(), self.version.clone());
+
+        // Insert all overrides/extras
+        if let Some(pairred_override) = extras.get(&id) {
+            let crate_extra = &pairred_override.crate_extra;
+
+            // Deps
+            if let Some(extra) = &crate_extra.deps {
+                self.common_attrs.extra_deps = extra.clone();
+            }
+
+            // Proc macro deps
+            if let Some(extra) = &crate_extra.proc_macro_deps {
+                self.common_attrs.extra_proc_macro_deps = extra.clone();
+            }
+
+            // Compile data
+            if let Some(extra) = &crate_extra.compile_data {
+                for data in extra.iter() {
+                    self.common_attrs.compile_data.insert(data.clone(), None);
+                }
+            }
+
+            // Compile data glob
+            if let Some(extra) = &crate_extra.compile_data_glob {
+                self.common_attrs.compile_data_glob.extend(extra.clone());
+            }
+
+            // Crate features
+            if let Some(extra) = &crate_extra.crate_features {
+                for data in extra.iter() {
+                    self.common_attrs.crate_features.insert(data.clone());
+                }
+            }
+
+            // Data
+            if let Some(extra) = &crate_extra.data {
+                for data in extra.iter() {
+                    self.common_attrs.data.insert(data.clone(), None);
+                }
+            }
+
+            // Data glob
+            if let Some(extra) = &crate_extra.data_glob {
+                self.common_attrs.data_glob.extend(extra.clone());
+            }
+
+            // Rustc flags
+            // TODO: SelectList is currently backed by `BTreeSet` which is generally incorrect
+            // for rustc flags. Should SelectList be refactored?
+            if let Some(extra) = &crate_extra.rustc_flags {
+                for data in extra.iter() {
+                    self.common_attrs.rustc_flags.insert(data.clone(), None);
+                }
+            }
+
+            // Rustc env
+            if let Some(extra) = &crate_extra.rustc_env {
+                self.common_attrs.rustc_env.insert(extra.clone(), None);
+            }
+
+            // Rustc env files
+            if let Some(extra) = &crate_extra.rustc_env_files {
+                for data in extra.iter() {
+                    self.common_attrs.rustc_env_files.insert(data.clone(), None);
+                }
+            }
+
+            // Build script Attributes
+            if let Some(attrs) = &mut self.build_script_attrs {
+                // Deps
+                if let Some(extra) = &crate_extra.build_script_deps {
+                    attrs.extra_deps = extra.clone();
+                }
+
+                // Proc macro deps
+                if let Some(extra) = &crate_extra.build_script_proc_macro_deps {
+                    attrs.extra_proc_macro_deps = extra.clone();
+                }
+
+                // Data
+                if let Some(extra) = &crate_extra.build_script_data {
+                    for data in extra {
+                        attrs.data.insert(data.clone(), None);
+                    }
+                }
+
+                // Data glob
+                if let Some(extra) = &crate_extra.build_script_data_glob {
+                    attrs.data_glob.extend(extra.clone());
+                }
+
+                // Rustc env
+                if let Some(extra) = &crate_extra.build_script_rustc_env {
+                    attrs.rustc_env.insert(extra.clone(), None);
+                }
+
+                // Build script env
+                if let Some(extra) = &crate_extra.build_script_env {
+                    attrs.build_script_env.insert(extra.clone(), None);
+                }
+            }
+
+            // Extra build contents
+            self.additive_build_file_content = crate_extra
+                .additive_build_file_content
+                .as_ref()
+                .map(|content| {
+                    // For prettier rendering, dedent the build contents
+                    textwrap::dedent(content)
+                });
+
+            // Git shallow_since
+            if let Some(SourceAnnotation::Git { shallow_since, .. }) = &mut self.repository {
+                *shallow_since = crate_extra.shallow_since.clone()
+            }
+
+            // Patch attributes
+            if let Some(repository) = &mut self.repository {
+                match repository {
+                    SourceAnnotation::Git {
+                        patch_args,
+                        patch_tool,
+                        patches,
+                        ..
+                    } => {
+                        *patch_args = crate_extra.patch_args.clone();
+                        *patch_tool = crate_extra.patch_tool.clone();
+                        *patches = crate_extra.patches.clone();
+                    }
+                    SourceAnnotation::Http {
+                        patch_args,
+                        patch_tool,
+                        patches,
+                        ..
+                    } => {
+                        *patch_args = crate_extra.patch_args.clone();
+                        *patch_tool = crate_extra.patch_tool.clone();
+                        *patches = crate_extra.patches.clone();
+                    }
+                }
+            }
+        }
+
+        self
+    }
+
+    /// Determine whether or not a crate __should__ include a build script
+    /// (build.rs) if it happens to have one.
+    fn crate_includes_build_script(
+        package: &Package,
+        overrides: &BTreeMap<CrateId, PairredExtras>,
+        default_generate_build_script: bool,
+    ) -> bool {
+        // Locate extra settings for the current package.
+        let settings = overrides
+            .iter()
+            .find(|(_, settings)| settings.package_id == package.id);
+
+        // If the crate has extra settings, which explicitly set `gen_build_script`, always use
+        // this value, otherwise, fallback to the provided default.
+        settings
+            .and_then(|(_, settings)| settings.crate_extra.gen_build_script)
+            .unwrap_or(default_generate_build_script)
+    }
+
+    /// Collect all Bazel targets that should be generated for a particular Package
+    fn collect_targets(
+        node: &Node,
+        packages: &BTreeMap<PackageId, Package>,
+        include_build_scripts: bool,
+    ) -> Vec<Rule> {
+        let package = &packages[&node.id];
+
+        let package_root = package
+            .manifest_path
+            .as_std_path()
+            .parent()
+            .expect("Every manifest should have a parent directory");
+
+        package
+            .targets
+            .iter()
+            .flat_map(|target| {
+                target
+                    .kind
+                    .iter()
+                    .filter_map(|kind| {
+                        // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
+                        // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
+                        // content to align when rendering, the package target names are always sanitized.
+                        let crate_name = sanitize_module_name(&target.name);
+
+                        // Locate the crate's root source file relative to the package root normalized for unix
+                        let crate_root =
+                            pathdiff::diff_paths(target.src_path.to_string(), package_root).map(
+                                // Normalize the path so that it always renders the same regardless of platform
+                                |root| root.to_string_lossy().replace("\\", "/"),
+                            );
+
+                        // Conditionally check to see if the dependencies is a build-script target
+                        if include_build_scripts && kind == "custom-build" {
+                            return Some(Rule::BuildScript(TargetAttributes {
+                                crate_name,
+                                crate_root,
+                                srcs: Glob::new_rust_srcs(),
+                            }));
+                        }
+
+                        // Check to see if the dependencies is a proc-macro target
+                        if kind == "proc-macro" {
+                            return Some(Rule::ProcMacro(TargetAttributes {
+                                crate_name,
+                                crate_root,
+                                srcs: Glob::new_rust_srcs(),
+                            }));
+                        }
+
+                        // Check to see if the dependencies is a library target
+                        if ["lib", "rlib"].contains(&kind.as_str()) {
+                            return Some(Rule::Library(TargetAttributes {
+                                crate_name,
+                                crate_root,
+                                srcs: Glob::new_rust_srcs(),
+                            }));
+                        }
+
+                        // Check to see if the dependencies is a library target
+                        if kind == "bin" {
+                            return Some(Rule::Binary(TargetAttributes {
+                                crate_name: target.name.clone(),
+                                crate_root,
+                                srcs: Glob::new_rust_srcs(),
+                            }));
+                        }
+
+                        None
+                    })
+                    .collect::<Vec<Rule>>()
+            })
+            .collect()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    use crate::config::CrateAnnotations;
+    use crate::metadata::Annotations;
+
+    fn common_annotations() -> Annotations {
+        Annotations::new(
+            crate::test::metadata::common(),
+            crate::test::lockfile::common(),
+            crate::config::Config::default(),
+        )
+        .unwrap()
+    }
+
+    #[test]
+    fn new_context() {
+        let annotations = common_annotations();
+
+        let crate_annotation = &annotations.metadata.crates[&PackageId {
+            repr: "common 0.1.0 (path+file://{TEMP_DIR}/common)".to_owned(),
+        }];
+
+        let context = CrateContext::new(
+            crate_annotation,
+            &annotations.metadata.packages,
+            &annotations.lockfile.crates,
+            &annotations.pairred_extras,
+            false,
+        );
+
+        assert_eq!(context.name, "common");
+        assert_eq!(
+            context.targets,
+            vec![
+                Rule::Library(TargetAttributes {
+                    crate_name: "common".to_owned(),
+                    crate_root: Some("lib.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                }),
+                Rule::Binary(TargetAttributes {
+                    crate_name: "common-bin".to_owned(),
+                    crate_root: Some("main.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                }),
+            ]
+        );
+    }
+
+    #[test]
+    fn context_with_overrides() {
+        let annotations = common_annotations();
+
+        let package_id = PackageId {
+            repr: "common 0.1.0 (path+file://{TEMP_DIR}/common)".to_owned(),
+        };
+
+        let crate_annotation = &annotations.metadata.crates[&package_id];
+
+        let mut pairred_extras = BTreeMap::new();
+        pairred_extras.insert(
+            CrateId::new("common".to_owned(), "0.1.0".to_owned()),
+            PairredExtras {
+                package_id,
+                crate_extra: CrateAnnotations {
+                    data_glob: Some(BTreeSet::from(["**/data_glob/**".to_owned()])),
+                    ..CrateAnnotations::default()
+                },
+            },
+        );
+
+        let context = CrateContext::new(
+            crate_annotation,
+            &annotations.metadata.packages,
+            &annotations.lockfile.crates,
+            &pairred_extras,
+            false,
+        );
+
+        assert_eq!(context.name, "common");
+        assert_eq!(
+            context.targets,
+            vec![
+                Rule::Library(TargetAttributes {
+                    crate_name: "common".to_owned(),
+                    crate_root: Some("lib.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                }),
+                Rule::Binary(TargetAttributes {
+                    crate_name: "common-bin".to_owned(),
+                    crate_root: Some("main.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                }),
+            ]
+        );
+        assert_eq!(
+            context.common_attrs.data_glob,
+            BTreeSet::from(["**/data_glob/**".to_owned()])
+        );
+    }
+
+    fn build_script_annotations() -> Annotations {
+        Annotations::new(
+            crate::test::metadata::build_scripts(),
+            crate::test::lockfile::build_scripts(),
+            crate::config::Config::default(),
+        )
+        .unwrap()
+    }
+
+    fn crate_type_annotations() -> Annotations {
+        Annotations::new(
+            crate::test::metadata::crate_types(),
+            crate::test::lockfile::crate_types(),
+            crate::config::Config::default(),
+        )
+        .unwrap()
+    }
+
+    #[test]
+    fn context_with_build_script() {
+        let annotations = build_script_annotations();
+
+        let package_id = PackageId {
+            repr: "openssl-sys 0.9.72 (registry+https://github.com/rust-lang/crates.io-index)"
+                .to_owned(),
+        };
+
+        let crate_annotation = &annotations.metadata.crates[&package_id];
+
+        let context = CrateContext::new(
+            crate_annotation,
+            &annotations.metadata.packages,
+            &annotations.lockfile.crates,
+            &annotations.pairred_extras,
+            true,
+        );
+
+        assert_eq!(context.name, "openssl-sys");
+        assert!(context.build_script_attrs.is_some());
+        assert_eq!(
+            context.targets,
+            vec![
+                Rule::Library(TargetAttributes {
+                    crate_name: "openssl_sys".to_owned(),
+                    crate_root: Some("src/lib.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                }),
+                Rule::BuildScript(TargetAttributes {
+                    crate_name: "build_script_main".to_owned(),
+                    crate_root: Some("build/main.rs".to_owned()),
+                    srcs: Glob::new_rust_srcs(),
+                })
+            ]
+        );
+
+        // Cargo build scripts should include all sources
+        assert!(context.build_script_attrs.unwrap().data_glob.contains("**"));
+    }
+
+    #[test]
+    fn context_disabled_build_script() {
+        let annotations = build_script_annotations();
+
+        let package_id = PackageId {
+            repr: "openssl-sys 0.9.72 (registry+https://github.com/rust-lang/crates.io-index)"
+                .to_owned(),
+        };
+
+        let crate_annotation = &annotations.metadata.crates[&package_id];
+
+        let context = CrateContext::new(
+            crate_annotation,
+            &annotations.metadata.packages,
+            &annotations.lockfile.crates,
+            &annotations.pairred_extras,
+            false,
+        );
+
+        assert_eq!(context.name, "openssl-sys");
+        assert!(context.build_script_attrs.is_none());
+        assert_eq!(
+            context.targets,
+            vec![Rule::Library(TargetAttributes {
+                crate_name: "openssl_sys".to_owned(),
+                crate_root: Some("src/lib.rs".to_owned()),
+                srcs: Glob::new_rust_srcs(),
+            })],
+        );
+    }
+
+    #[test]
+    fn context_rlib_crate_type() {
+        let annotations = crate_type_annotations();
+
+        let package_id = PackageId {
+            repr: "sysinfo 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)"
+                .to_owned(),
+        };
+
+        let crate_annotation = &annotations.metadata.crates[&package_id];
+
+        let context = CrateContext::new(
+            crate_annotation,
+            &annotations.metadata.packages,
+            &annotations.lockfile.crates,
+            &annotations.pairred_extras,
+            false,
+        );
+
+        assert_eq!(context.name, "sysinfo");
+        assert!(context.build_script_attrs.is_none());
+        assert_eq!(
+            context.targets,
+            vec![Rule::Library(TargetAttributes {
+                crate_name: "sysinfo".to_owned(),
+                crate_root: Some("src/lib.rs".to_owned()),
+                srcs: Glob::new_rust_srcs(),
+            })],
+        );
+    }
+}