Skip to content

Commit 8a196f9

Browse files
committed
3-stage compile to support Groovy <-> Java cycles
1 parent 91c58ff commit 8a196f9

File tree

7 files changed

+157
-88
lines changed

7 files changed

+157
-88
lines changed

libs/groovylib/api/src/mill/groovylib/worker/api/GroovyWorker.scala

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,34 @@ import mill.api.TaskCtx
44
import mill.api.Result
55
import mill.javalib.api.CompilationResult
66

7+
/**
8+
* Runs the actual compilation.
9+
*
10+
* Supports 3-stage compilation for Java <-> Groovy
11+
* 1. compile Java stubs
12+
* 2. compile Java sources (done externally)
13+
* 3. compile Groovy sources
14+
*/
715
trait GroovyWorker {
816

17+
/**
18+
* In a mixed setup this will compile the Groovy sources to Java stubs.
19+
*/
20+
def compileGroovyStubs(
21+
sourceFiles: Seq[os.Path],
22+
classpath: Seq[os.Path],
23+
outputDir: os.Path
24+
)(implicit
25+
ctx: TaskCtx
26+
)
27+
: Result[CompilationResult]
28+
29+
/**
30+
* Compiles the Groovy sources. In a mixed setup this method assumes that the Java stubs
31+
* are already present in the outputDir.
32+
*/
933
def compile(sourceFiles: Seq[os.Path], classpath: Seq[os.Path], outputDir: os.Path)(implicit
1034
ctx: TaskCtx
11-
): Result[CompilationResult]
35+
)
36+
: Result[CompilationResult]
1237
}

