Move dist rule to bazel_common_rules.

Test: presubmits

Bug: 197589841
Change-Id: I2cb506b3409a083da2ce4bf426adc0f8b126d5cc
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..4de1e69
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,8 @@
+# Platform build
+include platform/build/soong:/OWNERS
+
+# Kernel build
+#
+# The kernel follows a different branching model than that of platform, which
+# needs the 'master' branch to be explicitly declared.
+include kernel/build:master:/kleaf/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..21dbb5a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# Bazel Common Rules
+
+This directory contains common Bazel rules and tools shared between the Platform
+and Kernel builds.
+
+For platform-specific rules, place them in the platform checkout's
+[//build/bazel/rules](https://android.googlesource.com/platform/build/bazel/+/master/rules/)
+directory.
+
+For kernel-specific rules, place them in kernel checkout's [//build/kleaf
+directory](https://android.googlesource.com/kernel/build/+/master/kleaf/).
diff --git a/dist/BUILD b/dist/BUILD
new file mode 100644
index 0000000..ee3a4ca
--- /dev/null
+++ b/dist/BUILD
@@ -0,0 +1,11 @@
+load(":dist.bzl", "copy_to_dist_dir")
+exports_files(["dist.py"])
+
+# bazel run --package_path=out/soong/workspace //build/bazel_common_rules/dist:dist_bionic_example -- --dist_dir=/tmp/dist
+copy_to_dist_dir(
+    name = "dist_bionic_example",
+    data = [
+        "//bionic/libc:libc",
+        "//bionic/libdl:libdl",
+    ],
+)
diff --git a/dist/dist.bzl b/dist/dist.bzl
new file mode 100644
index 0000000..a4e29d6
--- /dev/null
+++ b/dist/dist.bzl
@@ -0,0 +1,68 @@
+# 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")
+
+def _generate_dist_manifest_impl(ctx):
+    # Create a manifest of dist files to differentiate them from other runfiles.
+    dist_manifest = ctx.actions.declare_file(ctx.attr.name + "_dist_manifest.txt")
+    dist_manifest_content = ""
+    all_dist_files = []
+    for f in ctx.attr.data:
+        dist_files = f[DefaultInfo].files.to_list()
+        all_dist_files += dist_files
+        dist_manifest_content += "\n".join([dist_file.short_path for dist_file in dist_files])
+    ctx.actions.write(
+        output = dist_manifest,
+        content = dist_manifest_content,
+    )
+
+    # Create the runfiles object.
+    runfiles = ctx.runfiles(files = [dist_manifest] + all_dist_files)
+
+    return [DefaultInfo(runfiles = runfiles)]
+
+
+_generate_dist_manifest = rule(
+    implementation = _generate_dist_manifest_impl,
+    doc = """Generate a manifest of files to be dist to a directory.""",
+    attrs = {
+        "data": attr.label_list(
+            mandatory = True,
+            allow_files = True,
+            doc = """Files or targets to copy to the dist dir.
+
+In the case of targets, the rule copies the list of `files` from the target's DefaultInfo provider.
+""",
+        ),
+    },
+)
+
+def copy_to_dist_dir(name, data):
+    """A dist rule to copy files out of Bazel's output directory into a custom location.
+
+Example:
+    bazel run //path/to/my:dist_target -- --dist_dir=/tmp/dist
+"""
+    _generate_dist_manifest(
+        name = name + "_dist_manifest",
+        data = data,
+    )
+
+    copy_file(
+        name = name + "_dist_tool",
+        src = "//build/bazel_common_rules/dist:dist.py",
+        out = name + "_dist.py",
+    )
+
+    # The dist py_binary tool must be colocated in the same package as the
+    # dist_manifest so that the runfiles directory is the same, and that the
+    # dist_manifest is in the data runfiles of the dist tool.
+    native.py_binary(
+        name = name,
+        main = name + "_dist.py",
+        srcs = [name + "_dist.py"],
+        python_version = "PY3",
+        visibility = ["//visibility:public"],
+        data = [name + "_dist_manifest"],
+    )
diff --git a/dist/dist.py b/dist/dist.py
new file mode 100644
index 0000000..c49dfd5
--- /dev/null
+++ b/dist/dist.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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.
+
+"""A Python script to copy files from Bazel's dist rule to a user specified dist directory.
+
+READ ME FIRST.
+
+This script is only meant to be executed with `bazel run`. `bazel build <this
+script>` doesn't actually copy the files, you'd have to `bazel run` a
+copy_to_dist_dir target.
+
+This script copies files from Bazel's output tree into a directory specified by
+the user.  It does not check if the dist dir already contains the file, and will
+simply overwrite it.
+
+One approach is to wipe the dist dir every time this script runs, but that may
+be overly destructive and best left to an explicit rm -rf call outside of this
+script.
+
+Another approach is to error out if the file being copied already exist in the dist dir,
+or perform some kind of content hash checking.
+"""
+
+
+import argparse
+import glob
+import os
+import shutil
+import sys
+
+
+def files_to_dist():
+    # Assume that dist.bzl is in the same package as dist.py
+    runfiles_directory = os.path.dirname(__file__)
+    dist_manifests = glob.glob(os.path.join(runfiles_directory, "*_dist_manifest.txt"))
+    if not dist_manifests:
+        print("Warning: could not find a file ending in *_dist_manifest.txt" +
+                           "in the runfiles directory: %s" % runfiles_directory)
+    files_to_dist = []
+    for dist_manifest in dist_manifests:
+        with open(dist_manifest, "r") as f:
+            files_to_dist += [line.strip() for line in f]
+    return files_to_dist
+
+def copy_files_to_dist_dir(files, dist_dir):
+    for src in files:
+        if not os.path.isfile(src):
+            continue
+
+        src_relpath = src
+        src_abspath = os.path.abspath(src)
+
+        dst = os.path.join(dist_dir, src_relpath)
+        dst_dirname = os.path.dirname(dst)
+        print("[dist] Copying file: %s" % dst)
+        if not os.path.exists(dst_dirname):
+            os.makedirs(dst_dirname)
+
+        shutil.copyfile(src_abspath, dst, follow_symlinks=True)
+
+def main():
+    parser = argparse.ArgumentParser(description="Dist Bazel output files into a custom directory.")
+    parser.add_argument("--dist_dir", required = True, help = "absolute path to the dist dir")
+    args = parser.parse_args()
+    dist_dir = args.dist_dir
+
+    if not os.path.isabs(dist_dir):
+        # BUILD_WORKSPACE_DIRECTORY is the root of the Bazel workspace containing this binary target.
+        # https://docs.bazel.build/versions/main/user-manual.html#run
+        dist_dir = os.path.join(os.environ.get("BUILD_WORKSPACE_DIRECTORY"), dist_dir)
+
+    copy_files_to_dist_dir(files_to_dist(), dist_dir)
+
+if __name__ == "__main__":
+    main()