Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ErrorProne plugin #3460

Merged
merged 12 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,10 @@ object Deps {
val sonatypeCentralClient = ivy"com.lumidion::sonatype-central-client-requests:0.3.0"

object RuntimeDeps {
val sbtTestInterface = ivy"com.github.sbt:junit-interface:0.13.2"
val errorProneCore = ivy"com.google.errorprone:error_prone_core:2.31.0"
val jupiterInterface = ivy"com.github.sbt.junit:jupiter-interface:0.11.4"
def all = Seq(sbtTestInterface, jupiterInterface)
val sbtTestInterface = ivy"com.github.sbt:junit-interface:0.13.2"
def all = Seq(errorProneCore, jupiterInterface, sbtTestInterface)
}

/** Used to manage transitive versions. */
Expand Down Expand Up @@ -730,6 +731,7 @@ object dist0 extends MillPublishJavaModule {
build.contrib.jmh.testDep(),
build.contrib.playlib.testDep(),
build.contrib.playlib.worker("2.8").testDep(),
build.contrib.errorprone.testDep(),
build.bsp.worker.testDep(),
build.testkit.testDep()
)
Expand Down
31 changes: 31 additions & 0 deletions contrib/errorprone/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
= Mill ErrorProne Plugin
:page-aliases: Plugin_ErrorProne.adoc

https://errorprone.info/index[Error Prone] augments the Java compiler's type checker and detect common mistakes at compile time.

You just need to mix the `ErrorProneModule` into your `JavaModule` and it will automatically run with every compilation.

.`build.mill.sc`: Enable `ErrorProne` in a module
[source,scala]
----
package build
import mill._, scalalib._
import $ivy.`com.lihaoyi::mill-contrib-errorprone:`
import mill.contrib.errorprone.ErrorProneModule
object foo extends JavaModule with ErrorProneModule {
}
----

== Configuration

The following configuration options exist:

`def errorProneVersion: T[String]`::
The `error-prone` version to use. Defaults to [[BuildInfo.errorProneVersion]], the version used to build and test the module.
Find the latest at https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core[mvnrepository.com]

`def errorProneOptions: T[Seq[String]]`::
Options directly given to the `error-prone` processor.
Those are documented as "flags" at https://errorprone.info/docs/flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package mill.contrib.errorprone

import mill.api.PathRef
import mill.{Agg, T}
import mill.scalalib.{Dep, DepSyntax, JavaModule}

import java.io.File

/**
* Integrated Error Prone into a [[JavaModule]].
*
* See https://errorprone.info/index
*/
trait ErrorProneModule extends JavaModule {

/** The `error-prone` version to use. Defaults to [[BuildInfo.errorProneVersion]]. */
def errorProneVersion: T[String] = T.input {
BuildInfo.errorProneVersion
}

/**
* The dependencies of the `error-prone` compiler plugin.
*/
def errorProneDeps: T[Agg[Dep]] = T {
Agg(
ivy"com.google.errorprone:error_prone_core:${errorProneVersion()}"
)
}

/**
* The classpath of the `error-prone` compiler plugin.
*/
def errorProneClasspath: T[Agg[PathRef]] = T {
resolveDeps(T.task { errorProneDeps().map(bindDependency()) })()
}

/**
* Options used to enable and configure the `eror-prone` plugin in the Java compiler.
*/
def errorProneJavacEnableOptions: T[Seq[String]] = T {
val processorPath = errorProneClasspath().map(_.path).mkString(File.pathSeparator)
val enableOpts = Seq(
"-XDcompilePolicy=simple",
"-processorpath",
processorPath,
(Seq("-Xplugin:ErrorProne") ++ errorProneOptions()).mkString(" ")
)
val java17Options = Option.when(scala.util.Properties.isJavaAtLeast(16))(Seq(
"--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
"--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
"--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED"
).map(o => s"-J${o}")).toSeq.flatten
java17Options ++ enableOpts
}

/**
* Options directly given to the `error-prone` processor.
*
* Those are documented as "flags" at https://errorprone.info/docs/flags
*/
def errorProneOptions: T[Seq[String]] = T { Seq.empty[String] }

/**
* Appends the [[errorProneJavacEnableOptions]] to the Java compiler options.
*/
override def javacOptions: T[Seq[String]] = T {
val supOpts = super.javacOptions()
val enableOpts = Option
.when(!supOpts.exists(o => o.startsWith("-Xplugin:ErrorProne")))(
errorProneJavacEnableOptions()
)
supOpts ++ enableOpts.toSeq.flatten
}
}
16 changes: 16 additions & 0 deletions contrib/errorprone/test/resources/simple/src/ShortSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package simple.src;

import java.util.HashSet;
import java.util.Set;

public class ShortSet {
public static void main (String[] args) {
Set<Short> s = new HashSet<>();
for (short i = 0; i < 100; i++) {
s.add(i);
s.remove(i - 1);
}
System.out.println(s.size());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package mill.contrib.errorprone

import mill.T
import mill.scalalib.JavaModule
import mill.testkit.{TestBaseModule, UnitTester}
import os.Path
import utest._

object ErrorProneTests extends TestSuite {

object noErrorProne extends TestBaseModule with JavaModule {}
object errorProne extends TestBaseModule with JavaModule with ErrorProneModule {}
object errorProneCustom extends TestBaseModule with JavaModule with ErrorProneModule {
override def errorProneOptions: T[Seq[String]] = T(Seq(
"-XepAllErrorsAsWarnings"
))
}

val testModuleSourcesPath: Path = os.Path(sys.env("MILL_TEST_RESOURCE_FOLDER")) / "simple"

def tests = Tests {
test("reference") {
test("compile") {
val eval = UnitTester(noErrorProne, testModuleSourcesPath)
val res = eval(noErrorProne.compile)
assert(res.isRight)
}
}
test("errorprone") {
test("compileFail") {
val eval = UnitTester(errorProne, testModuleSourcesPath)
val res = eval(errorProne.compile)
assert(res.isLeft)
}
test("compileWarn") {
val eval = UnitTester(errorProneCustom, testModuleSourcesPath, debugEnabled = true)
val Right(opts) = eval(errorProneCustom.javacOptions)
assert(opts.value.exists(_.contains("-XepAllErrorsAsWarnings")))
val res = eval(errorProneCustom.compile)
assert(res.isRight)
}
}
}
}
9 changes: 9 additions & 0 deletions contrib/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import mill.resolve.SelectMode
import mill.contrib.buildinfo.BuildInfo
import mill.T
import mill.define.Cross
import build.Deps

// plugins and dependencies
import $meta._
Expand Down Expand Up @@ -226,4 +227,12 @@ object `package` extends RootModule {
def compileModuleDeps = Seq(build.scalalib)
def testModuleDeps = super.testModuleDeps ++ Seq(build.scalalib)
}

object errorprone extends ContribModule with BuildInfo {
def compileModuleDeps = Seq(build.scalalib)
def testModuleDeps = super.testModuleDeps ++ Seq(build.scalalib)
def buildInfoPackageName = "mill.contrib.errorprone"
def buildInfoObjectName = "BuildInfo"
def buildInfoMembers = Seq(BuildInfo.Value("errorProneVersion", Deps.RuntimeDeps.errorProneCore.version))
}
}
3 changes: 3 additions & 0 deletions docs/modules/ROOT/pages/Java_Module_Config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ If you are using millw, a more permanent solution could be to set the environmen

include::example/javalib/module/13-jni.adoc[]

== Using the ErrorProne plugin to detect code problems

include::example/javalib/module/14-error-prone.adoc[]
35 changes: 35 additions & 0 deletions example/javalib/module/14-error-prone/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// When adding the `ErrorPromeModule` to your `JavaModule`,
// the `error-prone` compiler plugin automatically detects various kind of programming errors.

package build
import mill._, javalib._
import mill.contrib.errorprone._

import $ivy.`com.lihaoyi::mill-contrib-errorprone:`

object `package` extends RootModule with JavaModule with ErrorProneModule {
def errorProneOptions = Seq("-XepAllErrorsAsWarnings")
}

/** See Also: src/example/ShortSet.java */

/** Usage

> ./mill show errorProneOptions
[
"-XepAllErrorsAsWarnings"
]

*/
/*

> ./mill compile
error: [warn] .../src/ShortSet.java:11:15: [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
error: [warn] s.remove(i - 1);
error: [warn] ^ (see https://errorprone.info/bugpattern/CollectionIncompatibleType)
error: [warn] 1 warning
error: [warn] ^

*/


16 changes: 16 additions & 0 deletions example/javalib/module/14-error-prone/src/example/ShortSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package example;

import java.util.HashSet;
import java.util.Set;

public class ShortSet {
public static void main (String[] args) {
Set<Short> s = new HashSet<>();
for (short i = 0; i < 100; i++) {
s.add(i);
s.remove(i - 1);
}
System.out.println(s.size());
}
}

20 changes: 16 additions & 4 deletions scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,22 @@ class ZincWorkerImpl(
)(implicit ctx: ZincWorkerApi.Ctx): Result[CompilationResult] = {
os.makeDir.all(ctx.dest)

val classesDir =
if (compileToJar) ctx.dest / "classes.jar"
else ctx.dest / "classes"

if (ctx.log.debugEnabled) {
ctx.log.debug(
s"""Compiling:
| javacOptions: ${javacOptions.map("'" + _ + "'").mkString(" ")}
| scalacOptions: ${scalacOptions.map("'" + _ + "'").mkString(" ")}
| sources: ${sources.map("'" + _ + "'").mkString(" ")}
| classpath: ${compileClasspath.map("'" + _ + "'").mkString(" ")}
| output: ${classesDir}"""
.stripMargin
)
}

reporter.foreach(_.start())

val consoleAppender = ConsoleAppender(
Expand Down Expand Up @@ -549,10 +565,6 @@ class ZincWorkerImpl(

val lookup = MockedLookup(analysisMap)

val classesDir =
if (compileToJar) ctx.dest / "classes.jar"
else ctx.dest / "classes"

val store = fileAnalysisStore(ctx.dest / zincCache)

// Fix jdk classes marked as binary dependencies, see https://github.com/com-lihaoyi/mill/pull/1904
Expand Down
2 changes: 1 addition & 1 deletion testkit/src/mill/testkit/ExampleTester.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import scala.concurrent.duration.FiniteDuration
* 2. Output lines can be prefixed by `error: ` to indicate we expect that
* command to fail.
*
* 3. `..` can be used to indicate wildcards, which match anything. These can
* 3. `...` can be used to indicate wildcards, which match anything. These can
* be used alone as the entire line, or in the middle of another line
*
* 4. Every line of stdout/stderr output by the command must match at least
Expand Down
Loading