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/util/BUILD.bazel b/util/BUILD.bazel
new file mode 100644
index 0000000..217f1db
--- /dev/null
+++ b/util/BUILD.bazel
@@ -0,0 +1,4 @@
+sh_binary(
+    name = "fetch_shas",
+    srcs = ["fetch_shas.sh"],
+)
diff --git a/util/dir_zipper/BUILD.bazel b/util/dir_zipper/BUILD.bazel
new file mode 100644
index 0000000..43ab79e
--- /dev/null
+++ b/util/dir_zipper/BUILD.bazel
@@ -0,0 +1,8 @@
+load("//rust:defs.bzl", "rust_binary")
+
+rust_binary(
+    name = "dir_zipper",
+    srcs = ["dir_zipper.rs"],
+    edition = "2018",
+    visibility = ["//visibility:public"],
+)
diff --git a/util/dir_zipper/dir_zipper.rs b/util/dir_zipper/dir_zipper.rs
new file mode 100644
index 0000000..fbc4938
--- /dev/null
+++ b/util/dir_zipper/dir_zipper.rs
@@ -0,0 +1,77 @@
+use std::ffi::OsString;
+use std::path::PathBuf;
+use std::process::Command;
+
+const USAGE: &str = r#"usage: dir_zipper <zipper> <output> <root-dir> [<file>...]
+
+Creates a zip archive, stripping a directory prefix from each file name.
+
+Args:
+  zipper: Path to @bazel_tools//tools/zip:zipper.
+  output: Path to zip file to create: e.g., "/tmp/out.zip".
+  root_dir: Directory to strip from each archive name, with no trailing
+    slash: e.g., "/tmp/myfiles".
+  files: List of files to include in the archive, all under `root_dir`:
+    e.g., ["/tmp/myfiles/a", "/tmp/myfiles/b/c"].
+
+Example:
+  dir_zipper \
+    bazel-rules_rust/external/bazel_tools/tools/zip/zipper/zipper \
+    /tmp/out.zip \
+    /tmp/myfiles \
+    /tmp/myfiles/a /tmp/myfiles/b/c
+
+This will create /tmp/out.zip with file entries "a" and "b/c".
+"#;
+
+macro_rules! die {
+    ($($arg:tt)*) => {
+        {
+            eprintln!($($arg)*);
+            std::process::exit(1);
+        }
+    };
+}
+
+fn main() {
+    let mut args = std::env::args_os().skip(1);
+    let (zipper, output, root_dir) = match args.next().zip(args.next()).zip(args.next()) {
+        Some(((zipper, output), root_dir)) => (
+            PathBuf::from(zipper),
+            PathBuf::from(output),
+            PathBuf::from(root_dir),
+        ),
+        _ => {
+            die!("{}", USAGE);
+        }
+    };
+    let files = args.map(PathBuf::from).collect::<Vec<_>>();
+    let mut comm = Command::new(zipper);
+    comm.arg("c"); // create, but don't compress
+    comm.arg(output);
+    for f in files {
+        let rel = f.strip_prefix(&root_dir).unwrap_or_else(|_e| {
+            die!(
+                "fatal: non-descendant: {} not under {}",
+                f.display(),
+                root_dir.display()
+            );
+        });
+        let mut spec = OsString::new();
+        spec.push(rel);
+        spec.push("=");
+        spec.push(f);
+        comm.arg(spec);
+    }
+    let exit_status = comm
+        .spawn()
+        .unwrap_or_else(|e| die!("fatal: could not spawn zipper: {}", e))
+        .wait()
+        .unwrap_or_else(|e| die!("fatal: could not wait on zipper: {}", e));
+    if !exit_status.success() {
+        match exit_status.code() {
+            Some(c) => std::process::exit(c),
+            None => die!("fatal: zipper terminated by signal"),
+        }
+    }
+}
diff --git a/util/fetch_shas.sh b/util/fetch_shas.sh
new file mode 100755
index 0000000..2a538c1
--- /dev/null
+++ b/util/fetch_shas.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+
+# Enumerates the list of expected downloadable files, loads the SHAs for each file, then
+# dumps the result to //rust:known_shas.bzl
+
+export LC_ALL=C
+
+# Detect workspace root
+if [[ -z "${BUILD_WORKSPACE_DIRECTORY}" ]]; then
+    SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+    BUILD_WORKSPACE_DIRECTORY="$( dirname "${SCRIPT_DIR}")"
+fi
+
+TOOLS="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_TOOLS.txt")"
+HOST_TOOLS="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_HOST_TOOLS.txt")"
+TARGETS="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_TARGETS.txt")"
+VERSIONS="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_VERSIONS.txt")"
+BETA_ISO_DATES="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_BETA_ISO_DATES.txt")"
+NIGHTLY_ISO_DATES="$(cat "${BUILD_WORKSPACE_DIRECTORY}/util/fetch_shas_NIGHTLY_ISO_DATES.txt")"
+
+enumerate_keys() {
+  for TOOL in $TOOLS
+  do
+    for TARGET in $TARGETS
+    do
+      for VERSION in $VERSIONS
+      do
+        echo "$TOOL-$VERSION-$TARGET"
+      done
+
+      for ISO_DATE in $BETA_ISO_DATES
+      do
+        echo "$ISO_DATE/$TOOL-beta-$TARGET"
+      done
+
+      for ISO_DATE in $NIGHTLY_ISO_DATES
+      do
+        echo "$ISO_DATE/$TOOL-nightly-$TARGET"
+      done
+    done
+  done
+
+  for HOST_TOOL in $HOST_TOOLS
+  do
+    for VERSION in $VERSIONS
+    do
+      echo "$HOST_TOOL-$VERSION"
+    done
+  done
+}
+
+emit_bzl_file_contents() {
+  echo "$@" \
+    | parallel --trim lr -d ' ' --will-cite 'printf "%s %s\n", {}, $(curl --fail https://static.rust-lang.org/dist/{}.tar.gz.sha256 | cut -f1 -d" ")' \
+    | sed "s/,//g" \
+    | grep -v " $" \
+    > /tmp/reload_shas_shalist.txt
+
+  echo "\"\"\"A module containing a mapping of Rust tools to checksums"
+  echo ""
+  echo "This is a generated file -- see //util:fetch_shas"
+  echo "\"\"\""
+  echo ""
+  echo "FILE_KEY_TO_SHA = {"
+  cat /tmp/reload_shas_shalist.txt | sed '/^[[:space:]]*$/d' | sort | awk '{print "    \"" $1 "\": \"" $2 "\","}'
+  echo "}"
+  rm /tmp/reload_shas_shalist.txt
+}
+
+echo "$(emit_bzl_file_contents $(enumerate_keys))" > "${BUILD_WORKSPACE_DIRECTORY}/rust/known_shas.bzl"
diff --git a/util/fetch_shas_BETA_ISO_DATES.txt b/util/fetch_shas_BETA_ISO_DATES.txt
new file mode 100644
index 0000000..8e7996c
--- /dev/null
+++ b/util/fetch_shas_BETA_ISO_DATES.txt
@@ -0,0 +1,4 @@
+2018-10-30
+2018-11-01
+2018-11-02
+2020-12-30
diff --git a/util/fetch_shas_HOST_TOOLS.txt b/util/fetch_shas_HOST_TOOLS.txt
new file mode 100644
index 0000000..1a3bdb2
--- /dev/null
+++ b/util/fetch_shas_HOST_TOOLS.txt
@@ -0,0 +1 @@
+rust-src
diff --git a/util/fetch_shas_NIGHTLY_ISO_DATES.txt b/util/fetch_shas_NIGHTLY_ISO_DATES.txt
new file mode 100644
index 0000000..2d9a94e
--- /dev/null
+++ b/util/fetch_shas_NIGHTLY_ISO_DATES.txt
@@ -0,0 +1,14 @@
+2018-11-07
+2018-11-08
+2018-11-09
+2020-02-16
+2020-11-10
+2020-12-30
+2021-06-16
+2021-09-08
+2021-10-21
+2021-11-07
+2021-12-01
+2022-01-12
+2022-01-19
+2022-02-23
diff --git a/util/fetch_shas_TARGETS.txt b/util/fetch_shas_TARGETS.txt
new file mode 100644
index 0000000..e5bacb4
--- /dev/null
+++ b/util/fetch_shas_TARGETS.txt
@@ -0,0 +1,10 @@
+aarch64-apple-darwin
+aarch64-unknown-linux-gnu
+aarch64-unknown-linux-musl
+wasm32-unknown-unknown
+wasm32-wasi
+x86_64-apple-darwin
+x86_64-pc-windows-msvc
+x86_64-unknown-freebsd
+x86_64-unknown-linux-gnu
+x86_64-unknown-linux-musl
diff --git a/util/fetch_shas_TOOLS.txt b/util/fetch_shas_TOOLS.txt
new file mode 100644
index 0000000..cf0f7c8
--- /dev/null
+++ b/util/fetch_shas_TOOLS.txt
@@ -0,0 +1,7 @@
+cargo
+clippy
+llvm-tools
+rust
+rust-std
+rustc
+rustfmt
diff --git a/util/fetch_shas_VERSIONS.txt b/util/fetch_shas_VERSIONS.txt
new file mode 100644
index 0000000..18ad30c
--- /dev/null
+++ b/util/fetch_shas_VERSIONS.txt
@@ -0,0 +1,45 @@
+1.26.0
+1.26.1
+1.26.2
+1.27.0
+1.27.1
+1.27.2
+1.28.0
+1.29.0
+1.29.1
+1.29.2
+1.30.0
+1.30.1
+1.31.0
+1.31.1
+1.32.0
+1.33.0
+1.34.0
+1.35.0
+1.36.0
+1.37.0
+1.38.0
+1.39.0
+1.40.0
+1.41.0
+1.42.0
+1.43.0
+1.44.0
+1.45.0
+1.46.0
+1.47.0
+1.48.0
+1.49.0
+1.50.0
+1.51.0
+1.52.0
+1.52.1
+1.53.0
+1.54.0
+1.55.0
+1.56.0
+1.56.1
+1.57.0
+1.58.0
+1.58.1
+1.59.0
diff --git a/util/import/BUILD.bazel b/util/import/BUILD.bazel
new file mode 100644
index 0000000..7cb0395
--- /dev/null
+++ b/util/import/BUILD.bazel
@@ -0,0 +1,73 @@
+load("//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_test")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:transitions.bzl", "with_import_macro_bootstrapping_mode")
+
+with_import_macro_bootstrapping_mode(
+    name = "import_macro",
+    target = "import_macro_impl",
+)
+
+rust_proc_macro(
+    name = "import_macro_impl",
+    srcs = [
+        "import.rs",
+    ],
+    crate_name = "import",
+    deps = [
+        ":import_internal",
+        "//util/import/raze:syn",
+    ],
+)
+
+rust_library(
+    name = "import_internal",
+    srcs = [
+        "import_internal.rs",
+    ],
+    deps = [
+        "//util/import/raze:aho_corasick",
+        "//util/import/raze:lazy_static",
+        "//util/import/raze:proc_macro2",
+        "//util/import/raze:quote",
+        "//util/import/raze:syn",
+        "//util/label",
+    ],
+)
+
+rust_test(
+    name = "import_internal_test",
+    crate = ":import_internal",
+    deps = [
+        "//util/import/raze:quickcheck",
+    ],
+)
+
+alias(
+    name = "import",
+    actual = select({
+        ":use_fake_import_macro": ":fake_import_macro_impl",
+        "//conditions:default": ":import_macro_label",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+# This is there to cut the loading-time dependency on the import macro dependencies
+# (so users who don't use the macro don't need to add those deps to their WORKSPACE
+# file). Bazel in the loading phase doesn't "see" through `label_flag`.
+label_flag(
+    name = "import_macro_label",
+    build_setting_default = "import_macro",
+)
+
+config_setting(
+    name = "use_fake_import_macro",
+    flag_values = {
+        "@rules_rust//rust/settings:use_real_import_macro": "False",
+    },
+)
+
+sh_binary(
+    name = "fake_import_macro_impl",
+    srcs = ["fake_import_macro_impl.sh"],
+)
diff --git a/util/import/deps.bzl b/util/import/deps.bzl
new file mode 100644
index 0000000..be49b06
--- /dev/null
+++ b/util/import/deps.bzl
@@ -0,0 +1,11 @@
+"""
+The dependencies for running the gen_rust_project binary.
+"""
+
+load("//util/import/raze:crates.bzl", "rules_rust_util_import_fetch_remote_crates")
+
+def import_deps():
+    rules_rust_util_import_fetch_remote_crates()
+
+# For legacy support
+gen_rust_project_dependencies = import_deps
diff --git a/util/import/fake_import_macro_impl.sh b/util/import/fake_import_macro_impl.sh
new file mode 100755
index 0000000..1fa77d8
--- /dev/null
+++ b/util/import/fake_import_macro_impl.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+# Does nothing.
\ No newline at end of file
diff --git a/util/import/import.rs b/util/import/import.rs
new file mode 100644
index 0000000..fff5e78
--- /dev/null
+++ b/util/import/import.rs
@@ -0,0 +1,19 @@
+use syn::parse_macro_input;
+
+fn mode() -> import_internal::Mode {
+    match std::env::var("RULES_RUST_THIRD_PARTY_DIR")
+        .ok()
+        .and_then(|dir| dir.strip_prefix("//").map(|s| s.to_string()))
+    {
+        Some(third_party_dir) => import_internal::Mode::RenameFirstPartyCrates { third_party_dir },
+        _ => import_internal::Mode::NoRenaming,
+    }
+}
+
+#[proc_macro]
+pub fn import(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input = parse_macro_input!(input as import_internal::ImportMacroInput);
+    import_internal::expand_imports(input, &mode())
+        .unwrap_or_else(|errors| errors.into_iter().map(|e| e.into_compile_error()).collect())
+        .into()
+}
diff --git a/util/import/import_internal.rs b/util/import/import_internal.rs
new file mode 100644
index 0000000..1d44fe6
--- /dev/null
+++ b/util/import/import_internal.rs
@@ -0,0 +1,554 @@
+use std::iter;
+use std::vec;
+
+use aho_corasick::AhoCorasick;
+use lazy_static::lazy_static;
+use proc_macro2::{Span, TokenStream};
+use quote::quote_spanned;
+use syn::parse::{Parse, ParseStream};
+use syn::{Error, Ident, Lit, LitStr, Result, Token};
+
+/// The possible renaming modes for this macro.
+pub enum Mode {
+    /// No renaming will be done; the expansion will replace each label with
+    /// just the target.
+    NoRenaming,
+    /// First-party crates will be renamed, and third-party crates will not be.
+    /// The expansion will replace first-party labels with an encoded version,
+    /// and third-party labels with just their target.
+    RenameFirstPartyCrates { third_party_dir: String },
+}
+
+/// A special case of label::Label, which must be absolute and must not specify
+/// a repository.
+#[derive(Debug, PartialEq)]
+struct AbsoluteLabel<'s> {
+    package_name: &'s str,
+    name: &'s str,
+}
+
+impl<'s> AbsoluteLabel<'s> {
+    /// Parses a string as an absolute Bazel label. Labels must be for the
+    /// current repository.
+    fn parse(label: &'s str, span: &'s Span) -> Result<Self> {
+        if let Ok(label::Label {
+            repository_name: None,
+            package_name: Some(package_name),
+            name,
+        }) = label::analyze(label)
+        {
+            Ok(AbsoluteLabel { package_name, name })
+        } else {
+            Err(Error::new(
+                *span,
+                "Bazel labels must be of the form '//package[:target]'",
+            ))
+        }
+    }
+
+    /// Returns true iff this label should be renamed.
+    fn should_rename(&self, mode: &Mode) -> bool {
+        match mode {
+            Mode::NoRenaming => false,
+            Mode::RenameFirstPartyCrates { third_party_dir } => {
+                !self.package_name.starts_with(third_party_dir)
+            }
+        }
+    }
+
+    /// Returns the appropriate (encoded) alias to use, if this label is being
+    /// renamed; otherwise, returns None.
+    fn target_as_alias(&self, mode: &Mode) -> Option<String> {
+        self.should_rename(mode).then(|| encode(self.name))
+    }
+
+    /// Returns the full crate name, encoded if necessary.
+    fn crate_name(&self, mode: &Mode) -> String {
+        if self.should_rename(mode) {
+            encode(&format!("{}:{}", self.package_name, self.name))
+        } else {
+            self.name.to_string()
+        }
+    }
+}
+
+lazy_static! {
+    // Transformations are stored as "(unencoded, encoded)" tuples.
+    // Target names can include:
+    // !%-@^_` "#$&'()*-+,;<=>?[]{|}~/.
+    //
+    // Package names are alphanumeric, plus [_/-].
+    //
+    // Packages and targets are separated by colons.
+    static ref SUBSTITUTIONS: (Vec<String>, Vec<String>) =
+        iter::once(("_quote".to_string(), "_quotequote_".to_string()))
+        .chain(
+            vec![
+                    (":", "colon"),
+                    ("!", "bang"),
+                    ("%", "percent"),
+                    ("@", "at"),
+                    ("^", "caret"),
+                    ("`", "backtick"),
+                    (" ", "space"),
+                    ("\"", "quote"),
+                    ("#", "hash"),
+                    ("$", "dollar"),
+                    ("&", "ampersand"),
+                    ("'", "backslash"),
+                    ("(", "lparen"),
+                    (")", "rparen"),
+                    ("*", "star"),
+                    ("-", "dash"),
+                    ("+", "plus"),
+                    (",", "comma"),
+                    (";", "semicolon"),
+                    ("<", "langle"),
+                    ("=", "equal"),
+                    (">", "rangle"),
+                    ("?", "question"),
+                    ("[", "lbracket"),
+                    ("]", "rbracket"),
+                    ("{", "lbrace"),
+                    ("|", "pipe"),
+                    ("}", "rbrace"),
+                    ("~", "tilde"),
+                    ("/", "slash"),
+                    (".", "dot"),
+            ].into_iter()
+            .flat_map(|pair| {
+                vec![
+                    (format!("_{}_", &pair.1), format!("_quote{}_", &pair.1)),
+                    (pair.0.to_string(), format!("_{}_", &pair.1)),
+                ].into_iter()
+            })
+        )
+        .unzip();
+
+    static ref ENCODER: AhoCorasick = AhoCorasick::new(&SUBSTITUTIONS.0);
+    static ref DECODER: AhoCorasick = AhoCorasick::new(&SUBSTITUTIONS.1);
+}
+
+/// Encodes a string using the above encoding scheme.
+fn encode(s: &str) -> String {
+    ENCODER.replace_all(s, &SUBSTITUTIONS.1)
+}
+
+struct Import {
+    label: LitStr,
+    alias: Option<Ident>,
+}
+
+impl Import {
+    fn try_into_statement(self, mode: &Mode) -> Result<proc_macro2::TokenStream> {
+        let label_literal = &self.label.value();
+        let span = self.label.span();
+        let label = AbsoluteLabel::parse(label_literal, &span)?;
+        let crate_name = &label.crate_name(mode);
+
+        let crate_ident = Ident::new(crate_name, span);
+        let alias = self
+            .alias
+            .or_else(|| {
+                label
+                    .target_as_alias(mode)
+                    .map(|alias| Ident::new(&alias, span))
+            })
+            .filter(|alias| alias != crate_name);
+
+        Ok(if let Some(alias) = alias {
+            quote_spanned! {span=> extern crate #crate_ident as #alias; }
+        } else {
+            quote_spanned! {span=> extern crate #crate_ident;}
+        })
+    }
+}
+
+pub struct ImportMacroInput {
+    imports: Vec<Import>,
+}
+
+impl Parse for ImportMacroInput {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut imports: Vec<Import> = Vec::new();
+
+        while !input.is_empty() {
+            let label = match Lit::parse(input)
+                .map_err(|_| input.error("expected Bazel label as a string literal"))?
+            {
+                Lit::Str(label) => label,
+                lit => {
+                    return Err(input.error(format!(
+                        "expected Bazel label as string literal, found '{}' literal",
+                        quote::quote! {#lit}
+                    )));
+                }
+            };
+            let alias = if input.peek(Token![as]) {
+                <Token![as]>::parse(input)?;
+                Some(
+                    Ident::parse(input)
+                        .map_err(|_| input.error("alias must be a valid Rust identifier"))?,
+                )
+            } else {
+                None
+            };
+            imports.push(Import { label, alias });
+            <syn::Token![;]>::parse(input)?;
+        }
+
+        Ok(Self { imports })
+    }
+}
+
+pub fn expand_imports(
+    input: ImportMacroInput,
+    mode: &Mode,
+) -> std::result::Result<TokenStream, Vec<syn::Error>> {
+    let (statements, errs): (Vec<_>, Vec<_>) = input
+        .imports
+        .into_iter()
+        .map(|i| i.try_into_statement(mode))
+        .partition(Result::is_ok);
+
+    if !errs.is_empty() {
+        Err(errs.into_iter().map(Result::unwrap_err).collect())
+    } else {
+        Ok(statements.into_iter().map(Result::unwrap).collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use quickcheck::quickcheck;
+    use syn::parse_quote;
+
+    /// Decodes a string that was encoded using `encode`.
+    fn decode(s: &str) -> String {
+        DECODER.replace_all(s, &SUBSTITUTIONS.0)
+    }
+
+    #[test]
+    fn test_expand_imports_without_renaming() -> std::result::Result<(), Vec<syn::Error>> {
+        let mode = Mode::NoRenaming;
+
+        // Nothing to do.
+        let expanded = expand_imports(parse_quote! {}, &mode)?;
+        assert_eq!(expanded.to_string(), "");
+
+        // Package and a target.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and a target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and a target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils as my_utils ;");
+
+        // Package and an implicit target.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and an implicit target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils ;");
+
+        // Package and an implicit target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate utils as my_utils ;");
+
+        // A third-party target.
+        let expanded =
+            expand_imports(parse_quote! { "//third_party/rust/serde/v1:serde"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with a no-op alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with an alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as my_serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde as my_serde ;");
+
+        // Multiple targets.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:utils"; "//third_party/rust/serde/v1:serde"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate utils ; extern crate serde ;"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_expand_imports_with_renaming() -> std::result::Result<(), Vec<syn::Error>> {
+        let mode = Mode::RenameFirstPartyCrates {
+            third_party_dir: "third_party/rust".to_string(),
+        };
+
+        // Nothing to do.
+        let expanded = expand_imports(parse_quote! {}, &mode)?;
+        assert_eq!(expanded.to_string(), "");
+
+        // Package and a target.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ;"
+        );
+
+        // Package and a target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ;"
+        );
+
+        // Package and a target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project:utils" as my_utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as my_utils ;"
+        );
+
+        // Package and an implicit target.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as utils ;"
+        );
+
+        // Package and an implicit target, with a no-op alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as utils ;"
+        );
+
+        // Package and an implicit target, with an alias.
+        let expanded = expand_imports(parse_quote! { "//some_project/utils" as my_utils; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_slash_utils_colon_utils as my_utils ;"
+        );
+
+        // A third-party target.
+        let expanded =
+            expand_imports(parse_quote! { "//third_party/rust/serde/v1:serde"; }, &mode)?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with a no-op alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde ;");
+
+        // A third-party target with an alias.
+        let expanded = expand_imports(
+            parse_quote! { "//third_party/rust/serde/v1:serde" as my_serde; },
+            &mode,
+        )?;
+        assert_eq!(expanded.to_string(), "extern crate serde as my_serde ;");
+
+        // Multiple targets.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:utils"; "//third_party/rust/serde/v1:serde"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_utils as utils ; extern crate serde ;"
+        );
+
+        // Problematic target name.
+        let expanded = expand_imports(parse_quote! { "//some_project:thing-types"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_thing_dash_types as thing_dash_types ;"
+        );
+
+        // Problematic target name with alias.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project:thing-types" as types; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_colon_thing_dash_types as types ;"
+        );
+
+        // Problematic package name.
+        let expanded = expand_imports(parse_quote! { "//some_project-prototype:utils"; }, &mode)?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_dash_prototype_colon_utils as utils ;"
+        );
+
+        // Problematic package and target names.
+        let expanded = expand_imports(
+            parse_quote! { "//some_project-prototype:thing-types"; },
+            &mode,
+        )?;
+        assert_eq!(
+            expanded.to_string(),
+            "extern crate some_project_dash_prototype_colon_thing_dash_types as thing_dash_types ;"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_expansion_failures() -> Result<()> {
+        let mode = Mode::NoRenaming;
+
+        // Missing leading "//", not a valid label.
+        let errs = expand_imports(parse_quote! { "some_project:utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but relative.
+        let errs = expand_imports(parse_quote! { ":utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but a wildcard.
+        let errs = expand_imports(parse_quote! { "some_project/..."; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        // Valid label, but only in Bazel (not in Bazel).
+        let errs =
+            expand_imports(parse_quote! { "@repository//some_project:utils"; }, &mode).unwrap_err();
+        assert_eq!(
+            errs.into_iter()
+                .map(|e| e.to_string())
+                .collect::<Vec<String>>(),
+            vec!["Bazel labels must be of the form '//package[:target]'"]
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_macro_input_parsing_errors() -> Result<()> {
+        // Label is not a string literal.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("some_project:utils;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as a string literal"
+        );
+
+        // Label is the wrong kind of literal.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("true;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as string literal, found 'true' literal"
+        );
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>("123;")
+                .err()
+                .unwrap()
+                .to_string(),
+            "expected Bazel label as string literal, found '123' literal"
+        );
+
+        // Alias is not a valid identifier.
+        assert_eq!(
+            syn::parse_str::<ImportMacroInput>(r#""some_project:utils" as "!@#$%";"#)
+                .err()
+                .unwrap()
+                .to_string(),
+            "alias must be a valid Rust identifier"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_label_parsing() -> Result<()> {
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project:utils", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project",
+                name: "utils"
+            },
+        );
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project/utils", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project/utils",
+                name: "utils"
+            },
+        );
+        assert_eq!(
+            AbsoluteLabel::parse("//some_project", &Span::call_site())?,
+            AbsoluteLabel {
+                package_name: "some_project",
+                name: "some_project"
+            },
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_encode() -> Result<()> {
+        assert_eq!(encode("some_project:utils"), "some_project_colon_utils");
+        assert_eq!(&encode("_quotedot_"), "_quotequote_dot_");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_decode() -> Result<()> {
+        assert_eq!(decode("some_project_colon_utils"), "some_project:utils");
+        assert_eq!(decode("_quotequote_dot_"), "_quotedot_");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_substitutions_compose() -> Result<()> {
+        for s in SUBSTITUTIONS.0.iter().chain(SUBSTITUTIONS.1.iter()) {
+            assert_eq!(&decode(&encode(s)), s);
+        }
+
+        Ok(())
+    }
+
+    quickcheck! {
+        fn composition_is_identity(s: String) -> bool {
+            s == decode(&encode(&s))
+        }
+    }
+}
diff --git a/util/import/raze/BUILD.bazel b/util/import/raze/BUILD.bazel
new file mode 100644
index 0000000..2f14d0e
--- /dev/null
+++ b/util/import/raze/BUILD.bazel
@@ -0,0 +1,75 @@
+"""
+@generated
+cargo-raze generated Bazel file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # See individual crates for specific licenses
+])
+
+# Aliased targets
+alias(
+    name = "aho_corasick",
+    actual = "@rules_rust_util_import__aho_corasick__0_7_15//:aho_corasick",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+alias(
+    name = "lazy_static",
+    actual = "@rules_rust_util_import__lazy_static__1_4_0//:lazy_static",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+alias(
+    name = "proc_macro2",
+    actual = "@rules_rust_util_import__proc_macro2__1_0_33//:proc_macro2",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+alias(
+    name = "quickcheck",
+    actual = "@rules_rust_util_import__quickcheck__1_0_3//:quickcheck",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+alias(
+    name = "quote",
+    actual = "@rules_rust_util_import__quote__1_0_10//:quote",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+alias(
+    name = "syn",
+    actual = "@rules_rust_util_import__syn__1_0_82//:syn",
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+)
+
+# Export file for Stardoc support
+exports_files(
+    [
+        "crates.bzl",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/util/import/raze/Cargo.raze.lock b/util/import/raze/Cargo.raze.lock
new file mode 100644
index 0000000..72b143b
--- /dev/null
+++ b/util/import/raze/Cargo.raze.lock
@@ -0,0 +1,163 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "compile_with_bazel"
+version = "0.0.0"
+dependencies = [
+ "aho-corasick",
+ "lazy_static",
+ "proc-macro2",
+ "quickcheck",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quickcheck"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
+dependencies = [
+ "env_logger",
+ "log",
+ "rand",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "syn"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/util/import/raze/Cargo.toml b/util/import/raze/Cargo.toml
new file mode 100644
index 0000000..b942944
--- /dev/null
+++ b/util/import/raze/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "compile_with_bazel"
+version = "0.0.0"
+
+# Mandatory (or Cargo tooling is unhappy)
+[lib]
+path = "fake_lib.rs"
+
+[dependencies]
+aho-corasick = "=0.7.15"
+lazy_static = "=1.4.0"
+proc-macro2 = "=1.0.33"
+quote = "=1.0.10"
+syn = "=1.0.82"
+
+[dev-dependencies]
+quickcheck = "=1.0.3"
+
+[package.metadata.raze]
+genmode = "Remote"
+workspace_path = "//util/import/raze"
+gen_workspace_prefix = "rules_rust_util_import"
+rust_rules_workspace_name = "rules_rust"
+package_aliases_dir = "."
+default_gen_buildrs = true
\ No newline at end of file
diff --git a/util/import/raze/crates.bzl b/util/import/raze/crates.bzl
new file mode 100644
index 0000000..7da9f98
--- /dev/null
+++ b/util/import/raze/crates.bzl
@@ -0,0 +1,192 @@
+"""
+@generated
+cargo-raze generated Bazel file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")  # buildifier: disable=load
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")  # buildifier: disable=load
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")  # buildifier: disable=load
+
+def rules_rust_util_import_fetch_remote_crates():
+    """This function defines a collection of repos and should be called in a WORKSPACE file"""
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__aho_corasick__0_7_15",
+        url = "https://crates.io/api/v1/crates/aho-corasick/0.7.15/download",
+        type = "tar.gz",
+        sha256 = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5",
+        strip_prefix = "aho-corasick-0.7.15",
+        build_file = Label("//util/import/raze/remote:BUILD.aho-corasick-0.7.15.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__cfg_if__1_0_0",
+        url = "https://crates.io/api/v1/crates/cfg-if/1.0.0/download",
+        type = "tar.gz",
+        sha256 = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd",
+        strip_prefix = "cfg-if-1.0.0",
+        build_file = Label("//util/import/raze/remote:BUILD.cfg-if-1.0.0.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__env_logger__0_8_4",
+        url = "https://crates.io/api/v1/crates/env_logger/0.8.4/download",
+        type = "tar.gz",
+        sha256 = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3",
+        strip_prefix = "env_logger-0.8.4",
+        build_file = Label("//util/import/raze/remote:BUILD.env_logger-0.8.4.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__getrandom__0_2_3",
+        url = "https://crates.io/api/v1/crates/getrandom/0.2.3/download",
+        type = "tar.gz",
+        sha256 = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753",
+        strip_prefix = "getrandom-0.2.3",
+        build_file = Label("//util/import/raze/remote:BUILD.getrandom-0.2.3.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__lazy_static__1_4_0",
+        url = "https://crates.io/api/v1/crates/lazy_static/1.4.0/download",
+        type = "tar.gz",
+        sha256 = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646",
+        strip_prefix = "lazy_static-1.4.0",
+        build_file = Label("//util/import/raze/remote:BUILD.lazy_static-1.4.0.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__libc__0_2_112",
+        url = "https://crates.io/api/v1/crates/libc/0.2.112/download",
+        type = "tar.gz",
+        sha256 = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125",
+        strip_prefix = "libc-0.2.112",
+        build_file = Label("//util/import/raze/remote:BUILD.libc-0.2.112.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__log__0_4_14",
+        url = "https://crates.io/api/v1/crates/log/0.4.14/download",
+        type = "tar.gz",
+        sha256 = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710",
+        strip_prefix = "log-0.4.14",
+        build_file = Label("//util/import/raze/remote:BUILD.log-0.4.14.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__memchr__2_4_1",
+        url = "https://crates.io/api/v1/crates/memchr/2.4.1/download",
+        type = "tar.gz",
+        sha256 = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a",
+        strip_prefix = "memchr-2.4.1",
+        build_file = Label("//util/import/raze/remote:BUILD.memchr-2.4.1.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__proc_macro2__1_0_33",
+        url = "https://crates.io/api/v1/crates/proc-macro2/1.0.33/download",
+        type = "tar.gz",
+        sha256 = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a",
+        strip_prefix = "proc-macro2-1.0.33",
+        build_file = Label("//util/import/raze/remote:BUILD.proc-macro2-1.0.33.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__quickcheck__1_0_3",
+        url = "https://crates.io/api/v1/crates/quickcheck/1.0.3/download",
+        type = "tar.gz",
+        sha256 = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6",
+        strip_prefix = "quickcheck-1.0.3",
+        build_file = Label("//util/import/raze/remote:BUILD.quickcheck-1.0.3.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__quote__1_0_10",
+        url = "https://crates.io/api/v1/crates/quote/1.0.10/download",
+        type = "tar.gz",
+        sha256 = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05",
+        strip_prefix = "quote-1.0.10",
+        build_file = Label("//util/import/raze/remote:BUILD.quote-1.0.10.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__rand__0_8_4",
+        url = "https://crates.io/api/v1/crates/rand/0.8.4/download",
+        type = "tar.gz",
+        sha256 = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8",
+        strip_prefix = "rand-0.8.4",
+        build_file = Label("//util/import/raze/remote:BUILD.rand-0.8.4.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__rand_core__0_6_3",
+        url = "https://crates.io/api/v1/crates/rand_core/0.6.3/download",
+        type = "tar.gz",
+        sha256 = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7",
+        strip_prefix = "rand_core-0.6.3",
+        build_file = Label("//util/import/raze/remote:BUILD.rand_core-0.6.3.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__regex__1_4_6",
+        url = "https://crates.io/api/v1/crates/regex/1.4.6/download",
+        type = "tar.gz",
+        sha256 = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759",
+        strip_prefix = "regex-1.4.6",
+        build_file = Label("//util/import/raze/remote:BUILD.regex-1.4.6.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__regex_syntax__0_6_25",
+        url = "https://crates.io/api/v1/crates/regex-syntax/0.6.25/download",
+        type = "tar.gz",
+        sha256 = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b",
+        strip_prefix = "regex-syntax-0.6.25",
+        build_file = Label("//util/import/raze/remote:BUILD.regex-syntax-0.6.25.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__syn__1_0_82",
+        url = "https://crates.io/api/v1/crates/syn/1.0.82/download",
+        type = "tar.gz",
+        sha256 = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59",
+        strip_prefix = "syn-1.0.82",
+        build_file = Label("//util/import/raze/remote:BUILD.syn-1.0.82.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__unicode_xid__0_2_2",
+        url = "https://crates.io/api/v1/crates/unicode-xid/0.2.2/download",
+        type = "tar.gz",
+        sha256 = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3",
+        strip_prefix = "unicode-xid-0.2.2",
+        build_file = Label("//util/import/raze/remote:BUILD.unicode-xid-0.2.2.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "rules_rust_util_import__wasi__0_10_2_wasi_snapshot_preview1",
+        url = "https://crates.io/api/v1/crates/wasi/0.10.2+wasi-snapshot-preview1/download",
+        type = "tar.gz",
+        sha256 = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6",
+        strip_prefix = "wasi-0.10.2+wasi-snapshot-preview1",
+        build_file = Label("//util/import/raze/remote:BUILD.wasi-0.10.2+wasi-snapshot-preview1.bazel"),
+    )
diff --git a/util/import/raze/remote/BUILD.aho-corasick-0.7.15.bazel b/util/import/raze/remote/BUILD.aho-corasick-0.7.15.bazel
new file mode 100644
index 0000000..4fc8c0d
--- /dev/null
+++ b/util/import/raze/remote/BUILD.aho-corasick-0.7.15.bazel
@@ -0,0 +1,57 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "unencumbered",  # Unlicense from expression "Unlicense OR MIT"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "aho_corasick",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "std",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=aho_corasick",
+        "manual",
+    ],
+    version = "0.7.15",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__memchr__2_4_1//:memchr",
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.bazel b/util/import/raze/remote/BUILD.bazel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/util/import/raze/remote/BUILD.bazel
diff --git a/util/import/raze/remote/BUILD.cfg-if-1.0.0.bazel b/util/import/raze/remote/BUILD.cfg-if-1.0.0.bazel
new file mode 100644
index 0000000..1a5adcf
--- /dev/null
+++ b/util/import/raze/remote/BUILD.cfg-if-1.0.0.bazel
@@ -0,0 +1,56 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "cfg_if",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=cfg-if",
+        "manual",
+    ],
+    version = "1.0.0",
+    # buildifier: leave-alone
+    deps = [
+    ],
+)
+
+# Unsupported target "xcrate" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.env_logger-0.8.4.bazel b/util/import/raze/remote/BUILD.env_logger-0.8.4.bazel
new file mode 100644
index 0000000..a04b02c
--- /dev/null
+++ b/util/import/raze/remote/BUILD.env_logger-0.8.4.bazel
@@ -0,0 +1,65 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "env_logger",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "regex",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=env_logger",
+        "manual",
+    ],
+    version = "0.8.4",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__log__0_4_14//:log",
+        "@rules_rust_util_import__regex__1_4_6//:regex",
+    ],
+)
+
+# Unsupported target "init-twice-retains-filter" with type "test" omitted
+
+# Unsupported target "log-in-log" with type "test" omitted
+
+# Unsupported target "log_tls_dtors" with type "test" omitted
+
+# Unsupported target "regexp_filter" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.getrandom-0.2.3.bazel b/util/import/raze/remote/BUILD.getrandom-0.2.3.bazel
new file mode 100644
index 0000000..8333757
--- /dev/null
+++ b/util/import/raze/remote/BUILD.getrandom-0.2.3.bazel
@@ -0,0 +1,96 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+# Unsupported target "mod" with type "bench" omitted
+
+rust_library(
+    name = "getrandom",
+    srcs = glob(["**/*.rs"]),
+    aliases = {
+    },
+    crate_features = [
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=getrandom",
+        "manual",
+    ],
+    version = "0.2.3",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__cfg_if__1_0_0//:cfg_if",
+    ] + selects.with_or({
+        # cfg(target_os = "wasi")
+        (
+            "@rules_rust//rust/platform:wasm32-wasi",
+        ): [
+            "@rules_rust_util_import__wasi__0_10_2_wasi_snapshot_preview1//:wasi",
+        ],
+        "//conditions:default": [],
+    }) + selects.with_or({
+        # cfg(unix)
+        (
+            "@rules_rust//rust/platform:i686-apple-darwin",
+            "@rules_rust//rust/platform:i686-unknown-linux-gnu",
+            "@rules_rust//rust/platform:x86_64-apple-darwin",
+            "@rules_rust//rust/platform:x86_64-unknown-linux-gnu",
+            "@rules_rust//rust/platform:aarch64-apple-darwin",
+            "@rules_rust//rust/platform:aarch64-apple-ios",
+            "@rules_rust//rust/platform:aarch64-linux-android",
+            "@rules_rust//rust/platform:aarch64-unknown-linux-gnu",
+            "@rules_rust//rust/platform:arm-unknown-linux-gnueabi",
+            "@rules_rust//rust/platform:i686-linux-android",
+            "@rules_rust//rust/platform:i686-unknown-freebsd",
+            "@rules_rust//rust/platform:powerpc-unknown-linux-gnu",
+            "@rules_rust//rust/platform:s390x-unknown-linux-gnu",
+            "@rules_rust//rust/platform:x86_64-apple-ios",
+            "@rules_rust//rust/platform:x86_64-linux-android",
+            "@rules_rust//rust/platform:x86_64-unknown-freebsd",
+        ): [
+            "@rules_rust_util_import__libc__0_2_112//:libc",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+# Unsupported target "custom" with type "test" omitted
+
+# Unsupported target "normal" with type "test" omitted
+
+# Unsupported target "rdrand" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.lazy_static-1.4.0.bazel b/util/import/raze/remote/BUILD.lazy_static-1.4.0.bazel
new file mode 100644
index 0000000..5542226
--- /dev/null
+++ b/util/import/raze/remote/BUILD.lazy_static-1.4.0.bazel
@@ -0,0 +1,58 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "lazy_static",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=lazy_static",
+        "manual",
+    ],
+    version = "1.4.0",
+    # buildifier: leave-alone
+    deps = [
+    ],
+)
+
+# Unsupported target "no_std" with type "test" omitted
+
+# Unsupported target "test" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.libc-0.2.112.bazel b/util/import/raze/remote/BUILD.libc-0.2.112.bazel
new file mode 100644
index 0000000..2d8a3fe
--- /dev/null
+++ b/util/import/raze/remote/BUILD.libc-0.2.112.bazel
@@ -0,0 +1,86 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+# buildifier: disable=out-of-order-load
+# buildifier: disable=load-on-top
+load(
+    "@rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "libc_build_script",
+    srcs = glob(["**/*.rs"]),
+    build_script_env = {
+    },
+    crate_features = [
+    ],
+    crate_root = "build.rs",
+    data = glob(["**"]),
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+    version = "0.2.112",
+    visibility = ["//visibility:private"],
+    deps = [
+    ],
+)
+
+rust_library(
+    name = "libc",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=libc",
+        "manual",
+    ],
+    version = "0.2.112",
+    # buildifier: leave-alone
+    deps = [
+        ":libc_build_script",
+    ],
+)
+
+# Unsupported target "const_fn" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.log-0.4.14.bazel b/util/import/raze/remote/BUILD.log-0.4.14.bazel
new file mode 100644
index 0000000..02efe53
--- /dev/null
+++ b/util/import/raze/remote/BUILD.log-0.4.14.bazel
@@ -0,0 +1,93 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+# buildifier: disable=out-of-order-load
+# buildifier: disable=load-on-top
+load(
+    "@rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "log_build_script",
+    srcs = glob(["**/*.rs"]),
+    build_script_env = {
+    },
+    crate_features = [
+        "std",
+    ],
+    crate_root = "build.rs",
+    data = glob(["**"]),
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+    version = "0.4.14",
+    visibility = ["//visibility:private"],
+    deps = [
+    ],
+)
+
+# Unsupported target "value" with type "bench" omitted
+
+rust_library(
+    name = "log",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "std",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=log",
+        "manual",
+    ],
+    version = "0.4.14",
+    # buildifier: leave-alone
+    deps = [
+        ":log_build_script",
+        "@rules_rust_util_import__cfg_if__1_0_0//:cfg_if",
+    ],
+)
+
+# Unsupported target "filters" with type "test" omitted
+
+# Unsupported target "macros" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.memchr-2.4.1.bazel b/util/import/raze/remote/BUILD.memchr-2.4.1.bazel
new file mode 100644
index 0000000..c8e37c3
--- /dev/null
+++ b/util/import/raze/remote/BUILD.memchr-2.4.1.bazel
@@ -0,0 +1,90 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "unencumbered",  # Unlicense from expression "Unlicense OR MIT"
+])
+
+# Generated Targets
+# buildifier: disable=out-of-order-load
+# buildifier: disable=load-on-top
+load(
+    "@rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "memchr_build_script",
+    srcs = glob(["**/*.rs"]),
+    build_script_env = {
+    },
+    crate_features = [
+        "default",
+        "std",
+        "use_std",
+    ],
+    crate_root = "build.rs",
+    data = glob(["**"]),
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+    version = "2.4.1",
+    visibility = ["//visibility:private"],
+    deps = [
+    ],
+)
+
+rust_library(
+    name = "memchr",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "std",
+        "use_std",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=memchr",
+        "manual",
+    ],
+    version = "2.4.1",
+    # buildifier: leave-alone
+    deps = [
+        ":memchr_build_script",
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.proc-macro2-1.0.33.bazel b/util/import/raze/remote/BUILD.proc-macro2-1.0.33.bazel
new file mode 100644
index 0000000..0e479f2
--- /dev/null
+++ b/util/import/raze/remote/BUILD.proc-macro2-1.0.33.bazel
@@ -0,0 +1,99 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+# buildifier: disable=out-of-order-load
+# buildifier: disable=load-on-top
+load(
+    "@rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "proc_macro2_build_script",
+    srcs = glob(["**/*.rs"]),
+    build_script_env = {
+    },
+    crate_features = [
+        "default",
+        "proc-macro",
+    ],
+    crate_root = "build.rs",
+    data = glob(["**"]),
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+    version = "1.0.33",
+    visibility = ["//visibility:private"],
+    deps = [
+    ],
+)
+
+rust_library(
+    name = "proc_macro2",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "proc-macro",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=proc-macro2",
+        "manual",
+    ],
+    version = "1.0.33",
+    # buildifier: leave-alone
+    deps = [
+        ":proc_macro2_build_script",
+        "@rules_rust_util_import__unicode_xid__0_2_2//:unicode_xid",
+    ],
+)
+
+# Unsupported target "comments" with type "test" omitted
+
+# Unsupported target "features" with type "test" omitted
+
+# Unsupported target "marker" with type "test" omitted
+
+# Unsupported target "test" with type "test" omitted
+
+# Unsupported target "test_fmt" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.quickcheck-1.0.3.bazel b/util/import/raze/remote/BUILD.quickcheck-1.0.3.bazel
new file mode 100644
index 0000000..a9db2b4
--- /dev/null
+++ b/util/import/raze/remote/BUILD.quickcheck-1.0.3.bazel
@@ -0,0 +1,74 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "unencumbered",  # Unlicense from expression "Unlicense OR MIT"
+])
+
+# Generated Targets
+
+# Unsupported target "btree_set_range" with type "example" omitted
+
+# Unsupported target "out_of_bounds" with type "example" omitted
+
+# Unsupported target "reverse" with type "example" omitted
+
+# Unsupported target "reverse_single" with type "example" omitted
+
+# Unsupported target "sieve" with type "example" omitted
+
+# Unsupported target "sort" with type "example" omitted
+
+rust_library(
+    name = "quickcheck",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "env_logger",
+        "log",
+        "regex",
+        "use_logging",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=quickcheck",
+        "manual",
+    ],
+    version = "1.0.3",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__env_logger__0_8_4//:env_logger",
+        "@rules_rust_util_import__log__0_4_14//:log",
+        "@rules_rust_util_import__rand__0_8_4//:rand",
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.quote-1.0.10.bazel b/util/import/raze/remote/BUILD.quote-1.0.10.bazel
new file mode 100644
index 0000000..040d8cb
--- /dev/null
+++ b/util/import/raze/remote/BUILD.quote-1.0.10.bazel
@@ -0,0 +1,63 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+# Unsupported target "bench" with type "bench" omitted
+
+rust_library(
+    name = "quote",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "proc-macro",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=quote",
+        "manual",
+    ],
+    version = "1.0.10",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__proc_macro2__1_0_33//:proc_macro2",
+    ],
+)
+
+# Unsupported target "compiletest" with type "test" omitted
+
+# Unsupported target "test" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.rand-0.8.4.bazel b/util/import/raze/remote/BUILD.rand-0.8.4.bazel
new file mode 100644
index 0000000..ebbb9e3
--- /dev/null
+++ b/util/import/raze/remote/BUILD.rand-0.8.4.bazel
@@ -0,0 +1,57 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "rand",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "getrandom",
+        "small_rng",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=rand",
+        "manual",
+    ],
+    version = "0.8.4",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__rand_core__0_6_3//:rand_core",
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.rand_core-0.6.3.bazel b/util/import/raze/remote/BUILD.rand_core-0.6.3.bazel
new file mode 100644
index 0000000..1cc93d3
--- /dev/null
+++ b/util/import/raze/remote/BUILD.rand_core-0.6.3.bazel
@@ -0,0 +1,56 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "rand_core",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "getrandom",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=rand_core",
+        "manual",
+    ],
+    version = "0.6.3",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__getrandom__0_2_3//:getrandom",
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.regex-1.4.6.bazel b/util/import/raze/remote/BUILD.regex-1.4.6.bazel
new file mode 100644
index 0000000..0957c94
--- /dev/null
+++ b/util/import/raze/remote/BUILD.regex-1.4.6.bazel
@@ -0,0 +1,95 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+# Unsupported target "shootout-regex-dna" with type "example" omitted
+
+# Unsupported target "shootout-regex-dna-bytes" with type "example" omitted
+
+# Unsupported target "shootout-regex-dna-cheat" with type "example" omitted
+
+# Unsupported target "shootout-regex-dna-replace" with type "example" omitted
+
+# Unsupported target "shootout-regex-dna-single" with type "example" omitted
+
+# Unsupported target "shootout-regex-dna-single-cheat" with type "example" omitted
+
+rust_library(
+    name = "regex",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "aho-corasick",
+        "memchr",
+        "perf",
+        "perf-cache",
+        "perf-dfa",
+        "perf-inline",
+        "perf-literal",
+        "std",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=regex",
+        "manual",
+    ],
+    version = "1.4.6",
+    # buildifier: leave-alone
+    deps = [
+        "@rules_rust_util_import__aho_corasick__0_7_15//:aho_corasick",
+        "@rules_rust_util_import__memchr__2_4_1//:memchr",
+        "@rules_rust_util_import__regex_syntax__0_6_25//:regex_syntax",
+    ],
+)
+
+# Unsupported target "backtrack" with type "test" omitted
+
+# Unsupported target "backtrack-bytes" with type "test" omitted
+
+# Unsupported target "backtrack-utf8bytes" with type "test" omitted
+
+# Unsupported target "crates-regex" with type "test" omitted
+
+# Unsupported target "default" with type "test" omitted
+
+# Unsupported target "default-bytes" with type "test" omitted
+
+# Unsupported target "nfa" with type "test" omitted
+
+# Unsupported target "nfa-bytes" with type "test" omitted
+
+# Unsupported target "nfa-utf8bytes" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.regex-syntax-0.6.25.bazel b/util/import/raze/remote/BUILD.regex-syntax-0.6.25.bazel
new file mode 100644
index 0000000..71324e7
--- /dev/null
+++ b/util/import/raze/remote/BUILD.regex-syntax-0.6.25.bazel
@@ -0,0 +1,56 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+# Unsupported target "bench" with type "bench" omitted
+
+rust_library(
+    name = "regex_syntax",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=regex-syntax",
+        "manual",
+    ],
+    version = "0.6.25",
+    # buildifier: leave-alone
+    deps = [
+    ],
+)
diff --git a/util/import/raze/remote/BUILD.syn-1.0.82.bazel b/util/import/raze/remote/BUILD.syn-1.0.82.bazel
new file mode 100644
index 0000000..8031e00
--- /dev/null
+++ b/util/import/raze/remote/BUILD.syn-1.0.82.bazel
@@ -0,0 +1,157 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+# buildifier: disable=out-of-order-load
+# buildifier: disable=load-on-top
+load(
+    "@rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "syn_build_script",
+    srcs = glob(["**/*.rs"]),
+    build_script_env = {
+    },
+    crate_features = [
+        "clone-impls",
+        "default",
+        "derive",
+        "parsing",
+        "printing",
+        "proc-macro",
+        "quote",
+    ],
+    crate_root = "build.rs",
+    data = glob(["**"]),
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "manual",
+    ],
+    version = "1.0.82",
+    visibility = ["//visibility:private"],
+    deps = [
+    ],
+)
+
+# Unsupported target "file" with type "bench" omitted
+
+# Unsupported target "rust" with type "bench" omitted
+
+rust_library(
+    name = "syn",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "clone-impls",
+        "default",
+        "derive",
+        "parsing",
+        "printing",
+        "proc-macro",
+        "quote",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=syn",
+        "manual",
+    ],
+    version = "1.0.82",
+    # buildifier: leave-alone
+    deps = [
+        ":syn_build_script",
+        "@rules_rust_util_import__proc_macro2__1_0_33//:proc_macro2",
+        "@rules_rust_util_import__quote__1_0_10//:quote",
+        "@rules_rust_util_import__unicode_xid__0_2_2//:unicode_xid",
+    ],
+)
+
+# Unsupported target "test_asyncness" with type "test" omitted
+
+# Unsupported target "test_attribute" with type "test" omitted
+
+# Unsupported target "test_derive_input" with type "test" omitted
+
+# Unsupported target "test_expr" with type "test" omitted
+
+# Unsupported target "test_generics" with type "test" omitted
+
+# Unsupported target "test_grouping" with type "test" omitted
+
+# Unsupported target "test_ident" with type "test" omitted
+
+# Unsupported target "test_item" with type "test" omitted
+
+# Unsupported target "test_iterators" with type "test" omitted
+
+# Unsupported target "test_lit" with type "test" omitted
+
+# Unsupported target "test_meta" with type "test" omitted
+
+# Unsupported target "test_parse_buffer" with type "test" omitted
+
+# Unsupported target "test_parse_stream" with type "test" omitted
+
+# Unsupported target "test_pat" with type "test" omitted
+
+# Unsupported target "test_path" with type "test" omitted
+
+# Unsupported target "test_precedence" with type "test" omitted
+
+# Unsupported target "test_receiver" with type "test" omitted
+
+# Unsupported target "test_round_trip" with type "test" omitted
+
+# Unsupported target "test_shebang" with type "test" omitted
+
+# Unsupported target "test_should_parse" with type "test" omitted
+
+# Unsupported target "test_size" with type "test" omitted
+
+# Unsupported target "test_stmt" with type "test" omitted
+
+# Unsupported target "test_token_trees" with type "test" omitted
+
+# Unsupported target "test_ty" with type "test" omitted
+
+# Unsupported target "test_visibility" with type "test" omitted
+
+# Unsupported target "zzz_stable" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.unicode-xid-0.2.2.bazel b/util/import/raze/remote/BUILD.unicode-xid-0.2.2.bazel
new file mode 100644
index 0000000..a62237c
--- /dev/null
+++ b/util/import/raze/remote/BUILD.unicode-xid-0.2.2.bazel
@@ -0,0 +1,59 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+# Generated Targets
+
+# Unsupported target "xid" with type "bench" omitted
+
+rust_library(
+    name = "unicode_xid",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2015",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=unicode-xid",
+        "manual",
+    ],
+    version = "0.2.2",
+    # buildifier: leave-alone
+    deps = [
+    ],
+)
+
+# Unsupported target "exhaustive_tests" with type "test" omitted
diff --git a/util/import/raze/remote/BUILD.wasi-0.10.2+wasi-snapshot-preview1.bazel b/util/import/raze/remote/BUILD.wasi-0.10.2+wasi-snapshot-preview1.bazel
new file mode 100644
index 0000000..f4229fe
--- /dev/null
+++ b/util/import/raze/remote/BUILD.wasi-0.10.2+wasi-snapshot-preview1.bazel
@@ -0,0 +1,56 @@
+"""
+@generated
+cargo-raze crate build file.
+
+DO NOT EDIT! Replaced on runs of cargo-raze
+"""
+
+# buildifier: disable=load
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+# buildifier: disable=load
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_proc_macro",
+    "rust_test",
+)
+
+package(default_visibility = [
+    # Public for visibility by "@raze__crate__version//" targets.
+    #
+    # Prefer access through "//util/import/raze", which limits external
+    # visibility to explicit Cargo.toml dependencies.
+    "//visibility:public",
+])
+
+licenses([
+    "notice",  # Apache-2.0 from expression "Apache-2.0 OR (Apache-2.0 OR MIT)"
+])
+
+# Generated Targets
+
+rust_library(
+    name = "wasi",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "std",
+    ],
+    crate_root = "src/lib.rs",
+    data = [],
+    edition = "2018",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=wasi",
+        "manual",
+    ],
+    version = "0.10.2+wasi-snapshot-preview1",
+    # buildifier: leave-alone
+    deps = [
+    ],
+)
diff --git a/util/label/BUILD.bazel b/util/label/BUILD.bazel
new file mode 100644
index 0000000..405006d
--- /dev/null
+++ b/util/label/BUILD.bazel
@@ -0,0 +1,16 @@
+load("//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+    name = "label",
+    srcs = [
+        "label.rs",
+        "label_error.rs",
+    ],
+    edition = "2018",
+    visibility = ["//:__subpackages__"],
+)
+
+rust_test(
+    name = "label_test",
+    crate = ":label",
+)
diff --git a/util/label/label.rs b/util/label/label.rs
new file mode 100644
index 0000000..22d2bb4
--- /dev/null
+++ b/util/label/label.rs
@@ -0,0 +1,426 @@
+//! Bazel label parsing library.
+//!
+//! USAGE: `label::analyze("//foo/bar:baz")
+mod label_error;
+use label_error::LabelError;
+
+/// Parse and analyze given str.
+///
+/// TODO: validate . and .. in target name
+/// TODO: validate used characters in target name
+pub fn analyze(input: &'_ str) -> Result<Label<'_>> {
+    let label = input;
+    let (input, repository_name) = consume_repository_name(input, label)?;
+    let (input, package_name) = consume_package_name(input, label)?;
+    let name = consume_name(input, label)?;
+    let name = match (package_name, name) {
+        (None, None) => {
+            return Err(LabelError(err(
+                label,
+                "labels must have a package and/or a name.",
+            )))
+        }
+        (Some(package_name), None) => name_from_package(package_name),
+        (_, Some(name)) => name,
+    };
+    Ok(Label::new(repository_name, package_name, name))
+}
+
+#[derive(Debug, PartialEq)]
+pub struct Label<'s> {
+    pub repository_name: Option<&'s str>,
+    pub package_name: Option<&'s str>,
+    pub name: &'s str,
+}
+
+type Result<T, E = LabelError> = core::result::Result<T, E>;
+
+impl<'s> Label<'s> {
+    fn new(
+        repository_name: Option<&'s str>,
+        package_name: Option<&'s str>,
+        name: &'s str,
+    ) -> Label<'s> {
+        Label {
+            repository_name,
+            package_name,
+            name,
+        }
+    }
+
+    pub fn packages(&self) -> Vec<&'s str> {
+        match self.package_name {
+            Some(name) => name.split('/').collect(),
+            None => vec![],
+        }
+    }
+}
+
+fn err<'s>(label: &'s str, msg: &'s str) -> String {
+    let mut err_msg = label.to_string();
+    err_msg.push_str(" must be a legal label; ");
+    err_msg.push_str(msg);
+    err_msg
+}
+
+fn consume_repository_name<'s>(
+    input: &'s str,
+    label: &'s str,
+) -> Result<(&'s str, Option<&'s str>)> {
+    if !input.starts_with('@') {
+        return Ok((input, None));
+    }
+
+    let slash_pos = input
+        .find("//")
+        .ok_or_else(|| err(label, "labels with repository must contain //."))?;
+    let repository_name = &input[1..slash_pos];
+    if repository_name.is_empty() {
+        return Ok((&input[1..], None));
+    }
+    if !repository_name
+        .chars()
+        .next()
+        .unwrap()
+        .is_ascii_alphabetic()
+    {
+        return Err(LabelError(err(
+            label,
+            "workspace names must start with a letter.",
+        )));
+    }
+    if !repository_name
+        .chars()
+        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
+    {
+        return Err(LabelError(err(
+            label,
+            "workspace names \
+                may contain only A-Z, a-z, 0-9, '-', '_', and '.'.",
+        )));
+    }
+    Ok((&input[slash_pos..], Some(repository_name)))
+}
+
+fn consume_package_name<'s>(input: &'s str, label: &'s str) -> Result<(&'s str, Option<&'s str>)> {
+    let is_absolute = match input.rfind("//") {
+        None => false,
+        Some(0) => true,
+        Some(_) => {
+            return Err(LabelError(err(
+                label,
+                "'//' cannot appear in the middle of the label.",
+            )));
+        }
+    };
+
+    let (package_name, rest) = match (is_absolute, input.find(':')) {
+        (false, colon_pos) if colon_pos.map_or(true, |pos| pos != 0) => {
+            return Err(LabelError(err(
+                label,
+                "relative packages are not permitted.",
+            )));
+        }
+        (_, colon_pos) => {
+            let (input, colon_pos) = if is_absolute {
+                (&input[2..], colon_pos.map(|cp| cp - 2))
+            } else {
+                (input, colon_pos)
+            };
+            match colon_pos {
+                Some(colon_pos) => (&input[0..colon_pos], &input[colon_pos..]),
+                None => (input, ""),
+            }
+        }
+    };
+
+    if package_name.is_empty() {
+        return Ok((rest, None));
+    }
+
+    if !package_name.chars().all(|c| {
+        c.is_ascii_alphanumeric()
+            || c == '/'
+            || c == '-'
+            || c == '.'
+            || c == ' '
+            || c == '$'
+            || c == '('
+            || c == ')'
+            || c == '_'
+    }) {
+        return Err(LabelError(err(
+            label,
+            "package names may contain only A-Z, \
+        a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')' and '_'.",
+        )));
+    }
+    if package_name.ends_with('/') {
+        return Err(LabelError(err(
+            label,
+            "package names may not end with '/'.",
+        )));
+    }
+
+    if rest.is_empty() && is_absolute {
+        // This label doesn't contain the target name, we have to use
+        // last segment of the package name as target name.
+        return Ok((
+            match package_name.rfind('/') {
+                Some(pos) => &package_name[pos..],
+                None => package_name,
+            },
+            Some(package_name),
+        ));
+    }
+
+    Ok((rest, Some(package_name)))
+}
+
+fn consume_name<'s>(input: &'s str, label: &'s str) -> Result<Option<&'s str>> {
+    if input.is_empty() {
+        return Ok(None);
+    }
+    if input == ":" {
+        return Err(LabelError(err(label, "empty target name.")));
+    }
+    let name = input
+        .strip_prefix(':')
+        .or_else(|| input.strip_prefix('/'))
+        .unwrap_or(input);
+    if name.starts_with('/') {
+        return Err(LabelError(err(
+            label,
+            "target names may not start with '/'.",
+        )));
+    }
+    Ok(Some(name))
+}
+
+fn name_from_package(package_name: &str) -> &str {
+    package_name
+        .rsplit_once('/')
+        .map(|tup| tup.1)
+        .unwrap_or(package_name)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_new() {
+        assert_eq!(
+            Label::new(Some("repo"), Some("foo/bar"), "baz"),
+            Label {
+                repository_name: Some("repo"),
+                package_name: Some("foo/bar"),
+                name: "baz",
+            }
+        );
+    }
+
+    #[test]
+    fn test_repository_name_parsing() -> Result<()> {
+        assert_eq!(analyze("@repo//:foo")?.repository_name, Some("repo"));
+        assert_eq!(analyze("@//:foo")?.repository_name, None);
+        assert_eq!(analyze("//:foo")?.repository_name, None);
+        assert_eq!(analyze(":foo")?.repository_name, None);
+
+        assert_eq!(analyze("@repo//foo/bar")?.repository_name, Some("repo"));
+        assert_eq!(analyze("@//foo/bar")?.repository_name, None);
+        assert_eq!(analyze("//foo/bar")?.repository_name, None);
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.repository_name, Some("repo"));
+        assert_eq!(analyze("@//foo")?.repository_name, None);
+        assert_eq!(analyze("//foo")?.repository_name, None);
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(
+            analyze("@foo:bar"),
+            Err(LabelError(
+                "@foo:bar must be a legal label; labels with repository must contain //."
+                    .to_string()
+            ))
+        );
+
+        assert_eq!(
+            analyze("@AZab0123456789_-.//:foo")?.repository_name,
+            Some("AZab0123456789_-.")
+        );
+        assert_eq!(
+            analyze("@42//:baz"),
+            Err(LabelError(
+                "@42//:baz must be a legal label; workspace names must \
+            start with a letter."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("@foo#//:baz"),
+            Err(LabelError(
+                "@foo#//:baz must be a legal label; workspace names \
+            may contain only A-Z, a-z, 0-9, '-', '_', and '.'."
+                    .to_string()
+            ))
+        );
+        Ok(())
+    }
+    #[test]
+    fn test_package_name_parsing() -> Result<()> {
+        assert_eq!(analyze("//:baz/qux")?.package_name, None);
+        assert_eq!(analyze(":baz/qux")?.package_name, None);
+
+        assert_eq!(analyze("//foo:baz/qux")?.package_name, Some("foo"));
+        assert_eq!(analyze("//foo/bar:baz/qux")?.package_name, Some("foo/bar"));
+        assert_eq!(
+            analyze("foo:baz/qux"),
+            Err(LabelError(
+                "foo:baz/qux must be a legal label; relative packages are not permitted."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("foo/bar:baz/qux"),
+            Err(LabelError(
+                "foo/bar:baz/qux must be a legal label; relative packages are not permitted."
+                    .to_string()
+            ))
+        );
+
+        assert_eq!(analyze("//foo")?.package_name, Some("foo"));
+
+        assert_eq!(
+            analyze("foo//bar"),
+            Err(LabelError(
+                "foo//bar must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("//foo//bar"),
+            Err(LabelError(
+                "//foo//bar must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("foo//bar:baz"),
+            Err(LabelError(
+                "foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("//foo//bar:baz"),
+            Err(LabelError(
+                "//foo//bar:baz must be a legal label; '//' cannot appear in the middle of the label."
+                    .to_string()
+            ))
+        );
+
+        assert_eq!(
+            analyze("//azAZ09/-. $()_:baz")?.package_name,
+            Some("azAZ09/-. $()_")
+        );
+        assert_eq!(
+            analyze("//bar#:baz"),
+            Err(LabelError(
+                "//bar#:baz must be a legal label; package names may contain only A-Z, \
+                a-z, 0-9, '/', '-', '.', ' ', '$', '(', ')' and '_'."
+                    .to_string()
+            ))
+        );
+        assert_eq!(
+            analyze("//bar/:baz"),
+            Err(LabelError(
+                "//bar/:baz must be a legal label; package names may not end with '/'.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo/bar")?.package_name, Some("foo/bar"));
+        assert_eq!(analyze("//foo/bar")?.package_name, Some("foo/bar"));
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.package_name, Some("foo"));
+        assert_eq!(analyze("//foo")?.package_name, Some("foo"));
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_name_parsing() -> Result<()> {
+        assert_eq!(analyze("//foo:baz")?.name, "baz");
+        assert_eq!(analyze("//foo:baz/qux")?.name, "baz/qux");
+
+        assert_eq!(
+            analyze("//bar:"),
+            Err(LabelError(
+                "//bar: must be a legal label; empty target name.".to_string()
+            ))
+        );
+        assert_eq!(analyze("//foo")?.name, "foo");
+
+        assert_eq!(
+            analyze("//bar:/foo"),
+            Err(LabelError(
+                "//bar:/foo must be a legal label; target names may not start with '/'."
+                    .to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo/bar")?.name, "bar");
+        assert_eq!(analyze("//foo/bar")?.name, "bar");
+        assert_eq!(
+            analyze("foo/bar"),
+            Err(LabelError(
+                "foo/bar must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        assert_eq!(analyze("@repo//foo")?.name, "foo");
+        assert_eq!(analyze("//foo")?.name, "foo");
+        assert_eq!(
+            analyze("foo"),
+            Err(LabelError(
+                "foo must be a legal label; relative packages are not permitted.".to_string()
+            ))
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_packages() -> Result<()> {
+        assert_eq!(analyze("@repo//:baz")?.packages(), Vec::<&str>::new());
+        assert_eq!(analyze("@repo//foo:baz")?.packages(), vec!["foo"]);
+        assert_eq!(
+            analyze("@repo//foo/bar:baz")?.packages(),
+            vec!["foo", "bar"]
+        );
+
+        Ok(())
+    }
+}
diff --git a/util/label/label_error.rs b/util/label/label_error.rs
new file mode 100644
index 0000000..b1d7199
--- /dev/null
+++ b/util/label/label_error.rs
@@ -0,0 +1,20 @@
+#[derive(Debug, PartialEq)]
+pub struct LabelError(pub String);
+
+impl std::fmt::Display for LabelError {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl std::error::Error for LabelError {
+    fn description(&self) -> &str {
+        &self.0
+    }
+}
+
+impl From<String> for LabelError {
+    fn from(msg: String) -> Self {
+        Self(msg)
+    }
+}
diff --git a/util/process_wrapper/BUILD.bazel b/util/process_wrapper/BUILD.bazel
new file mode 100644
index 0000000..c26d9a6
--- /dev/null
+++ b/util/process_wrapper/BUILD.bazel
@@ -0,0 +1,45 @@
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//rust:defs.bzl", "rust_binary", "rust_test")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:transitions.bzl", "without_process_wrapper")
+
+alias(
+    name = "process_wrapper",
+    actual = select({
+        # This will never get used, it's only here to break the circular dependency to allow building process_wrapper
+        ":use_fake_process_wrapper": ":process_wrapper_fake",
+        "//conditions:default": ":process_wrapper_impl",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+cc_binary(
+    name = "process_wrapper_fake",
+    srcs = ["fake.cc"],
+)
+
+config_setting(
+    name = "use_fake_process_wrapper",
+    flag_values = {
+        "//rust/settings:use_process_wrapper": "False",
+    },
+)
+
+# Changing the name of this rule requires a corresponding
+# change in //rust/private/rustc.bzl:925
+without_process_wrapper(
+    name = "process_wrapper_impl",
+    target = ":process_wrapper_bin",
+    visibility = ["//visibility:public"],
+)
+
+rust_binary(
+    name = "process_wrapper_bin",
+    srcs = glob(["*.rs"]),
+)
+
+rust_test(
+    name = "process_wrapper_test",
+    crate = ":process_wrapper_bin",
+)
diff --git a/util/process_wrapper/fake.cc b/util/process_wrapper/fake.cc
new file mode 100644
index 0000000..a0b6869
--- /dev/null
+++ b/util/process_wrapper/fake.cc
@@ -0,0 +1,4 @@
+int main() {
+    // Noop on purpose.
+    return 0;
+}
\ No newline at end of file
diff --git a/util/process_wrapper/flags.rs b/util/process_wrapper/flags.rs
new file mode 100644
index 0000000..d3d6fe5
--- /dev/null
+++ b/util/process_wrapper/flags.rs
@@ -0,0 +1,276 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::collections::{BTreeMap, HashSet};
+use std::error::Error;
+use std::fmt;
+use std::fmt::Write;
+use std::iter::Peekable;
+use std::mem::take;
+
+#[derive(Debug, Clone)]
+pub(crate) enum FlagParseError {
+    UnknownFlag(String),
+    ValueMissing(String),
+    ProvidedMultipleTimes(String),
+    ProgramNameMissing,
+}
+
+impl fmt::Display for FlagParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::UnknownFlag(ref flag) => write!(f, "unknown flag \"{}\"", flag),
+            Self::ValueMissing(ref flag) => write!(f, "flag \"{}\" missing parameter(s)", flag),
+            Self::ProvidedMultipleTimes(ref flag) => {
+                write!(f, "flag \"{}\" can only appear once", flag)
+            }
+            Self::ProgramNameMissing => {
+                write!(f, "program name (argv[0]) missing")
+            }
+        }
+    }
+}
+impl Error for FlagParseError {}
+
+struct FlagDef<'a, T> {
+    name: String,
+    help: String,
+    output_storage: &'a mut Option<T>,
+}
+
+impl<'a, T> fmt::Display for FlagDef<'a, T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}\t{}", self.name, self.help)
+    }
+}
+
+impl<'a, T> fmt::Debug for FlagDef<'a, T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("FlagDef")
+            .field("name", &self.name)
+            .field("help", &self.help)
+            .finish()
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct Flags<'a> {
+    single: BTreeMap<String, FlagDef<'a, String>>,
+    repeated: BTreeMap<String, FlagDef<'a, Vec<String>>>,
+}
+
+#[derive(Debug)]
+pub(crate) enum ParseOutcome {
+    Help(String),
+    Parsed(Vec<String>),
+}
+
+impl<'a> Flags<'a> {
+    pub(crate) fn new() -> Flags<'a> {
+        Flags {
+            single: BTreeMap::new(),
+            repeated: BTreeMap::new(),
+        }
+    }
+
+    pub(crate) fn define_flag(
+        &mut self,
+        name: impl Into<String>,
+        help: impl Into<String>,
+        output_storage: &'a mut Option<String>,
+    ) {
+        let name = name.into();
+        if self.repeated.contains_key(&name) {
+            panic!("argument \"{}\" already defined as repeated flag", name)
+        }
+        self.single.insert(
+            name.clone(),
+            FlagDef::<'a, String> {
+                name,
+                help: help.into(),
+                output_storage,
+            },
+        );
+    }
+
+    pub(crate) fn define_repeated_flag(
+        &mut self,
+        name: impl Into<String>,
+        help: impl Into<String>,
+        output_storage: &'a mut Option<Vec<String>>,
+    ) {
+        let name = name.into();
+        if self.single.contains_key(&name) {
+            panic!("argument \"{}\" already defined as flag", name)
+        }
+        self.repeated.insert(
+            name.clone(),
+            FlagDef::<'a, Vec<String>> {
+                name,
+                help: help.into(),
+                output_storage,
+            },
+        );
+    }
+
+    fn help(&self, program_name: String) -> String {
+        let single = self.single.values().map(|fd| fd.to_string());
+        let repeated = self.repeated.values().map(|fd| fd.to_string());
+        let mut all: Vec<String> = single.chain(repeated).collect();
+        all.sort();
+
+        let mut help_text = String::new();
+        writeln!(
+            &mut help_text,
+            "Help for {}: [options] -- [extra arguments]",
+            program_name
+        )
+        .unwrap();
+        for line in all {
+            writeln!(&mut help_text, "\t{}", line).unwrap();
+        }
+        help_text
+    }
+
+    pub(crate) fn parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError> {
+        let mut argv_iter = argv.into_iter().peekable();
+        let program_name = argv_iter.next().ok_or(FlagParseError::ProgramNameMissing)?;
+
+        // To check if a non-repeated flag has been set already.
+        let mut seen_single_flags = HashSet::<String>::new();
+
+        while let Some(flag) = argv_iter.next() {
+            if flag == "--help" {
+                return Ok(ParseOutcome::Help(self.help(program_name)));
+            }
+            if !flag.starts_with("--") {
+                return Err(FlagParseError::UnknownFlag(flag));
+            }
+            let mut args = consume_args(&flag, &mut argv_iter);
+            if flag == "--" {
+                return Ok(ParseOutcome::Parsed(args));
+            }
+            if args.is_empty() {
+                return Err(FlagParseError::ValueMissing(flag.clone()));
+            }
+            if let Some(flag_def) = self.single.get_mut(&flag) {
+                if args.len() > 1 || seen_single_flags.contains(&flag) {
+                    return Err(FlagParseError::ProvidedMultipleTimes(flag.clone()));
+                }
+                let arg = args.first_mut().unwrap();
+                seen_single_flags.insert(flag);
+                *flag_def.output_storage = Some(take(arg));
+                continue;
+            }
+            if let Some(flag_def) = self.repeated.get_mut(&flag) {
+                flag_def
+                    .output_storage
+                    .get_or_insert_with(Vec::new)
+                    .append(&mut args);
+                continue;
+            }
+            return Err(FlagParseError::UnknownFlag(flag));
+        }
+        Ok(ParseOutcome::Parsed(vec![]))
+    }
+}
+
+fn consume_args<I: Iterator<Item = String>>(
+    flag: &str,
+    argv_iter: &mut Peekable<I>,
+) -> Vec<String> {
+    if flag == "--" {
+        // If we have found --, the rest of the iterator is just returned as-is.
+        argv_iter.collect()
+    } else {
+        let mut args = vec![];
+        while let Some(arg) = argv_iter.next_if(|s| !s.starts_with("--")) {
+            args.push(arg);
+        }
+        args
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn args(args: &[&str]) -> Vec<String> {
+        ["foo"].iter().chain(args).map(|&s| s.to_owned()).collect()
+    }
+
+    #[test]
+    fn test_flag_help() {
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--help"])).unwrap();
+        if let ParseOutcome::Help(h) = result {
+            assert!(h.contains("Help for foo"));
+            assert!(h.contains("--bar\tbar help"));
+        } else {
+            panic!("expected that --help would invoke help, instead parsed arguments")
+        }
+    }
+
+    #[test]
+    fn test_flag_single_repeated() {
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "bb"]));
+        if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+            assert_eq!(f, "--bar");
+        } else {
+            panic!("expected error, got {:?}", result)
+        }
+        let mut parser = Flags::new();
+        parser.define_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"]));
+        if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+            assert_eq!(f, "--bar");
+        } else {
+            panic!("expected error, got {:?}", result)
+        }
+    }
+
+    #[test]
+    fn test_repeated_flags() {
+        // Test case 1) --bar something something_else should work as a repeated flag.
+        let mut bar = None;
+        let mut parser = Flags::new();
+        parser.define_repeated_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "bb"])).unwrap();
+        assert!(matches!(result, ParseOutcome::Parsed(_)));
+        assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+        // Test case 2) --bar something --bar something_else should also work as a repeated flag.
+        bar = None;
+        let mut parser = Flags::new();
+        parser.define_repeated_flag("--bar", "bar help", &mut bar);
+        let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"])).unwrap();
+        assert!(matches!(result, ParseOutcome::Parsed(_)));
+        assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+    }
+
+    #[test]
+    fn test_extra_args() {
+        let parser = Flags::new();
+        let result = parser.parse(args(&["--", "bb"])).unwrap();
+        if let ParseOutcome::Parsed(got) = result {
+            assert_eq!(got, vec!["bb".to_owned()])
+        } else {
+            panic!("expected correct parsing, got {:?}", result)
+        }
+    }
+}
diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs
new file mode 100644
index 0000000..41140a3
--- /dev/null
+++ b/util/process_wrapper/main.rs
@@ -0,0 +1,79 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod flags;
+mod options;
+mod util;
+
+use std::fs::{copy, OpenOptions};
+use std::process::{exit, Command, Stdio};
+
+use crate::options::options;
+
+fn main() {
+    let opts = match options() {
+        Err(err) => panic!("process wrapper error: {}", err),
+        Ok(v) => v,
+    };
+    let stdout = if let Some(stdout_file) = opts.stdout_file {
+        OpenOptions::new()
+            .create(true)
+            .truncate(true)
+            .write(true)
+            .open(stdout_file)
+            .expect("process wrapper error: unable to open stdout file")
+            .into()
+    } else {
+        Stdio::inherit()
+    };
+    let stderr = if let Some(stderr_file) = opts.stderr_file {
+        OpenOptions::new()
+            .create(true)
+            .truncate(true)
+            .write(true)
+            .open(stderr_file)
+            .expect("process wrapper error: unable to open stderr file")
+            .into()
+    } else {
+        Stdio::inherit()
+    };
+    let status = Command::new(opts.executable)
+        .args(opts.child_arguments)
+        .env_clear()
+        .envs(opts.child_environment)
+        .stdout(stdout)
+        .stderr(stderr)
+        .status()
+        .expect("process wrapper error: failed to spawn child process");
+
+    if status.success() {
+        if let Some(tf) = opts.touch_file {
+            OpenOptions::new()
+                .create(true)
+                .write(true)
+                .open(tf)
+                .expect("process wrapper error: failed to create touch file");
+        }
+        if let Some((copy_source, copy_dest)) = opts.copy_output {
+            copy(&copy_source, &copy_dest).unwrap_or_else(|_| {
+                panic!(
+                    "process wrapper error: failed to copy {} into {}",
+                    copy_source, copy_dest
+                )
+            });
+        }
+    }
+
+    exit(status.code().unwrap())
+}
diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs
new file mode 100644
index 0000000..24bba9f
--- /dev/null
+++ b/util/process_wrapper/options.rs
@@ -0,0 +1,226 @@
+use std::collections::HashMap;
+use std::env;
+use std::fmt;
+use std::process::exit;
+
+use crate::flags::{FlagParseError, Flags, ParseOutcome};
+use crate::util::*;
+
+#[derive(Debug)]
+pub(crate) enum OptionError {
+    FlagError(FlagParseError),
+    Generic(String),
+}
+
+impl fmt::Display for OptionError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::FlagError(e) => write!(f, "error parsing flags: {}", e),
+            Self::Generic(s) => write!(f, "{}", s),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct Options {
+    // Contains the path to the child executable
+    pub(crate) executable: String,
+    // Contains arguments for the child process fetched from files.
+    pub(crate) child_arguments: Vec<String>,
+    // Contains environment variables for the child process fetched from files.
+    pub(crate) child_environment: HashMap<String, String>,
+    // If set, create the specified file after the child process successfully
+    // terminated its execution.
+    pub(crate) touch_file: Option<String>,
+    // If set to (source, dest) copies the source file to dest.
+    pub(crate) copy_output: Option<(String, String)>,
+    // If set, redirects the child process stdout to this file.
+    pub(crate) stdout_file: Option<String>,
+    // If set, redirects the child process stderr to this file.
+    pub(crate) stderr_file: Option<String>,
+}
+
+pub(crate) fn options() -> Result<Options, OptionError> {
+    // Process argument list until -- is encountered.
+    // Everything after is sent to the child process.
+    let mut subst_mapping_raw = None;
+    let mut volatile_status_file_raw = None;
+    let mut env_file_raw = None;
+    let mut arg_file_raw = None;
+    let mut touch_file = None;
+    let mut copy_output_raw = None;
+    let mut stdout_file = None;
+    let mut stderr_file = None;
+    let mut flags = Flags::new();
+    flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
+    flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
+    flags.define_repeated_flag(
+        "--env-file",
+        "File(s) containing environment variables to pass to the child process.",
+        &mut env_file_raw,
+    );
+    flags.define_repeated_flag(
+        "--arg-file",
+        "File(s) containing command line arguments to pass to the child process.",
+        &mut arg_file_raw,
+    );
+    flags.define_flag(
+        "--touch-file",
+        "Create this file after the child process runs successfully.",
+        &mut touch_file,
+    );
+    flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
+    flags.define_flag(
+        "--stdout-file",
+        "Redirect subprocess stdout in this file.",
+        &mut stdout_file,
+    );
+    flags.define_flag(
+        "--stderr-file",
+        "Redirect subprocess stderr in this file.",
+        &mut stderr_file,
+    );
+
+    let mut child_args = match flags
+        .parse(env::args().collect())
+        .map_err(OptionError::FlagError)?
+    {
+        ParseOutcome::Help(help) => {
+            eprintln!("{}", help);
+            exit(0);
+        }
+        ParseOutcome::Parsed(p) => p,
+    };
+    let current_dir = std::env::current_dir()
+        .map_err(|e| OptionError::Generic(format!("failed to get current directory: {}", e)))?
+        .to_str()
+        .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
+        .to_owned();
+    let subst_mappings = subst_mapping_raw
+        .unwrap_or_default()
+        .into_iter()
+        .map(|arg| {
+            let (key, val) = arg.split_once('=').ok_or_else(|| {
+                OptionError::Generic(format!("empty key for substitution '{}'", arg))
+            })?;
+            let v = if val == "${pwd}" {
+                current_dir.as_str()
+            } else {
+                val
+            }
+            .to_owned();
+            Ok((key.to_owned(), v))
+        })
+        .collect::<Result<Vec<(String, String)>, OptionError>>()?;
+    let stamp_mappings =
+        volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
+
+    let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
+    let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
+    // Process --copy-output
+    let copy_output = copy_output_raw
+        .map(|co| {
+            if co.len() != 2 {
+                return Err(OptionError::Generic(format!(
+                    "\"--copy-output\" needs exactly 2 parameters, {} provided",
+                    co.len()
+                )));
+            }
+            let copy_source = &co[0];
+            let copy_dest = &co[1];
+            if copy_source == copy_dest {
+                return Err(OptionError::Generic(format!(
+                    "\"--copy-output\" source ({}) and dest ({}) need to be different.",
+                    copy_source, copy_dest
+                )));
+            }
+            Ok((copy_source.to_owned(), copy_dest.to_owned()))
+        })
+        .transpose()?;
+
+    // Prepare the environment variables, unifying those read from files with the ones
+    // of the current process.
+    let vars = environment_block(environment_file_block, &stamp_mappings, &subst_mappings);
+    // Append all the arguments fetched from files to those provided via command line.
+    child_args.append(&mut file_arguments);
+    let child_args = prepare_args(child_args, &subst_mappings);
+    // Split the executable path from the rest of the arguments.
+    let (exec_path, args) = child_args.split_first().ok_or_else(|| {
+        OptionError::Generic(
+            "at least one argument after -- is required (the child process path)".to_owned(),
+        )
+    })?;
+
+    Ok(Options {
+        executable: exec_path.to_owned(),
+        child_arguments: args.to_vec(),
+        child_environment: vars,
+        touch_file,
+        copy_output,
+        stdout_file,
+        stderr_file,
+    })
+}
+
+fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
+    let mut args = vec![];
+    for path in paths.into_iter() {
+        let mut lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+        args.append(&mut lines);
+    }
+    Ok(args)
+}
+
+fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
+    let mut env_vars = HashMap::new();
+    for path in paths.into_iter() {
+        let lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+        for line in lines.into_iter() {
+            let (k, v) = line
+                .split_once('=')
+                .ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
+            env_vars.insert(k.to_owned(), v.to_owned());
+        }
+    }
+    Ok(env_vars)
+}
+
+fn prepare_args(mut args: Vec<String>, subst_mappings: &[(String, String)]) -> Vec<String> {
+    for (f, replace_with) in subst_mappings {
+        for arg in args.iter_mut() {
+            let from = format!("${{{}}}", f);
+            let new = arg.replace(from.as_str(), replace_with);
+            *arg = new;
+        }
+    }
+    args
+}
+
+fn environment_block(
+    environment_file_block: HashMap<String, String>,
+    stamp_mappings: &[(String, String)],
+    subst_mappings: &[(String, String)],
+) -> HashMap<String, String> {
+    // Taking all environment variables from the current process
+    // and sending them down to the child process
+    let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
+    // Have the last values added take precedence over the first.
+    // This is simpler than needing to track duplicates and explicitly override
+    // them.
+    environment_variables.extend(environment_file_block.into_iter());
+    for (f, replace_with) in stamp_mappings {
+        for value in environment_variables.values_mut() {
+            let from = format!("{{{}}}", f);
+            let new = value.replace(from.as_str(), replace_with);
+            *value = new;
+        }
+    }
+    for (f, replace_with) in subst_mappings {
+        for value in environment_variables.values_mut() {
+            let from = format!("${{{}}}", f);
+            let new = value.replace(from.as_str(), replace_with);
+            *value = new;
+        }
+    }
+    environment_variables
+}
diff --git a/util/process_wrapper/util.rs b/util/process_wrapper/util.rs
new file mode 100644
index 0000000..4b3d6bb
--- /dev/null
+++ b/util/process_wrapper/util.rs
@@ -0,0 +1,103 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fs::File;
+use std::io::{BufRead, BufReader, Read};
+
+pub(crate) fn read_file_to_array(path: String) -> Result<Vec<String>, String> {
+    let file = File::open(path).map_err(|e| e.to_string())?;
+    read_to_array(file)
+}
+
+pub(crate) fn read_stamp_status_to_array(path: String) -> Result<Vec<(String, String)>, String> {
+    let file = File::open(path).map_err(|e| e.to_string())?;
+    stamp_status_to_array(file)
+}
+
+fn read_to_array(reader: impl Read) -> Result<Vec<String>, String> {
+    let reader = BufReader::new(reader);
+    let mut ret = vec![];
+    let mut escaped_line = String::new();
+    for l in reader.lines() {
+        let line = l.map_err(|e| e.to_string())?;
+        if line.is_empty() {
+            continue;
+        }
+        // a \ at the end of a line allows us to escape the new line break,
+        // \\ yields a single \, so \\\ translates to a single \ and a new line
+        // escape
+        let end_backslash_count = line.chars().rev().take_while(|&c| c == '\\').count();
+        // a 0 or even number of backslashes do not lead to a new line escape
+        let escape = end_backslash_count % 2 == 1;
+        //  remove backslashes and add back two for every one
+        let l = line.trim_end_matches('\\');
+        escaped_line.push_str(l);
+        for _ in 0..end_backslash_count / 2 {
+            escaped_line.push('\\');
+        }
+        if escape {
+            // we add a newline as we expect a line after this
+            escaped_line.push('\n');
+        } else {
+            ret.push(escaped_line);
+            escaped_line = String::new();
+        }
+    }
+    Ok(ret)
+}
+
+fn stamp_status_to_array(reader: impl Read) -> Result<Vec<(String, String)>, String> {
+    let escaped_lines = read_to_array(reader)?;
+    escaped_lines
+        .into_iter()
+        .map(|l| {
+            let (s1, s2) = l
+                .split_once(' ')
+                .ok_or_else(|| format!("wrong workspace status file format for \"{}\"", l))?;
+            Ok((s1.to_owned(), s2.to_owned()))
+        })
+        .collect()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_read_to_array() {
+        let input = r#"some escaped \\\
+string
+with other lines"#
+            .to_owned();
+        let expected = vec![
+            r#"some escaped \
+string"#,
+            "with other lines",
+        ];
+        let got = read_to_array(input.as_bytes()).unwrap();
+        assert_eq!(expected, got);
+    }
+
+    #[test]
+    fn test_stamp_status_to_array() {
+        let lines = "aaa bbb\\\nvvv\nccc ddd\neee fff";
+        let got = stamp_status_to_array(lines.as_bytes()).unwrap();
+        let expected = vec![
+            ("aaa".to_owned(), "bbb\nvvv".to_owned()),
+            ("ccc".to_owned(), "ddd".to_owned()),
+            ("eee".to_owned(), "fff".to_owned()),
+        ];
+        assert_eq!(expected, got);
+    }
+}