Skip to content

Commit 329658d

Browse files
authored
Add RepackageModule to build assemblies based on Spring Boot tools (#4489)
This adds a `mill.javalib.repackage.RepackageModule` which enhances a `Module` with a `repackagedJar` task to produce self-executable JAR files. If the module is a `JavaModule` everything should work out of the box. Otherwise, the use may need to override various tasks, which is analogue to using `RunModule` or `AssemblyModule`. The Spring-Boot tools are encapsulated into a separate `mill.javalib.spring.boot.SpringBootToolsModule` to avoid instantiating multiple instance. A pre-configured version using the tools version present at built-time is available as `ExternalModule` and used by default. Fix #4479 Pull request: #4489
1 parent 574fa87 commit 329658d

File tree

14 files changed

+468
-4
lines changed

14 files changed

+468
-4
lines changed

build.mill

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ object Deps {
192192
val semanticDBscala = mvn"org.scalameta:::semanticdb-scalac:4.13.4"
193193
val semanticDbJava = mvn"com.sourcegraph:semanticdb-java:0.10.3"
194194
val sourcecode = mvn"com.lihaoyi::sourcecode:0.4.3-M5"
195+
val springBootTools = mvn"org.springframework.boot:spring-boot-loader-tools:3.4.5"
195196
val upickle = mvn"com.lihaoyi::upickle:4.1.0"
196197
// Using "native-terminal-no-ffm" rather than just "native-terminal", as the GraalVM releases currently
197198
// lacks support for FFM on Mac ARM. That should be fixed soon, see oracle/graal#8113.

core/util/src/mill/mill.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ package object mill extends mill.define.JsonFormatters with mill.util.TokenReade
88
type Module = define.Module
99
type Cross[T <: Cross.Module[?]] = define.Cross[T]
1010
val Cross = define.Cross
11+
@deprecated("Use Seq[T] instead", "Mill 0.13.0-M1")
1112
type Agg[T] = Seq[T]
13+
@deprecated("Use Seq instead", "Mill 0.13.0-M1")
1214
val Agg = Seq
1315

1416
type Args = define.Args
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package bar;
2+
3+
import org.thymeleaf.TemplateEngine;
4+
import org.thymeleaf.context.Context;
5+
6+
public class Bar {
7+
public static String value() {
8+
Context context = new Context();
9+
context.setVariable("text", "world");
10+
return new TemplateEngine().process("<p th:text=\"${text}\"></p>", context);
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package bar;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class BarTests {
8+
9+
@Test
10+
public void test() {
11+
assertEquals(Bar.value(), "<p>world</p>");
12+
}
13+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// An alternative way to produce self-executable assemblies is the `RepackageModule` which used the https://docs.spring.io/spring-boot/build-tool-plugin/index.html[Spring Boot Tools suite].
2+
// Instead of copying and merging dependencies classes and resources into a flat jar file, it embeds all dependencies as-is in the final jar.
3+
// One of the pros of this approach is, that all dependency archives are kept unextracted, which makes later introspection for checksums, authorship and copyright questions easier.
4+
5+
package build
6+
7+
import mill._, javalib._, publish._
8+
import mill.javalib.repackage.RepackageModule
9+
10+
trait MyModule extends JavaModule with PublishModule {
11+
def publishVersion = "0.0.1"
12+
13+
def pomSettings = PomSettings(
14+
description = "Hello",
15+
organization = "com.lihaoyi",
16+
url = "https://github.com/lihaoyi/example",
17+
licenses = Seq(License.MIT),
18+
versionControl = VersionControl.github("lihaoyi", "example"),
19+
developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
20+
)
21+
22+
def mvnDeps = Seq(mvn"org.thymeleaf:thymeleaf:3.1.1.RELEASE")
23+
24+
object test extends JavaTests with TestModule.Junit4
25+
}
26+
27+
object foo extends MyModule with RepackageModule { // <1>
28+
def moduleDeps = Seq(bar, qux)
29+
}
30+
31+
object bar extends MyModule {
32+
def moduleDeps = Seq(qux)
33+
}
34+
35+
object qux extends MyModule
36+
37+
// <1> Add the `mill.javalib.repackage.RepackageModule` to the executable module.
38+
39+
/** Usage
40+
41+
> mill foo.run
42+
Foo.value: <h1>hello</h1>
43+
Bar.value: <p>world</p>
44+
Qux.value: 31337
45+
46+
> mill show foo.repackagedJar
47+
".../out/foo/repackagedJar.dest/out.jar"
48+
49+
> ./out/foo/repackagedJar.dest/out.jar
50+
Foo.value: <h1>hello</h1>
51+
Bar.value: <p>world</p>
52+
Qux.value: 31337
53+
54+
> unzip -l ./out/foo/repackagedJar.dest/out.jar "BOOT-INF/lib*"
55+
...BOOT-INF/lib/thymeleaf-3.1.1.RELEASE.jar
56+
...BOOT-INF/lib/ognl-3.3.4.jar
57+
...BOOT-INF/lib/attoparser-2.0.6.RELEASE.jar
58+
...BOOT-INF/lib/unbescape-1.1.6.RELEASE.jar
59+
...BOOT-INF/lib/slf4j-api-2.0.5.jar
60+
...BOOT-INF/lib/javassist-3.29.0-GA.jar
61+
...BOOT-INF/lib/qux-0.0.1.jar
62+
...BOOT-INF/lib/bar-0.0.1.jar
63+
64+
*/
65+
66+
// *Futher notes:*
67+
//
68+
// * a small wrapper application needs to be added, which is run as entry point and transparently manages loading the embedded jars and running your `main` method.
69+
// This works for all Java (also Scala or Kotlin) applications.
70+
// * It's not necessary to use the Spring Framework in the application.
71+
// * The resulting jar is a self-executable application, but it might not suitable to be used on the classpath of other applications.
72+
73+
// * Since the final jar produced with the `RepackageModule.repackagedJar` task often contains significantly less ZIP entries
74+
// then the jar file produced with `.assembly`, it's possible to workaround
75+
// an https://github.com/com-lihaoyi/mill/issues/2650[issue where `JavaModule.assembly` cannot produce executable assemblies]
76+
// due to some JVM limitations in ZIP file handling of large files.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package foo;
2+
3+
public class Foo {
4+
public static String value = "<h1>hello</h1>";
5+
6+
public static void main(String[] args) {
7+
System.out.println("Foo.value: " + Foo.value);
8+
System.out.println("Bar.value: " + bar.Bar.value());
9+
System.out.println("Qux.value: " + qux.Qux.value);
10+
}
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package foo;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class FooTests {
8+
9+
@Test
10+
public void test() {
11+
assertEquals(Foo.value, "<h1>hello</h1>");
12+
}
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package qux;
2+
3+
public class Qux {
4+
public static final int value = 31337;
5+
6+
public static void main(String[] args) {
7+
System.out.println("Qux.value: " + Qux.value);
8+
}
9+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package mill.javalib.spring.boot.worker
2+
3+
import mill.define.TaskCtx
4+
5+
@mill.api.experimental
6+
trait SpringBootTools {
7+
def repackageJar(
8+
dest: os.Path,
9+
base: os.Path,
10+
mainClass: String,
11+
libs: Seq[os.Path],
12+
assemblyScript: Option[String]
13+
)(implicit ctx: TaskCtx): Unit
14+
15+
/**
16+
* Find a SpringBootApplication entry point.
17+
* @param classesPath
18+
*/
19+
def findSpringBootApplicationClass(classesPath: Seq[os.Path]): Either[String, String]
20+
}

libs/scalalib/package.mill

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package build.libs.scalalib
22
import scala.util.Properties
33
import scala.util.chaining._
4+
45
import coursier.maven.MavenRepository
56
import mill._
67
import mill.define.NamedTask
@@ -91,13 +92,21 @@ object `package` extends build.MillStableScalaModule {
9192
"revApiVersion",
9293
build.Deps.RuntimeDeps.revApi.version,
9394
"Version of revApi"
95+
),
96+
BuildInfo.Value("springBuildToolsVersion", build.Deps.springBootTools.version),
97+
BuildInfo.Value(
98+
"millSpringBootWorkerDep", {
99+
val dep = `spring-boot-worker`.publishSelfDependency()
100+
s"${dep.group}:${dep.id}:${dep.version}"
101+
},
102+
"The dependency containing the worker implementation to be loaded at runtime."
94103
)
95104
)
96105
}
97106

98107
object worker extends build.MillPublishScalaModule with BuildInfo {
99108
def moduleDeps = Seq(api, build.core.util)
100-
def mvnDeps = Agg(build.Deps.zinc, build.Deps.log4j2Core)
109+
def mvnDeps = Seq(build.Deps.zinc, build.Deps.log4j2Core)
101110
def buildInfoPackageName = "mill.scalalib.worker"
102111
def buildInfoObjectName = "Versions"
103112
def buildInfoMembers = Seq(
@@ -107,12 +116,20 @@ object `package` extends build.MillStableScalaModule {
107116

108117
object `classgraph-worker` extends build.MillPublishScalaModule {
109118
def moduleDeps = Seq(api, build.core.util)
110-
def mvnDeps = Agg(build.Deps.classgraph)
119+
def mvnDeps = Seq(build.Deps.classgraph)
111120
}
112121

113122
object `jarjarabrams-worker` extends build.MillPublishScalaModule {
114123
def moduleDeps = Seq(api, build.core.util, build.libs.scalalib)
115124
def mvnDeps = Agg(build.Deps.jarjarabrams)
116125
}
117126

127+
object `spring-boot-worker` extends build.MillPublishScalaModule {
128+
def moduleDeps = Seq(api)
129+
def compileMvnDeps = Seq(
130+
// we just bind against the API, will be loaded at Mill runtime by the module
131+
build.Deps.springBootTools
132+
)
133+
}
134+
118135
}

0 commit comments

Comments
 (0)