Skip to content

Commit 7e3b3fa

Browse files
committed
feat: sbt plugin 'unmanaged' codegen
Not entirely happy with the approach: generating the code from the regular protoc generators, but then 'filtering them out' of the managed sources (that they get added to automatically) afterwards, and copying them over 'manually'. It does save us from duplicating part of the sbt-protoc implementation (in particular finding the correct `.proto` files and converting them into `com.google.protobuf.Descriptors.FileDescriptor` instances). Let's try it out, but if it leads to trouble we should definitely consider avoiding the generators and doing that step 'ourselves'.
1 parent 4fb56a5 commit 7e3b3fa

6 files changed

Lines changed: 136 additions & 6 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2021 Lightbend Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.akkaserverless.codegen.scalasdk
18+
19+
import com.google.protobuf.ExtensionRegistry
20+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
21+
import com.lightbend.akkasls.codegen.ModelBuilder
22+
import protocbridge.Artifact
23+
import protocgen.{ CodeGenApp, CodeGenRequest, CodeGenResponse }
24+
25+
object AkkaserverlessUnmanagedGenerator extends CodeGenApp {
26+
val enableDebug = "enableDebug"
27+
28+
override def registerExtensions(registry: ExtensionRegistry): Unit = {
29+
registry.add(com.akkaserverless.Annotations.service)
30+
registry.add(com.akkaserverless.Annotations.file)
31+
}
32+
33+
override def process(request: CodeGenRequest): CodeGenResponse = {
34+
val debugEnabled = request.parameter.contains(enableDebug)
35+
val model = ModelBuilder.introspectProtobufClasses(request.filesToGenerate)(DebugPrintlnLog(debugEnabled))
36+
try {
37+
CodeGenResponse.succeed(
38+
SourceGenerator
39+
.generateUnmanaged(model)
40+
.map(file =>
41+
CodeGeneratorResponse.File
42+
.newBuilder()
43+
.setName(file.name)
44+
.setContent(file.content)
45+
.build()))
46+
} catch {
47+
case t: Throwable =>
48+
t.printStackTrace()
49+
CodeGenResponse.fail(t.getMessage)
50+
}
51+
}
52+
53+
override def suggestedDependencies: Seq[Artifact] = Nil
54+
}

codegen/scala-gen/src/main/scala/com/akkaserverless/codegen/scalasdk/SourceGenerator.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ object SourceGenerator {
2525
* Generate the 'managed' code for this model: code that will be regenerated regularly in the 'compile' configuratio
2626
*/
2727
def generateManaged(model: ModelBuilder.Model): Seq[File] =
28-
Seq(File("foo/bar/Baz.scala", "package foo.bar\n\ntrait Baz"))
28+
Seq(File("foo/bar/AbstractBaz.scala", "package foo.bar\n\nabstract class AbstractBaz"))
2929

3030
/**
3131
* Generate the 'managed' code for this model: code that will be regenerated regularly in the 'compile' configuratio
3232
*/
3333
def generateManagedTest(model: ModelBuilder.Model): Seq[File] =
34-
Seq(File("foo/bar/BazSpec.scala", "package foo.bar\n\ntrait BazSpec"))
34+
Seq(File("foo/bar/BazSpec.scala", "package foo.bar\n\nclass BazSpec { new Baz() }"))
3535

3636
/**
3737
* Generate the 'unmanaged' code for this model: code that is generated once on demand and then maintained by the
3838
* user.
3939
*/
4040
def generateUnmanaged(model: ModelBuilder.Model): Seq[File] =
41-
Seq.empty
41+
Seq(File("foo/bar/Baz.scala", "package foo.bar\n\nclass Baz extends AbstractBaz"))
4242
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2021 Lightbend Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.akkaserverless.codegen.scalasdk
18+
19+
import protocbridge.SandboxedJvmGenerator
20+
21+
object genUnmanaged {
22+
def apply(options: Seq[String] = Seq.empty): (SandboxedJvmGenerator, Seq[String]) =
23+
gen(options, "com.akkaserverless.codegen.scalasdk.AkkaserverlessUnmanagedGenerator$")
24+
}

sbt-plugin/src/main/scala/com/akkaserverless/sbt/AkkaserverlessPlugin.scala

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package com.akkaserverless.sbt
1818

19-
import com.akkaserverless.codegen.scalasdk.{ gen, genTests, AkkaserverlessGenerator }
19+
import java.nio.file.Path
20+
import java.nio.file.Paths
21+
import java.nio.file.Files
22+
23+
import com.akkaserverless.codegen.scalasdk.{ gen, genTests, genUnmanaged, AkkaserverlessGenerator, SourceGenerator }
2024
import sbt.{ Compile, _ }
2125
import sbt.Keys._
2226
import sbtprotoc.ProtocPlugin
@@ -27,14 +31,52 @@ object AkkaserverlessPlugin extends AutoPlugin {
2731
override def trigger = allRequirements
2832
override def requires = ProtocPlugin
2933

34+
trait Keys { _: autoImport.type =>
35+
val generateUnmanaged = taskKey[Unit](
36+
"Generate \"unmanaged\" akkaserverless scaffolding code based on the available .proto definitions.\n" +
37+
"These are the source files that are placed in the source tree, and after initial generation should typically be maintained by the user.\n" +
38+
"Files that already exist they are not re-generated.")
39+
val temporaryUnmanagedDirectory = settingKey[File]("Directory to generate 'unmanaged' sources into")
40+
}
41+
object autoImport extends Keys
42+
import autoImport._
43+
3044
override def projectSettings: Seq[sbt.Setting[_]] = Seq(
3145
libraryDependencies ++= Seq(
3246
"com.akkaserverless" % "akkaserverless-sdk-protocol" % "0.7.0-beta.18" % "protobuf",
3347
"com.google.protobuf" % "protobuf-java" % "3.17.3" % "protobuf"),
3448
Compile / PB.targets +=
3549
gen(Seq(AkkaserverlessGenerator.enableDebug)) -> (Compile / sourceManaged).value / "akkaserverless",
50+
Compile / temporaryUnmanagedDirectory := (Compile / baseDirectory).value / "target" / "akkaserverless-unmanaged",
51+
Compile / PB.targets +=
52+
genUnmanaged(Seq(AkkaserverlessGenerator.enableDebug)) -> (Compile / temporaryUnmanagedDirectory).value,
3653
Test / PB.protoSources ++= (Compile / PB.protoSources).value,
3754
Test / PB.targets +=
38-
genTests(Seq(AkkaserverlessGenerator.enableDebug)) -> (Test / sourceManaged).value / "akkaserverless")
55+
genTests(Seq(AkkaserverlessGenerator.enableDebug)) -> (Test / sourceManaged).value / "akkaserverless",
56+
Compile / generateUnmanaged := {
57+
// Make sure generation has happened
58+
(Compile / PB.generate).value
59+
// Then copy over any new generated unmanaged sources
60+
copyIfNotExist(
61+
java.nio.file.Paths.get((Compile / temporaryUnmanagedDirectory).value.toURI),
62+
Paths.get((Compile / sourceDirectory).value.toURI).resolve("scala"))
63+
},
64+
Compile / managedSources :=
65+
(Compile / managedSources).value.filter(s => !isIn(s, (Compile / temporaryUnmanagedDirectory).value)))
66+
67+
def isIn(file: File, dir: File): Boolean =
68+
Paths.get(file.toURI).startsWith(Paths.get(dir.toURI))
3969

70+
private def copyIfNotExist(from: Path, to: Path): Unit = {
71+
java.nio.file.Files
72+
.walk(from)
73+
.filter(Files.isRegularFile(_))
74+
.forEach(file => {
75+
val target = to.resolve(from.relativize(file))
76+
if (!Files.exists(target)) {
77+
Files.createDirectories(target.getParent)
78+
Files.copy(file, target)
79+
}
80+
})
81+
}
4082
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import com.akkaserverless.sbt.AkkaserverlessPlugin.autoImport.generateUnmanaged
2+
3+
Compile / compile := {
4+
// Make sure 'generateUnmanaged' is executed on each compile, to generate scaffolding code for
5+
// newly-introduced concepts.
6+
// After initial generation they are to be maintained manually and will not be overwritten.
7+
(Compile / generateUnmanaged).value
8+
(Compile / compile).value
9+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
> compile
2-
$ exists target/scala-2.12/src_managed/main/akkaserverless/foo/bar/Baz.scala
2+
$ exists src/main/scala/foo/bar/Baz.scala
3+
$ exists target/scala-2.12/src_managed/main/akkaserverless/foo/bar/AbstractBaz.scala
34
> Test/compile
45
$ exists target/scala-2.12/src_managed/test/akkaserverless/foo/bar/BazSpec.scala
56
$ absent target/scala-2.12/src_managed/test/akkaserverless/foo/bar/Baz.scala

0 commit comments

Comments
 (0)