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

coursier.cli.Bootstrap.scala Maven / Gradle / Ivy

The newest version!
package coursier
package cli

import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, FileInputStream, IOException}
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
import java.util.Properties
import java.util.zip.{ZipEntry, ZipInputStream, ZipOutputStream}

import caseapp._
import coursier.cli.util.Zip
import coursier.internal.FileUtil

case class Bootstrap(
  @Recurse
    artifactOptions: ArtifactOptions,
  @Recurse
    options: BootstrapOptions
) extends App {

  import scala.collection.JavaConverters._

  val helper = new Helper(
    options.common,
    remainingArgs,
    isolated = options.isolated,
    warnBaseLoaderNotFound = false
  )

  val output0 = new File(options.output)
  if (!options.force && output0.exists()) {
    Console.err.println(s"Error: ${options.output} already exists, use -f option to force erasing it.")
    sys.exit(1)
  }

  val mainClass =
    if (options.mainClass.isEmpty)
      helper.retainedMainClass
    else
      options.mainClass

  if (options.native) {

    val files = helper.fetch(
      sources = false,
      javadoc = false,
      artifactTypes = artifactOptions.artifactTypes(sources = false, javadoc = false)
    )

    val log: String => Unit =
      if (options.common.verbosityLevel >= 0)
        s => Console.err.println(s)
      else
        _ => ()

    val tmpDir = new File(options.target)

    try {
      coursier.extra.Native.create(
        mainClass,
        files,
        output0,
        tmpDir,
        log,
        verbosity = options.common.verbosityLevel
      )
    } finally {
      if (!options.keepTarget)
        coursier.extra.Native.deleteRecursive(tmpDir)
    }
  } else {

    val (validProperties, wrongProperties) = options.property.partition(_.contains("="))
    if (wrongProperties.nonEmpty) {
      Console.err.println(s"Wrong -P / --property option(s):\n${wrongProperties.mkString("\n")}")
      sys.exit(255)
    }

    val properties0 = validProperties.map { s =>
      val idx = s.indexOf('=')
      assert(idx >= 0)
      (s.take(idx), s.drop(idx + 1))
    }

    val bootstrapJar =
      Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
        case Some(is) => Cache.readFullySync(is)
        case None =>
          Console.err.println(s"Error: bootstrap JAR not found")
          sys.exit(1)
      }

    val isolatedDeps = options.isolated.isolatedDeps(options.common.scalaVersion)

    val (_, isolatedArtifactFiles) =
      options.isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) {
        case ((done, acc), target) =>
          val subRes = helper.res.subset(isolatedDeps.getOrElse(target, Nil).toSet)

          val (done0, subUrls, subFiles) =
            if (options.standalone) {
              val subFiles0 = helper.fetch(
                sources = false,
                javadoc = false,
                artifactTypes = artifactOptions.artifactTypes(sources = false, javadoc = false),
                subset = isolatedDeps.getOrElse(target, Seq.empty).toSet
              )

              (done, Nil, subFiles0)
            } else {
              val subArtifacts0 = subRes.dependencyArtifacts.map(_._2)
              val artifactTypes = artifactOptions.artifactTypes(sources = false, javadoc = false)
              val subArtifacts =
                if (artifactTypes("*"))
                  subArtifacts0
                else
                  subArtifacts0.filter(a => artifactTypes(a.`type`))
              val filteredSubArtifacts = subArtifacts.map(_.url).diff(done)
              (done ++ filteredSubArtifacts, filteredSubArtifacts, Nil)
            }

          val updatedAcc = acc + (target -> (subUrls, subFiles))

          (done0, updatedAcc)
      }

    val (urls, files) =
      helper.fetchMap(
        sources = false,
        javadoc = false,
        artifactTypes = artifactOptions.artifactTypes(sources = false, javadoc = false)
      ).toList.foldLeft((List.empty[String], List.empty[File])){
        case ((urls, files), (url, file)) =>
          if (options.standalone) (urls, file :: files)
          else if (url.startsWith("file:/")) (urls, file :: files)
          else (url :: urls, files)
      }

    val isolatedUrls = isolatedArtifactFiles.map { case (k, (v, _)) => k -> v }
    val isolatedFiles = isolatedArtifactFiles.map { case (k, (_, v)) => k -> v }

    val buffer = new ByteArrayOutputStream

    val bootstrapZip = new ZipInputStream(new ByteArrayInputStream(bootstrapJar))
    val outputZip = new ZipOutputStream(buffer)

    for ((ent, data) <- Zip.zipEntries(bootstrapZip)) {
      outputZip.putNextEntry(ent)
      outputZip.write(data)
      outputZip.closeEntry()
    }


    val time = System.currentTimeMillis()

    def putStringEntry(name: String, content: String): Unit = {
      val entry = new ZipEntry(name)
      entry.setTime(time)

      outputZip.putNextEntry(entry)
      outputZip.write(content.getBytes("UTF-8"))
      outputZip.closeEntry()
    }

    def putEntryFromFile(name: String, f: File): Unit = {
      val entry = new ZipEntry(name)
      entry.setTime(f.lastModified())

      outputZip.putNextEntry(entry)
      outputZip.write(Cache.readFullySync(new FileInputStream(f)))
      outputZip.closeEntry()
    }

    putStringEntry("bootstrap-jar-urls", urls.mkString("\n"))

    if (options.isolated.anyIsolatedDep) {
      putStringEntry("bootstrap-isolation-ids", options.isolated.targets.mkString("\n"))

      for (target <- options.isolated.targets) {
        val urls = isolatedUrls.getOrElse(target, Nil)
        val files = isolatedFiles.getOrElse(target, Nil)
        putStringEntry(s"bootstrap-isolation-$target-jar-urls", urls.mkString("\n"))
        putStringEntry(s"bootstrap-isolation-$target-jar-resources", files.map(pathFor).mkString("\n"))
      }
    }

    def pathFor(f: File) = s"jars/${f.getName}"

    for (f <- files)
      putEntryFromFile(pathFor(f), f)

    putStringEntry("bootstrap-jar-resources", files.map(pathFor).mkString("\n"))

    val propsEntry = new ZipEntry("bootstrap.properties")
    propsEntry.setTime(time)

    val properties = new Properties
    properties.setProperty("bootstrap.mainClass", mainClass)

    outputZip.putNextEntry(propsEntry)
    properties.store(outputZip, "")
    outputZip.closeEntry()

    outputZip.close()

    // escaping of  javaOpt  possibly a bit loose :-|
    val shellPreamble = Seq(
      "#!/usr/bin/env sh",
      "exec java -jar " + options.javaOpt.map(s => "'" + s.replace("'", "\\'") + "'").mkString(" ") + " \"$0\" \"$@\""
    ).mkString("", "\n", "\n")

    try FileUtil.write(output0, shellPreamble.getBytes("UTF-8") ++ buffer.toByteArray)
    catch { case e: IOException =>
      Console.err.println(s"Error while writing $output0${Option(e.getMessage).fold("")(" (" + _ + ")")}")
      sys.exit(1)
    }

    try {
      val perms = Files.getPosixFilePermissions(output0.toPath).asScala.toSet

      var newPerms = perms
      if (perms(PosixFilePermission.OWNER_READ))
        newPerms += PosixFilePermission.OWNER_EXECUTE
      if (perms(PosixFilePermission.GROUP_READ))
        newPerms += PosixFilePermission.GROUP_EXECUTE
      if (perms(PosixFilePermission.OTHERS_READ))
        newPerms += PosixFilePermission.OTHERS_EXECUTE

      if (newPerms != perms)
        Files.setPosixFilePermissions(
          output0.toPath,
          newPerms.asJava
        )
    } catch {
      case e: UnsupportedOperationException =>
      // Ignored
      case e: IOException =>
        Console.err.println(
          s"Error while making $output0 executable" +
            Option(e.getMessage).fold("")(" (" + _ + ")")
        )
        sys.exit(1)
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy