mirror of https://github.com/sbt/sbt.git
Small refacto in Bootstrap
This commit is contained in:
parent
962debc050
commit
cf1e0a0a5a
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue