diffplug/spotless

Add plugin support for Bazel

jbduncan opened this issue · 14 comments

Bazel is something which has interested me for a while now, so writing a Spotless plugin for it may prove to be a good way to get my feet wet with it. However I'm limited for time with my studies and I suspect I won't be able to work on something this big until late May 2017 or later.

Here's what I currently understand about Bazel with regards to plugin-writing:

  1. Bazel uses a subset of Python called Skylark for writing extensions (Bazel's terminology for plugins AFAICT). So it may very well be that Spotless would need to be ported to Skylark first, which isn't ideal.
  2. There seem to be ways of integrating shell scripts and binaries as extensions into Bazel. I'm hoping that we can use one of these options or some another option to call spotless-lib (or a Java binary wrapper around it) without porting it to Skylark.

Note to self: AFAICT, Bazel only has one build command, so we may have to choose one of spotlessApply and spotlessCheck to use as the plugged-in sub-command, or give up trying to integrate Spotless into Bazel directly and provide a command-line wrapper similarly to google-java-format instead (to allow Bazel users to manually integrate Spotless in whatever way they see fit).

AFAICT, Bazel only has one build command, so we may have to choose one of spotlessApply and spotlessCheck to use as the plugged-in sub-command...

Ah, just to correct myself, I now understand that Bazel has a build command which can build any so-called "target" defined in a BUILD file (equivalent to Gradle's build.gradle file), so it may be possible to write one target for spotlessCheck and another for spotlessApply...

@nedtwigg I've realised that I've not had the time and interest to pursue this further as of late. How would you like me to deal with this issue for now?

Just upload anything you think might be useful to somebody else who might wanna do this 👍

Note for whoever wants to take this on: When naming the Bazel plugin, the Bazel developers recommend following these guidelines for naming it.

Another note for whoever wants to take this on: IIUC, Spotless would need a command-line interface first, such that it could be run from the terminal without needing Gradle or Maven as an intermediary. Only then could Spotless be integrated as a Bazel "plugin" ("rule" being the correct term IIUC).

...or, maybe I'm mistaken about needing a command-line interface! For all I know, Bazel's extension language Starlark could be powerful enough to allow Spotless to be ported. But further research would be needed.

A colleague and I have been thinking about this a lot, and the cleanest way we've found is to start with a binary wrapper (as Jonathan suggested early on in this thread) that takes a config that specifies the same stuff you would in a build.gradle, use a custom Bazel rule to generate the config from attributes passed from the target, then write another Bazel rule calling the executable as an action. The config generation is a bit wonky, but this is still more or less in line with how Bazel was intended to work. We unfortunately did not run across any functionality in Starlark that allows interoperation between it and Java libraries in its extensions. Someone please let me know if they find out I missed something.

Seems to me like implementing said binary wrapper would basically amount to building a command line application, so it seems silly not to expose it as such for any users who want that (even if it's not really a standard use case). Would you guys accept a well written and tested implementation of a command line application for Spotless? It might be a while before I have the time, but we'd be willing to work on such a thing. Let me know and I'll open a ticket for it. Then I'd also be willing to put in some work on this ticket after that is complete.

Would you guys accept a well written and tested implementation of a command line application for Spotless?

Definitely! Some minor points

  • if it requires no deps, can live in lib
  • if it requires a few deps, it can live in lib-extra
  • if it requires a lot of new deps, it should live in a new project called cli (or something)

A broader, structural point is the "current feature matrix" in the root readme, and especially this note about it:

Why are there empty squares? Many projects get harder to work on as they get bigger. Spotless is easier to work on than ever, and one of the reasons why is that we don't require contributors to "fill the matrix". If you want to add Bazel support, we'd happily accept the PR even if it only supports the one formatter you use. And if you want to add FooFormatter support, we'll happily accept the PR even if it only supports the one build system you use.

I am fine with adding a new column for "bazel", or a new column for "CLI", but it's even better if "bazel" piggybacks on "CLI" which somehow piggybacks on either gradle or maven. jbang is a clever project which (ab)uses gradle to smash java into single-file-scripts, might have useful lessons for a similar piggybacking.

Implications of a CLI column

If the CLI gets its own column (which is fine), there are a few questions which have obvious answers in the context of a build-system-plugin (see docs), which are not-at-all-obvious in the context of an independent CLI.

  • "does the CLI define just the formatter function (which steps to apply and how they are configured), or does it define both the formatter function and the files to be formatted". Either is fine, but it's an important distinction to make.
  • how does the CLI download artifacts from a central repository / how is that configured / how are they cached locally

I don't know much about Bazel, but I would imagine that it has some kind of convention / opinion on these two topics. If it does, then the spirit of YAGNI might recommend that this CLI should have a package com.diffplug.spotless.bazel and another package com.diffplug.spotless.cli. It might not be practical for a raw CLI to figure out the questions above without having some opinionated build system (e.g. bazel) to draw certain utilities from. In this approach, the CLI could only actually be run within a Bazel build, but there's the option that a future contributor could leverage the same CLI to add e.g. SCons, CMake or some other non-JVM build system.

Thanks Ned! I've created a ticket for the CLI and I'm going to respond to your last comment there. I know there's some crossover between talking about CLI and Bazel there, but it seems mostly focused around how to design the CLI.

This looks like the kind of thing that we'd hook into https://github.com/apple/apple_rules_lint and expose via https://github.com/bazel-contrib/rules_jvm . Doing so would allow a test target and a "fix" target to be exposed as part of the build.

Can Spotless be run on a subset of the tree? Bazel targets tend to focus on a "per package" level in Java projects, and we'd want to have the same level of granularity here.

Can Spotless be run on a subset of the tree?

spotless-lib lets you take a list of steps and put them together in a Formatter. Then you call PaddedCell.calculateDirtyState(formatter, File input) and it tells you if that file is clean or not, and if it's not if tells you what it should be.

Defining a source tree to run on, up-to-date-checking, how you define the steps, etc. is all a concern of the plugin. spotless-plugin-maven does it one way, spotless-plugin-gradle does it a totally different way. Presumably Bazel has its own way.

If google-java-format issue google/google-java-format#917 ever reaches a positive conclusion, it could make porting Spotless to Bazel easier.