[bazel] Create release automation

1. Import the github CLI tool `gh`.
2. Use bazel rules to automate the release process.

A release can be performed via:
```bash
bazel run //release -- RELEASE_TAG_NAME
```

This will build the release artifacts, create a tag within the
repository with `RELEASE_TAG_NAME`, create a release from that tag and
upload the release artifacts to github under that release.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/WORKSPACE b/WORKSPACE
index af21688..07b2a31 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -19,6 +19,10 @@
 load("//third_party/lowrisc_toolchain:deps.bzl", "lowrisc_toolchain_deps")
 lowrisc_toolchain_deps()
 
+# Tools for release automation
+load("//third_party/github:repos.bzl", "github_tools_repos")
+github_tools_repos()
+
 # Go Toolchain
 load("//third_party/go:repos.bzl", "go_repos")
 go_repos()
diff --git a/release/BUILD b/release/BUILD
index 2d918b3..d24bccf 100644
--- a/release/BUILD
+++ b/release/BUILD
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
+load("//third_party/github:rules.bzl", "release")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -20,3 +21,11 @@
     extension = "tar.xz",
     tags = ["manual"],
 )
+
+release(
+    name = "release",
+    artifacts = {
+        ":opentitan": "Opentitan software and FPGA artifacts",
+    },
+    tags = ["manual"],
+)
diff --git a/rules/exclude_files.bzl b/rules/exclude_files.bzl
index e47443b..4315a84 100644
--- a/rules/exclude_files.bzl
+++ b/rules/exclude_files.bzl
@@ -23,7 +23,7 @@
             doc = "Targets producing file outputs",
         ),
         "exclude_suffix": attr.string_list(
-            doc = "File suffixes to exlucude from the result",
+            doc = "File suffixes to exclude from the result",
         ),
     },
 )
diff --git a/third_party/github/BUILD.bazel b/third_party/github/BUILD.bazel
new file mode 100644
index 0000000..4d86275
--- /dev/null
+++ b/third_party/github/BUILD.bazel
@@ -0,0 +1,8 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+exports_files(
+    glob(["**"]),
+    visibility = ["//visibility:public"],
+)
diff --git a/third_party/github/BUILD.gh.bazel b/third_party/github/BUILD.gh.bazel
new file mode 100644
index 0000000..a7f3504
--- /dev/null
+++ b/third_party/github/BUILD.gh.bazel
@@ -0,0 +1,10 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "gh",
+    srcs = ["bin/gh"],
+)
diff --git a/third_party/github/release.template.bash b/third_party/github/release.template.bash
new file mode 100644
index 0000000..88e5bb7
--- /dev/null
+++ b/third_party/github/release.template.bash
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ARTIFACTS=(@@ARTIFACTS@@)
+FILES=(@@FILES@@)
+GH=@@GH@@
+REMOTE="@@REMOTE@@"
+@@ENV@@
+
+BRANCH=$(cd "$BUILD_WORKSPACE_DIRECTORY" && git branch --show-current)
+RELEASE_TAG=$(cd "$BUILD_WORKSPACE_DIRECTORY" && git describe --abbrev=0 --tags)
+
+if $(${GH} release list | egrep -q "\s${RELEASE_TAG}\s"); then
+    echo "A release with tag ${RELEASE_TAG} already exists."
+    echo
+    echo "To make a new release, create a new tag first."
+    exit 1
+fi
+
+declare -A DIGEST=()
+for f in "${FILES[@]}"; do
+    b=$(basename ${f})
+    DIGEST[${b}]=$(sha256sum ${f} | cut -f1 -d' ')
+done
+
+export ARTIFACTS BRANCH FILES GH REMOTE RELEASE_TAG DIGEST
+@@SCRIPT@@
+
+${GH} release create --target="${BRANCH}" "$@" "${RELEASE_TAG}" "${ARTIFACTS[@]}"
diff --git a/third_party/github/repos.bzl b/third_party/github/repos.bzl
new file mode 100644
index 0000000..c124408
--- /dev/null
+++ b/third_party/github/repos.bzl
@@ -0,0 +1,14 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+def github_tools_repos():
+    http_archive(
+        name = "com_github_gh",
+        url = "https://github.com/cli/cli/releases/download/v2.13.0/gh_2.13.0_linux_amd64.tar.gz",
+        sha256 = "9e833e02428cd49e0af73bc7dc4cafa329fe3ecba1bfe92f0859bf5b11916401",
+        build_file = Label("//third_party/github:BUILD.gh.bazel"),
+        strip_prefix = "gh_2.13.0_linux_amd64",
+    )
diff --git a/third_party/github/rules.bzl b/third_party/github/rules.bzl
new file mode 100644
index 0000000..ede6eca
--- /dev/null
+++ b/third_party/github/rules.bzl
@@ -0,0 +1,65 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+def _release_impl(ctx):
+    artifacts = []
+    runfiles = []
+    for k, v in ctx.attr.artifacts.items():
+        files = k[DefaultInfo].files.to_list()
+        if len(files) > 1:
+            fail("Artifacts must produce a single file")
+        runfiles.extend(files)
+        artifacts.append("'{}#{}'".format(files[0].short_path, v))
+
+    env = "\n".join(["export {}=\"{}\"".format(k, v) for k, v in ctx.attr.env.items()])
+    runner = ctx.actions.declare_file(ctx.label.name + ".bash")
+    ctx.actions.expand_template(
+        template = ctx.file._runner,
+        output = runner,
+        is_executable = True,
+        substitutions = {
+            "@@ARTIFACTS@@": " ".join(artifacts),
+            "@@ENV@@": env,
+            "@@FILES@@": " ".join([f.short_path for f in runfiles]),
+            "@@REMOTE@@": ctx.attr.remote,
+            "@@SCRIPT@@": ctx.attr.script,
+            "@@GH@@": ctx.executable._gh.path,
+        },
+    )
+
+    return DefaultInfo(
+        files = depset([runner]),
+        runfiles = ctx.runfiles(files = [ctx.executable._gh] + runfiles),
+        executable = runner,
+    )
+
+release = rule(
+    implementation = _release_impl,
+    attrs = {
+        "artifacts": attr.label_keyed_string_dict(
+            doc = "Mapping of release artifacts to their text descriptions",
+            allow_files = True,
+        ),
+        "remote": attr.string(
+            default = "origin",
+            doc = "The remote to push the release tag to",
+        ),
+        "script": attr.string(
+            doc = "Script operation to perform before the github release operation",
+        ),
+        "env": attr.string_dict(
+            doc = "Additional environment variables for the script",
+        ),
+        "_gh": attr.label(
+            default = "@com_github_gh//:gh",
+            cfg = "exec",
+            executable = True,
+        ),
+        "_runner": attr.label(
+            default = "//third_party/github:release.template.bash",
+            allow_single_file = True,
+        ),
+    },
+    executable = True,
+)