2023-01-08 17:13:31 +08:00
|
|
|
/*
|
|
|
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
|
* license agreements; and to You under the Apache License, version 2.0:
|
|
|
|
|
*
|
|
|
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
2023-06-22 14:19:26 +01:00
|
|
|
* This file is part of the Apache Pekko project, which was derived from Akka.
|
2023-01-08 17:13:31 +08:00
|
|
|
*/
|
|
|
|
|
|
2019-01-02 18:55:26 +08:00
|
|
|
/*
|
2022-02-04 12:36:44 +01:00
|
|
|
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
|
2013-09-11 12:52:52 +02:00
|
|
|
*/
|
|
|
|
|
|
2017-10-30 03:13:14 +02:00
|
|
|
import java.io.File
|
|
|
|
|
import java.io.PrintWriter
|
|
|
|
|
|
|
|
|
|
import scala.sys.process._
|
|
|
|
|
|
2013-09-11 12:52:52 +02:00
|
|
|
import sbt._
|
2017-10-30 03:13:14 +02:00
|
|
|
import sbt.util.CacheStoreFactory
|
2013-09-11 12:52:52 +02:00
|
|
|
import Keys._
|
|
|
|
|
|
2021-04-29 11:59:27 +02:00
|
|
|
import sbtassembly.AssemblyKeys._
|
|
|
|
|
|
2013-09-11 12:52:52 +02:00
|
|
|
object Protobuf {
|
2024-01-22 07:15:16 +01:00
|
|
|
lazy val paths = SettingKey[Seq[File]]("protobuf-paths", "The paths that contain *.proto files.")
|
|
|
|
|
lazy val outputPaths =
|
2019-08-15 16:43:19 +01:00
|
|
|
SettingKey[Seq[File]]("protobuf-output-paths", "The paths where to save the generated *.java files.")
|
2024-01-22 07:15:16 +01:00
|
|
|
lazy val importPath = SettingKey[Option[File]](
|
2019-08-15 16:43:19 +01:00
|
|
|
"protobuf-import-path",
|
|
|
|
|
"The path that contain additional *.proto files that can be imported.")
|
2024-01-22 07:15:16 +01:00
|
|
|
lazy val protoc = SettingKey[String]("protobuf-protoc", "The path and name of the protoc executable.")
|
|
|
|
|
lazy val protocVersion = SettingKey[String]("protobuf-protoc-version", "The version of the protoc executable.")
|
|
|
|
|
lazy val generate = TaskKey[Unit]("protobuf-generate", "Compile the protobuf sources and do all processing.")
|
2013-09-11 12:52:52 +02:00
|
|
|
|
|
|
|
|
lazy val settings: Seq[Setting[_]] = Seq(
|
2021-05-25 12:50:51 +02:00
|
|
|
paths := Seq((Compile / sourceDirectory).value, (Test / sourceDirectory).value).map(_ / "protobuf"),
|
|
|
|
|
outputPaths := Seq((Compile / sourceDirectory).value, (Test / sourceDirectory).value).map(_ / "java"),
|
2017-10-03 14:56:23 +02:00
|
|
|
importPath := None,
|
2019-08-15 16:43:19 +01:00
|
|
|
// this keeps intellij happy for files that use the shaded protobuf
|
2024-02-02 16:20:41 +01:00
|
|
|
Compile / unmanagedJars += (LocalProject("protobuf-v3") / Compile / packageBin).value,
|
2013-09-11 12:52:52 +02:00
|
|
|
protoc := "protoc",
|
2024-02-18 08:43:33 +01:00
|
|
|
protocVersion := "3.20.3",
|
2015-02-18 00:15:50 +01:00
|
|
|
generate := {
|
|
|
|
|
val sourceDirs = paths.value
|
|
|
|
|
val targetDirs = outputPaths.value
|
2017-10-30 03:13:14 +02:00
|
|
|
val log = streams.value.log
|
2015-02-18 00:15:50 +01:00
|
|
|
|
|
|
|
|
if (sourceDirs.size != targetDirs.size)
|
2019-08-15 16:43:19 +01:00
|
|
|
sys.error(
|
|
|
|
|
s"Unbalanced number of paths and destination paths!\nPaths: $sourceDirs\nDestination Paths: $targetDirs")
|
2015-02-18 00:15:50 +01:00
|
|
|
|
2019-08-15 16:43:19 +01:00
|
|
|
if (sourceDirs.exists(_.exists)) {
|
2015-02-18 00:15:50 +01:00
|
|
|
val cmd = protoc.value
|
2017-10-30 03:13:14 +02:00
|
|
|
|
2015-02-18 00:15:50 +01:00
|
|
|
checkProtocVersion(cmd, protocVersion.value, log)
|
|
|
|
|
|
|
|
|
|
val base = baseDirectory.value
|
|
|
|
|
val sources = base / "src"
|
|
|
|
|
val targets = target.value
|
|
|
|
|
val cache = targets / "protoc" / "cache"
|
|
|
|
|
|
2019-08-15 16:43:19 +01:00
|
|
|
sourceDirs.zip(targetDirs).map {
|
2019-02-09 15:25:39 +01:00
|
|
|
case (src, dst) =>
|
2019-08-15 16:43:19 +01:00
|
|
|
val relative = src
|
|
|
|
|
.relativeTo(sources)
|
|
|
|
|
.getOrElse(throw new Exception(s"path $src is not a in source tree $sources"))
|
|
|
|
|
.toString
|
2017-10-06 10:30:28 +02:00
|
|
|
val tmp = targets / "protoc" / relative
|
|
|
|
|
IO.delete(tmp)
|
2017-10-03 14:56:23 +02:00
|
|
|
generate(cmd, src, tmp, log, importPath.value)
|
2019-08-15 16:43:19 +01:00
|
|
|
transformDirectory(
|
|
|
|
|
tmp,
|
|
|
|
|
dst,
|
|
|
|
|
_ => true,
|
|
|
|
|
transformFile(
|
2022-11-12 10:21:24 +01:00
|
|
|
_.replace("com.google.protobuf", "org.apache.pekko.protobufv3.internal")
|
2019-08-15 16:43:19 +01:00
|
|
|
// this is the one thing that protobufGenerate doesn't fully qualify and causes
|
|
|
|
|
// api doc generation to fail
|
|
|
|
|
.replace(
|
|
|
|
|
"UnusedPrivateParameter",
|
2022-11-12 10:21:24 +01:00
|
|
|
"org.apache.pekko.protobufv3.internal.GeneratedMessageV3.UnusedPrivateParameter")),
|
2019-08-15 16:43:19 +01:00
|
|
|
cache,
|
|
|
|
|
log)
|
2015-02-18 00:15:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-06 10:30:28 +02:00
|
|
|
})
|
2013-09-11 12:52:52 +02:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
private def callProtoc[T](protoc: String, args: Seq[String], log: Logger, thunk: (ProcessBuilder, Logger) => T): T =
|
2013-09-11 12:52:52 +02:00
|
|
|
try {
|
|
|
|
|
val proc = Process(protoc, args)
|
|
|
|
|
thunk(proc, log)
|
2017-10-06 10:30:28 +02:00
|
|
|
} catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case e: Exception =>
|
2019-08-15 16:43:19 +01:00
|
|
|
throw new RuntimeException("error while executing '%s' with args: %s".format(protoc, args.mkString(" ")), e)
|
2013-09-11 12:52:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def checkProtocVersion(protoc: String, protocVersion: String, log: Logger): Unit = {
|
2019-08-15 16:43:19 +01:00
|
|
|
val res = callProtoc(protoc, Seq("--version"), log,
|
|
|
|
|
{ (p, l) =>
|
|
|
|
|
p !! l
|
|
|
|
|
})
|
2013-09-11 12:52:52 +02:00
|
|
|
val version = res.split(" ").last.trim
|
|
|
|
|
if (version != protocVersion) {
|
2019-08-15 16:43:19 +01:00
|
|
|
sys.error("Wrong protoc version! Expected %s but got %s".format(protocVersion, version))
|
2013-09-11 12:52:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-03 14:56:23 +02:00
|
|
|
private def generate(protoc: String, srcDir: File, targetDir: File, log: Logger, importPath: Option[File]): Unit = {
|
2013-09-11 12:52:52 +02:00
|
|
|
val protoFiles = (srcDir ** "*.proto").get
|
|
|
|
|
if (srcDir.exists)
|
|
|
|
|
if (protoFiles.isEmpty)
|
2019-08-15 16:43:19 +01:00
|
|
|
log.info("Skipping empty source directory %s".format(srcDir))
|
2013-09-11 12:52:52 +02:00
|
|
|
else {
|
|
|
|
|
targetDir.mkdirs()
|
|
|
|
|
|
|
|
|
|
log.info("Generating %d protobuf files from %s to %s".format(protoFiles.size, srcDir, targetDir))
|
2019-08-15 16:43:19 +01:00
|
|
|
protoFiles.foreach { proto =>
|
|
|
|
|
log.info("Compiling %s".format(proto))
|
|
|
|
|
}
|
2013-09-11 12:52:52 +02:00
|
|
|
|
2017-10-03 14:56:23 +02:00
|
|
|
val protoPathArg = importPath match {
|
2019-08-15 16:43:19 +01:00
|
|
|
case None => Nil
|
2017-10-03 14:56:23 +02:00
|
|
|
case Some(p) => Seq("--proto_path", p.absolutePath)
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 16:43:19 +01:00
|
|
|
val exitCode = callProtoc(
|
|
|
|
|
protoc,
|
|
|
|
|
Seq("-I" + srcDir.absolutePath, "--java_out=%s".format(targetDir.absolutePath)) ++
|
|
|
|
|
protoPathArg ++ protoFiles.map(_.absolutePath),
|
|
|
|
|
log,
|
|
|
|
|
{ (p, l) =>
|
|
|
|
|
p ! l
|
|
|
|
|
})
|
2013-09-11 12:52:52 +02:00
|
|
|
if (exitCode != 0)
|
2019-08-15 16:43:19 +01:00
|
|
|
sys.error("protoc returned exit code: %d".format(exitCode))
|
2013-09-11 12:52:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
2017-10-30 03:13:14 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a transformed version of all files in a directory, given a predicate and a transform function for each file. From sbt-site
|
|
|
|
|
*/
|
2019-08-15 16:43:19 +01:00
|
|
|
private def transformDirectory(
|
|
|
|
|
sourceDir: File,
|
|
|
|
|
targetDir: File,
|
|
|
|
|
transformable: File => Boolean,
|
|
|
|
|
transform: (File, File) => Unit,
|
|
|
|
|
cache: File,
|
|
|
|
|
log: Logger): File = {
|
|
|
|
|
val runTransform = FileFunction.cached(CacheStoreFactory(cache), FilesInfo.hash, FilesInfo.exists) {
|
|
|
|
|
(in: ChangeReport[File], out: ChangeReport[File]) =>
|
|
|
|
|
val map = Path.rebase(sourceDir, targetDir)
|
|
|
|
|
if (in.removed.nonEmpty || in.modified.nonEmpty) {
|
|
|
|
|
log.info("Preprocessing directory %s...".format(sourceDir))
|
|
|
|
|
for (source <- in.removed; target <- map(source)) {
|
|
|
|
|
IO.delete(target)
|
2017-10-30 03:13:14 +02:00
|
|
|
}
|
2019-08-15 16:43:19 +01:00
|
|
|
val updated = for (source <- in.modified; target <- map(source)) yield {
|
|
|
|
|
if (source.isFile) {
|
|
|
|
|
if (transformable(source)) transform(source, target)
|
|
|
|
|
else IO.copyFile(source, target)
|
|
|
|
|
}
|
|
|
|
|
target
|
|
|
|
|
}
|
|
|
|
|
log.info("Directory preprocessed: " + targetDir)
|
|
|
|
|
updated
|
|
|
|
|
} else Set.empty
|
2017-10-30 03:13:14 +02:00
|
|
|
}
|
|
|
|
|
val sources = sourceDir.allPaths.get.toSet
|
|
|
|
|
runTransform(sources)
|
|
|
|
|
targetDir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transform a file, line by line.
|
|
|
|
|
*/
|
2019-02-09 15:25:39 +01:00
|
|
|
def transformFile(transform: String => String)(source: File, target: File): Unit = {
|
|
|
|
|
IO.reader(source) { reader =>
|
|
|
|
|
IO.writer(target, "", IO.defaultCharset) { writer =>
|
2017-10-30 03:13:14 +02:00
|
|
|
val pw = new PrintWriter(writer)
|
2019-08-15 16:43:19 +01:00
|
|
|
IO.foreachLine(reader) { line =>
|
|
|
|
|
pw.println(transform(line))
|
|
|
|
|
}
|
2017-10-30 03:13:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-11 12:52:52 +02:00
|
|
|
}
|