add a way to distinguish proc-macro deps (#1420)

diff --git a/BUILD.bazel b/BUILD.bazel
index ff49a9c..d4cbb9a 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-load("//rust:defs.bzl", "capture_clippy_output", "clippy_flags", "error_format", "extra_exec_rustc_flag", "extra_exec_rustc_flags", "extra_rustc_flag", "extra_rustc_flags")
+load("//rust:defs.bzl", "capture_clippy_output", "clippy_flags", "error_format", "extra_exec_rustc_flag", "extra_exec_rustc_flags", "extra_rustc_flag", "extra_rustc_flags", "is_proc_macro_dep", "is_proc_macro_dep_enabled")
 
 exports_files(["LICENSE"])
 
@@ -45,6 +45,24 @@
     visibility = ["//visibility:public"],
 )
 
+# This setting may be used to identify dependencies of proc-macro-s.
+# This feature is only enabled if `is_proc_macro_dep_enabled` is true.
+# Its value controls the BAZEL_RULES_RUST_IS_PROC_MACRO_DEP environment variable
+# made available to the rustc invocation.
+is_proc_macro_dep(
+    name = "is_proc_macro_dep",
+    build_setting_default = False,
+    visibility = ["//visibility:public"],
+)
+
+# This setting enables the feature to identify dependencies of proc-macro-s,
+# see `is_proc_macro_dep`.
+is_proc_macro_dep_enabled(
+    name = "is_proc_macro_dep_enabled",
+    build_setting_default = False,
+    visibility = ["//visibility:public"],
+)
+
 # This setting may be used to pass extra options to rustc from the command line
 # in non-exec configuration.
 # It applies across all targets whereas the rustc_flags option on targets applies only
diff --git a/rust/defs.bzl b/rust/defs.bzl
index a0560b5..92c8a77 100644
--- a/rust/defs.bzl
+++ b/rust/defs.bzl
@@ -47,6 +47,8 @@
     _extra_exec_rustc_flags = "extra_exec_rustc_flags",
     _extra_rustc_flag = "extra_rustc_flag",
     _extra_rustc_flags = "extra_rustc_flags",
+    _is_proc_macro_dep = "is_proc_macro_dep",
+    _is_proc_macro_dep_enabled = "is_proc_macro_dep_enabled",
 )
 load(
     "//rust/private:rustdoc.bzl",
@@ -116,6 +118,12 @@
 extra_exec_rustc_flags = _extra_exec_rustc_flags
 # See @rules_rust//rust/private:rustc.bzl for a complete description.
 
+is_proc_macro_dep = _is_proc_macro_dep
+# See @rules_rust//rust/private:rustc.bzl for a complete description.
+
+is_proc_macro_dep_enabled = _is_proc_macro_dep_enabled
+# See @rules_rust//rust/private:rustc.bzl for a complete description.
+
 rust_common = _rust_common
 # See @rules_rust//rust/private:common.bzl for a complete description.
 
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index ce61713..5b02292 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -685,6 +685,12 @@
         default = Label("//util/import"),
         cfg = "exec",
     ),
+    "_is_proc_macro_dep": attr.label(
+        default = Label("//:is_proc_macro_dep"),
+    ),
+    "_is_proc_macro_dep_enabled": attr.label(
+        default = Label("//:is_proc_macro_dep_enabled"),
+    ),
     "_process_wrapper": attr.label(
         doc = "A process wrapper for running rustc on all platforms.",
         default = Label("//util/process_wrapper"),
@@ -866,10 +872,38 @@
         """),
 )
 
+def _proc_macro_dep_transition_impl(settings, _attr):
+    if settings["//:is_proc_macro_dep_enabled"]:
+        return {"//:is_proc_macro_dep": True}
+    else:
+        return []
+
+_proc_macro_dep_transition = transition(
+    inputs = ["//:is_proc_macro_dep_enabled"],
+    outputs = ["//:is_proc_macro_dep"],
+    implementation = _proc_macro_dep_transition_impl,
+)
+
 rust_proc_macro = rule(
     implementation = _rust_proc_macro_impl,
     provides = _common_providers,
-    attrs = dict(_common_attrs.items()),
+    # Start by copying the common attributes, then override the `deps` attribute
+    # to apply `_proc_macro_dep_transition`. To add this transition we additionally
+    # need to declare `_allowlist_function_transition`, see
+    # https://docs.bazel.build/versions/main/skylark/config.html#user-defined-transitions.
+    attrs = dict(
+        _common_attrs.items(),
+        _allowlist_function_transition = attr.label(default = Label("//tools/allowlists/function_transition_allowlist")),
+        deps = attr.label_list(
+            doc = dedent("""\
+            List of other libraries to be linked to this library target.
+
+            These can be either other `rust_library` targets or `cc_library` targets if
+            linking a native library.
+        """),
+            cfg = _proc_macro_dep_transition,
+        ),
+    ),
     fragments = ["cpp"],
     host_fragments = ["cpp"],
     toolchains = [
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 44b799c..fbd8dd0 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -61,6 +61,34 @@
     fields = {"extra_exec_rustc_flags": "List[string] Extra flags to pass to rustc in exec configuration"},
 )
 
+IsProcMacroDepInfo = provider(
+    doc = "Records if this is a transitive dependency of a proc-macro.",
+    fields = {"is_proc_macro_dep": "Boolean"},
+)
+
+def _is_proc_macro_dep_impl(ctx):
+    return IsProcMacroDepInfo(is_proc_macro_dep = ctx.build_setting_value)
+
+is_proc_macro_dep = rule(
+    doc = "Records if this is a transitive dependency of a proc-macro.",
+    implementation = _is_proc_macro_dep_impl,
+    build_setting = config.bool(flag = True),
+)
+
+IsProcMacroDepEnabledInfo = provider(
+    doc = "Enables the feature to record if a library is a transitive dependency of a proc-macro.",
+    fields = {"enabled": "Boolean"},
+)
+
+def _is_proc_macro_dep_enabled_impl(ctx):
+    return IsProcMacroDepEnabledInfo(enabled = ctx.build_setting_value)
+
+is_proc_macro_dep_enabled = rule(
+    doc = "Enables the feature to record if a library is a transitive dependency of a proc-macro.",
+    implementation = _is_proc_macro_dep_enabled_impl,
+    build_setting = config.bool(flag = True),
+)
+
 def _get_rustc_env(attr, toolchain, crate_name):
     """Gathers rustc environment variables
 
@@ -78,7 +106,7 @@
         patch, pre = patch.split("-", 1)
     else:
         pre = ""
-    return {
+    result = {
         "CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
         "CARGO_CFG_TARGET_OS": toolchain.os,
         "CARGO_CRATE_NAME": crate_name,
@@ -92,6 +120,12 @@
         "CARGO_PKG_VERSION_PATCH": patch,
         "CARGO_PKG_VERSION_PRE": pre,
     }
+    if hasattr(attr, "_is_proc_macro_dep_enabled") and attr._is_proc_macro_dep_enabled[IsProcMacroDepEnabledInfo].enabled:
+        is_proc_macro_dep = "0"
+        if hasattr(attr, "_is_proc_macro_dep") and attr._is_proc_macro_dep[IsProcMacroDepInfo].is_proc_macro_dep:
+            is_proc_macro_dep = "1"
+        result["BAZEL_RULES_RUST_IS_PROC_MACRO_DEP"] = is_proc_macro_dep
+    return result
 
 def get_compilation_mode_opts(ctx, toolchain):
     """Gathers rustc flags for the current compilation mode (opt/debug)
diff --git a/test/unit/is_proc_macro_dep/BUILD.bazel b/test/unit/is_proc_macro_dep/BUILD.bazel
new file mode 100644
index 0000000..ac655a5
--- /dev/null
+++ b/test/unit/is_proc_macro_dep/BUILD.bazel
@@ -0,0 +1,5 @@
+load(":is_proc_macro_dep_test.bzl", "is_proc_macro_dep_test_suite")
+
+is_proc_macro_dep_test_suite(
+    name = "is_proc_macro_dep_test_suite",
+)
diff --git a/test/unit/is_proc_macro_dep/is_proc_macro_dep_test.bzl b/test/unit/is_proc_macro_dep/is_proc_macro_dep_test.bzl
new file mode 100644
index 0000000..eb947bd
--- /dev/null
+++ b/test/unit/is_proc_macro_dep/is_proc_macro_dep_test.bzl
@@ -0,0 +1,169 @@
+"""Unittests for the is_proc_macro_dep setting."""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("//rust:defs.bzl", "rust_library", "rust_proc_macro")
+
+DepActionsInfo = provider(
+    "Contains information about dependencies actions.",
+    fields = {"actions": "List[Action]"},
+)
+
+def _collect_dep_actions_aspect_impl(target, ctx):
+    actions = []
+    actions.extend(target.actions)
+    for dep in ctx.rule.attr.deps:
+        actions.extend(dep[DepActionsInfo].actions)
+    return [DepActionsInfo(actions = actions)]
+
+collect_dep_actions_aspect = aspect(
+    implementation = _collect_dep_actions_aspect_impl,
+    attr_aspects = ["deps"],
+)
+
+def _attach_dep_actions_aspect_impl(ctx):
+    return [ctx.attr.dep[DepActionsInfo]]
+
+attach_dep_actions_aspect = rule(
+    implementation = _attach_dep_actions_aspect_impl,
+    attrs = {
+        "dep": attr.label(aspects = [collect_dep_actions_aspect]),
+    },
+)
+
+def _enable_is_proc_macro_dep_transition_impl(_settings, _attr):
+    return {"//:is_proc_macro_dep_enabled": True}
+
+enable_is_proc_macro_dep_transition = transition(
+    inputs = [],
+    outputs = ["//:is_proc_macro_dep_enabled"],
+    implementation = _enable_is_proc_macro_dep_transition_impl,
+)
+
+attach_dep_actions_and_enable_is_proc_macro_dep_aspect = rule(
+    implementation = _attach_dep_actions_aspect_impl,
+    attrs = {
+        "dep": attr.label(aspects = [collect_dep_actions_aspect]),
+        "_allowlist_function_transition": attr.label(default = Label("//tools/allowlists/function_transition_allowlist")),
+    },
+    cfg = enable_is_proc_macro_dep_transition,
+)
+
+def _is_proc_macro_dep_is_not_in_env_for_top_level_action(ctx):
+    env = analysistest.begin(ctx)
+    top_level_action = analysistest.target_under_test(env).actions[0]
+    asserts.false(env, "BAZEL_RULES_RUST_IS_PROC_MACRO_DEP" in top_level_action.env)
+    return analysistest.end(env)
+
+is_proc_macro_dep_is_not_in_env_for_top_level_action_test = analysistest.make(_is_proc_macro_dep_is_not_in_env_for_top_level_action)
+
+def _is_proc_macro_dep_is_false_for_proc_macro(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    proc_macro_dep_action = [a for a in tut[DepActionsInfo].actions if str(a) == "action 'Compiling Rust proc-macro proc_macro_crate (1 files)'"][0]
+    asserts.equals(env, proc_macro_dep_action.env["BAZEL_RULES_RUST_IS_PROC_MACRO_DEP"], "0")
+    return analysistest.end(env)
+
+is_proc_macro_dep_is_false_for_proc_macro_test = analysistest.make(_is_proc_macro_dep_is_false_for_proc_macro)
+
+def _is_proc_macro_dep_is_false_for_top_level_library(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    top_level_library_action = [a for a in tut[DepActionsInfo].actions if str(a) == "action 'Compiling Rust rlib top_level_library (1 files)'"][0]
+    asserts.equals(env, top_level_library_action.env["BAZEL_RULES_RUST_IS_PROC_MACRO_DEP"], "0")
+    return analysistest.end(env)
+
+is_proc_macro_dep_is_false_for_top_level_library_test = analysistest.make(_is_proc_macro_dep_is_false_for_top_level_library)
+
+def _is_proc_macro_dep_is_true_for_proc_macro_dep(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    proc_macro_dep_action = [a for a in tut[DepActionsInfo].actions if str(a) == "action 'Compiling Rust rlib proc_macro_dep (1 files)'"][0]
+    asserts.equals(env, proc_macro_dep_action.env["BAZEL_RULES_RUST_IS_PROC_MACRO_DEP"], "1")
+    return analysistest.end(env)
+
+is_proc_macro_dep_is_true_for_proc_macro_dep_test = analysistest.make(_is_proc_macro_dep_is_true_for_proc_macro_dep)
+
+def _is_proc_macro_dep_is_not_in_env_for_proc_macro_dep(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    proc_macro_dep_action = [a for a in tut[DepActionsInfo].actions if str(a) == "action 'Compiling Rust rlib proc_macro_dep (1 files)'"][0]
+    asserts.true(env, "BAZEL_RULES_RUST_IS_PROC_MACRO_DEP" not in proc_macro_dep_action.env)
+    return analysistest.end(env)
+
+is_proc_macro_dep_is_not_in_env_for_proc_macro_dep_test = analysistest.make(_is_proc_macro_dep_is_not_in_env_for_proc_macro_dep)
+
+def _is_proc_macro_dep_test():
+    """Generate targets and tests."""
+
+    rust_library(
+        name = "proc_macro_dep",
+        srcs = ["proc_macro_dep.rs"],
+        edition = "2018",
+    )
+
+    rust_proc_macro(
+        name = "proc_macro_crate",
+        srcs = ["proc_macro_crate.rs"],
+        deps = ["proc_macro_dep"],
+        edition = "2018",
+    )
+
+    is_proc_macro_dep_is_not_in_env_for_top_level_action_test(
+        name = "is_proc_macro_dep_is_not_in_env_for_top_level_proc_macro",
+        target_under_test = ":proc_macro_crate",
+    )
+
+    is_proc_macro_dep_is_not_in_env_for_top_level_action_test(
+        name = "is_proc_macro_dep_is_not_in_env_for_top_level_library",
+        target_under_test = ":proc_macro_dep",
+    )
+
+    attach_dep_actions_and_enable_is_proc_macro_dep_aspect(
+        name = "proc_macro_crate_enabled_with_actions",
+        dep = ":proc_macro_crate",
+    )
+
+    is_proc_macro_dep_is_false_for_proc_macro_test(
+        name = "is_proc_macro_dep_is_false_for_proc_macro",
+        target_under_test = ":proc_macro_crate_enabled_with_actions",
+    )
+
+    is_proc_macro_dep_is_true_for_proc_macro_dep_test(
+        name = "is_proc_macro_dep_is_true_for_proc_macro_dep",
+        target_under_test = ":proc_macro_crate_enabled_with_actions",
+    )
+
+    rust_library(
+        name = "top_level_library",
+        srcs = ["proc_macro_dep.rs"],
+        edition = "2018",
+    )
+
+    attach_dep_actions_and_enable_is_proc_macro_dep_aspect(
+        name = "top_level_library_enabled_with_actions",
+        dep = ":top_level_library",
+    )
+
+    is_proc_macro_dep_is_false_for_top_level_library_test(
+        name = "is_proc_macro_dep_is_false_for_top_level_library",
+        target_under_test = ":top_level_library_enabled_with_actions",
+    )
+
+def is_proc_macro_dep_test_suite(name):
+    """Entry-point macro called from the BUILD file.
+
+    Args:
+        name: Name of the macro.
+    """
+    _is_proc_macro_dep_test()
+
+    native.test_suite(
+        name = name,
+        tests = [
+            "is_proc_macro_dep_is_not_in_env_for_top_level_proc_macro",
+            "is_proc_macro_dep_is_not_in_env_for_top_level_library",
+            "is_proc_macro_dep_is_false_for_proc_macro",
+            "is_proc_macro_dep_is_false_for_top_level_library",
+            "is_proc_macro_dep_is_true_for_proc_macro_dep",
+        ],
+    )
diff --git a/test/unit/is_proc_macro_dep/proc_macro_crate.rs b/test/unit/is_proc_macro_dep/proc_macro_crate.rs
new file mode 100644
index 0000000..8830ab5
--- /dev/null
+++ b/test/unit/is_proc_macro_dep/proc_macro_crate.rs
@@ -0,0 +1,11 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(_item: TokenStream) -> TokenStream {
+    let answer = proc_macro_dep::proc_macro_dep();
+    format!("fn answer() -> u32 {{ {} }}", answer)
+        .parse()
+        .unwrap()
+}
diff --git a/test/unit/is_proc_macro_dep/proc_macro_dep.rs b/test/unit/is_proc_macro_dep/proc_macro_dep.rs
new file mode 100644
index 0000000..3358e72
--- /dev/null
+++ b/test/unit/is_proc_macro_dep/proc_macro_dep.rs
@@ -0,0 +1,3 @@
+pub fn proc_macro_dep() -> i64 {
+    42
+}