diff --git a/README.md b/README.md
index c9f01f2d5..eaf4be9e8 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ This project defines core build rules for [Scala](https://www.scala-lang.org/) t
* [scalapb_proto_library](docs/scalapb_proto_library.md)
* [scala_toolchain](docs/scala_toolchain.md)
* [scala_import](docs/scala_import.md)
+* [scala_doc](docs/scala_doc.md)
## Getting started
diff --git a/docs/scala_doc.md b/docs/scala_doc.md
new file mode 100644
index 000000000..5a7fe34cc
--- /dev/null
+++ b/docs/scala_doc.md
@@ -0,0 +1,48 @@
+# scala_doc
+
+```python
+scala_binary(
+ name,
+ deps,
+)
+```
+
+`scala_doc` generates [Scaladoc](https://docs.scala-lang.org/style/scaladoc.html) for sources
+for targets, including sources from upstream deps. Readily hostable HTML is written to a `name.html` output folder.
+
+Scaladoc can be somewhat slow to build. In that case, you can tell Bazel to build this target manually,
+i.e. only when named explicitly and not through wildcards: `tags = ["manual"]`.
+
+## Example
+
+```python
+scala_doc(
+ name = "scala_docs",
+ tags = ["manual"],
+ deps = [
+ ":target1",
+ ":target2",
+ ":anothertarget",
+ ],
+ scalacopts = [
+ "-Ypartial-unification",
+ "-Ywarn-unused-import",
+ ],
+)
+
+# Use pkg_tar to tarball up
+# https://docs.bazel.build/versions/master/be/pkg.html#pkg_tar
+pkg_tar(
+ name = "scala_docs_archive",
+ srcs = [":scala_docs"],
+ extension = "tar.gz",
+)
+```
+
+## Attributes
+
+| Attribute name | Description |
+| --------------------- | ----------------------------------------------------- |
+| name | `Name, required`
A unique name for this target.
+| deps | `List of labels, optional`
Labels for which you want to create scaladoc.
+| scalacopts | `List of strings, optional`
Extra compiler options for this library to be passed to scalac.
\ No newline at end of file
diff --git a/scala/private/common.bzl b/scala/private/common.bzl
index 2cea9ffd3..4fcabc2f6 100644
--- a/scala/private/common.bzl
+++ b/scala/private/common.bzl
@@ -36,6 +36,23 @@ def collect_jars(
else:
return _collect_jars_when_dependency_analyzer_is_on(dep_targets)
+def collect_plugin_paths(plugins):
+ """Get the actual jar paths of plugins as a depset."""
+ paths = []
+ for p in plugins:
+ if hasattr(p, "path"):
+ paths.append(p)
+ elif hasattr(p, "scala"):
+ paths.extend([j.class_jar for j in p.scala.outputs.jars])
+ elif hasattr(p, "java"):
+ paths.extend([j.class_jar for j in p.java.outputs.jars])
+ # support http_file pointed at a jar. http_jar uses ijar,
+ # which breaks scala macros
+
+ elif hasattr(p, "files"):
+ paths.extend([f for f in p.files if not_sources_jar(f.basename)])
+ return depset(paths)
+
def _collect_jars_when_dependency_analyzer_is_off(
dep_targets,
unused_dependency_checker_is_off,
diff --git a/scala/private/rule_impls.bzl b/scala/private/rule_impls.bzl
index 0d2c4ce8c..216dd97b2 100644
--- a/scala/private/rule_impls.bzl
+++ b/scala/private/rule_impls.bzl
@@ -26,6 +26,7 @@ load(
":common.bzl",
"add_labels_of_jars_to",
"collect_jars",
+ "collect_plugin_paths",
"collect_srcjars",
"create_java_provider",
"not_sources_jar",
@@ -127,22 +128,6 @@ touch {statsfile}
arguments = [],
)
-def _collect_plugin_paths(plugins):
- paths = []
- for p in plugins:
- if hasattr(p, "path"):
- paths.append(p)
- elif hasattr(p, "scala"):
- paths.extend([j.class_jar for j in p.scala.outputs.jars])
- elif hasattr(p, "java"):
- paths.extend([j.class_jar for j in p.java.outputs.jars])
- # support http_file pointed at a jar. http_jar uses ijar,
- # which breaks scala macros
-
- elif hasattr(p, "files"):
- paths.extend([f for f in p.files if not_sources_jar(f.basename)])
- return depset(paths)
-
def _expand_location(ctx, flags):
return [ctx.expand_location(f, ctx.attr.data) for f in flags]
@@ -172,7 +157,7 @@ def compile_scala(
unused_dependency_checker_mode = "off",
unused_dependency_checker_ignored_targets = []):
# look for any plugins:
- plugins = _collect_plugin_paths(plugins)
+ plugins = collect_plugin_paths(plugins)
internal_plugin_jars = []
dependency_analyzer_mode = "off"
compiler_classpath_jars = cjars
diff --git a/scala/scala.bzl b/scala/scala.bzl
index 375fa0d1c..f85b5dd78 100644
--- a/scala/scala.bzl
+++ b/scala/scala.bzl
@@ -32,6 +32,10 @@ load(
"@io_bazel_rules_scala//scala:plusone.bzl",
_collect_plus_one_deps_aspect = "collect_plus_one_deps_aspect",
)
+load(
+ "@io_bazel_rules_scala//scala:scala_doc.bzl",
+ _scala_doc = "scala_doc",
+)
_launcher_template = {
"_java_stub_template": attr.label(
@@ -700,3 +704,5 @@ def scala_specs2_junit_test(name, **kwargs):
suite_class = "io.bazel.rulesscala.specs2.Specs2DiscoveredTestSuite",
**kwargs
)
+
+scala_doc = _scala_doc
\ No newline at end of file
diff --git a/scala/scala_doc.bzl b/scala/scala_doc.bzl
new file mode 100644
index 000000000..2aed41c78
--- /dev/null
+++ b/scala/scala_doc.bzl
@@ -0,0 +1,103 @@
+"""Scaladoc support"""
+
+load("@io_bazel_rules_scala//scala/private:common.bzl", "collect_plugin_paths")
+
+_ScaladocAspectInfo = provider(fields = [
+ "src_files",
+ "compile_jars",
+ "plugins",
+])
+
+def _scaladoc_aspect_impl(target, ctx):
+ """Collect source files and compile_jars from JavaInfo-returning deps."""
+
+ # We really only care about visited targets with srcs, so only look at those.
+ if hasattr(ctx.rule.attr, "srcs"):
+ # Collect only Java and Scala sources enumerated in visited targets, including src_files in deps.
+ src_files = depset(
+ direct = [file for file in ctx.rule.files.srcs if file.extension.lower() in ["java", "scala"]],
+ transitive = [dep[_ScaladocAspectInfo].src_files for dep in ctx.rule.attr.deps if _ScaladocAspectInfo in dep],
+ )
+
+ # Collect compile_jars from visited targets' deps.
+ compile_jars = depset(
+ direct = [file for file in ctx.rule.files.deps],
+ transitive = (
+ [dep[JavaInfo].compile_jars for dep in ctx.rule.attr.deps if JavaInfo in dep] +
+ [dep[_ScaladocAspectInfo].compile_jars for dep in ctx.rule.attr.deps if _ScaladocAspectInfo in dep]
+ ),
+ )
+
+ plugins = depset()
+ if hasattr(ctx.rule.attr, "plugins"):
+ plugins = depset(direct = ctx.rule.attr.plugins)
+
+ return [_ScaladocAspectInfo(
+ src_files = src_files,
+ compile_jars = compile_jars,
+ plugins = plugins,
+ )]
+ else:
+ return []
+
+_scaladoc_aspect = aspect(
+ implementation = _scaladoc_aspect_impl,
+ attr_aspects = ["deps"],
+ required_aspect_providers = [
+ [JavaInfo],
+ ],
+)
+
+def _scala_doc_impl(ctx):
+ # scaladoc warns if you don't have the output directory already created, which is annoying.
+ output_path = ctx.actions.declare_directory("{}.html".format(ctx.attr.name))
+
+ # Collect all source files and compile_jars to pass to scaladoc by way of an aspect.
+ src_files = depset(transitive = [dep[_ScaladocAspectInfo].src_files for dep in ctx.attr.deps])
+ compile_jars = depset(transitive = [dep[_ScaladocAspectInfo].compile_jars for dep in ctx.attr.deps])
+
+ # Get the 'real' paths to the plugin jars.
+ plugins = collect_plugin_paths(depset(transitive = [dep[_ScaladocAspectInfo].plugins for dep in ctx.attr.deps]))
+
+ # Construct the full classpath depset since we need to add compiler plugins too.
+ classpath = depset(transitive = [plugins, compile_jars])
+
+ # Construct scaladoc args, which also include scalac args.
+ # See `scaladoc -help` for more information.
+ args = ctx.actions.args()
+ args.add("-usejavacp")
+ args.add("-nowarn") # turn off warnings for now since they can obscure actual errors for large scala_doc targets
+ args.add_all(ctx.attr.scalacopts)
+ args.add("-d", output_path.path)
+ args.add_all(plugins, format_each = "-Xplugin:%s")
+ args.add_joined("-classpath", classpath, join_with = ctx.configuration.host_path_separator)
+ args.add_all(src_files)
+
+ # Run the scaladoc tool!
+ ctx.actions.run(
+ inputs = depset(transitive = [src_files, classpath]),
+ outputs = [output_path],
+ executable = ctx.attr._scaladoc.files_to_run.executable,
+ mnemonic = "ScalaDoc",
+ progress_message = "scaladoc {}".format(ctx.label),
+ arguments = [args],
+ )
+
+ return [DefaultInfo(files = depset(direct = [output_path]))]
+
+scala_doc = rule(
+ attrs = {
+ "deps": attr.label_list(
+ aspects = [_scaladoc_aspect],
+ providers = [JavaInfo],
+ ),
+ "scalacopts": attr.string_list(),
+ "_scaladoc": attr.label(
+ cfg = "host",
+ executable = True,
+ default = Label("//src/scala/io/bazel/rules_scala/scaladoc_support:scaladoc_generator"),
+ ),
+ },
+ doc = "Generate Scaladoc HTML documentation for source files in from the given dependencies.",
+ implementation = _scala_doc_impl,
+)
diff --git a/src/scala/io/bazel/rules_scala/scaladoc_support/BUILD b/src/scala/io/bazel/rules_scala/scaladoc_support/BUILD
new file mode 100644
index 000000000..b48ad5fb4
--- /dev/null
+++ b/src/scala/io/bazel/rules_scala/scaladoc_support/BUILD
@@ -0,0 +1,17 @@
+load("//scala:scala.bzl", "scala_binary")
+
+# A simple scala_binary to run scaladoc.
+# `bazel run` this target with "-help" as a param for usage text:
+# bazel run -- "//src/scala/io/bazel/rules_scala/scaladoc_support:scaladoc_generator" -help
+scala_binary(
+ name = "scaladoc_generator",
+ main_class = "scala.tools.nsc.ScalaDoc",
+ visibility = ["//visibility:public"],
+ runtime_deps = [
+ "//external:io_bazel_rules_scala/dependency/scala/parser_combinators",
+ "//external:io_bazel_rules_scala/dependency/scala/scala_compiler",
+ "//external:io_bazel_rules_scala/dependency/scala/scala_library",
+ "//external:io_bazel_rules_scala/dependency/scala/scala_reflect",
+ "//external:io_bazel_rules_scala/dependency/scala/scala_xml",
+ ],
+)
diff --git a/test/BUILD b/test/BUILD
index 319d20edc..a1be14ae4 100644
--- a/test/BUILD
+++ b/test/BUILD
@@ -3,6 +3,7 @@ package(default_testonly = 1)
load(
"//scala:scala.bzl",
"scala_binary",
+ "scala_doc",
"scala_library",
"scala_test",
"scala_macro_library",
@@ -137,6 +138,15 @@ scala_library(
deps = ["ExportOnly"],
)
+scala_doc(
+ name = "ScalaDoc",
+ deps = [
+ ":HelloLib",
+ ":OtherLib",
+ "//test/src/main/scala/scalarules/test/compiler_plugin", # brings kind-projector compiler plugin with it
+ ]
+)
+
scala_library(
name = "UnusedLib",
srcs = ["UnusedLib.scala"]
diff --git a/test/src/main/scala/scalarules/test/compiler_plugin/BUILD.bazel b/test/src/main/scala/scalarules/test/compiler_plugin/BUILD.bazel
index 368ae414c..53db39a9b 100644
--- a/test/src/main/scala/scalarules/test/compiler_plugin/BUILD.bazel
+++ b/test/src/main/scala/scalarules/test/compiler_plugin/BUILD.bazel
@@ -2,6 +2,7 @@ load("//scala:scala.bzl", "scala_library")
scala_library(
name = "compiler_plugin",
- srcs = [ "KindProjected.scala" ],
- plugins = ["@org_spire_math_kind_projector//jar"]
-)
\ No newline at end of file
+ srcs = ["KindProjected.scala"],
+ plugins = ["@org_spire_math_kind_projector//jar"],
+ visibility = ["//visibility:public"],
+)