From cf1e0a0a5a9c49567adafa448a3606be9b96c725 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 30 Apr 2018 00:02:02 +0200 Subject: [PATCH] Small refacto in Bootstrap --- .../scala-2.12/coursier/cli/Bootstrap.scala | 425 ++++++++++-------- 1 file changed, 228 insertions(+), 197 deletions(-) diff --git a/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala b/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala index 500a88977..6e2072a1e 100644 --- a/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala +++ b/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala @@ -17,6 +17,213 @@ import scala.collection.JavaConverters._ object Bootstrap extends CaseApp[BootstrapOptions] { + private def createNativeBootstrap( + options: BootstrapOptions, + helper: Helper, + mainClass: String + ): Unit = { + + val files = helper.fetch( + sources = false, + javadoc = false, + artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false) + ) + + val log: String => Unit = + if (options.options.common.verbosityLevel >= 0) + s => Console.err.println(s) + else + _ => () + + val tmpDir = new File(options.options.target) + + try { + coursier.extra.Native.create( + mainClass, + files, + new File(options.options.output), + tmpDir, + log, + verbosity = options.options.common.verbosityLevel + ) + } finally { + if (!options.options.keepTarget) + coursier.extra.Native.deleteRecursive(tmpDir) + } + } + + private def createJarBootstrap(javaOpts: Seq[String], output: File, content: Array[Byte]): Unit = { + + val javaCmd = Seq("java") ++ + javaOpts + // escaping possibly a bit loose :-| + .map(s => "'" + s.replace("'", "\\'") + "'") ++ + Seq( + "-jar", + "\"$0\"", + "\"$@\"" + ) + + val shellPreamble = Seq( + "#!/usr/bin/env sh", + "exec " + javaCmd.mkString(" ") + ).mkString("", "\n", "\n") + + try Files.write(output.toPath, shellPreamble.getBytes(UTF_8) ++ content) + catch { case e: IOException => + Console.err.println(s"Error while writing $output${Option(e.getMessage).fold("")(" (" + _ + ")")}") + sys.exit(1) + } + + try { + val perms = Files.getPosixFilePermissions(output.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( + output.toPath, + newPerms.asJava + ) + } catch { + case e: UnsupportedOperationException => + // Ignored + case e: IOException => + Console.err.println( + s"Error while making $output executable" + + Option(e.getMessage).fold("")(" (" + _ + ")") + ) + sys.exit(1) + } + } + + private def createOneJarLikeJarBootstrap( + options: BootstrapOptions, + helper: Helper, + mainClass: String, + javaOpts: Seq[String], + urls: Seq[String], + files: Seq[File], + output: File + ): Unit = { + + val bootstrapJar = + Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match { + case Some(is) => FileUtil.readFully(is) + case None => + Console.err.println(s"Error: bootstrap JAR not found") + sys.exit(1) + } + + val isolatedDeps = options.options.isolated.isolatedDeps(options.options.common.scalaVersion) + + val (_, isolatedArtifactFiles) = + options.options.isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) { + case ((done, acc), target) => + + // TODO Add non regression test checking that optional artifacts indeed land in the isolated loader URLs + + val m = helper.fetchMap( + sources = false, + javadoc = false, + artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false), + subset = isolatedDeps.getOrElse(target, Seq.empty).toSet + ) + + val (done0, subUrls, subFiles) = + if (options.options.standalone) { + val subFiles0 = m.values.toSeq + (done, Nil, subFiles0) + } else { + val filteredSubArtifacts = m.keys.toSeq.diff(done) + (done ++ filteredSubArtifacts, filteredSubArtifacts, Nil) + } + + val updatedAcc = acc + (target -> (subUrls, subFiles)) + + (done0, updatedAcc) + } + + 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(FileUtil.readFully(new FileInputStream(f))) + outputZip.closeEntry() + } + + putStringEntry("bootstrap-jar-urls", urls.mkString("\n")) + + if (options.options.isolated.anyIsolatedDep) { + putStringEntry("bootstrap-isolation-ids", options.options.isolated.targets.mkString("\n")) + + for (target <- options.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() + + createJarBootstrap( + javaOpts, + output, + buffer.toByteArray + ) + } + def run(options: BootstrapOptions, args: RemainingArgs): Unit = { val helper = new Helper( @@ -38,36 +245,9 @@ object Bootstrap extends CaseApp[BootstrapOptions] { else options.options.mainClass - if (options.options.native) { - - val files = helper.fetch( - sources = false, - javadoc = false, - artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false) - ) - - val log: String => Unit = - if (options.options.common.verbosityLevel >= 0) - s => Console.err.println(s) - else - _ => () - - val tmpDir = new File(options.options.target) - - try { - coursier.extra.Native.create( - mainClass, - files, - output0, - tmpDir, - log, - verbosity = options.options.common.verbosityLevel - ) - } finally { - if (!options.options.keepTarget) - coursier.extra.Native.deleteRecursive(tmpDir) - } - } else { + if (options.options.native) + createNativeBootstrap(options, helper, mainClass) + else { val (validProperties, wrongProperties) = options.options.property.partition(_.contains("=")) if (wrongProperties.nonEmpty) { @@ -82,177 +262,28 @@ object Bootstrap extends CaseApp[BootstrapOptions] { } } - val bootstrapJar = - Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match { - case Some(is) => FileUtil.readFully(is) - case None => - Console.err.println(s"Error: bootstrap JAR not found") - sys.exit(1) - } - - val isolatedDeps = options.options.isolated.isolatedDeps(options.options.common.scalaVersion) - - val (_, isolatedArtifactFiles) = - options.options.isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) { - case ((done, acc), target) => - - // TODO Add non regression test checking that optional artifacts indeed land in the isolated loader URLs - - val m = helper.fetchMap( - sources = false, - javadoc = false, - artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false), - subset = isolatedDeps.getOrElse(target, Seq.empty).toSet - ) - - val (done0, subUrls, subFiles) = - if (options.options.standalone) { - val subFiles0 = m.values.toSeq - (done, Nil, subFiles0) - } else { - val filteredSubArtifacts = m.keys.toSeq.diff(done) - (done ++ filteredSubArtifacts, filteredSubArtifacts, Nil) - } - - val updatedAcc = acc + (target -> (subUrls, subFiles)) - - (done0, updatedAcc) - } - val (urls, files) = helper.fetchMap( - sources = false, - javadoc = false, - artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false) - ).toList.foldLeft((List.empty[String], List.empty[File])){ - case ((urls, files), (url, file)) => - if (options.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(FileUtil.readFully(new FileInputStream(f))) - outputZip.closeEntry() - } - - putStringEntry("bootstrap-jar-urls", urls.mkString("\n")) - - if (options.options.isolated.anyIsolatedDep) { - putStringEntry("bootstrap-isolation-ids", options.options.isolated.targets.mkString("\n")) - - for (target <- options.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")) + sources = false, + javadoc = false, + artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false) + ).toList.foldLeft((List.empty[String], List.empty[File])){ + case ((urls, files), (url, file)) => + if (options.options.standalone) (urls, file :: files) + else if (url.startsWith("file:/")) (urls, file :: files) + else (url :: urls, files) } - } - 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() - - val javaCmd = Seq("java") ++ - options - .options - .javaOpt - // escaping possibly a bit loose :-| - .map(s => "'" + s.replace("'", "\\'") + "'") ++ - properties0 - .map { - case (k, v) => - // escaping possibly a bit loose :-| - s"'-D$k=${v.replace("'", "\\'")}'" - } ++ - Seq( - "-jar", - "\"$0\"", - "\"$@\"" - ) - - val shellPreamble = Seq( - "#!/usr/bin/env sh", - "exec " + javaCmd.mkString(" ") - ).mkString("", "\n", "\n") - - try Files.write(output0.toPath, 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) - } + createOneJarLikeJarBootstrap( + options, + helper, + mainClass, + options.options.javaOpt ++ + properties0.map { case (k, v) => s"-D$k=$v" }, + urls, + files, + output0 + ) } }