libs/groovylib/package.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import millbuild.*
88
object `package` extends MillPublishScalaModule with BuildInfo {
99

1010
def moduleDeps = Seq(build.libs.javalib, build.libs.javalib.testrunner, api)
11-
def localTestExtraModules =
11+
def localTestExtraModules: Seq[MillJavaModule] =
1212
super.localTestExtraModules ++ Seq(worker)
1313

1414
def buildInfoPackageName = "mill.groovylib"

libs/groovylib/src/mill/groovylib/GroovyModule.scala

Lines changed: 32 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import mill.api.daemon.internal.bsp.{BspBuildTarget, BspModuleApi}
1212
import mill.javalib.api.internal.{JavaCompilerOptions, JvmWorkerApi, ZincCompileJava}
1313

1414
/**
15-
* Core configuration required to compile a single Groovy module
15+
* Core configuration required to compile a single Groovy module.
16+
*
17+
* Resolves
1618
*/
1719
trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
1820

@@ -33,36 +35,31 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
3335
* All individual source files fed into the compiler.
3436
*/
3537
override def allSourceFiles: T[Seq[PathRef]] = Task {
36-
Lib.findSourceFiles(allSources(), Seq("groovy", "java")).map(PathRef(_))
38+
allGroovySourceFiles() ++ allJavaSourceFiles()
3739
}
3840

3941
/**
4042
* All individual Java source files fed into the compiler.
4143
* Subset of [[allSourceFiles]].
4244
*/
4345
private def allJavaSourceFiles: T[Seq[PathRef]] = Task {
44-
allSourceFiles().filter(_.path.ext.toLowerCase() == "java")
46+
Lib.findSourceFiles(allSources(), Seq("java")).map(PathRef(_))
4547
}
4648

4749
/**
4850
* All individual Groovy source files fed into the compiler.
4951
* Subset of [[allSourceFiles]].
5052
*/
5153
private def allGroovySourceFiles: T[Seq[PathRef]] = Task {
52-
allSourceFiles().filter(path => Seq("groovy").contains(path.path.ext.toLowerCase()))
54+
Lib.findSourceFiles(allSources(), Seq("groovy")).map(PathRef(_))
5355
}
5456

5557
/**
5658
* The dependencies of this module.
57-
* Defaults to add the groovy dependency matching the [[groovyVersion]].
59+
* Defaults to add the Groovy dependency matching the [[groovyVersion]].
5860
*/
5961
override def mandatoryMvnDeps: T[Seq[Dep]] = Task {
60-
super.mandatoryMvnDeps()
61-
++
62-
groovyCompilerMvnDeps()
63-
// Seq(
64-
// mvn"org.apache.groovy:groovy:${groovyVersion()}"
65-
// )
62+
super.mandatoryMvnDeps() ++ groovyCompilerMvnDeps()
6663
}
6764

6865
def jvmWorkerRef: ModuleRef[JvmWorkerModule] = jvmWorker
@@ -86,31 +83,11 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
8683
/**
8784
* The Ivy/Coursier dependencies resembling the Groovy compiler.
8885
*
89-
* Default is derived from [[groovyCompilerVersion]].
86+
* Default is derived from [[groovyVersion]].
9087
*/
9188
def groovyCompilerMvnDeps: T[Seq[Dep]] = Task {
9289
val gv = groovyVersion()
93-
94-
val compilerDep = mvn"org.apache.groovy:groovy-all:$gv"
95-
// val annotationDep = mvn"org.codehaus.groovy:groovy-all-annotations:$gv"
96-
97-
Seq(compilerDep)
98-
}
99-
100-
/**
101-
* Compiler Plugin dependencies.
102-
*/
103-
def groovyCompilerPluginMvnDeps: T[Seq[Dep]] = Task { Seq.empty[Dep] }
104-
105-
/**
106-
* The resolved plugin jars
107-
*/
108-
def groovyCompilerPluginJars: T[Seq[PathRef]] = Task {
109-
val jars = defaultResolver().classpath(
110-
allMvnDeps(),
111-
resolutionParamsMapOpt = None
112-
)
113-
jars.toSeq
90+
Seq(mvn"org.apache.groovy:groovy:$gv")
11491
}
11592

11693
/**
@@ -121,9 +98,8 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
12198
}
12299

123100
/**
124-
* The actual Groovy compile task (used by [[compile]] and [[groovycHelp]]).
101+
* The actual Groovy compile task (used by [[compile]]).
125102
*/
126-
// TODO joint compilation: generate groovy-stubs -> compile java -> compile groovy -> delete stubs (or keep for debugging)
127103
protected def groovyCompileTask(): Task[CompilationResult] =
128104
Task.Anon {
129105
val ctx = Task.ctx()
@@ -143,9 +119,9 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
143119

144120
def compileJava: Result[CompilationResult] = {
145121
ctx.log.info(
146-
s"Compiling ${javaSourceFiles.size} Java sources to ${classes} ..."
122+
s"Compiling ${javaSourceFiles.size} Java sources to $classes ..."
147123
)
148-
// The compile step is lazy, but its dependencies are not!
124+
// The compiler step is lazy, but its dependencies are not!
149125
internalCompileJavaFiles(
150126
worker = jvmWorkerRef().internalWorker(),
151127
upstreamCompileOutput = updateCompileOutput,
@@ -158,40 +134,43 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
158134
)
159135
}
160136

137+
if (isMixed) {
138+
ctx.log.info("Compiling Groovy stubs for mixed compilation")
139+
140+
val workerStubResult =
141+
GroovyWorkerManager.groovyWorker().withValue(groovyCompilerClasspath()) {
142+
_.compileGroovyStubs(groovySourceFiles, compileCp, classes)
143+
}
144+
workerStubResult match {
145+
case Result.Success(_) => compileJava
146+
case Result.Failure(reason) => Result.Failure(reason)
147+
}
148+
}
149+
161150
if (isMixed || isGroovy) {
162151
ctx.log.info(
163-
s"Compiling ${groovySourceFiles.size} Groovy sources to ${classes} ..."
152+
s"Compiling ${groovySourceFiles.size} Groovy sources to $classes ..."
164153
)
165154

166-
val compileCp = compileClasspath().map(_.path).filter(os.exists)
167-
168-
val workerResult =
155+
val workerGroovyResult =
169156
GroovyWorkerManager.groovyWorker().withValue(groovyCompilerClasspath()) {
170157
_.compile(groovySourceFiles, compileCp, classes)
171158
}
172159

160+
// TODO figure out if there is a better way to do this
173161
val analysisFile = dest / "groovy.analysis.dummy" // needed for mills CompilationResult
174162
os.write(target = analysisFile, data = "", createFolders = true)
175163

176-
workerResult match {
164+
workerGroovyResult match {
177165
case Result.Success(_) =>
178-
val cr = CompilationResult(analysisFile, PathRef(classes))
179-
if (!isJava) {
180-
// pure Groovy project
181-
cr
182-
} else {
183-
// also run Java compiler and use it's returned result
184-
compileJava
185-
}
166+
CompilationResult(analysisFile, PathRef(classes))
186167
case Result.Failure(reason) => Result.Failure(reason)
187168
}
188169
} else {
189-
// it's Java only
190170
compileJava
191171
}
192172
}
193173

194-
195174
private[groovylib] def internalCompileJavaFiles(
196175
worker: JvmWorkerApi,
197176
upstreamCompileOutput: Seq[CompilationResult],
@@ -236,7 +215,7 @@ trait GroovyModule extends JavaModule with GroovyModuleApi { outer =>
236215
}
237216

238217
/**
239-
* A test sub-module linked to its parent module best suited for unit-tests.
218+
* A test submodule linked to its parent module best suited for unit-tests.
240219
*/
241220
trait GroovyTests extends JavaTests with GroovyModule {
242221

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hello.maven.tests
2+
3+
//import hello.Hello
4+
import org.junit.jupiter.api.Test
5+
import static org.junit.jupiter.api.Assertions.assertEquals
6+
7+
class HelloMavenTestOnly {
8+
@Test
9+
void testSuccess() {
10+
assertEquals(true, true)
11+
}
12+
}

libs/groovylib/test/src/mill/groovylib/HelloGroovyTests.scala

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package mill
22
package groovylib
33

4-
import mill.javalib.{JavaModule, MavenModule, TestModule}
5-
import mill.api.{ExecResult, Task}
4+
import mill.javalib.{JavaModule, TestModule}
5+
import mill.api.{Task}
66
import mill.api.Discover
77
import mill.testkit.{TestRootModule, UnitTester}
88
import utest.*
@@ -17,12 +17,12 @@ object HelloGroovyTests extends TestSuite {
1717
lazy val millDiscover = Discover[this.type]
1818

1919
// needed for a special test where only the tests are written in Groovy while appcode remains Java
20-
object `mixed-compile` extends JavaModule with MavenModule {
20+
object `groovy-tests` extends JavaMavenModuleWithGroovyTests {
2121

22-
object `test` extends TestGroovyMavenModule with TestModule.Junit5 {
22+
object `test` extends GroovyMavenTests with TestModule.Junit5 {
2323

2424
override def moduleDeps: Seq[JavaModule] = Seq(
25-
HelloGroovy.`mixed-compile`, // TODO improve: TestOnly does not inherit outer deps
25+
HelloGroovy.`groovy-tests`
2626
)
2727

2828
override def groovyVersion = groovy4Version
@@ -37,6 +37,7 @@ object HelloGroovyTests extends TestSuite {
3737

3838
object `joint-compile` extends GroovyModule {
3939
override def groovyVersion: T[String] = groovy4Version
40+
override def mainClass = Some("jointcompile.JavaMain")
4041
}
4142

4243
trait Test extends GroovyModule {
@@ -61,20 +62,16 @@ object HelloGroovyTests extends TestSuite {
6162
override def mainClass = Some("hellostatic.HelloStatic")
6263
}
6364

64-
object spock extends GroovyTests with TestModule.Junit5 {
65+
object spock extends GroovyTests with TestModule.Spock {
6566
override def junitPlatformVersion = "1.13.4"
66-
def spockVersion: T[String] = "2.3-groovy-4.0"
67+
override def spockVersion = "2.3-groovy-4.0"
6768
override def groovyVersion = "4.0.28"
6869

6970
def bomMvnDeps = Seq(
7071
mvn"org.junit:junit-bom:5.13.4",
71-
mvn"org.apache.groovy:groovy-bom:${groovyVersion()}",
72+
// mvn"org.apache.groovy:groovy-bom:${groovyVersion()}",
7273
mvn"org.spockframework:spock-bom:${spockVersion()}"
7374
)
74-
75-
def mvnDeps = Seq(
76-
mvn"org.spockframework:spock-core"
77-
)
7875
}
7976
}
8077
object main extends Test {
@@ -92,7 +89,14 @@ object HelloGroovyTests extends TestSuite {
9289
def tests: Tests = Tests {
9390

9491
def m = HelloGroovy.main
95-
def mixed = HelloGroovy.`mixed-compile`
92+
def mixed = HelloGroovy.`groovy-tests`
93+
def joint = HelloGroovy.`joint-compile`
94+
95+
test("running a Groovy script") {
96+
testEval().scoped { eval =>
97+
val Right(_) = eval.apply(m.script.run()): @unchecked
98+
}
99+
}
96100

97101
test("running a Groovy script") {
98102
testEval().scoped { eval =>
@@ -128,7 +132,7 @@ object HelloGroovyTests extends TestSuite {
128132
}
129133
}
130134

131-
test("compiling & running a statically compiled Groovy") {
135+
test("compile & run a statically compiled Groovy") {
132136
testEval().scoped { eval =>
133137
val Right(result) = eval.apply(m.staticcompile.compile): @unchecked
134138
assert(
@@ -138,17 +142,12 @@ object HelloGroovyTests extends TestSuite {
138142
}
139143
}
140144

141-
test("compile & run test-only Maven JUnit5 test") {
145+
test("compile & test module (only test uses Groovy)") {
142146
testEval().scoped { eval =>
143147

144-
val Right(resultCompile) = eval.apply(mixed.compile): @unchecked
145-
assert(
146-
os.walk(resultCompile.value.classes.path).exists(_.last == "Greeter.class")
147-
)
148-
149148
val Right(_) = eval.apply(mixed.test.compile): @unchecked
150149
val Right(discovered) = eval.apply(mixed.test.discoveredTestClasses): @unchecked
151-
assert(discovered.value == Seq("tests.GreeterTests"))
150+
assert(discovered.value == Seq("hello.maven.tests.HelloMavenTestOnly"))
152151

153152
val Right(_) = eval.apply(mixed.test.testForked()): @unchecked
154153
}
@@ -169,7 +168,20 @@ object HelloGroovyTests extends TestSuite {
169168
}
170169
}
171170

171+
test("compile joint (groovy <-> java cycle) & run") {
172+
testEval().scoped { eval =>
173+
val Right(result) = eval.apply(joint.compile): @unchecked
172174

175+
assert(
176+
os.walk(result.value.classes.path).exists(_.last == "JavaPrinter.class")
177+
)
178+
assert(
179+
os.walk(result.value.classes.path).exists(_.last == "GroovyGreeter.class")
180+
)
181+
182+
val Right(_) = eval.apply(joint.run()): @unchecked
183+
}
184+
}
173185

174186
}
175187
}

0 commit comments

Comments
 (0)