Add OSS-Fuzz support to the Bazel fuzzing rules. (#96)

* The OSS-Fuzz support in one change.

* Clarify why we're skipping the fuzzing build mode flag.
diff --git a/.bazelrc b/.bazelrc
index 4544b7b..7bece0c 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -40,3 +40,7 @@
 build:msan-honggfuzz --//fuzzing:cc_engine=//fuzzing/engines:honggfuzz
 build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz
 build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=msan
+
+build:oss-fuzz --//fuzzing:cc_engine=@rules_fuzzing_oss_fuzz//:oss_fuzz_engine
+build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=oss-fuzz
+build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=none
diff --git a/docs/BUILD b/docs/BUILD
index 5995503..3a7cb7a 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -43,6 +43,8 @@
         "//fuzzing/private:engine.bzl",
         "//fuzzing/private:fuzz_test.bzl",
         "//fuzzing/private:instrum_opts.bzl",
+        "//fuzzing/private/oss_fuzz:package.bzl",
+        "@rules_fuzzing_oss_fuzz//:instrum.bzl",
     ],
     deps = [
         ":rules_cc",
diff --git a/examples/BUILD b/examples/BUILD
index 0a0c4fa..f53eb22 100644
--- a/examples/BUILD
+++ b/examples/BUILD
@@ -42,6 +42,11 @@
 cc_fuzz_test(
     name = "empty_fuzz_test_with_dict",
     srcs = ["empty_fuzz_test.cc"],
+    corpus = [
+        "corpus_0.txt",
+        ":corpus_filegroup",
+        "test_corpus_dir",
+    ],
     dicts = ["dict_dir/valid.dict"],
 )
 
diff --git a/fuzzing/BUILD b/fuzzing/BUILD
index d6c3110..55aeb13 100644
--- a/fuzzing/BUILD
+++ b/fuzzing/BUILD
@@ -27,6 +27,7 @@
         "none",
         "libfuzzer",
         "honggfuzz",
+        "oss-fuzz",
     ],
     visibility = ["//visibility:public"],
 )
diff --git a/fuzzing/instrum_opts.bzl b/fuzzing/instrum_opts.bzl
index c658b06..b2c16d5 100644
--- a/fuzzing/instrum_opts.bzl
+++ b/fuzzing/instrum_opts.bzl
@@ -24,12 +24,17 @@
     "instrum_defaults",
     "instrum_opts",
 )
+load(
+    "@rules_fuzzing_oss_fuzz//:instrum.bzl",
+    "oss_fuzz_opts",
+)
 
 # Fuzz test binary instrumentation configurations.
 instrum_configs = {
     "none": instrum_opts.make(),
     "libfuzzer": instrum_defaults.libfuzzer,
     "honggfuzz": instrum_defaults.honggfuzz,
+    "oss-fuzz": oss_fuzz_opts,
 }
 
 # Sanitizer configurations.
diff --git a/fuzzing/private/fuzz_test.bzl b/fuzzing/private/fuzz_test.bzl
index a9cd5ad..8a9d3f8 100644
--- a/fuzzing/private/fuzz_test.bzl
+++ b/fuzzing/private/fuzz_test.bzl
@@ -17,6 +17,7 @@
 load("@rules_cc//cc:defs.bzl", "cc_test")
 load("//fuzzing/private:common.bzl", "fuzzing_corpus", "fuzzing_dictionary", "fuzzing_launcher")
 load("//fuzzing/private:binary.bzl", "fuzzing_binary")
+load("//fuzzing/private/oss_fuzz:package.bzl", "oss_fuzz_package")
 
 def cc_fuzz_test(
         name,
@@ -96,3 +97,9 @@
         # this attribute must be set.
         testonly = True,
     )
