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/utils/starlark.rs b/crate_universe/src/utils/starlark.rs
new file mode 100644
index 0000000..57007b7
--- /dev/null
+++ b/crate_universe/src/utils/starlark.rs
@@ -0,0 +1,12 @@
+//! A module for representations of starlark constructs
+
+mod glob;
+mod label;
+mod select;
+
+pub use glob::*;
+pub use label::*;
+pub use select::*;
+
+pub type SelectStringList = SelectList<String>;
+pub type SelectStringDict = SelectDict<String>;
diff --git a/crate_universe/src/utils/starlark/glob.rs b/crate_universe/src/utils/starlark/glob.rs
new file mode 100644
index 0000000..23b17a5
--- /dev/null
+++ b/crate_universe/src/utils/starlark/glob.rs
@@ -0,0 +1,16 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+pub struct Glob {
+    pub include: Vec<String>,
+    pub exclude: Vec<String>,
+}
+
+impl Glob {
+    pub fn new_rust_srcs() -> Self {
+        Self {
+            include: vec!["**/*.rs".to_owned()],
+            ..Default::default()
+        }
+    }
+}
diff --git a/crate_universe/src/utils/starlark/label.rs b/crate_universe/src/utils/starlark/label.rs
new file mode 100644
index 0000000..1716944
--- /dev/null
+++ b/crate_universe/src/utils/starlark/label.rs
@@ -0,0 +1,317 @@
+use std::fmt::{self, Display};
+use std::path::Path;
+use std::str::FromStr;
+
+use anyhow::{anyhow, bail, Context, Result};
+use regex::Regex;
+use serde::de::Visitor;
+use serde::{Deserialize, Serialize, Serializer};
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub struct Label {
+    pub repository: Option<String>,
+    pub package: Option<String>,
+    pub target: String,
+}
+
+impl FromStr for Label {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let re = Regex::new(r"^(@[\w\d\-_\.]*)?/{0,2}([\w\d\-_\./]+)?:?([\+\w\d\-_\./]+)$")?;
+        let cap = re
+            .captures(s)
+            .with_context(|| format!("Failed to parse label from string: {}", s))?;
+
+        let repository = cap
+            .get(1)
+            .map(|m| m.as_str().trim_start_matches('@').to_owned());
+        let package = cap.get(2).map(|m| m.as_str().to_owned());
+        let mut target = cap.get(3).map(|m| m.as_str().to_owned());
+
+        if target.is_none() {
+            if let Some(pkg) = &package {
+                target = Some(pkg.clone());
+            } else if let Some(repo) = &repository {
+                target = Some(repo.clone())
+            } else {
+                bail!("The label is missing a label")
+            }
+        }
+
+        // The target should be set at this point
+        let target = target.unwrap();
+
+        Ok(Self {
+            repository,
+            package,
+            target,
+        })
+    }
+}
+
+impl Display for Label {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let mut label = String::new();
+
+        // Add the repository
+        if let Some(repo) = &self.repository {
+            label = format!("@{}", repo);
+        }
+
+        // Add the package
+        if let Some(pkg) = &self.package {
+            label = format!("{}//{}", label, pkg);
+        }
+
+        write!(f, "{}:{}", &label, &self.target,)
+    }
+}
+
+impl Label {
+    /// Generates a label appropriate for the passed Path by walking the filesystem to identify its
+    /// workspace and package.
+    pub fn from_absolute_path(p: &Path) -> Result<Self, anyhow::Error> {
+        let mut workspace_root = None;
+        let mut package_root = None;
+        for ancestor in p.ancestors().skip(1) {
+            if package_root.is_none()
+                && (ancestor.join("BUILD").exists() || ancestor.join("BUILD.bazel").exists())
+            {
+                package_root = Some(ancestor);
+            }
+            if workspace_root.is_none()
+                && (ancestor.join("WORKSPACE").exists()
+                    || ancestor.join("WORKSPACE.bazel").exists())
+            {
+                workspace_root = Some(ancestor);
+                break;
+            }
+        }
+        match (workspace_root, package_root) {
+            (Some(workspace_root), Some(package_root)) => {
+                // These unwraps are safe by construction of the ancestors and prefix calls which set up these paths.
+                let target = p.strip_prefix(package_root).unwrap();
+                let workspace_relative = p.strip_prefix(workspace_root).unwrap();
+                let mut package_path = workspace_relative.to_path_buf();
+                for _ in target.components() {
+                    package_path.pop();
+                }
+
+                let package = if package_path.components().count() > 0 {
+                    Some(path_to_label_part(&package_path)?)
+                } else {
+                    None
+                };
+                let target = path_to_label_part(target)?;
+
+                Ok(Label {
+                    repository: None,
+                    package,
+                    target,
+                })
+            }
+            (Some(_workspace_root), None) => {
+                bail!(
+                    "Could not identify package for path {}. Maybe you need to add a BUILD.bazel file.",
+                    p.display()
+                );
+            }
+            _ => {
+                bail!("Could not identify workspace for path {}", p.display());
+            }
+        }
+    }
+}
+
+/// Converts a path to a forward-slash-delimited label-appropriate path string.
+fn path_to_label_part(path: &Path) -> Result<String, anyhow::Error> {
+    let components: Result<Vec<_>, _> = path
+        .components()
+        .map(|c| {
+            c.as_os_str().to_str().ok_or_else(|| {
+                anyhow!(
+                    "Found non-UTF8 component turning path into label: {}",
+                    path.display()
+                )
+            })
+        })
+        .collect();
+    Ok(components?.join("/"))
+}
+
+impl Serialize for Label {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(&self.repr())
+    }
+}
+
+struct LabelVisitor;
+impl<'de> Visitor<'de> for LabelVisitor {
+    type Value = Label;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("Expected string value of `{name} {version}`.")
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Label::from_str(v).map_err(E::custom)
+    }
+}
+
+impl<'de> Deserialize<'de> for Label {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        deserializer.deserialize_str(LabelVisitor)
+    }
+}
+
+impl Label {
+    pub fn repr(&self) -> String {
+        self.to_string()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use spectral::prelude::*;
+    use std::fs::{create_dir_all, File};
+    use tempfile::tempdir;
+
+    #[test]
+    fn full_label() {
+        let label = Label::from_str("@repo//package/sub_package:target").unwrap();
+        assert_eq!(label.repository.unwrap(), "repo");
+        assert_eq!(label.package.unwrap(), "package/sub_package");
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn no_repository() {
+        let label = Label::from_str("//package:target").unwrap();
+        assert_eq!(label.repository, None);
+        assert_eq!(label.package.unwrap(), "package");
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn no_slashes() {
+        let label = Label::from_str("package:target").unwrap();
+        assert_eq!(label.repository, None);
+        assert_eq!(label.package.unwrap(), "package");
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn root_label() {
+        let label = Label::from_str("@repo//:target").unwrap();
+        assert_eq!(label.repository.unwrap(), "repo");
+        assert_eq!(label.package, None);
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn root_label_no_repository() {
+        let label = Label::from_str("//:target").unwrap();
+        assert_eq!(label.repository, None);
+        assert_eq!(label.package, None);
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn root_label_no_slashes() {
+        let label = Label::from_str(":target").unwrap();
+        assert_eq!(label.repository, None);
+        assert_eq!(label.package, None);
+        assert_eq!(label.target, "target");
+    }
+
+    #[test]
+    fn full_label_with_slash_after_colon() {
+        let label = Label::from_str("@repo//package/sub_package:subdir/target").unwrap();
+        assert_eq!(label.repository.unwrap(), "repo");
+        assert_eq!(label.package.unwrap(), "package/sub_package");
+        assert_eq!(label.target, "subdir/target");
+    }
+
+    #[test]
+    fn invalid_double_colon() {
+        assert!(Label::from_str("::target").is_err());
+    }
+
+    #[test]
+    fn invalid_double_at() {
+        assert!(Label::from_str("@@repo//pkg:target").is_err());
+    }
+
+    #[test]
+    #[ignore = "This currently fails. The Label parsing logic needs to be updated"]
+    fn invalid_no_double_slash() {
+        assert!(Label::from_str("@repo:target").is_err());
+    }
+
+    #[test]
+    fn from_absolute_path_exists() {
+        let dir = tempdir().unwrap();
+        let workspace = dir.path().join("WORKSPACE.bazel");
+        let build_file = dir.path().join("parent").join("child").join("BUILD.bazel");
+        let subdir = dir.path().join("parent").join("child").join("grandchild");
+        let actual_file = subdir.join("greatgrandchild");
+        create_dir_all(subdir).unwrap();
+        {
+            File::create(&workspace).unwrap();
+            File::create(&build_file).unwrap();
+            File::create(&actual_file).unwrap();
+        }
+        let label = Label::from_absolute_path(&actual_file).unwrap();
+        assert_eq!(label.repository, None);
+        assert_eq!(label.package.unwrap(), "parent/child");
+        assert_eq!(label.target, "grandchild/greatgrandchild")
+    }
+
+    #[test]
+    fn from_absolute_path_no_workspace() {
+        let dir = tempdir().unwrap();
+        let build_file = dir.path().join("parent").join("child").join("BUILD.bazel");
+        let subdir = dir.path().join("parent").join("child").join("grandchild");
+        let actual_file = subdir.join("greatgrandchild");
+        create_dir_all(subdir).unwrap();
+        {
+            File::create(&build_file).unwrap();
+            File::create(&actual_file).unwrap();
+        }
+        let err = Label::from_absolute_path(&actual_file)
+            .unwrap_err()
+            .to_string();
+        assert_that(&err).contains("Could not identify workspace");
+        assert_that(&err).contains(format!("{}", actual_file.display()).as_str());
+    }
+
+    #[test]
+    fn from_absolute_path_no_build_file() {
+        let dir = tempdir().unwrap();
+        let workspace = dir.path().join("WORKSPACE.bazel");
+        let subdir = dir.path().join("parent").join("child").join("grandchild");
+        let actual_file = subdir.join("greatgrandchild");
+        create_dir_all(subdir).unwrap();
+        {
+            File::create(&workspace).unwrap();
+            File::create(&actual_file).unwrap();
+        }
+        let err = Label::from_absolute_path(&actual_file)
+            .unwrap_err()
+            .to_string();
+        assert_that(&err).contains("Could not identify package");
+        assert_that(&err).contains("Maybe you need to add a BUILD.bazel file");
+        assert_that(&err).contains(format!("{}", actual_file.display()).as_str());
+    }
+}
diff --git a/crate_universe/src/utils/starlark/select.rs b/crate_universe/src/utils/starlark/select.rs
new file mode 100644
index 0000000..4a8a3cc
--- /dev/null
+++ b/crate_universe/src/utils/starlark/select.rs
@@ -0,0 +1,166 @@
+use serde::{Deserialize, Serialize};
+use std::collections::{btree_set, BTreeMap, BTreeSet};
+use std::iter::once;
+
+pub trait SelectMap<T, U> {
+    // A selectable should also implement a `map` function allowing one type of selectable
+    // to be mutated into another. However, the approach I'm looking for requires GAT
+    // (Generic Associated Types) which are not yet stable.
+    // https://github.com/rust-lang/rust/issues/44265
+    type Mapped;
+    fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped;
+}
+
+pub trait Select<T> {
+    /// Gather a list of all conditions currently set on the selectable. A conditional
+    /// would be the key of the select statement.
+    fn configurations(&self) -> BTreeSet<Option<&String>>;
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+pub struct SelectList<T: Ord> {
+    common: BTreeSet<T>,
+    selects: BTreeMap<String, BTreeSet<T>>,
+}
+
+impl<T: Ord> Default for SelectList<T> {
+    fn default() -> Self {
+        Self {
+            common: BTreeSet::new(),
+            selects: BTreeMap::new(),
+        }
+    }
+}
+
+impl<T: Ord> SelectList<T> {
+    // TODO: This should probably be added to the [Select] trait
+    pub fn insert(&mut self, value: T, configuration: Option<String>) {
+        match configuration {
+            None => {
+                self.common.insert(value);
+            }
+            Some(cfg) => {
+                match self.selects.get_mut(&cfg) {
+                    None => {
+                        let mut set = BTreeSet::new();
+                        set.insert(value);
+                        self.selects.insert(cfg, set);
+                    }
+                    Some(set) => {
+                        set.insert(value);
+                    }
+                };
+            }
+        };
+    }
+
+    // TODO: This should probably be added to the [Select] trait
+    pub fn get_iter<'a>(&'a self, config: Option<&String>) -> Option<btree_set::Iter<T>> {
+        match config {
+            Some(conf) => self.selects.get(conf).map(|set| set.iter()),
+            None => Some(self.common.iter()),
+        }
+    }
+
+    /// Determine whether or not the select should be serialized
+    pub fn should_skip_serializing(&self) -> bool {
+        self.common.is_empty() && self.selects.is_empty()
+    }
+}
+
+impl<T: Ord> Select<T> for SelectList<T> {
+    fn configurations(&self) -> BTreeSet<Option<&String>> {
+        let configs = self.selects.keys().map(Some);
+        match self.common.is_empty() {
+            true => configs.collect(),
+            false => configs.chain(once(None)).collect(),
+        }
+    }
+}
+
+impl<T: Ord, U: Ord> SelectMap<T, U> for SelectList<T> {
+    type Mapped = SelectList<U>;
+
+    fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
+        SelectList {
+            common: self.common.into_iter().map(func).collect(),
+            selects: self
+                .selects
+                .into_iter()
+                .map(|(key, map)| (key, map.into_iter().map(func).collect()))
+                .collect(),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+pub struct SelectDict<T: Ord> {
+    common: BTreeMap<String, T>,
+    selects: BTreeMap<String, BTreeMap<String, T>>,
+}
+
+impl<T: Ord> Default for SelectDict<T> {
+    fn default() -> Self {
+        Self {
+            common: BTreeMap::new(),
+            selects: BTreeMap::new(),
+        }
+    }
+}
+
+impl<T: Ord> SelectDict<T> {
+    // TODO: This should probably be added to the [Select] trait
+    pub fn insert(&mut self, value: BTreeMap<String, T>, configuration: Option<String>) {
+        match configuration {
+            None => {
+                self.common.extend(value);
+            }
+            Some(cfg) => {
+                match self.selects.get_mut(&cfg) {
+                    None => {
+                        let mut set = BTreeMap::new();
+                        set.extend(value);
+                        self.selects.insert(cfg, set);
+                    }
+                    Some(set) => {
+                        set.extend(value);
+                    }
+                };
+            }
+        };
+    }
+
+    /// Determine whether or not the select should be serialized
+    pub fn should_skip_serializing(&self) -> bool {
+        self.common.is_empty() && self.selects.is_empty()
+    }
+}
+
+impl<T: Ord> Select<T> for SelectDict<T> {
+    fn configurations(&self) -> BTreeSet<Option<&String>> {
+        let configs = self.selects.keys().map(Some);
+        match self.common.is_empty() {
+            true => configs.collect(),
+            false => configs.chain(once(None)).collect(),
+        }
+    }
+}
+
+impl<T: Ord, U: Ord> SelectMap<T, U> for SelectDict<T> {
+    type Mapped = SelectDict<U>;
+
+    fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
+        SelectDict {
+            common: self
+                .common
+                .into_iter()
+                .map(|(key, val)| (key, func(val)))
+                .collect(),
+            selects: self
+                .selects
+                .into_iter()
+                .map(|(key, map)| (key, map.into_iter().map(|(k, v)| (k, func(v))).collect()))
+                .collect(),
+        }
+    }
+}