Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow passing the same PluginGenerator several times #158

Merged
merged 2 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 30 additions & 35 deletions bridge/src/main/scala/protocbridge/ProtocBridge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,33 +36,46 @@ object ProtocBridge {
classLoader: Artifact => ClassLoader
): ExitCode = {

// The same JvmGenerator might be passed several times, but requires separate frontends
val targetsSuffixed = targets.zipWithIndex.map {
case (t @ Target(gen: JvmGenerator, _, _), i) =>
t.copy(generator = gen.copy(name = s"${gen.name}_$i"))
case (t @ Target(gen: SandboxedJvmGenerator, _, _), i) =>
val codeGen =
gen.resolver(classLoader(gen.artifact))
t.copy(generator =
JvmGenerator(name = codeGen.name + s"_$i", gen = codeGen)
)
case (t, _) => t
}
// The same JvmGenerator or PluginGenerator might be passed several times with different options
val targetsSuffixed =
targets
.groupBy(_.generator.name)
.values
.flatMap { targets =>
// don't add suffix if we have only one generator for that name
if (targets.length == 1) targets
else {
targets.zipWithIndex.map {
case (t @ Target(gen: JvmGenerator, _, _), i) =>
t.copy(generator = gen.copy(name = s"${gen.name}_$i"))
case (t @ Target(gen: SandboxedJvmGenerator, _, _), i) =>
val codeGen =
gen.resolver(classLoader(gen.artifact))
t.copy(generator =
JvmGenerator(name = codeGen.name + s"_$i", gen = codeGen)
)
case (t @ Target(gen: PluginGenerator, _, _), i) =>
t.copy(generator = gen.copy(name = s"${gen.name}_$i"))
case (t, _) => t
}
}
}
.toSeq

val namedGenerators: Seq[(String, ProtocCodeGenerator)] =
targetsSuffixed.collect { case Target(gen: JvmGenerator, _, _) =>
(gen.name, gen.gen)
}

val cmdLine: Seq[String] = pluginArgs(targets) ++ targetsSuffixed.flatMap {
p =>
val cmdLine: Seq[String] =
pluginArgs(targetsSuffixed) ++ (targetsSuffixed).flatMap { p =>
val maybeOptions =
if (p.options.isEmpty) Nil
else {
s"--${p.generator.name}_opt=${p.options.mkString(",")}" :: Nil
}
s"--${p.generator.name}_out=${p.outputPath.getAbsolutePath}" :: maybeOptions
} ++ params
} ++ params

runWithGenerators(protoc, namedGenerators, cmdLine, pluginFrontend)
}
Expand Down Expand Up @@ -96,28 +109,10 @@ object ProtocBridge {
PluginFrontend.newInstance
)

