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

scala.cli.commands.github.LibSodiumJni.scala Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package scala.cli.commands.github

import coursier.cache.{ArchiveCache, Cache, CacheLogger}
import coursier.core.Type
import coursier.util.Task

import java.io.InputStream
import java.nio.charset.StandardCharsets
import java.util.Locale

import scala.build.EitherCps.{either, value}
import scala.build.Logger
import scala.build.errors.BuildException
import scala.build.internal.{Constants, FetchExternalBinary}
import scala.build.internals.EnvVar
import scala.cli.internal.Constants as CliConstants
import scala.util.Properties
import scala.util.control.NonFatal

object LibSodiumJni {

  private def isGraalvmNativeImage: Boolean =
    sys.props.contains("org.graalvm.nativeimage.imagecode")

  private def launcherKindOpt = {
    val path   = CliConstants.launcherTypeResourcePath
    val resUrl = Thread.currentThread.getContextClassLoader.getResource(path)
    if (resUrl == null) None
    else {
      var is: InputStream = null
      try {
        is = resUrl.openStream()
        Some(new String(resUrl.openStream().readAllBytes(), StandardCharsets.UTF_8))
      }
      finally
        if (is != null)
          is.close()
    }
  }

  private def libsodiumVersion    = Constants.libsodiumVersion
  private def libsodiumjniVersion = Constants.libsodiumjniVersion

  private def archiveUrlAndPath() =
    if (Properties.isLinux && launcherKindOpt.contains("static"))
      // Should actually be unused, as we statically link libsodium from the static launcher
      // Keeping it just-in-case. This could be useful from a musl-based JVM.
      (
        s"https://dl-cdn.alpinelinux.org/alpine/v3.15/main/x86_64/libsodium-$libsodiumVersion-r0.apk",
        os.rel / "usr" / "lib" / "libsodium.so.23.3.0" // FIXME Could this change?
      )
    else {
      val condaPlatform = FetchExternalBinary.condaPlatform
      // FIXME These suffixes seem to be hashes, and seem to change at every version…
      // We'd need a way to find them automatically.
      val suffix = condaPlatform match {
        case "linux-64"      => "-h36c2ea0_1"
        case "linux-aarch64" => "-hb9de7d4_1"
        case "osx-64"        => "-hbcb3906_1"
        case "osx-arm64"     => "-h27ca646_1"
        case "win-64"        => "-h62dcd97_1"
        case other           => sys.error(s"Unrecognized conda platform $other")
      }
      val relPath = condaPlatform match {
        case "linux-64"      => os.rel / "lib" / "libsodium.so"
        case "linux-aarch64" => os.rel / "lib" / "libsodium.so"
        case "osx-64"        => os.rel / "lib" / "libsodium.dylib"
        case "osx-arm64"     => os.rel / "lib" / "libsodium.dylib"
        case "win-64"        => os.rel / "Library" / "bin" / "libsodium.dll"
        case other           => sys.error(s"Unrecognized conda platform $other")
      }
      (
        s"https://anaconda.org/conda-forge/libsodium/$libsodiumVersion/download/$condaPlatform/libsodium-$libsodiumVersion$suffix.tar.bz2",
        relPath
      )
    }

  private def jniLibArtifact(cache: Cache[Task]) = {

    import dependency._
    import scala.build.internal.Util.DependencyOps

    val classifier = FetchExternalBinary.platformSuffix(supportsMusl = false)
    val ext =
      if (Properties.isLinux) "so"
      else if (Properties.isMac) "dylib"
      else if (Properties.isWin) "dll"
      else sys.error(s"Unrecognized operating system: ${sys.props("os.name")}")

    val dep =
      dep"org.virtuslab.scala-cli:libsodiumjni:$libsodiumjniVersion,intransitive,classifier=$classifier,ext=$ext,type=$ext"
    val fetch = coursier.Fetch()
      .addDependencies(dep.toCs)
      .addArtifactTypes(Type(ext))
    val files = cache.loggerOpt.getOrElse(CacheLogger.nop).use {
      try fetch.run()
      catch {
        case NonFatal(e) =>
          throw new Exception(e)
      }
    }
    files match {
      case Seq()     => sys.error(s"Cannot find $dep")
      case Seq(file) => file
      case other     => sys.error(s"Unexpectedly got too many files while resolving $dep: $other")
    }
  }

  def init(
    cache: Cache[Task],
    archiveCache: ArchiveCache[Task],
    logger: Logger
  ): Either[BuildException, Unit] = either {

    val allStaticallyLinked =
      Properties.isWin && isGraalvmNativeImage ||
      Properties.isLinux && launcherKindOpt.contains("static")

    if (!allStaticallyLinked) {
      val (archiveUrl, pathInArchive) = archiveUrlAndPath()
      val sodiumLibOpt = value {
        FetchExternalBinary.fetchLauncher(
          archiveUrl,
          changing = false,
          archiveCache,
          logger,
          "",
          launcherPathOpt = Some(pathInArchive),
          makeExecutable = false
        )
      }

      val f = jniLibArtifact(cache)

      sodiumLibOpt match {
        case Some(sodiumLib) =>
          System.load(sodiumLib.toString)
        case None =>
          val allow = EnvVar.ScalaCli.allowSodiumJni.valueOpt
            .map(_.toLowerCase(Locale.ROOT))
            .forall {
              case "false" | "0" => false
              case _             => true
            }
          if (allow)
            System.loadLibrary("sodium")
          else
            value(Left(new LibSodiumNotFound(archiveUrl)))
      }

      libsodiumjni.internal.LoadLibrary.initialize(f.toString)
    }

    libsodiumjni.Sodium.init()
  }

  final class LibSodiumNotFound(url: String) extends BuildException(s"libsodium: $url not found")

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy