Skip to content

Commit 69daaec

Browse files
lefouArmin Motekalem
andauthored
Support Java generation in ScalaPBModule (#5996)
Resurrection of abandoned PR #5817. * Support for alternative code generators * Configuration happens via `Generator` enum, supporting Scala (`ScalaGen`, default), Java (`JavaGen`) and/or freely configurable (`CustomGen`) Pull request: #5996 Co-authored-by: Armin Motekalem <[email protected]>
1 parent a7ad91c commit 69daaec

File tree

5 files changed

+128
-60
lines changed

5 files changed

+128
-60
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package mill.contrib.scalapblib
2+
3+
import upickle.ReadWriter
4+
5+
/**
6+
* A ScalaPB generator
7+
* @param generator The CLI option to enable the generator
8+
* @param supportsScalaPbOptions `true` if the [[ScalaPBModule.scalaPBOptions]] should be used to read options.
9+
*/
10+
enum Generator(val generator: String, val supportsScalaPbOptions: Boolean) derives ReadWriter {
11+
case ScalaGen extends Generator("--scala_out", true)
12+
case JavaGen extends Generator("--java_out", false)
13+
case CustomGen(override val generator: String, override val supportsScalaPbOptions: Boolean)
14+
extends Generator(generator, supportsScalaPbOptions)
15+
}

contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBModule.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package mill
2-
package contrib.scalapblib
1+
package mill.contrib.scalapblib
32

43
import coursier.core.Version
5-
import mill.api.{PathRef}
6-
import mill.scalalib._
4+
import mill.api.{PathRef, Task}
5+
import mill.scalalib.*
6+
import mill.T
77

88
import java.util.zip.ZipInputStream
99
import scala.util.Using
@@ -22,6 +22,8 @@ trait ScalaPBModule extends ScalaModule {
2222

2323
def scalaPBVersion: T[String]
2424

25+
def scalaPBGenerators: T[Seq[Generator]] = Seq(Generator.ScalaGen)
26+
2527
def scalaPBFlatPackage: T[Boolean] = Task { false }
2628

2729
def scalaPBJavaConversions: T[Boolean] = Task { false }
@@ -141,7 +143,8 @@ trait ScalaPBModule extends ScalaModule {
141143
scalaPBSources().map(_.path),
142144
scalaPBOptions(),
143145
Task.dest,
144-
scalaPBCompileOptions()
146+
scalaPBCompileOptions(),
147+
scalaPBGenerators()
145148
)
146149
}
147150
}

contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBWorker.scala

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package mill
2-
package contrib.scalapblib
3-
4-
import java.io.File
1+
package mill.contrib.scalapblib
52

63
import mill.api.PathRef
7-
import mill.api.{Discover, ExternalModule}
4+
5+
import java.io.File
86

97
class ScalaPBWorker {
108

@@ -15,16 +13,18 @@ class ScalaPBWorker {
1513
sources: Seq[File],
1614
scalaPBOptions: String,
1715
generatedDirectory: File,
18-
otherArgs: Seq[String]
16+
otherArgs: Seq[String],
17+
generators: Seq[Generator]
1918
): Unit = {
2019
val pbcClasspath = scalaPBClasspath.map(_.path).toVector
2120
mill.util.Jvm.withClassLoader(pbcClasspath, null) { cl =>
2221
val scalaPBCompilerClass = cl.loadClass("scalapb.ScalaPBC")
2322
val mainMethod = scalaPBCompilerClass.getMethod("main", classOf[Array[java.lang.String]])
24-
val opts = if (scalaPBOptions.isEmpty) "" else scalaPBOptions + ":"
25-
val args = otherArgs ++ Seq(
26-
s"--scala_out=${opts}${generatedDirectory.getCanonicalPath}"
27-
) ++ roots.map(root => s"--proto_path=${root.getCanonicalPath}") ++ sources.map(
23+
val args = otherArgs ++ generators.map { gen =>
24+
val opts = if (scalaPBOptions.isEmpty || !gen.supportsScalaPbOptions) ""
25+
else scalaPBOptions + ":"
26+
s"${gen.generator}=$opts${generatedDirectory.getCanonicalPath}"
27+
} ++ roots.map(root => s"--proto_path=${root.getCanonicalPath}") ++ sources.map(
2828
_.getCanonicalPath
2929
)
3030
ctx.log.debug(s"ScalaPBC args: ${args.mkString(" ")}")
@@ -62,6 +62,7 @@ class ScalaPBWorker {
6262
* @param scalaPBOptions option string specific for scala generator. (the options in `--scala_out=<options>:output_path`)
6363
* @param dest output path
6464
* @param scalaPBCExtraArgs extra arguments other than `--scala_out=<options>:output_path`, `--proto_path=source_parent`, `source`
65+
* @oaram generators At least on generators to use. See [[Generator]] for available generators.
6566
*
6667
* @return execute result with path ref to `dest`
6768
*/
@@ -70,7 +71,8 @@ class ScalaPBWorker {
7071
scalaPBSources: Seq[os.Path],
7172
scalaPBOptions: String,
7273
dest: os.Path,
73-
scalaPBCExtraArgs: Seq[String]
74+
scalaPBCExtraArgs: Seq[String],
75+
generators: Seq[Generator]
7476
)(using ctx: mill.api.TaskCtx): mill.api.Result[PathRef] = {
7577
val compiler = scalaPB(scalaPBClasspath)
7678
val sources = scalaPBSources.flatMap {
@@ -86,22 +88,14 @@ class ScalaPBWorker {
8688
Seq(ioFile)
8789
}
8890
val roots = scalaPBSources.map(_.toIO).filter(_.isDirectory)
89-
compiler.compileScalaPB(roots, sources, scalaPBOptions, dest.toIO, scalaPBCExtraArgs)
91+
compiler.compileScalaPB(
92+
roots,
93+
sources,
94+
scalaPBOptions,
95+
dest.toIO,
96+
scalaPBCExtraArgs,
97+
generators
98+
)
9099
mill.api.Result.Success(PathRef(dest))
91100
}
92101
}
93-
94-
trait ScalaPBWorkerApi {
95-
def compileScalaPB(
96-
roots: Seq[File],
97-
source: Seq[File],
98-
scalaPBOptions: String,
99-
generatedDirectory: File,
100-
otherArgs: Seq[String]
101-
): Unit
102-
}
103-
104-
object ScalaPBWorkerApi extends ExternalModule {
105-
def scalaPBWorker: Worker[ScalaPBWorker] = Task.Worker { new ScalaPBWorker() }
106-
lazy val millDiscover = Discover[this.type]
107-
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package mill.contrib.scalapblib
2+
3+
import mill.api.{Discover, ExternalModule, Task}
4+
5+
import java.io.File
6+
7+
trait ScalaPBWorkerApi {
8+
def compileScalaPB(
9+
roots: Seq[File],
10+
source: Seq[File],
11+
scalaPBOptions: String,
12+
generatedDirectory: File,
13+
otherArgs: Seq[String],
14+
generators: Seq[Generator]
15+
): Unit
16+
}
17+
18+
object ScalaPBWorkerApi extends ExternalModule {
19+
def scalaPBWorker: Task.Worker[ScalaPBWorker] = Task.Worker { new ScalaPBWorker() }
20+
protected lazy val millDiscover = Discover[this.type]
21+
}

contrib/scalapblib/test/src/mill/contrib/scalapblib/TutorialTests.scala

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import utest.{TestSuite, Tests, assert, *}
1111
object TutorialTests extends TestSuite {
1212
val testScalaPbVersion = "0.11.7"
1313

14-
trait TutorialBase extends TestRootModule
14+
trait TutorialBase extends TestRootModule {
15+
val core: TutorialModule
16+
}
1517

1618
trait TutorialModule extends ScalaPBModule {
1719
def scalaVersion = sys.props.getOrElse("TEST_SCALA_2_12_VERSION", ???)
@@ -70,19 +72,69 @@ object TutorialTests extends TestSuite {
7072
lazy val millDiscover = Discover[this.type]
7173
}
7274

75+
object TutorialWithJavaGen extends TutorialBase {
76+
object core extends TutorialModule {
77+
override def scalaPBGenerators = Seq(Generator.JavaGen)
78+
}
79+
80+
lazy val millDiscover = Discover[this.type]
81+
}
82+
83+
object TutorialWithScalaAndJavaGen extends TutorialBase {
84+
object core extends TutorialModule {
85+
override def scalaPBGenerators = Seq(Generator.ScalaGen, Generator.JavaGen)
86+
}
87+
88+
lazy val millDiscover = Discover[this.type]
89+
}
90+
7391
val resourcePath: os.Path = os.Path(sys.env("MILL_TEST_RESOURCE_DIR"))
7492

7593
def protobufOutPath(eval: UnitTester): os.Path =
7694
eval.outPath / "core/compileScalaPB.dest/com/example/tutorial"
7795

78-
def compiledSourcefiles: Seq[os.RelPath] = Seq[os.RelPath](
96+
def compiledScalaSourcefiles: Seq[os.RelPath] = Seq[os.RelPath](
7997
os.rel / "AddressBook.scala",
8098
os.rel / "Person.scala",
8199
os.rel / "TutorialProto.scala",
82100
os.rel / "Include.scala",
83101
os.rel / "IncludeProto.scala"
84102
)
85103

104+
def compiledJavaSourcefiles: Seq[os.RelPath] = Seq[os.RelPath](
105+
os.rel / "AddressBookProtos.java",
106+
os.rel / "IncludeOuterClass.java"
107+
)
108+
109+
// Helper function to test compilation with different generators
110+
def testCompilation(
111+
module: TutorialBase,
112+
expectedFiles: Seq[os.RelPath]
113+
): Unit = {
114+
UnitTester(module, resourcePath).scoped { eval =>
115+
if (Util.isWindows) "Skipped test on Windows"
116+
else {
117+
val Right(result) = eval.apply(module.core.compileScalaPB): @unchecked
118+
119+
val outPath = protobufOutPath(eval)
120+
val outputFiles = os.walk(result.value.path).filter(os.isFile)
121+
val expectedSourcefiles = expectedFiles.map(outPath / _)
122+
123+
assert(
124+
result.value.path == eval.outPath / "core/compileScalaPB.dest",
125+
outputFiles.nonEmpty,
126+
outputFiles.forall(expectedSourcefiles.contains),
127+
outputFiles.size == outputFiles.size,
128+
result.evalCount > 0
129+
)
130+
131+
// don't recompile if nothing changed
132+
val Right(result2) = eval.apply(module.core.compileScalaPB): @unchecked
133+
assert(result2.evalCount == 0)
134+
}
135+
}
136+
}
137+
86138
def tests: Tests = Tests {
87139
test("scalapbVersion") {
88140

@@ -97,36 +149,19 @@ object TutorialTests extends TestSuite {
97149
}
98150

99151
test("compileScalaPB") {
100-
test("calledDirectly") - UnitTester(Tutorial, resourcePath).scoped { eval =>
101-
if (!mill.constants.Util.isWindows) {
102-
val Right(result) = eval.apply(Tutorial.core.compileScalaPB): @unchecked
103-
104-
val outPath = protobufOutPath(eval)
105-
106-
val outputFiles = os.walk(result.value.path).filter(os.isFile)
107-
108-
val expectedSourcefiles = compiledSourcefiles.map(outPath / _)
109-
110-
assert(
111-
result.value.path == eval.outPath / "core/compileScalaPB.dest",
112-
outputFiles.nonEmpty,
113-
outputFiles.forall(expectedSourcefiles.contains),
114-
outputFiles.size == 5,
115-
result.evalCount > 0
116-
)
117-
118-
// don't recompile if nothing changed
119-
val Right(result2) = eval.apply(Tutorial.core.compileScalaPB): @unchecked
120-
121-
assert(result2.evalCount == 0)
122-
}
123-
}
152+
test("scalaGen") - testCompilation(Tutorial, compiledScalaSourcefiles)
153+
test("javaGen") - testCompilation(TutorialWithJavaGen, compiledJavaSourcefiles)
154+
test("scalaAndJavaGen") - testCompilation(
155+
TutorialWithScalaAndJavaGen,
156+
compiledScalaSourcefiles ++ compiledJavaSourcefiles
157+
)
124158

125159
test("calledWithSpecificFile") - UnitTester(
126160
TutorialWithSpecificSources,
127161
resourcePath
128162
).scoped { eval =>
129-
if (!Util.isWindows) {
163+
if (Util.isWindows) "Skipped test on Windows"
164+
else {
130165
val Right(result) =
131166
eval.apply(TutorialWithSpecificSources.core.compileScalaPB): @unchecked
132167

@@ -165,7 +200,7 @@ object TutorialTests extends TestSuite {
165200
//
166201
// // val outputFiles = os.walk(outPath).filter(_.isFile)
167202
//
168-
// // val expectedSourcefiles = compiledSourcefiles.map(outPath / _)
203+
// // val expectedSourcefiles = compiledScalaSourcefiles.map(outPath / _)
169204
//
170205
// // assert(
171206
// // outputFiles.nonEmpty,

0 commit comments

Comments
 (0)