feat: Add support for python-wheel data directory (#1801)

Fixes #1777

* Adds `data_files` attribute to `py_wheel` rule. 
* Minimal validation of the data-files target directories per
[specification](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl)
* Added two tests.  
* Added example
diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl
index 5919abe..2aed9b9 100644
--- a/python/private/py_wheel.bzl
+++ b/python/private/py_wheel.bzl
@@ -120,6 +120,7 @@
 
 _feature_flags = {}
 
+ALLOWED_DATA_FILE_PREFIX = ("purelib", "platlib", "headers", "scripts", "data")
 _requirement_attrs = {
     "extra_requires": attr.string_list_dict(
         doc = ("A mapping of [extras](https://peps.python.org/pep-0508/#extras) options to lists of requirements (similar to `requires`). This attribute " +
@@ -172,6 +173,11 @@
     "classifiers": attr.string_list(
         doc = "A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers",
     ),
+    "data_files": attr.label_keyed_string_dict(
+        doc = ("Any file that is not normally installed inside site-packages goes into the .data directory, named " +
+               "as the .dist-info directory but with the .data/ extension.  Allowed paths: {prefixes}".format(prefixes = ALLOWED_DATA_FILE_PREFIX)),
+        allow_files = True,
+    ),
     "description_content_type": attr.string(
         doc = ("The type of contents in description_file. " +
                "If not provided, the type will be inferred from the extension of description_file. " +
@@ -473,6 +479,28 @@
             filename + ";" + target_files[0].path,
         )
 
+    for target, filename in ctx.attr.data_files.items():
+        target_files = target.files.to_list()
+        if len(target_files) != 1:
+            fail(
+                "Multi-file target listed in data_files %s",
+                filename,
+            )
+
+        if filename.partition("/")[0] not in ALLOWED_DATA_FILE_PREFIX:
+            fail(
+                "The target data file must start with one of these prefixes: '%s'.  Target filepath: '%s'" %
+                (
+                    ",".join(ALLOWED_DATA_FILE_PREFIX),
+                    filename,
+                ),
+            )
+        other_inputs.extend(target_files)
+        args.add(
+            "--data_files",
+            filename + ";" + target_files[0].path,
+        )
+
     ctx.actions.run(
         mnemonic = "PyWheel",
         inputs = depset(direct = other_inputs, transitive = [inputs_to_package]),
diff --git a/python/private/repack_whl.py b/python/private/repack_whl.py
index be113ef..ea9c01f 100644
--- a/python/private/repack_whl.py
+++ b/python/private/repack_whl.py
@@ -150,8 +150,9 @@
         logging.debug(f"Found dist-info dir: {distinfo_dir}")
         record_path = distinfo_dir / "RECORD"
         record_contents = record_path.read_text() if record_path.exists() else ""
+        distribution_prefix = distinfo_dir.with_suffix("").name
 
-        with _WhlFile(args.output, mode="w", distinfo_dir=distinfo_dir) as out:
+        with _WhlFile(args.output, mode="w", distribution_prefix=distribution_prefix) as out:
             for p in _files_to_pack(patched_wheel_dir, record_contents):
                 rel_path = p.relative_to(patched_wheel_dir)
                 out.add_file(str(rel_path), p)