private def pluginArgs(targets: Seq[Target]): Seq[String] = {
val pluginsAndPaths: Seq[(String, String)] = targets.collect {
case Target(PluginGenerator(pluginName, _, Some(pluginPath)), _, _) =>
(pluginName, pluginPath)
}.distinct

val pluginsWithDifferentPaths =
pluginsAndPaths.groupBy(_._1).values.collect {
case (pluginName, _) :: rest if rest.nonEmpty => pluginName
}

if (pluginsWithDifferentPaths.nonEmpty) {
throw new RuntimeException(
"Different paths found for the plugin: " + pluginsWithDifferentPaths
.mkString(",")
)
}

pluginsAndPaths.map { case (name, path) =>
private def pluginArgs(targets: Seq[Target]): Seq[String] =
targets.collect { case Target(PluginGenerator(name, _, Some(path)), _, _) =>
s"--plugin=protoc-gen-$name=$path"
}
}

def runWithGenerators[ExitCode](
protoc: ProtocRunner[ExitCode],
Expand Down
39 changes: 14 additions & 25 deletions bridge/src/test/scala/protocbridge/ProtocBridgeSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,36 +90,25 @@ class ProtocBridgeSpec extends AnyFlatSpec with Matchers {
run(
Seq(
Target(gens.plugin("foo", "/path/to/plugin"), TmpPath),
Target(gens.plugin("foo", "/path/to/plugin"), TmpPath1),
Target(gens.plugin("foo", "/otherpath/to/plugin"), TmpPath1),
Target(gens.plugin("foo"), TmpPath2),
Target(gens.plugin("bar"), TmpPath)
)
) must be(
Seq(
"--plugin=protoc-gen-foo=/path/to/plugin",
s"--foo_out=$TmpPath",
s"--foo_out=$TmpPath1",
s"--foo_out=$TmpPath2",
"--plugin=protoc-gen-foo_0=/path/to/plugin",
"--plugin=protoc-gen-foo_1=/otherpath/to/plugin",
s"--foo_0_out=$TmpPath",
s"--foo_1_out=$TmpPath1",
s"--foo_2_out=$TmpPath2",
s"--bar_out=$TmpPath"
)
)
}

it should "not allow ambigious paths for plugins" in {
intercept[RuntimeException] {
run(
Seq(
Target(gens.plugin("foo", "/path/to/plugin"), TmpPath1),
Target(gens.plugin("foo", "/other/path/to/plugin"), TmpPath1)
)
)
}.getMessage() must be("Different paths found for the plugin: foo")
}
Comment on lines -108 to -117
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't tell if this was a safeguard or something unsupported?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing --protoc-gen-foo=path1 and --protoc-gen-foo=path2 is ambiguous, which one should protoc invoke for --foo_out=/dir ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess that's a limitation of the current implementation, as this PR handles it by giving unique names to each target

run(
Seq(
Target(gens.plugin("foo", "/path/to/plugin"), TmpPath),
Target(gens.plugin("foo", "/otherpath/to/plugin"), TmpPath1),
Target(gens.plugin("foo"), TmpPath2),
Target(gens.plugin("bar"), TmpPath)
)
) must be(
Seq(
"--plugin=protoc-gen-foo_0=/path/to/plugin",
"--plugin=protoc-gen-foo_1=/otherpath/to/plugin",
s"--foo_0_out=$TmpPath",
s"--foo_1_out=$TmpPath1",
s"--foo_2_out=$TmpPath2",
s"--bar_out=$TmpPath"
)
)
. In other words, the current implementation in this PR does not leverage the fact that the same plugin may have different outputs, in order to support different options for each output. We could continue using that feature as long as options are the same across outputs, but it would make the implementation harder, and I don't know if there is any gain to that?

Copy link
Contributor Author

@bjaglin bjaglin Jan 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added 8f2f6a9 75124d5 ede30d3 d37025e to showcase this


val DefineFlag = "--plugin=protoc-gen-jvm_(.*?)=null".r
val UseFlag = s"--jvm_(.*?)_out=${Pattern.quote(TmpPath.toString)}".r
val UseFlagParams =
s"--jvm_(.*?)_opt=x,y".r
val DefineFlag = "--plugin=protoc-gen-(.*?)=null".r
val UseFlag = s"--(.*?)_out=${Pattern.quote(TmpPath.toString)}".r
val UseFlagParams = s"--(.*?)_opt=x,y".r

it should "allow using FooBarGen" in {
run(Seq(Target(FoobarGen, TmpPath))) match {
Expand All @@ -142,13 +131,13 @@ class ProtocBridgeSpec extends AnyFlatSpec with Matchers {
) must be(
Seq(
"--plugin=protoc-gen-fff_0=null",
"--plugin=protoc-gen-jvm_1=null",
"--plugin=protoc-gen-fff_2=null",
"--plugin=protoc-gen-fff_1=null",
"--plugin=protoc-gen-jvm=null",
s"--fff_0_out=$TmpPath",
s"--fff_0_opt=x,y",
s"--jvm_1_out=$TmpPath1",
s"--fff_2_out=$TmpPath2",
s"--fff_2_opt=foo,bar"
s"--fff_1_out=$TmpPath2",
s"--fff_1_opt=foo,bar",
s"--jvm_out=$TmpPath1"
)
)
}
Expand Down