rules_scala icon indicating copy to clipboard operation
rules_scala copied to clipboard

Rule for running onejar on plugins

Open bjchambers opened this issue 4 years ago • 3 comments

If you attempt to use a scalac plugin such as scapegoat or silencer, you'll get the following warning from phase_classpaths:

WARNING! It is slightly inefficient to use a JVM target with dependencies directly as a scalac plugin. Please SingleJar the target before using it as a scalac plugin in order to avoid additional overhead.

[1] https://github.com/higherkindness/rules_scala/blob/master/rules/private/phases/phase_classpaths.bzl#L25

However, there is no available rule that makes it possible to "SingleJar" the target.

I wrote a quick rule that exposes the existing singlejar wrapper from rules_scala, but that feels a bit wrong since needs to load the action_singlejar from private/utils.bzl. It seems like it would make sense to add such a rule to make it easier to package plugins?

load("@rules_scala_annex//rules/common:private/utils.bzl", _action_singlejar = "action_singlejar")

def _scala_plugin_impl(ctx):
    plugin = ctx.attr.plugin
    plugin_runtime_jars = plugin[JavaInfo].transitive_runtime_jars.to_list()

    output_jar = ctx.outputs.plugin_singlejar
    _action_singlejar(
        ctx,
        inputs = plugin_runtime_jars,
        output = output_jar,
        progress_message = "singlejar scalac plugin %s" % plugin.label.name,
    )

    return struct(
        providers = [
            JavaInfo(
                output_jar = output_jar,
                compile_jar = output_jar,
            ),
        ],
    )

scala_plugin = rule(
    implementation = _scala_plugin_impl,
    attrs = {
        "plugin": attr.label(
            allow_single_file = True,
            mandatory = True,
            doc = "The Scalac plugin.",
            providers = [JavaInfo],
        ),
        "_singlejar": attr.label(
            cfg = "host",
            default = "@bazel_tools//tools/jdk:singlejar",
            executable = True,
        ),
    },
    outputs = {
        "plugin_singlejar": "%{name}_singlejar.jar",
    },
)

bjchambers avatar Aug 24 '19 21:08 bjchambers

Hey @bjchambers! Thanks for opening this issue. For plugins that are only referenced directly in one package (like global plugins you specify when configuring the compiler) this shouldn't be an issue (since we only run the singlejar action once either way).

However, if you're specifying a plugin in multiple projects (for instance, directly in the plugins attribute of multiple scala_library/scala_binary targets) then the current approach would singlejar the plugin multiple times. A scala_plugin rule could help in those cases.

Are you seeing the warning multiple times?

SrodriguezO avatar Aug 27 '19 00:08 SrodriguezO

I'm only seeing the warning once, but I theorized that is because I have enabled de-duplicating messages. When I used the above to explicitly run onejar in the top-level BUILD where I was defining the global plugins, the total number of actions executed on a clean build was reduced by 57, which made me suspect that the onejar was actually being produced multiple times.

If this really only happens once, it would be good if there was a way to suppress the warning the first time. Could the code that produces this warning (and runs onejar) detect that is being used as part of setting the global plugins and not log?

bjchambers avatar Aug 27 '19 20:08 bjchambers

scalac expects each plugin to be fully isolated, so we need to smash everything together with singlejar

Really? This contradicts https://github.com/sbt/sbt/issues/2255 which claims that the entire plugin classpath is shared.

pauldraper avatar Oct 14 '19 19:10 pauldraper