Snap for 8782634 from 0b6d79dba77cdf99a3a31cf566da27693e38e1b8 to sdk-release

Change-Id: I90033598c8bcc2864d98a2fa3affb038be77fb24
diff --git a/dist/dist.bzl b/dist/dist.bzl
index 19626bc..2ecc8c2 100644
--- a/dist/dist.bzl
+++ b/dist/dist.bzl
@@ -64,6 +64,7 @@
         prefix = None,
         archive_prefix = None,
         dist_dir = None,
+        log = None,
         **kwargs):
     """A dist rule to copy files out of Bazel's output directory into a custom location.
 
@@ -91,6 +92,10 @@
           In particular, if this is a relative path, it is interpreted as a relative path
           under workspace root when the target is executed with `bazel run`.
           See details by running the target with `--help`.
+        log: If specified, `--log <log>` is provided to the script by default. This sets the
+          default log level of the script.
+
+          See `dist.py` for allowed values and the default value.
         kwargs: Additional attributes to the internal rule, e.g.
           [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
 
@@ -106,6 +111,8 @@
         default_args += ["--archive_prefix", archive_prefix]
     if dist_dir != None:
         default_args += ["--dist_dir", dist_dir]
+    if log != None:
+        default_args += ["--log", log]
 
     _generate_dist_manifest(
         name = name + "_dist_manifest",
diff --git a/dist/dist.py b/dist/dist.py
index 39d3c12..c9538c4 100644
--- a/dist/dist.py
+++ b/dist/dist.py
@@ -33,6 +33,7 @@
 
 import argparse
 import glob
+import logging
 import os
 import shutil
 import sys
@@ -45,8 +46,8 @@
     dist_manifests = glob.glob(
         os.path.join(runfiles_directory, pattern))
     if not dist_manifests:
-        print("Warning: could not find a file with pattern " + pattern +
-              " in the runfiles directory: %s" % runfiles_directory)
+        logging.warning("Could not find a file with pattern %s"
+                        " in the runfiles directory: %s", pattern, runfiles_directory)
     files_to_dist = []
     for dist_manifest in dist_manifests:
         with open(dist_manifest, "r") as f:
@@ -55,7 +56,8 @@
 
 
 def copy_files_to_dist_dir(files, archives, dist_dir, flat, prefix,
-    archive_prefix):
+    archive_prefix, **ignored):
+    logging.info("Copying to %s", dist_dir)
 
     for src in files:
         src_relpath = os.path.basename(src) if flat else src
@@ -65,32 +67,40 @@
         dst = os.path.join(dist_dir, src_relpath)
         if os.path.isfile(src):
             dst_dirname = os.path.dirname(dst)
-            print("[dist] Copying file: %s" % dst)
+            logging.debug("Copying file: %s" % dst)
             if not os.path.exists(dst_dirname):
                 os.makedirs(dst_dirname)
 
             shutil.copyfile(src_abspath, dst, follow_symlinks=True)
         elif os.path.isdir(src):
-            print("[dist] Copying dir: %s" % dst)
+            logging.debug("Copying dir: %s" % dst)
             shutil.copytree(src_abspath, dst, copy_function=shutil.copyfile, dirs_exist_ok=True)
 
     for archive in archives:
         try:
             with tarfile.open(archive) as tf:
                 dst_dirname = os.path.join(dist_dir, archive_prefix)
-                print("[dist] Extracting archive: {} -> {}".format(archive,
-                                                                   dst_dirname))
+                logging.debug("Extracting archive: %s -> %s", archive, dst_dirname)
                 tf.extractall(dst_dirname)
         except tarfile.TarError:
             # toybox does not support creating empty tar files, hence the build
             # system may use empty files as empty archives.
             if os.path.getsize(archive) == 0:
-                print("Warning: skipping empty tar file: {}".format(archive))
+                logging.warning("Skipping empty tar file: %s", archive)
                 continue
              # re-raise if we do not know anything about this error
+            logging.exception("Unknown TarError.")
             raise
 
 
+def config_logging(log_level_str):
+    level = getattr(logging, log_level_str.upper(), None)
+    if not isinstance(level, int):
+        sys.stderr.write("ERROR: Invalid --log {}\n".format(log_level_str))
+        sys.exit(1)
+    logging.basicConfig(level=level, format="[dist] %(levelname)s: %(message)s")
+
+
 def main():
     parser = argparse.ArgumentParser(
         description="Dist Bazel output files into a custom directory.")
@@ -110,9 +120,12 @@
         "--archive_prefix", default="",
         help="Path prefix to apply within dist_dir for extracted archives. " +
              "Supported archives: tar.")
+    parser.add_argument("--log", help="Log level (debug, info, warning, error)", default="debug")
 
     args = parser.parse_args(sys.argv[1:])
 
+    config_logging(args.log)
+
     if not os.path.isabs(args.dist_dir):
         # BUILD_WORKSPACE_DIRECTORY is the root of the Bazel workspace containing
         # this binary target.
diff --git a/exec/exec.bzl b/exec/exec.bzl
index 0769354..ee18f94 100644
--- a/exec/exec.bzl
+++ b/exec/exec.bzl
@@ -67,3 +67,30 @@
     },
     executable = True,
 )
+
+exec_test = rule(
+    implementation = _impl,
+    doc = """Run a test script when `bazel test` this target.
+
+See [documentation] for the `args` attribute.
+""",
+    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 = "/bin/bash -e", 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,
+)
diff --git a/workspace/BUILD b/workspace/BUILD
new file mode 100644
index 0000000..410a6b5
--- /dev/null
+++ b/workspace/BUILD
@@ -0,0 +1,13 @@
+# 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.
diff --git a/workspace/README.md b/workspace/README.md
new file mode 100644
index 0000000..15c5406
--- /dev/null
+++ b/workspace/README.md
@@ -0,0 +1 @@
+These extensions are expected to be loaded by `WORKSPACE` files only.
diff --git a/workspace/external.bzl b/workspace/external.bzl
new file mode 100644
index 0000000..8964ec5
--- /dev/null
+++ b/workspace/external.bzl
@@ -0,0 +1,53 @@
+# 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_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+def import_external_repositories(
+        bazel_skylib = None,
+        io_abseil_py = None,
+        io_bazel_stardoc = None):
+    """Import repositories in external/ that are common to Bazel builds for Android.
+
+    In particular, these external projects are shared by Android platform
+    repo manifest and Android kernel repo manifest.
+
+    Caller of this function (in the `WORKSPACE` file) should manage a list
+    of repositories imported by providing them in the arguments.
+
+    Args:
+        bazel_skylib: If `True`, load `bazel_skylib`.
+        io_abseil_py: If `True`, load `io_abseil_py`.
+        io_bazel_stardoc: If `True`, load `io_bazel_stardoc`.
+    """
+    if bazel_skylib:
+        maybe(
+            repo_rule = native.local_repository,
+            name = "bazel_skylib",
+            path = "external/bazel-skylib",
+        )
+
+    if io_abseil_py:
+        maybe(
+            repo_rule = native.local_repository,
+            name = "io_abseil_py",
+            path = "external/python/absl-py",
+        )
+
+    if io_bazel_stardoc:
+        maybe(
+            repo_rule = native.local_repository,
+            name = "io_bazel_stardoc",
+            path = "external/stardoc",
+        )