+
+    oss_fuzz_package(
+        name = name + "_oss_fuzz",
+        binary = name,
+        testonly = True,
+    )
diff --git a/fuzzing/private/oss_fuzz/BUILD b/fuzzing/private/oss_fuzz/BUILD
new file mode 100644
index 0000000..670e678
--- /dev/null
+++ b/fuzzing/private/oss_fuzz/BUILD
@@ -0,0 +1,17 @@
+# Copyright 2020 Google LLC
+#
+# 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
+#
+#    https://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.
+
+exports_files([
+    "package.bzl",
+])
diff --git a/fuzzing/private/oss_fuzz/BUILD.tpl b/fuzzing/private/oss_fuzz/BUILD.tpl
new file mode 100644
index 0000000..901afdd
--- /dev/null
+++ b/fuzzing/private/oss_fuzz/BUILD.tpl
@@ -0,0 +1,34 @@
+# Copyright 2020 Google LLC
+#
+# 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
+#
+#    https://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("@rules_fuzzing//fuzzing:cc_deps.bzl", "cc_fuzzing_engine")
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+cc_fuzzing_engine(
+    name = "oss_fuzz_engine",
+    display_name = "OSS-Fuzz",
+    launcher = "oss_fuzz_launcher.sh",
+    library = ":oss_fuzz_stub",
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "oss_fuzz_stub",
+    srcs = [%{stub_srcs}],
+    linkopts = [%{stub_linkopts}],
+)
+
+exports_files([
+    "instrum.bzl",
+])
\ No newline at end of file
diff --git a/fuzzing/private/oss_fuzz/instrum.bzl.tpl b/fuzzing/private/oss_fuzz/instrum.bzl.tpl
new file mode 100644
index 0000000..04f1e54
--- /dev/null
+++ b/fuzzing/private/oss_fuzz/instrum.bzl.tpl
@@ -0,0 +1,22 @@
+# Copyright 2020 Google LLC
+#
+# 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
+#
+#    https://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.
+
+"""Instrumentation options for OSS-Fuzz."""
+
+load("@rules_fuzzing//fuzzing/private:instrum_opts.bzl", "instrum_opts")
+
+oss_fuzz_opts = instrum_opts.make(
+    conlyopts = [%{conlyopts}],
+    cxxopts = [%{cxxopts}],
+)
diff --git a/fuzzing/private/oss_fuzz/package.bzl b/fuzzing/private/oss_fuzz/package.bzl
new file mode 100644
index 0000000..24cdcf7
--- /dev/null
+++ b/fuzzing/private/oss_fuzz/package.bzl
@@ -0,0 +1,71 @@
+# Copyright 2020 Google LLC
+#
+# 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
+#
+#    https://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.
+
+"""Rule for packaging fuzz tests in the expected OSS-Fuzz format."""
+
+load("//fuzzing/private:binary.bzl", "CcFuzzingBinaryInfo")
+
+def _oss_fuzz_package_impl(ctx):
+    output_archive = ctx.actions.declare_file(ctx.label.name + ".tar")
+    binary_info = ctx.attr.binary[CcFuzzingBinaryInfo]
+
+    action_inputs = [binary_info.binary_file]
+    if binary_info.corpus_dir:
+        action_inputs.append(binary_info.corpus_dir)
+    if binary_info.dictionary_file:
+        action_inputs.append(binary_info.dictionary_file)
+    ctx.actions.run_shell(
+        outputs = [output_archive],
+        inputs = action_inputs,
+        command = """
+            declare -r STAGING_DIR="$(pwd)/{output}.staging"
+            mkdir "$STAGING_DIR"
+            ln -s "$(pwd)/{binary_path}" "$STAGING_DIR/{base_name}"
+            if [[ -n "{corpus_dir}" ]]; then
+                pushd "{corpus_dir}" >/dev/null
+                zip --quiet -r "$STAGING_DIR/{base_name}_seed_corpus.zip" ./*
+                popd >/dev/null
+            fi
+            if [[ -n "{dictionary_path}" ]]; then
+                ln -s "$(pwd)/{dictionary_path}" "$STAGING_DIR/{base_name}.dict"
+            fi
+            tar -chf "{output}" -C "$STAGING_DIR" .
+        """.format(
+            base_name = ctx.executable.binary.basename,
+            binary_path = binary_info.binary_file.path,
+            corpus_dir = binary_info.corpus_dir.path if binary_info.corpus_dir else "",
+            dictionary_path = binary_info.dictionary_file.path if binary_info.dictionary_file else "",
+            output = output_archive.path,
+        ),
+    )
+    return [DefaultInfo(files = depset([output_archive]))]
+
+oss_fuzz_package = rule(
+    implementation = _oss_fuzz_package_impl,
+    doc = """
+Packages a fuzz test in a TAR archive compatible with the OSS-Fuzz format.
+
+> NOTE: The current implementation does not yet support packaging the
+> binary runfiles.
+""",
+    attrs = {
+        "binary": attr.label(
+            executable = True,
+            doc = "The fuzz test executable.",
+            providers = [CcFuzzingBinaryInfo],
+            mandatory = True,
+            cfg = "target",
+        ),
+    },
+)
diff --git a/fuzzing/private/oss_fuzz/repository.bzl b/fuzzing/private/oss_fuzz/repository.bzl
new file mode 100644
index 0000000..6713bfe
--- /dev/null
+++ b/fuzzing/private/oss_fuzz/repository.bzl
@@ -0,0 +1,112 @@
+# Copyright 2020 Google LLC
+#
+# 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
+#
+#    https://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.
+
+"""Repository rule for configuring the OSS-Fuzz engine and instrumentation."""
+
+def _to_list_repr(elements):
+    return ", ".join([repr(element) for element in elements])
+
+def _extract_build_params(
+        repository_ctx,
+        fuzzing_engine_library,
+        cflags,
+        cxxflags):
+    stub_srcs = []
+    stub_linkopts = []
+    instrum_conlyopts = []
+    instrum_cxxopts = []
+
+    if fuzzing_engine_library:
+        if fuzzing_engine_library.startswith("-"):
+            # This is actually a flag, add it to the linker flags.
+            stub_linkopts.append(fuzzing_engine_library)
+        elif fuzzing_engine_library.endswith(".a"):
+            repository_ctx.symlink(
+                repository_ctx.path(fuzzing_engine_library),
+                "oss_fuzz_engine.a",
+            )
+            stub_srcs.append("oss_fuzz_engine.a")
+        else:
+            fail("Unsupported $LIB_FUZZING_ENGINE value '%s'" % fuzzing_engine_library)
+    for cflag in cflags:
+        # Skip the fuzzing build more flag, since it is separately controlled
+        # by the --//fuzzing:cc_fuzzing_build_mode configuration flag.
+        if cflag == "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION":
+            continue
+        instrum_conlyopts.append(cflag)
+        if cflag not in stub_linkopts:
+            stub_linkopts.append(cflag)
+    for cxxflag in cxxflags:
+        if cxxflag == "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION":
+            continue
+        instrum_cxxopts.append(cxxflag)
+        if cxxflag not in stub_linkopts:
+            stub_linkopts.append(cxxflag)
+
+    return struct(
+        stub_srcs = stub_srcs,
+        stub_linkopts = stub_linkopts,
+        instrum_conlyopts = instrum_conlyopts,
+        instrum_cxxopts = instrum_cxxopts,
+    )
+
+def _oss_fuzz_repository(repository_ctx):
+    environ = repository_ctx.os.environ
+    fuzzing_engine_library = environ.get("LIB_FUZZING_ENGINE")
+    cflags = environ.get("CFLAGS", "").split(" ")
+    cxxflags = environ.get("CXXFLAGS", "").split(" ")
+
+    build_params = _extract_build_params(
+        repository_ctx,
+        fuzzing_engine_library,
+        cflags,
+        cxxflags,
+    )
+
+    repository_ctx.template(
+        "BUILD",
+        repository_ctx.path(Label("@rules_fuzzing//fuzzing/private/oss_fuzz:BUILD.tpl")),
+        {
+            "%{stub_srcs}": _to_list_repr(build_params.stub_srcs),
+            "%{stub_linkopts}": _to_list_repr(build_params.stub_linkopts),
+        },
+    )
+    repository_ctx.template(
+        "instrum.bzl",
+        repository_ctx.path(Label("@rules_fuzzing//fuzzing/private/oss_fuzz:instrum.bzl.tpl")),
+        {
+            "%{conlyopts}": _to_list_repr(build_params.instrum_conlyopts),
+            "%{cxxopts}": _to_list_repr(build_params.instrum_cxxopts),
+        },
+    )
+    repository_ctx.file(
+        "oss_fuzz_launcher.sh",
+        "echo 'The OSS-Fuzz engine is not meant to be executed.'; exit 1",
+    )
+
+oss_fuzz_repository = repository_rule(
+    implementation = _oss_fuzz_repository,
+    environ = [
+        "LIB_FUZZING_ENGINE",
+        "CFLAGS",
+        "CXXFLAGS",
+        "SANITIZER",
+    ],
+    local = True,
+    doc = """
+Generates a repository containing an OSS-Fuzz fuzzing engine defintion.
+
+The fuzzing engine is defined in the //:oss_fuzz_engine target.
+""",
+)
diff --git a/fuzzing/repositories.bzl b/fuzzing/repositories.bzl
index cf7db2b..d897f5e 100644
--- a/fuzzing/repositories.bzl
+++ b/fuzzing/repositories.bzl
@@ -16,6 +16,7 @@
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//fuzzing/private/oss_fuzz:repository.bzl", "oss_fuzz_repository")
 
 def rules_fuzzing_dependencies():
     """Instantiates the dependencies of the fuzzing rules."""
@@ -56,3 +57,7 @@
         url = "https://github.com/google/honggfuzz/archive/e0670137531242d66c9cf8a6dee677c055a8aacb.zip",
         strip_prefix = "honggfuzz-e0670137531242d66c9cf8a6dee677c055a8aacb",
     )
+
+    oss_fuzz_repository(
+        name = "rules_fuzzing_oss_fuzz",
+    )