Merge changes from topic "exec_deprecate" into main

* changes:
  Prevent public usage of exec.bzl and exec_aspect.bzl (2/2)
  Prevent public usage of exec.bzl and exec_aspect.bzl (1/2)
diff --git a/dist/dist.bzl b/dist/dist.bzl
index 3d3ac66..154d09b 100644
--- a/dist/dist.bzl
+++ b/dist/dist.bzl
@@ -1,8 +1,9 @@
-# Rule to support Bazel in copying its output files to the dist dir outside of
-# the standard Bazel output user root.
+"""Rule to support Bazel in copying its output files to the dist dir outside of
+the standard Bazel output user root.
+"""
 
 load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
-load("//build/bazel_common_rules/exec:embedded_exec.bzl", "embedded_exec")
+load("//build/bazel_common_rules/exec/impl:embedded_exec.bzl", "embedded_exec")
 
 def _label_list_to_manifest(lst):
     """Convert the outputs of a label list to manifest content."""
@@ -140,7 +141,7 @@
           on reverse dependencies.
 
           See `dist.py` for allowed values and the default value.
-        kwargs: Additional attributes to the internal rule, e.g.
+        **kwargs: Additional attributes to the internal rule, e.g.
           [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
 
           These additional attributes are only passed to the underlying embedded_exec rule.
diff --git a/exec/BUILD b/exec/BUILD
index e5e9cff..bb4a111 100644
--- a/exec/BUILD
+++ b/exec/BUILD
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# Copyright (C) 2024 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@
     name = "exec_aspect",
     srcs = ["exec_aspect.bzl"],
     visibility = ["//visibility:public"],
+    deps = [
+        "//build/bazel_common_rules/exec/impl:exec_aspect",
+    ],
 )
 
 bzl_library(
@@ -26,6 +29,7 @@
     visibility = ["//visibility:public"],
     deps = [
         ":exec_aspect",
+        "//build/bazel_common_rules/exec/impl:embedded_exec",
         "@bazel_skylib//lib:shell",
     ],
 )
@@ -36,6 +40,7 @@
     visibility = ["//visibility:public"],
     deps = [
         ":exec_aspect",
+        "//build/bazel_common_rules/exec/impl:exec",
         "@bazel_skylib//lib:shell",
     ],
 )
diff --git a/exec/embedded_exec.bzl b/exec/embedded_exec.bzl
index 482902e..e54f74d 100644
--- a/exec/embedded_exec.bzl
+++ b/exec/embedded_exec.bzl
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# Copyright (C) 2024 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,54 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@bazel_skylib//lib:shell.bzl", "shell")
-load(":exec_aspect.bzl", "ExecAspectInfo", "exec_aspect")
+"""Helps embedding `args` of an executable target."""
 
