Skip to content

Commit

Permalink
Automatically add package-scoped options from manifest
Browse files Browse the repository at this point in the history
When a protobuf dependency jar contains a Scalapb-Options-Proto in its
manifest, the mentioned file will get automatically added to the list
of sources for the protoc invocation. This makes ScalaPB aware of
the options used to compile the third-party jar.

See scalapb/scalapb-validate#70
  • Loading branch information
thesamet committed Jan 30, 2021
1 parent 0741fe4 commit ff5f43a
Showing 1 changed file with 49 additions and 7 deletions.
56 changes: 49 additions & 7 deletions src/main/scala/sbtprotoc/ProtocPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package sbtprotoc

import sbt._
import Keys._
import java.io.File
import java.io.{File, FileInputStream, IOException}

import protocbridge.{DescriptorSetGenerator, SandboxedJvmGenerator, Target, ProtocRunner}
import sbt.librarymanagement.{CrossVersion, ModuleID}
Expand All @@ -12,6 +12,7 @@ import sbt.util.CacheImplicits
import sjsonnew.JsonFormat
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport.platformDepsCrossVersion
import java.net.URLClassLoader
import java.util.jar.JarInputStream
import sbt.librarymanagement.DependencyResolution
import protocbridge.{Artifact => BridgeArtifact}
import protocbridge.{SystemDetector => BridgeSystemDetector, FileCache}
Expand Down Expand Up @@ -339,11 +340,16 @@ object ProtocPlugin extends AutoPlugin {
unmanagedSourceDirectories += sourceDirectory.value / "protobuf"
)

case class UnpackedDependencies(mappedFiles: Map[File, Seq[File]]) {
def files: Seq[File] = mappedFiles.values.flatten.toSeq
case class UnpackedDependency(files: Seq[File], optionProtos: Seq[File])

case class UnpackedDependencies(mappedFiles: Map[File, UnpackedDependency]) {
def files: Seq[File] = mappedFiles.values.flatMap(_.files).toSeq
}

private[sbtprotoc] object UnpackedDependencies extends CacheImplicits {
implicit val UnpackedDependencyFormat: JsonFormat[UnpackedDependency] =
caseClassArray(UnpackedDependency.apply _, UnpackedDependency.unapply _)

implicit val UnpackedDependenciesFormat: JsonFormat[UnpackedDependencies] =
caseClassArray(UnpackedDependencies.apply _, UnpackedDependencies.unapply _)
}
Expand Down Expand Up @@ -452,7 +458,7 @@ object ProtocPlugin extends AutoPlugin {
deps: Seq[File],
extractTarget: File,
streams: TaskStreams
): Seq[(File, Seq[File])] = {
): Seq[(File, UnpackedDependency)] = {
def cachedExtractDep(dep: File): Seq[File] = {
val cached = FileFunction.cached(
streams.cacheDirectory / dep.name,
Expand All @@ -470,7 +476,38 @@ object ProtocPlugin extends AutoPlugin {
cached(Set(dep)).toSeq
}

deps.map { dep => dep -> cachedExtractDep(dep) }
deps.map { dep =>
val fileSet = cachedExtractDep(dep)
dep ->
UnpackedDependency(fileSet, getOptionProtos(dep, extractTarget, fileSet))
}
}

// Unpacked proto jars may contain a key in their manifest to tell us about
// the generator options used to generate the sources. The option will be
// sourced into ScalaPB.
def getOptionProtos(jar: File, extractTarget: File, fileSet: Seq[File]): Seq[File] = {
val jin = new JarInputStream(new FileInputStream(jar))
try {
val manifest = jin.getManifest()
val optionProtos = Option(manifest.getMainAttributes().getValue("Scalapb-Options-Proto"))
.map(_.split(',').toSeq)
.getOrElse(Seq.empty)
.map(new File(extractTarget, _))

optionProtos.foreach { optionProto =>
if (!fileSet.contains(optionProto)) {
throw new IOException(
s"Dependency $jar manifest references a non-existant proto $optionProto"
)
}
}

optionProtos

} finally {
jin.close()
}
}

private[this] def isNativePlugin(dep: Attributed[File]): Boolean =
Expand All @@ -487,12 +524,15 @@ object ProtocPlugin extends AutoPlugin {
val log = (key / streams).value.log
val toInclude = (key / includeFilter).value
val toExclude = (key / excludeFilter).value
val optionProtos =
(key / PB.unpackDependencies).value.mappedFiles.values.flatMap(_.optionProtos)
val schemas = (key / PB.protoSources).value
.toSet[File]
.flatMap(srcDir =>
(srcDir ** (toInclude -- toExclude)).get
.map(_.getAbsoluteFile)
)
) ++ optionProtos

// Include Scala binary version like "_2.11" for cross building.
val cacheFile =
(key / streams).value.cacheDirectory / s"protobuf_${scalaBinaryVersion.value}"
Expand Down Expand Up @@ -632,7 +672,9 @@ object ProtocPlugin extends AutoPlugin {
(key / PB.externalSourcePath).value,
(key / streams).value
)
val unpackedDeps = UnpackedDependencies((extractedFiles ++ extractedSrcFiles).toMap)
val unpackedDeps = UnpackedDependencies(
(extractedFiles ++ extractedSrcFiles).toMap
)

// clean up stale files that are no longer pulled by a dependency
val previouslyExtractedFiles = key.previous.map(_.files).toSeq.flatten
Expand Down

0 comments on commit ff5f43a

Please sign in to comment.