From 9bac8442b2e0b232c972cf18fd5ed2f866078021 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Tue, 14 Apr 2026 21:11:14 +0200 Subject: [PATCH] Add sbt-java-formatter-add-opens plugin - Adds alias for the `javafmt[Check][All]` commands - Launches a new `sbt ` session and adds the required `-add-opens` flags From a user perspective, the commands now just work again on Java 17+. No need to set flags. --- README.md | 15 ++++ build.sbt | 88 +++++++++++-------- .../sbt/JavaFormatterWrapperPlugin.scala | 62 +++++++++++++ 3 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 plugin-add-opens/src/main/scala/com/github/sbt/JavaFormatterWrapperPlugin.scala diff --git a/README.md b/README.md index ae515c4..bbd5492 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,36 @@ and this [maven plugin](https://github.com/revelc/formatter-maven-plugin), thoug Add the plugin to `project/plugins.sbt`: ```scala +// Default plugin: addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % --latest version---) + +// Alternative for Java 17+: wraps formatter commands in a fresh sbt JVM, +// so you do not need to configure `--add-opens` manually, see below. +addSbtPlugin("com.github.sbt" % "sbt-java-formatter-add-opens" % --latest version---) ``` For available versions see [releases](https://github.com/sbt/sbt-java-formatter/releases). +The following commands are available: + * `javafmt` formats Java files * `javafmtAll` formats Java files for all configurations (`Compile` and `Test` by default) * `javafmtCheck` fails if files need reformatting * `javafmtCheckAll` fails if files need reformatting in any configuration (`Compile` and `Test` by default) +The `sbt-java-formatter-add-opens` plugin wraps the above commands and, on Java 17+, runs them in a fresh sbt JVM with the required `jdk.compiler` module access flags. From a user perspective, the commands stay the same and no manual JVM flags need to be configured. + * The `javafmtOnCompile` setting controls whether the formatter kicks in on compile (`false` by default). * The `javafmtStyle` setting defines the formatting style: Google Java Style (by default) or AOSP style. This plugin requires sbt 1.3.0+. +## Java 17+ + +`google-java-format` relies on internal `jdk.compiler` APIs. On Java 17 and newer, access to those APIs is strongly encapsulated by the module system. + +If you depend on `sbt-java-formatter-add-opens`, the formatter commands (`javafmt`, `javafmtAll`, `javafmtCheck`, `javafmtCheckAll`) automatically relaunch in a JVM with the required module flags, instead of requiring manual `-J--add-opens=...` setup. + ## Enable in other scopes (eg `IntegrationTest`) The sbt plugin is enabled by default for the `Test` and `Compile` configurations. Use `JavaFormatterPlugin.toBeScopedSettings` to enable the plugin for the `IntegrationTest` scope and then use `It/javafmt` to format. diff --git a/build.sbt b/build.sbt index 4cfa968..4c751c6 100644 --- a/build.sbt +++ b/build.sbt @@ -3,51 +3,63 @@ lazy val scala3 = "3.8.3" ThisBuild / scalaVersion := scala212 ThisBuild / crossScalaVersions := Seq(scala212, scala3) -lazy val sbtJavaFormatter = project.in(file(".")).aggregate(plugin).settings(publish / skip := true) +def commonSettings: Seq[Setting[?]] = Seq( + homepage := scmInfo.value.map(_.browseUrl), + scmInfo := Some( + ScmInfo(url("https://github.com/sbt/sbt-java-formatter"), "scm:git:git@github.com:sbt/sbt-java-formatter.git")), + developers := List( + Developer("ktoso", "Konrad 'ktoso' Malawski", "", url("https://github.com/ktoso"))), + startYear := Some(2015), + description := "Formats Java code in your project.", + licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html")), + (pluginCrossBuild / sbtVersion) := { + scalaBinaryVersion.value match { + case "2.12" => "1.9.0" + case _ => "2.0.0-RC11" + } + }, + scalacOptions ++= { + Vector("-encoding", "UTF-8", "-unchecked", "-deprecation", "-feature") ++ (scalaBinaryVersion.value match { + case "2.12" => Vector("-Xsource:3", "-release:11") + case _ => Vector("-Wconf:error") + }) + }, + javacOptions ++= Seq("-encoding", "UTF-8"), + scriptedLaunchOpts := { + scriptedLaunchOpts.value ++ + Seq("-Xmx1024M", "-Dplugin.version=" + version.value) + }, + scriptedLaunchOpts ++= { + if (scala.util.Properties.isJavaAtLeast("17")) { + Seq("api", "code", "file", "parser", "tree", "util").map { x => + s"--add-exports=jdk.compiler/com.sun.tools.javac.${x}=ALL-UNNAMED" + } + } else { + Nil + } + }, + scriptedBufferLog := false, + scalafmtOnCompile := true) + +lazy val sbtJavaFormatter = + project.in(file(".")).aggregate(plugin).aggregate(`plugin-add-opens`).settings(publish / skip := true) lazy val plugin = project .in(file("plugin")) .enablePlugins(SbtPlugin) .enablePlugins(AutomateHeaderPlugin) + .settings(commonSettings *) .settings( name := "sbt-java-formatter", - homepage := scmInfo.value.map(_.browseUrl), - scmInfo := Some( - ScmInfo(url("https://github.com/sbt/sbt-java-formatter"), "scm:git:git@github.com:sbt/sbt-java-formatter.git")), - developers := List( - Developer("ktoso", "Konrad 'ktoso' Malawski", "", url("https://github.com/ktoso"))), - libraryDependencies ++= Seq("com.google.googlejavaformat" % "google-java-format" % "1.24.0"), - startYear := Some(2015), - description := "Formats Java code in your project.", - licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html")), - (pluginCrossBuild / sbtVersion) := { - scalaBinaryVersion.value match { - case "2.12" => "1.9.0" - case _ => "2.0.0-RC11" - } - }, - scalacOptions ++= { - Vector("-encoding", "UTF-8", "-unchecked", "-deprecation", "-feature") ++ (scalaBinaryVersion.value match { - case "2.12" => Vector("-Xsource:3", "-release:11") - case _ => Vector("-Wconf:error") - }) - }, - javacOptions ++= Seq("-encoding", "UTF-8"), - scriptedLaunchOpts := { - scriptedLaunchOpts.value ++ - Seq("-Xmx1024M", "-Dplugin.version=" + version.value) - }, - scriptedLaunchOpts ++= { - if (scala.util.Properties.isJavaAtLeast("17")) { - Seq("api", "code", "file", "parser", "tree", "util").map { x => - s"--add-exports=jdk.compiler/com.sun.tools.javac.${x}=ALL-UNNAMED" - } - } else { - Nil - } - }, - scriptedBufferLog := false, - scalafmtOnCompile := true) + libraryDependencies ++= Seq("com.google.googlejavaformat" % "google-java-format" % "1.24.0")) + +lazy val `plugin-add-opens` = project + .in(file("plugin-add-opens")) + .enablePlugins(SbtPlugin) + .enablePlugins(AutomateHeaderPlugin) + .settings(commonSettings *) + .settings(name := "sbt-java-formatter-add-opens") + .dependsOn(plugin) ThisBuild / organization := "com.github.sbt" ThisBuild / organizationName := "sbt community" diff --git a/plugin-add-opens/src/main/scala/com/github/sbt/JavaFormatterWrapperPlugin.scala b/plugin-add-opens/src/main/scala/com/github/sbt/JavaFormatterWrapperPlugin.scala new file mode 100644 index 0000000..94224d2 --- /dev/null +++ b/plugin-add-opens/src/main/scala/com/github/sbt/JavaFormatterWrapperPlugin.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2015 sbt community + * + * 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 + * + * http://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. + */ + +package com.github.sbt + +import scala.sys.process.Process + +import sbt._ +import sbt.Keys._ + +//import com.github.sbt.JavaFormatterPlugin + +object JavaFormatterWrapperPlugin extends AutoPlugin { + override def requires = JavaFormatterPlugin + + override def trigger = allRequirements + + override def globalSettings = + Seq(commands += javafmt, commands += javafmtCheck, commands += javafmtAll, commands += javafmtCheckAll) + + private val javafmtWrapperProp = "play.javafmt.wrapper" + + private val javafmtExports = + Seq("api", "code", "file", "parser", "tree", "util").map { exportedPackage => + s"-J--add-opens=jdk.compiler/com.sun.tools.javac.${exportedPackage}=ALL-UNNAMED" + } + + private def javafmtCommand(name: String, delegatedCommand: String): Command = + Command.command( + name, + Help.more( + name, + s"Runs $delegatedCommand in a fresh sbt JVM with the required jdk.compiler module-opening flags")) { state => + if (sys.props.get(javafmtWrapperProp).contains("true")) { + delegatedCommand :: state + } else { + val extracted = Project.extract(state) + val base = extracted.get(ThisBuild / baseDirectory) + val sbtArgs = Seq("sbt", "--server", s"-D$javafmtWrapperProp=true") ++ javafmtExports ++ Seq(name) + val exitCode = Process(sbtArgs, base).! + if (exitCode == 0) state else state.fail + } + } + + private val javafmt = javafmtCommand("javafmt", "javafmt") + private val javafmtCheck = javafmtCommand("javafmtCheck", "javafmtCheck") + private val javafmtAll = javafmtCommand("javafmtAll", "all javafmtAll") + private val javafmtCheckAll = javafmtCommand("javafmtCheckAll", "all javafmtCheckAll") +}