-def _impl(ctx):
-    target = ctx.attr.actual
-    files_to_run = target[DefaultInfo].files_to_run
-    if not files_to_run or not files_to_run.executable:
-        fail("{}: {} is not an executable".format(ctx.label, target))
-
-    out_file = ctx.actions.declare_file(ctx.label.name)
-
-    content = "#!{}\n".format(ctx.attr.hashbang)
-
-    expand_location_targets = []
-    for dependant_attr in ("data", "srcs", "deps"):
-        dependants = getattr(target[ExecAspectInfo], dependant_attr)
-        if dependants:
-            expand_location_targets += dependants
-
-    args = target[ExecAspectInfo].args
-    if not args:
-        args = []
-    quoted_args = " ".join([shell.quote(ctx.expand_location(arg, expand_location_targets)) for arg in args])
-
-    env = target[ExecAspectInfo].env
-    if not env:
-        env = {}
-
-    quoted_env = " ".join(["{}={}".format(k, shell.quote(ctx.expand_location(v, expand_location_targets))) for k, v in env.items()])
-
-    content += '{} {} {} "$@"'.format(quoted_env, target[DefaultInfo].files_to_run.executable.short_path, quoted_args)
-
-    ctx.actions.write(out_file, content, is_executable = True)
-
-    runfiles = ctx.runfiles(files = ctx.files.actual)
-    runfiles = runfiles.merge_all([target[DefaultInfo].default_runfiles])
-
-    return DefaultInfo(
-        files = depset([out_file]),
-        executable = out_file,
-        runfiles = runfiles,
-    )
-
-embedded_exec = rule(
-    implementation = _impl,
-    attrs = {
-        "actual": attr.label(doc = "The actual executable.", aspects = [exec_aspect]),
-        "hashbang": attr.string(doc = "The hashbang of the script", default = "/bin/bash -e"),
-    },
-    executable = True,
+load(
+    "//build/bazel_common_rules/exec/impl:embedded_exec.bzl",
+    _embedded_exec = "embedded_exec",
 )
+
+visibility("public")
+
+embedded_exec = _embedded_exec
diff --git a/exec/exec.bzl b/exec/exec.bzl
index 91e6385..f64cbdc 100644
--- a/exec/exec.bzl
+++ b/exec/exec.bzl
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# Copyright (C) 2024 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,101 +12,128 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@bazel_skylib//lib:shell.bzl", "shell")
-load(":exec_aspect.bzl", "ExecAspectInfo", "exec_aspect")
+"""Helps embedding `args` of an executable target."""
 
-_DEFAULT_HASHBANG = "/bin/bash -e"
+load(
+    "//build/bazel_common_rules/exec/impl:exec.bzl",
+    _exec = "exec",
+    _exec_rule = "exec_rule",
+    _exec_test = "exec_test",
+)
 
-def _impl(ctx):
-    out_file = ctx.actions.declare_file(ctx.label.name)
+visibility("public")
 
-    for target in ctx.attr.data:
-        if ExecAspectInfo not in target:
-            continue
-        if target[ExecAspectInfo].args:
-            fail("{}: {} must not have args. Use embedded_exec to wrap it.".format(ctx.label, target.label))
-        if target[ExecAspectInfo].env:
-            fail("{}: {} must not have env. Use embedded_exec to wrap it.".format(ctx.label, target.label))
+def exec(
+        name,
+        data = None,
+        hashbang = None,
+        script = None,
+        **kwargs):
+    """Runs a script when `bazel run` this target.
 
-    content = "#!{}\n".format(ctx.attr.hashbang)
-    content += ctx.attr.script
+    See [documentation] for the `args` attribute.
 
-    content = ctx.expand_location(content, ctx.attr.data)
-    ctx.actions.write(out_file, content, is_executable = True)
+    **NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
+    hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
+    See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
+    for details.
 
-    runfiles = ctx.runfiles(files = ctx.files.data + [out_file])
-    runfiles = runfiles.merge_all([target[DefaultInfo].default_runfiles for target in ctx.attr.data])
+    Args:
+        name: name of the target
+        data: A list of labels providing runfiles. Labels may be used in `script`.
 
-    return DefaultInfo(
-        files = depset([out_file]),
-        executable = out_file,
-        runfiles = runfiles,
+            Executables in `data` must not have the `args` and `env` attribute. Use
+            [`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
+            are preserved.
+        hashbang: hashbang of the script, default is `"/bin/bash -e"`.
+        script: The script.
+
+            Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
+            [documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
+
+            Use `$@` to refer to the args attribute of this target.
+
+            See `build/bazel_common_rules/exec/tests/BUILD` for examples.
+        **kwargs: Additional attributes to the internal rule, e.g.
+            [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
+            See complete list
+            [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes).
+
+    Deprecated:
+        Use `hermetic_exec` for stronger hermeticity.
+    """
+
+    # buildifier: disable=print
+    print("WARNING: {}: exec is deprecated. Use `hermetic_exec` instead.".format(
+        native.package_relative_label(name),
+    ))
+
+    kwargs.setdefault("deprecation", "Use hermetic_exec for stronger hermeticity")
+
+    _exec(
+        name = name,
+        data = data,
+        hashbang = hashbang,
+        script = script,
+        **kwargs
     )
 
-exec = rule(
-    implementation = _impl,
-    doc = """Run a script when `bazel run` this target.
+def exec_test(
+        name,
+        data = None,
+        hashbang = None,
+        script = None,
+        **kwargs):
+    """Runs a script when `bazel test` this target.
 
-See [documentation] for the `args` attribute.
+    See [documentation] for the `args` attribute.
 
-**NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
-hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
-See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
-for details.
-""",
-    attrs = {
-        "data": attr.label_list(aspects = [exec_aspect], allow_files = True, doc = """A list of labels providing runfiles. Labels may be used in `script`.
+    **NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
+    hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
+    See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
+    for details.
 
-Executables in `data` must not have the `args` and `env` attribute. Use
-[`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
-are preserved.
-"""),
-        "hashbang": attr.string(default = _DEFAULT_HASHBANG, doc = "Hashbang of the script."),
-        "script": attr.string(doc = """The script.
+    Args:
+        name: name of the target
+        data: A list of labels providing runfiles. Labels may be used in `script`.
 
-Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
-[documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
+            Executables in `data` must not have the `args` and `env` attribute. Use
+            [`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
+            are preserved.
+        hashbang: hashbang of the script, default is `"/bin/bash -e"`.
+        script: The script.
 
-Use `$@` to refer to the args attribute of this target.
+            Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
+            [documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
 
-See `build/bazel_common_rules/exec/tests/BUILD` for examples.
-"""),
-    },
-    executable = True,
-)
+            Use `$@` to refer to the args attribute of this target.
 
-exec_test = rule(
-    implementation = _impl,
-    doc = """Run a test script when `bazel test` this target.
+            See `build/bazel_common_rules/exec/tests/BUILD` for examples.
+        **kwargs: Additional attributes to the internal rule, e.g.
+            [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
+            See complete list
+            [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes).
 
-See [documentation] for the `args` attribute.
+    Deprecated:
+        Use `hermetic_exec` for stronger hermeticity.
+    """
 
-**NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
-hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
-See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
-for details.
-""",
-    attrs = {
-        "data": attr.label_list(aspects = [exec_aspect], allow_files = True, doc = """A list of labels providing runfiles. Labels may be used in `script`.
+    # buildifier: disable=print
+    print("WARNING: {}: exec_test is deprecated. Use `hermetic_exec_test` instead.".format(
+        native.package_relative_label(name),
+    ))
 
-Executables in `data` must not have the `args` and `env` attribute. Use
-[`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
-are preserved.
-"""),
-        "hashbang": attr.string(default = _DEFAULT_HASHBANG, doc = "Hashbang of the script."),
-        "script": attr.string(doc = """The script.
+    kwargs.setdefault("deprecation", "Use hermetic_exec_test for stronger hermeticity")
 
-Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
-[documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
+    _exec_test(
+        name = name,
+        data = data,
+        hashbang = hashbang,
+        script = script,
+        **kwargs
+    )
 
-Use `$@` to refer to the args attribute of this target.
-
-See `build/bazel_common_rules/exec/tests/BUILD` for examples.
-"""),
-    },
-    test = True,
-)
-
+# buildifier: disable=unnamed-macro
 def exec_rule(
         cfg = None,
         attrs = None):
@@ -128,19 +155,10 @@
         a rule
     """
 
-    fixed_attrs = {
-        "data": attr.label_list(aspects = [exec_aspect], allow_files = True),
-        "hashbang": attr.string(default = _DEFAULT_HASHBANG),
-        "script": attr.string(),
-    }
+    # buildifier: disable=print
+    print("WARNING: exec_rule is deprecated.")
 
-    if attrs == None:
-        attrs = {}
-    attrs = attrs | fixed_attrs
-
-    return rule(
-        implementation = _impl,
-        attrs = attrs,
+    _exec_rule(
         cfg = cfg,
-        executable = True,
+        attrs = attrs,
     )
diff --git a/exec/exec_aspect.bzl b/exec/exec_aspect.bzl
index 900e10c..6e7aa1f 100644
--- a/exec/exec_aspect.bzl
+++ b/exec/exec_aspect.bzl
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# Copyright (C) 2024 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,22 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-_attrs = ["args", "env", "data", "srcs", "deps"]
+"""Helps embedding `args` of an executable target.
 
-ExecAspectInfo = provider(
-    doc = "See [`exec_aspect`](#exec_aspect).",
-    fields = {attr: attr + " of the target" for attr in _attrs},
+**DEPRECTED**. This is an implementation detail and should not be relied upon.
+"""
+
+load(
+    "//build/bazel_common_rules/exec/impl:exec_aspect.bzl",
+    _ExecAspectInfo = "ExecAspectInfo",
+    _exec_aspect = "exec_aspect",
 )
 
-def _aspect_impl(target, ctx):
-    kwargs = {}
-    for attr in _attrs:
-        value = getattr(ctx.rule.attr, attr, None)
-        kwargs[attr] = value
-    return ExecAspectInfo(**kwargs)
+# TODO(b/329305827): make this private
+visibility("public")
 
-exec_aspect = aspect(
-    implementation = _aspect_impl,
-    doc = "Make arguments available for targets depending on executables.",
-    attr_aspects = _attrs,
-)
+ExecAspectInfo = _ExecAspectInfo
+exec_aspect = _exec_aspect
diff --git a/exec/impl/BUILD b/exec/impl/BUILD
new file mode 100644
index 0000000..e5e9cff
--- /dev/null
+++ b/exec/impl/BUILD
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+    name = "exec_aspect",
+    srcs = ["exec_aspect.bzl"],
+    visibility = ["//visibility:public"],
+)
+
+bzl_library(
+    name = "embedded_exec",
+    srcs = ["embedded_exec.bzl"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":exec_aspect",
+        "@bazel_skylib//lib:shell",
+    ],
+)
+
+bzl_library(
+    name = "exec",
+    srcs = ["exec.bzl"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":exec_aspect",
+        "@bazel_skylib//lib:shell",
+    ],
+)
diff --git a/exec/impl/embedded_exec.bzl b/exec/impl/embedded_exec.bzl
new file mode 100644
index 0000000..2ff33d9
--- /dev/null
+++ b/exec/impl/embedded_exec.bzl
@@ -0,0 +1,72 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+"""Impl of `embedded_exec`."""
+
+load("@bazel_skylib//lib:shell.bzl", "shell")
+load(":exec_aspect.bzl", "ExecAspectInfo", "exec_aspect")
+
+visibility([
+    "//build/bazel_common_rules/exec/...",
+    "//build/bazel_common_rules/dist/...",
+])
+
+def _impl(ctx):
+    target = ctx.attr.actual
+    files_to_run = target[DefaultInfo].files_to_run
+    if not files_to_run or not files_to_run.executable:
+        fail("{}: {} is not an executable".format(ctx.label, target))
+
+    out_file = ctx.actions.declare_file(ctx.label.name)
+
+    content = "#!{}\n".format(ctx.attr.hashbang)
+
+    expand_location_targets = []
+    for dependant_attr in ("data", "srcs", "deps"):
+        dependants = getattr(target[ExecAspectInfo], dependant_attr)
+        if dependants:
+            expand_location_targets += dependants
+
+    args = target[ExecAspectInfo].args
+    if not args:
+        args = []
+    quoted_args = " ".join([shell.quote(ctx.expand_location(arg, expand_location_targets)) for arg in args])
+
+    env = target[ExecAspectInfo].env
+    if not env:
+        env = {}
+
+    quoted_env = " ".join(["{}={}".format(k, shell.quote(ctx.expand_location(v, expand_location_targets))) for k, v in env.items()])
+
+    content += '{} {} {} "$@"'.format(quoted_env, target[DefaultInfo].files_to_run.executable.short_path, quoted_args)
+
+    ctx.actions.write(out_file, content, is_executable = True)
+
+    runfiles = ctx.runfiles(files = ctx.files.actual)
+    runfiles = runfiles.merge_all([target[DefaultInfo].default_runfiles])
+
+    return DefaultInfo(
+        files = depset([out_file]),
+        executable = out_file,
+        runfiles = runfiles,
+    )
+
+embedded_exec = rule(
+    implementation = _impl,
+    attrs = {
+        "actual": attr.label(doc = "The actual executable.", aspects = [exec_aspect]),
+        "hashbang": attr.string(doc = "The hashbang of the script", default = "/bin/bash -e"),
+    },
+    executable = True,
+)
diff --git a/exec/impl/exec.bzl b/exec/impl/exec.bzl
new file mode 100644
index 0000000..b78728b
--- /dev/null
+++ b/exec/impl/exec.bzl
@@ -0,0 +1,152 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+"""Impl of `exec`."""
+
+load(":exec_aspect.bzl", "ExecAspectInfo", "exec_aspect")
+
+visibility([
+    "//build/bazel_common_rules/exec/...",
+    "//build/kernel/kleaf/...",
+])
+
+_DEFAULT_HASHBANG = "/bin/bash -e"
+
+def _impl(ctx):
+    out_file = ctx.actions.declare_file(ctx.label.name)
+
+    for target in ctx.attr.data:
+        if ExecAspectInfo not in target:
+            continue
+        if target[ExecAspectInfo].args:
+            fail("{}: {} must not have args. Use embedded_exec to wrap it.".format(ctx.label, target.label))
+        if target[ExecAspectInfo].env:
+            fail("{}: {} must not have env. Use embedded_exec to wrap it.".format(ctx.label, target.label))
+
+    content = "#!{}\n".format(ctx.attr.hashbang)
+    content += ctx.attr.script
+
+    content = ctx.expand_location(content, ctx.attr.data)
+    ctx.actions.write(out_file, content, is_executable = True)
+
+    runfiles = ctx.runfiles(files = ctx.files.data + [out_file])
+    runfiles = runfiles.merge_all([target[DefaultInfo].default_runfiles for target in ctx.attr.data])
+
+    return DefaultInfo(
+        files = depset([out_file]),
+        executable = out_file,
+        runfiles = runfiles,
+    )
+
+exec = rule(
+    implementation = _impl,
+    doc = """Run a script when `bazel run` this target.
+
+See [documentation] for the `args` attribute.
+
+**NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
+hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
+See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
+for details.
+""",
+    attrs = {
+        "data": attr.label_list(aspects = [exec_aspect], allow_files = True, doc = """A list of labels providing runfiles. Labels may be used in `script`.
+
+Executables in `data` must not have the `args` and `env` attribute. Use
+[`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
+are preserved.
+"""),
+        "hashbang": attr.string(default = _DEFAULT_HASHBANG, doc = "Hashbang of the script."),
+        "script": attr.string(doc = """The script.
+
+Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
+[documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
+
+Use `$@` to refer to the args attribute of this target.
+
+See `build/bazel_common_rules/exec/tests/BUILD` for examples.
+"""),
+    },
+    executable = True,
+)
+
+exec_test = rule(
+    implementation = _impl,
+    doc = """Run a test script when `bazel test` this target.
+
+See [documentation] for the `args` attribute.
+
+**NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
+hermeticity is not enforced or guaranteed, especially if `script` accesses PATH.
+See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
+for details.
+""",
+    attrs = {
+        "data": attr.label_list(aspects = [exec_aspect], allow_files = True, doc = """A list of labels providing runfiles. Labels may be used in `script`.
+
+Executables in `data` must not have the `args` and `env` attribute. Use
+[`embedded_exec`](#embedded_exec) to wrap the depended target so its env and args
+are preserved.
+"""),
+        "hashbang": attr.string(default = _DEFAULT_HASHBANG, doc = "Hashbang of the script."),
+        "script": attr.string(doc = """The script.
+
+Use `$(rootpath <label>)` to refer to the path of a target specified in `data`. See
+[documentation](https://bazel.build/reference/be/make-variables#predefined_label_variables).
+
+Use `$@` to refer to the args attribute of this target.
+
+See `build/bazel_common_rules/exec/tests/BUILD` for examples.
+"""),
+    },
+    test = True,
+)
+
+def exec_rule(
+        cfg = None,
+        attrs = None):
+    """Returns a rule() that is similar to `exec`, but with the given incoming transition.
+
+    **NOTE**: Like [genrule](https://bazel.build/reference/be/general#genrule)s,
+    hermeticity is not enforced or guaranteed for targets of the returned
+    rule, especially if a target specifies `script` that accesses PATH.
+    See [`Genrule Environment`](https://bazel.build/reference/be/general#genrule-environment)
+    for details.
+
+    Args:
+        cfg: [Incoming edge transition](https://bazel.build/extending/config#incoming-edge-transitions)
+            on the rule
+        attrs: Additional attributes to be added to the rule.
+
+            Specify `_allowlist_function_transition` if you need a transition.
+    Returns:
+        a rule
+    """
+
+    fixed_attrs = {
+        "data": attr.label_list(aspects = [exec_aspect], allow_files = True),
+        "hashbang": attr.string(default = _DEFAULT_HASHBANG),
+        "script": attr.string(),
+    }
+
+    if attrs == None:
+        attrs = {}
+    attrs = attrs | fixed_attrs
+
+    return rule(
+        implementation = _impl,
+        attrs = attrs,
+        cfg = cfg,
+        executable = True,
+    )
diff --git a/exec/impl/exec_aspect.bzl b/exec/impl/exec_aspect.bzl
new file mode 100644
index 0000000..cd0c101
--- /dev/null
+++ b/exec/impl/exec_aspect.bzl
@@ -0,0 +1,37 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+"""Impl of `exec_aspect`."""
+
+visibility("//build/bazel_common_rules/exec/...")
+
+_attrs = ["args", "env", "data", "srcs", "deps"]
+
+ExecAspectInfo = provider(
+    doc = "See [`exec_aspect`](#exec_aspect).",
+    fields = {attr: attr + " of the target" for attr in _attrs},
+)
+
+def _aspect_impl(_target, ctx):
+    kwargs = {}
+    for attr in _attrs:
+        value = getattr(ctx.rule.attr, attr, None)
+        kwargs[attr] = value
+    return ExecAspectInfo(**kwargs)
+
+exec_aspect = aspect(
+    implementation = _aspect_impl,
+    doc = "Make arguments available for targets depending on executables.",
+    attr_aspects = _attrs,
+)
diff --git a/exec/tests/BUILD b/exec/tests/BUILD
index 299fe45..0a89a40 100644
--- a/exec/tests/BUILD
+++ b/exec/tests/BUILD
@@ -13,8 +13,8 @@
 # limitations under the License.
 
 # BUILD
-load("//build/bazel_common_rules/exec:embedded_exec.bzl", "embedded_exec")
-load("//build/bazel_common_rules/exec:exec.bzl", "exec")
+load("//build/bazel_common_rules/exec/impl:embedded_exec.bzl", "embedded_exec")
+load("//build/bazel_common_rules/exec/impl:exec.bzl", "exec")
 
 exec(
     name = "script_a",