Spring Boot: Document getting started docs
This is to track the status quo while I'm testing various spring boot guides against mill, see what's working and any possible issues/difficulties in setting up.
I'll accumulate all my findings in this issue and then we can create a spring boot guide focused on mill in the docs.
For potential issues/fixes/missing features I'll open different issues
Spring Initializr
- Generate a sample from https://start.spring.io/ or using IntelliJ spring boot initializr plugin
- Run
./mill init
What's working
./mill runworks- Test trait is created, but neither
./mill testor./mill __.testwork, a test target doesn't seem to exist
trait test extends MavenTests {
def mvnDeps =
Seq(mvn"org.springframework.boot:spring-boot-starter-test:3.5.6")
def testSandboxWorkingDir = false
def testParallelism = false
def forkWorkingDir = moduleDir
}
On first look it seems the test framework cannot be detected (or maybe the maven migration does not support it?)
changing the code to
object test extends MavenTests with Junit4 {
def mvnDeps =
Seq(mvn"org.springframework.boot:spring-boot-starter-test:3.5.6")
def testSandboxWorkingDir = false
def testParallelism = false
def forkWorkingDir = moduleDir
}
does not crash, but the tests are not detected.
Working configuration needs junit platform version. The below works properly
object test extends MavenTests, Junit5 {
override def junitPlatformVersion: T[String] = "6.0.0"
def mvnDeps =
Seq(mvn"org.springframework.boot:spring-boot-starter-test:3.5.6")
def testSandboxWorkingDir = false
def testParallelism = false
def forkWorkingDir = moduleDir
}
Setting up Spring boot with Kotlin
This one fails to run with:
./mill run
[59/59, 1 failed] ============================== run ==============================
1 tasks failed
[58] finalMainClass No main class specified or found
The reason for the failure is due to the init not configuring the module as KotlinMavenModule.
Setting it though results in a different error
[68] Compiling 1 Kotlin sources to /home/vnicolaou/Downloads/demo-kotlin/out/compile.dest/classes ...
[68] /home/vnicolaou/Downloads/demo-kotlin/src/main/kotlin/com/example/demo_kotlin/DemoKotlinApplication.kt:10:2: error: cannot inline bytecode built with JVM target 17 into bytecode that is being built with JVM target 1.8. Please specify proper '-jvm-target' option
[68] runApplication<DemoKotlinApplication>(*args)
[68] ^
[75/75, 1 failed] ============================== run ============================== 2s
1 tasks failed
[68] compile Kotlin compiler failed with exit code 1 (COMPILATION_ERROR)
Adding
override def kotlincOptions: T[Seq[String]] = super.kotlincOptions() ++ Seq("-jvm-target", "17")
works
Tests
After the main module fixes and setting tests with:
object test extends KotlinMavenTests, Junit5 {
def mvnDeps = Seq(
mvn"org.jetbrains.kotlin:kotlin-test-junit5:1.9.25",
mvn"org.springframework.boot:spring-boot-starter-test:3.5.6"
)
def testSandboxWorkingDir = false
def testParallelism = false
def forkWorkingDir = moduleDir
}
The tests fail with
org.junit.platform.commons.JUnitException: OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path.
This seems to not crash by changing to JUnit4 but no tests are discovered
The test configuration that works is
object test extends KotlinMavenTests, Junit5 {
override def junitPlatformVersion: T[String] = "6.0.0"
def mvnDeps = Seq(
mvn"org.jetbrains.kotlin:kotlin-test-junit5:1.9.25",
mvn"org.springframework.boot:spring-boot-starter-test:3.5.6"
)
def testSandboxWorkingDir = false
def testParallelism = false
def forkWorkingDir = moduleDir
}
I think I can start by documenting these 2, I'm thinking creating a spring boot section in the docs and going at it with increasing complexity, what do you think @lihaoyi ?
It also doesn't seem very difficult implementing this in the initialzr project for mill and sending a PR, so the init + migration won't be needed once that is ready
Thank you @vaslabs for these feedback. We currently document spring-related modules under the "{Language} Web Project Examples" section, Where we also cover other frameworks like Micronaut or Ktor. But having a more explicit navigation section for "frameworks" and dedicated pages for each framework might be indeed a better fit. Esp. since frameworks like Spring aren't dedicated to just Java but work with all supported languages: Java, Scala, Kotlin and later Groovy.
Of course, landing a PR to Spring Initializr would be the best. I don't think we want guide users to generate projects for different build tools and then require them to migrate to Mill. While we should try to make it work, there is probably no reasonable ratio between effort and result.
thanks for the heads up, I did some exploration today, I'll aim for the initializr PR first then, I guess if it gets accepted, no point doing the docs twice
Don't know, how common it is, but Mill currently does not support creating WARs out of the box, but Spring Initializr does have an option to select "JAR" or "WAR".
Don't know, how common it is, but Mill currently does not support creating WARs out of the box, but Spring Initializr does have an option to select "JAR" or "WAR".
I'll give that a go as well
I think I can start by documenting these 2, I'm thinking creating a spring boot section in the docs and going at it with increasing complexity, what do you think @lihaoyi ?
It also doesn't seem very difficult implementing this in the initialzr project for mill and sending a PR, so the init + migration won't be needed once that is ready
I think this sounds reasonable. 1st we want working examples in Mill, 2nd want to figure out why ./mill init isn't working and if there's anything we can do to make it work, 3rd is to send a PR upstream to Spring Initializr (that may or may not be accepted yet since Mill is still relatively small compared to Maven and Gradle, but we should send the PR anyway)
mill init from gradle project
In contrast with generating a maven project with https://start.spring.io/ , initialising a gradle one has one additional hiccup for mill init
The gradle effective (meaning the bit we care about to translate in mill) config is
plugins {
java
id("org.springframework.boot") version "3.5.7"
id("io.spring.dependency-management") version "1.1.7"
}
...
dependencies {
implementation("org.springframework.boot:spring-boot-starter-websocket")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
Notice in dependencies that they have no versions, which is probably injected via a bom file by the declared spring boot plugin
So after init with mill 1.0.6 we have
//| mill-version: 1.0.6
package build
import mill._
import mill.javalib._
import mill.javalib.publish._
object `package` extends MavenModule {
def artifactName = "websocketdemo"
def javacOptions = Seq("-parameters")
def mvnDeps = Seq(mvn"org.springframework.boot:spring-boot-starter-websocket")
def publishVersion = "0.0.1-SNAPSHOT"
trait test extends MavenTests {
def mvnDeps = Seq(mvn"org.springframework.boot:spring-boot-starter-test")
def testSandboxWorkingDir = false
def testParallelism = false
}
}
which has the issues previously stated + the missing version
With the maven project, the version detection worked with it having
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
~The main change is ready, but I still try to figure out why some tests fail in CI.~
Edit: wrong issue
another issue I've found is while mill cli works properly for compile and test
Mill version SNAPSHOT is different than configured for this directory!
Configured version is 1.0.6 (/home/vnicolaou/mill/example/springboot/java/2-web-socket-initializr/build.mill)
[61/64] compile
[61] [info] compiling 3 Java sources to /home/vnicolaou/mill/example/springboot/java/2-web-socket-initializr/out/compile.dest/classes ...
[61] [info] done compiling
[64/64] test.compile
[64] [info] compiling 1 Java source to /home/vnicolaou/mill/example/springboot/java/2-web-socket-initializr/out/test/compile.dest/classes ...
[64] [info] done compiling
[64/64] ============================== test.compile ==============================
, running the main class from the IDE while the tests are not compiling, fails. perhaps some bsp wiring gets both directories for src/main and src/test
Would be good to detail the Spring Gradle / Maven plugin equivalents. Does AOT or native mode just work? AOT has some pre-processors that generate bytecode as a build step.
By "AOT or native mode", you mean "Ahead of Time Optimizations" as documented here: https://docs.spring.io/spring-framework/reference/core/aot.html ?
By "AOT or native mode", you mean "Ahead of Time Optimizations" as documented here: https://docs.spring.io/spring-framework/reference/core/aot.html ?
Yes, you can enable it with spring.aot.enabled flag to true even if not building a GraalVM native image. It reduces memory, improves startup time and overall performance e.g. by not requiring reflection on IoC. It's not so clear from the linked article but there definitely is a build step that gets run and is embedded in the plugins.
Yes, you can enable it with
spring.aot.enabledflag totrueeven if not building a GraalVM native image.
You mean mvn spring-boot:process-aot package and then running with java -Dspring.aot.enabled=true -jar <jar-name>. We currently don't have this in Mill. We already have a SpringBootModule which holds the spring boot tools and provided the repackage functionality, so it should be possible to add easily. But I don't know, what really happens under the hood, most likely some annotation processing.
Yes, you can enable it with
spring.aot.enabledflag totrueeven if not building a GraalVM native image.You mean
mvn spring-boot:process-aot packageand then running withjava -Dspring.aot.enabled=true -jar <jar-name>. We currently don't have this in Mill. We already have aSpringBootModulewhich holds the spring boot tools and provided therepackagefunctionality, so it should be possible to add easily. But I don't know, what really happens under the hood, most likely some annotation processing.
https://github.com/spring-projects/spring-boot/blob/main/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java org.springframework.boot.SpringApplicationAotProcessor gets run as part of a build step that does this.
Yes, you can enable it with
spring.aot.enabledflag totrueeven if not building a GraalVM native image.You mean
mvn spring-boot:process-aot packageand then running withjava -Dspring.aot.enabled=true -jar <jar-name>. We currently don't have this in Mill. We already have aSpringBootModulewhich holds the spring boot tools and provided therepackagefunctionality, so it should be possible to add easily. But I don't know, what really happens under the hood, most likely some annotation processing.https://github.com/spring-projects/spring-boot/blob/main/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java
org.springframework.boot.SpringApplicationAotProcessorgets run as part of a build step that does this.
Thank you for this, created an issue, I can start working on it early next week.
Probably we can also look at/test graalvm support separately
started the work here