[bazel] Introduce a "py_import" rule, analogous to "java_import"

This allows us to use wheels and other tar.gz third party libraries in
our python code. This will prove useful.

Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/SeleniumHQ/selenium
Cr-Mirrored-Commit: 6681d50936e832c87373b63bed3c834876acc3ac
diff --git a/defs.bzl b/defs.bzl
new file mode 100644
index 0000000..ad5c317
--- /dev/null
+++ b/defs.bzl
@@ -0,0 +1,3 @@
+load("//py:import.bzl", _py_import = "py_import")
+
+py_import = _py_import
diff --git a/import.bzl b/import.bzl
new file mode 100644
index 0000000..36dda2f
--- /dev/null
+++ b/import.bzl
@@ -0,0 +1,100 @@
+def _py_import_impl(ctx):
+    # Unpack the file somewhere, and present as a python library. We need to
+    # know all the files in the zip, and that's problematic. For now, we might
+    # be able to get away with just creating and declaring the directory.
+
+    root = ctx.actions.declare_directory("%s-pyroot" % ctx.attr.name)
+    args = ctx.actions.args()
+
+    if ctx.file.wheel.path.endswith(".zip") or ctx.file.wheel.path.endswith(".whl"):
+        args.add("x")
+        args.add(ctx.file.wheel.path)
+        args.add_all(["-d", root.path])
+
+        ctx.actions.run(
+            outputs = [root],
+            inputs = [ctx.file.wheel],
+            arguments = [args],
+            executable = ctx.executable._zip,
+        )
+    elif ctx.file.wheel.path.endswith(".tar.gz"):
+        args.add(ctx.file.wheel.path)
+        args.add(root.path)
+
+        ctx.actions.run(
+            outputs = [root],
+            inputs = [ctx.file.wheel],
+            arguments = [args],
+            executable = ctx.executable._untar,
+        )
+    else:
+        fail("Unrecognised file extension: %s" % ctx.attr.wheel)
+
+    runfiles = ctx.runfiles(files = [root])
+    for dep in ctx.attr.deps:
+        runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
+
+    imports = depset(
+        items = [
+          "%s/%s/%s-pyroot" % (ctx.workspace_name, ctx.label.package, ctx.label.name),
+        ],
+        transitive = [dep[PyInfo].imports for dep in ctx.attr.deps],
+    )
+    transitive_sources = depset(
+        items = [],
+        transitive = [dep[PyInfo].transitive_sources for dep in ctx.attr.deps],
+    )
+
+    py_srcs = ctx.attr.srcs_version
+
+    info = PyInfo(
+        imports = imports,
+        has_py2_only_sources = py_srcs == "PY2",
+        has_py3_only_sources = py_srcs == "PY3",
+        transitive_sources = transitive_sources,
+        uses_shared_libraries = not ctx.attr.zip_safe,
+    )
+
+    return [
+        DefaultInfo(
+            files = depset(items = [root]),
+            default_runfiles = runfiles,
+        ),
+        info,
+    ]
+
+py_import = rule(
+    _py_import_impl,
+    attrs = {
+        "wheel": attr.label(
+            allow_single_file = True,
+            mandatory = True,
+        ),
+        "zip_safe": attr.bool(
+            default = True,
+        ),
+        "python_version": attr.string(
+            default = "PY3",
+            values = ["PY2", "PY3"],
+        ),
+        "srcs_version": attr.string(
+            default = "PY2AND3",
+            values = ["PY2", "PY3", "PY2AND3"],
+        ),
+        "deps": attr.label_list(
+            allow_empty = True,
+            providers = [PyInfo],
+        ),
+        "_zip": attr.label(
+            allow_single_file = True,
+            cfg = "host",
+            default = "@bazel_tools//tools/zip:zipper",
+            executable = True,
+        ),
+        "_untar": attr.label(
+            cfg = "exec",
+            default = "//py:untar",
+            executable = True,
+        ),
+    },
+)
diff --git a/untar.py b/untar.py
new file mode 100644
index 0000000..b1fa50e
--- /dev/null
+++ b/untar.py
@@ -0,0 +1,26 @@
+import os
+import sys
+import tarfile
+
+if __name__ == '__main__':
+    outdir = sys.argv[2]
+    if not os.path.exists(outdir):
+        os.makedirs(outdir)
+
+    tar = tarfile.open(sys.argv[1])
+    for member in tar.getmembers():
+        parts = member.name.split("/")
+        parts.pop(0)
+        if not len(parts):
+            continue
+
+        basepath = os.path.join(*parts)
+        basepath = os.path.normpath(basepath)
+        member.name = basepath
+
+        dir = os.path.join(outdir, os.path.dirname(basepath))
+        if not os.path.exists(dir):
+            os.makedirs(dir)
+
+        tar.extract(member, outdir)
+    tar.close()