From 5c3036e57a50a4af810985c27a1e558a76b69201 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 22 Jan 2024 13:22:18 +0100 Subject: [PATCH] windows: write arguments to protoc to a file --- .../scala/protocbridge/ProtocRunner.scala | 46 ++++++++++++++++--- .../protocbridge/ProtocCacheCoursier.scala | 8 ++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/bridge/src/main/scala/protocbridge/ProtocRunner.scala b/bridge/src/main/scala/protocbridge/ProtocRunner.scala index 771abd2..2000f22 100644 --- a/bridge/src/main/scala/protocbridge/ProtocRunner.scala +++ b/bridge/src/main/scala/protocbridge/ProtocRunner.scala @@ -1,5 +1,6 @@ package protocbridge +import java.nio.file.Files import scala.io.Source import scala.sys.process.Process import scala.sys.process.ProcessLogger @@ -57,6 +58,37 @@ object ProtocRunner { None } + def maybeBoxArgsInFile[T](args: Seq[String])(withArgs: Seq[String] => T): T = + detectedOs match { + case "windows" => + // The default command line length limit is 32767, which we might exceed. + // See also https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553 + // As of protobuf v3.5.0, you can pass arguments via a file instead with @. + // Arguments in the file are delimited by a newline and not escaped in any way. + val argumentFile = Files.createTempFile("scalapb-arguments-", ".txt") + + try { + val writer = Files.newBufferedWriter(argumentFile) + + try { + for (arg <- args) { + writer.write(arg) + writer.write("\n") + } + } finally { + writer.close() + } + + val fileArgument = s"@${argumentFile.toString}" + withArgs(Seq(fileArgument)) + } finally { + Files.delete(argumentFile) + } + case _ => + // No special handling: just use the arguments as-is. + withArgs(args) + } + // This version of maybeNixDynamicLinker() finds ld-linux and also uses it // to verify that the executable is dynamic. Newer version (>=3.23.0) of // protoc are static, and thus do not load with ld-linux. @@ -67,11 +99,13 @@ object ProtocRunner { def apply(executable: String): ProtocRunner[Int] = ProtocRunner.fromFunction { case (args, extraEnv) => - Process( - command = - (maybeNixDynamicLinker(executable).toSeq :+ executable) ++ args, - cwd = None, - extraEnv: _* - ).! + maybeBoxArgsInFile(args) { args => + Process( + command = + (maybeNixDynamicLinker(executable).toSeq :+ executable) ++ args, + cwd = None, + extraEnv: _* + ).! + } } } diff --git a/protoc-cache-coursier/src/main/scala/protocbridge/ProtocCacheCoursier.scala b/protoc-cache-coursier/src/main/scala/protocbridge/ProtocCacheCoursier.scala index 3ee8ad0..efbd42d 100644 --- a/protoc-cache-coursier/src/main/scala/protocbridge/ProtocCacheCoursier.scala +++ b/protoc-cache-coursier/src/main/scala/protocbridge/ProtocCacheCoursier.scala @@ -24,9 +24,11 @@ object CoursierProtocCache { val protoc = getProtoc(version).getAbsolutePath() - val cmd = - (ProtocRunner.maybeNixDynamicLinker(protoc).toSeq :+ protoc) ++ args - Process(command = cmd, cwd = None, extraEnv: _*).! + ProtocRunner.maybeBoxArgsInFile(args) { args => + val cmd = + (ProtocRunner.maybeNixDynamicLinker(protoc).toSeq :+ protoc) ++ args + Process(command = cmd, cwd = None, extraEnv: _*).! + } } private[this] def download(tmpDir: File, dep: Dependency): Future[File] = {