All Downloads are FREE. Search and download functionalities are using the official Maven repository.

protocbridge.ProtocRunner.scala Maven / Gradle / Ivy

The newest version!
package protocbridge

import java.nio.file.Files
import scala.io.Source
import scala.sys.process.Process
import scala.sys.process.ProcessLogger

abstract class ProtocRunner[ExitCode] {
  self =>
  def run(args: Seq[String], extraEnv: Seq[(String, String)]): ExitCode

  /** Returns a new ProtocRunner that is executed after this value. The exit
    * codes are combined into a tuple.
    */
  def zip[E](
      other: ProtocRunner[E]
  ): ProtocRunner[(ExitCode, E)] = ProtocRunner.fromFunction {
    (args, extraEnv) => (run(args, extraEnv), other.run(args, extraEnv))
  }

  /** Returns a new ProtocRunner that maps the exit code of this runner. */
  def map[E](f: ExitCode => E): ProtocRunner[E] = ProtocRunner.fromFunction {
    (args, extraEnv) => f(run(args, extraEnv))
  }
}

object ProtocRunner {

  /** Makes a ProtocRunner that runs the given command line, but discards the
    * extra environment. Exists only for backwards compatility.
    */
  private[protocbridge] def apply[A](f: Seq[String] => A): ProtocRunner[A] =
    new ProtocRunner[A] {
      def run(args: Seq[String], extraEnv: Seq[(String, String)]): A = f(args)
    }

  private[this] def detectedOs: String =
    SystemDetector.normalizedOs(SystemDetector.detectedClassifier())

  def fromFunction[A](
      f: (Seq[String], Seq[(String, String)]) => A
  ): ProtocRunner[A] =
    new ProtocRunner[A] {
      def run(args: Seq[String], extraEnv: Seq[(String, String)]): A =
        f(args, extraEnv)
    }

  def maybeNixDynamicLinker(): Option[String] =
    detectedOs match {
      case "linux" =>
        sys.env.get("NIX_CC").map { nixCC =>
          val source = Source.fromFile(nixCC + "/nix-support/dynamic-linker")
          val linker = source.mkString.trim()
          source.close()
          linker
        }
      case _ =>
        None
    }

  // 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.
  def maybeNixDynamicLinker(executable: String): Option[String] =
    maybeNixDynamicLinker().filter { linker =>
      Process(command = Seq(linker, "--verify", executable)).! == 0
    }

  def apply(executable: String): ProtocRunner[Int] = ProtocRunner.fromFunction {
    case (args, extraEnv) =>
      Process(
        command =
          (maybeNixDynamicLinker(executable).toSeq :+ executable) ++ args,
        cwd = None,
        extraEnv: _*
      ).!
  }

  // Transforms the given protoc runner to a new runner that writes the
  // options into a temporary file and passes the file to `protoc` as an `@`
  // parameter.
  def withParametersAsFile[T](underlying: ProtocRunner[T]): ProtocRunner[T] =
    fromFunction { (args, extraEnv) =>
      {
        val argumentFile = Files.createTempFile("protoc-args-", ".txt")
        try {
          val writer = Files.newBufferedWriter(argumentFile)

          try {
            args.foreach { arg =>
              writer.write(arg)
              writer.write('\n')
            }
          } finally {
            writer.close()
          }

          val fileArgument = s"@${argumentFile.toString}"
          underlying.run(Seq(fileArgument), extraEnv)
        } finally {
          Files.delete(argumentFile)
        }
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy