mirror of https://github.com/sbt/sbt.git
Boostrap can now be launched directly
Doesn't require to be launched via a shell
This commit is contained in:
parent
997e3f4a80
commit
3ead95b324
|
|
@ -11,9 +11,10 @@ import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class Bootstrap {
|
public class Bootstrap {
|
||||||
|
|
||||||
|
|
@ -36,10 +37,55 @@ public class Bootstrap {
|
||||||
return buffer.toByteArray();
|
return buffer.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
final static String usage = "Usage: bootstrap main-class JAR-directory JAR-URLs...";
|
static String[] readJarUrls() throws IOException {
|
||||||
|
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||||
|
InputStream is = loader.getResourceAsStream("bootstrap-jar-urls");
|
||||||
|
byte[] rawContent = readFullySync(is);
|
||||||
|
String content = new String(rawContent, "UTF-8");
|
||||||
|
return content.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
final static int concurrentDownloadCount = 6;
|
final static int concurrentDownloadCount = 6;
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/872272/how-to-reference-another-property-in-java-util-properties/27724276#27724276
|
||||||
|
public static Map<String,String> loadPropertiesMap(InputStream s) throws IOException {
|
||||||
|
final Map<String, String> ordered = new LinkedHashMap<>();
|
||||||
|
//Hack to use properties class to parse but our map for preserved order
|
||||||
|
Properties bp = new Properties() {
|
||||||
|
@Override
|
||||||
|
public synchronized Object put(Object key, Object value) {
|
||||||
|
ordered.put((String)key, (String)value);
|
||||||
|
return super.put(key, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bp.load(s);
|
||||||
|
|
||||||
|
|
||||||
|
final Pattern propertyRegex = Pattern.compile(Pattern.quote("${") + "[^" + Pattern.quote("{[()]}") + "]*" + Pattern.quote("}"));
|
||||||
|
|
||||||
|
final Map<String,String> resolved = new LinkedHashMap<>(ordered.size());
|
||||||
|
|
||||||
|
for (String k : ordered.keySet()) {
|
||||||
|
String value = ordered.get(k);
|
||||||
|
|
||||||
|
Matcher matcher = propertyRegex.matcher(value);
|
||||||
|
|
||||||
|
// cycles would loop indefinitely here :-|
|
||||||
|
while (matcher.find()) {
|
||||||
|
int start = matcher.start(0);
|
||||||
|
int end = matcher.end(0);
|
||||||
|
String subKey = value.substring(start + 2, end - 1);
|
||||||
|
String subValue = resolved.get(subKey);
|
||||||
|
if (subValue == null)
|
||||||
|
subValue = System.getProperty(subKey);
|
||||||
|
value = value.substring(0, start) + subValue + value.substring(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved.put(k, value);
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
|
|
||||||
ThreadFactory threadFactory = new ThreadFactory() {
|
ThreadFactory threadFactory = new ThreadFactory() {
|
||||||
|
|
@ -54,25 +100,15 @@ public class Bootstrap {
|
||||||
|
|
||||||
ExecutorService pool = Executors.newFixedThreadPool(concurrentDownloadCount, threadFactory);
|
ExecutorService pool = Executors.newFixedThreadPool(concurrentDownloadCount, threadFactory);
|
||||||
|
|
||||||
boolean prependClasspath = false;
|
Map<String,String> properties = loadPropertiesMap(Thread.currentThread().getContextClassLoader().getResourceAsStream("bootstrap.properties"));
|
||||||
|
for (Map.Entry<String, String> ent : properties.entrySet()) {
|
||||||
if (args.length > 0 && args[0].equals("-B"))
|
System.setProperty(ent.getKey(), ent.getValue());
|
||||||
prependClasspath = true;
|
|
||||||
|
|
||||||
if (args.length < 2 || (prependClasspath && args.length < 3)) {
|
|
||||||
exit(usage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = 0;
|
String mainClass0 = System.getProperty("bootstrap.mainClass");
|
||||||
if (prependClasspath)
|
String jarDir0 = System.getProperty("bootstrap.jarDir");
|
||||||
offset += 1;
|
|
||||||
|
|
||||||
String mainClass0 = args[offset];
|
boolean prependClasspath = Boolean.parseBoolean(System.getProperty("bootstrap.prependClasspath", "false"));
|
||||||
String jarDir0 = args[offset + 1];
|
|
||||||
|
|
||||||
List<String> remainingArgs = new ArrayList<>();
|
|
||||||
for (int i = offset + 2; i < args.length; i++)
|
|
||||||
remainingArgs.add(args[i]);
|
|
||||||
|
|
||||||
final File jarDir = new File(jarDir0);
|
final File jarDir = new File(jarDir0);
|
||||||
|
|
||||||
|
|
@ -82,17 +118,7 @@ public class Bootstrap {
|
||||||
} else if (!jarDir.mkdirs())
|
} else if (!jarDir.mkdirs())
|
||||||
System.err.println("Warning: cannot create " + jarDir0 + ", continuing anyway.");
|
System.err.println("Warning: cannot create " + jarDir0 + ", continuing anyway.");
|
||||||
|
|
||||||
int splitIdx = remainingArgs.indexOf("--");
|
String[] jarStrUrls = readJarUrls();
|
||||||
List<String> jarStrUrls;
|
|
||||||
List<String> userArgs;
|
|
||||||
|
|
||||||
if (splitIdx < 0) {
|
|
||||||
jarStrUrls = remainingArgs;
|
|
||||||
userArgs = new ArrayList<>();
|
|
||||||
} else {
|
|
||||||
jarStrUrls = remainingArgs.subList(0, splitIdx);
|
|
||||||
userArgs = remainingArgs.subList(splitIdx + 1, remainingArgs.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> errors = new ArrayList<>();
|
List<String> errors = new ArrayList<>();
|
||||||
List<URL> urls = new ArrayList<>();
|
List<URL> urls = new ArrayList<>();
|
||||||
|
|
@ -204,7 +230,8 @@ public class Bootstrap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userArgs0.addAll(userArgs);
|
for (int i = 0; i < args.length; i++)
|
||||||
|
userArgs0.add(args[i]);
|
||||||
|
|
||||||
thread.setContextClassLoader(classLoader);
|
thread.setContextClassLoader(classLoader);
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package coursier
|
package coursier
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import java.io.{ File, IOException }
|
import java.io.{ByteArrayOutputStream, FileOutputStream, File, IOException}
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.{ Files => NIOFiles }
|
import java.nio.file.{ Files => NIOFiles }
|
||||||
import java.nio.file.attribute.PosixFilePermission
|
import java.nio.file.attribute.{FileTime, PosixFilePermission}
|
||||||
|
import java.util.Properties
|
||||||
|
import java.util.zip.{ZipEntry, ZipOutputStream, ZipInputStream, ZipFile}
|
||||||
|
|
||||||
import caseapp._
|
import caseapp._
|
||||||
import coursier.util.ClasspathFilter
|
import coursier.util.ClasspathFilter
|
||||||
|
|
@ -211,9 +213,9 @@ case class Bootstrap(
|
||||||
@ExtraName("b")
|
@ExtraName("b")
|
||||||
prependClasspath: Boolean,
|
prependClasspath: Boolean,
|
||||||
@HelpMessage("Set environment variables in the generated launcher. No escaping is done. Value is simply put between quotes in the launcher preamble.")
|
@HelpMessage("Set environment variables in the generated launcher. No escaping is done. Value is simply put between quotes in the launcher preamble.")
|
||||||
@ValueDescription("NAME=VALUE")
|
@ValueDescription("key=value")
|
||||||
@ExtraName("e")
|
@ExtraName("P")
|
||||||
env: List[String],
|
property: List[String],
|
||||||
@Recurse
|
@Recurse
|
||||||
common: CommonOptions
|
common: CommonOptions
|
||||||
) extends CoursierCommand {
|
) extends CoursierCommand {
|
||||||
|
|
@ -231,24 +233,18 @@ case class Bootstrap(
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (validEnv, wrongEnv) = env.partition(_.contains("="))
|
val (validProperties, wrongProperties) = property.partition(_.contains("="))
|
||||||
if (wrongEnv.nonEmpty) {
|
if (wrongProperties.nonEmpty) {
|
||||||
Console.err.println(s"Wrong -e / --env option(s):\n${wrongEnv.mkString("\n")}")
|
Console.err.println(s"Wrong -P / --property option(s):\n${wrongProperties.mkString("\n")}")
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
val env0 = validEnv.map { s =>
|
val properties0 = validProperties.map { s =>
|
||||||
val idx = s.indexOf('=')
|
val idx = s.indexOf('=')
|
||||||
assert(idx >= 0)
|
assert(idx >= 0)
|
||||||
(s.take(idx), s.drop(idx + 1))
|
(s.take(idx), s.drop(idx + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadDir0 =
|
|
||||||
if (downloadDir.isEmpty)
|
|
||||||
"$HOME/"
|
|
||||||
else
|
|
||||||
downloadDir
|
|
||||||
|
|
||||||
val bootstrapJar =
|
val bootstrapJar =
|
||||||
Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
|
Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
|
||||||
case Some(is) => Cache.readFullySync(is)
|
case Some(is) => Cache.readFullySync(is)
|
||||||
|
|
@ -257,6 +253,32 @@ case class Bootstrap(
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val output0 = new File(output)
|
||||||
|
if (!force && output0.exists()) {
|
||||||
|
Console.err.println(s"Error: $output already exists, use -f option to force erasing it.")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def zipEntries(zipStream: ZipInputStream): Iterator[(ZipEntry, Array[Byte])] =
|
||||||
|
new Iterator[(ZipEntry, Array[Byte])] {
|
||||||
|
var nextEntry = Option.empty[ZipEntry]
|
||||||
|
def update() =
|
||||||
|
nextEntry = Option(zipStream.getNextEntry)
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
def hasNext = nextEntry.nonEmpty
|
||||||
|
def next() = {
|
||||||
|
val ent = nextEntry.get
|
||||||
|
val data = Platform.readFullySync(zipStream)
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
(ent, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val helper = new Helper(common, remainingArgs)
|
val helper = new Helper(common, remainingArgs)
|
||||||
|
|
||||||
val artifacts = helper.res.artifacts
|
val artifacts = helper.res.artifacts
|
||||||
|
|
@ -267,29 +289,57 @@ case class Bootstrap(
|
||||||
if (unrecognized.nonEmpty)
|
if (unrecognized.nonEmpty)
|
||||||
Console.err.println(s"Warning: non HTTP URLs:\n${unrecognized.mkString("\n")}")
|
Console.err.println(s"Warning: non HTTP URLs:\n${unrecognized.mkString("\n")}")
|
||||||
|
|
||||||
val output0 = new File(output)
|
val buffer = new ByteArrayOutputStream()
|
||||||
if (!force && output0.exists()) {
|
|
||||||
Console.err.println(s"Error: $output already exists, use -f option to force erasing it.")
|
val bootstrapZip = new ZipInputStream(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar"))
|
||||||
sys.exit(1)
|
val outputZip = new ZipOutputStream(buffer)
|
||||||
|
|
||||||
|
for ((ent, data) <- zipEntries(bootstrapZip)) {
|
||||||
|
outputZip.putNextEntry(ent)
|
||||||
|
outputZip.write(data)
|
||||||
|
outputZip.closeEntry()
|
||||||
}
|
}
|
||||||
|
|
||||||
val shellPreamble = {
|
val time = FileTime.fromMillis(System.currentTimeMillis())
|
||||||
Seq(
|
|
||||||
"#!/usr/bin/env sh"
|
|
||||||
) ++
|
|
||||||
env0.map { case (k, v) => "export " + k + "=\"" + v + "\"" } ++
|
|
||||||
Seq(
|
|
||||||
"exec java -jar \"$0\" " + (if (prependClasspath) "-B " else "") + "\"" + mainClass + "\" \"" + downloadDir + "\" " + urls.map("\"" + _ + "\"").mkString(" ") + " -- \"$@\"",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
}.mkString("\n")
|
|
||||||
|
|
||||||
try NIOFiles.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ bootstrapJar)
|
val jarListEntry = new ZipEntry("bootstrap-jar-urls")
|
||||||
|
jarListEntry.setCreationTime(time)
|
||||||
|
jarListEntry.setLastAccessTime(time)
|
||||||
|
jarListEntry.setLastModifiedTime(time)
|
||||||
|
|
||||||
|
outputZip.putNextEntry(jarListEntry)
|
||||||
|
outputZip.write(urls.mkString("\n").getBytes("UTF-8"))
|
||||||
|
outputZip.closeEntry()
|
||||||
|
|
||||||
|
val propsEntry = new ZipEntry("bootstrap.properties")
|
||||||
|
propsEntry.setCreationTime(time)
|
||||||
|
propsEntry.setLastAccessTime(time)
|
||||||
|
propsEntry.setLastModifiedTime(time)
|
||||||
|
|
||||||
|
val properties = new Properties()
|
||||||
|
properties.setProperty("bootstrap.mainClass", mainClass)
|
||||||
|
properties.setProperty("bootstrap.jarDir", downloadDir)
|
||||||
|
properties.setProperty("bootstrap.prependClasspath", prependClasspath.toString)
|
||||||
|
|
||||||
|
outputZip.putNextEntry(propsEntry)
|
||||||
|
properties.store(outputZip, "")
|
||||||
|
outputZip.closeEntry()
|
||||||
|
|
||||||
|
outputZip.close()
|
||||||
|
|
||||||
|
|
||||||
|
val shellPreamble = Seq(
|
||||||
|
"#!/usr/bin/env sh",
|
||||||
|
"exec java -jar \"$0\" \"$@\""
|
||||||
|
).mkString("", "\n", "\n")
|
||||||
|
|
||||||
|
try NIOFiles.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ buffer.toByteArray)
|
||||||
catch { case e: IOException =>
|
catch { case e: IOException =>
|
||||||
Console.err.println(s"Error while writing $output0: ${e.getMessage}")
|
Console.err.println(s"Error while writing $output0: ${e.getMessage}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val perms = NIOFiles.getPosixFilePermissions(output0.toPath).asScala.toSet
|
val perms = NIOFiles.getPosixFilePermissions(output0.toPath).asScala.toSet
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ CACHE_VERSION=v1
|
||||||
com.github.alexarchambault:coursier-cli_2.11:$VERSION \
|
com.github.alexarchambault:coursier-cli_2.11:$VERSION \
|
||||||
-V com.github.alexarchambault:coursier_2.11:$VERSION \
|
-V com.github.alexarchambault:coursier_2.11:$VERSION \
|
||||||
-V com.github.alexarchambault:coursier-cache_2.11:$VERSION \
|
-V com.github.alexarchambault:coursier-cache_2.11:$VERSION \
|
||||||
-D "\$HOME/.coursier/bootstrap/$VERSION" \
|
-D "\${user.home}/.coursier/bootstrap/$VERSION" \
|
||||||
-r https://repo1.maven.org/maven2 \
|
-r https://repo1.maven.org/maven2 \
|
||||||
-r https://oss.sonatype.org/content/repositories/releases \
|
-r https://oss.sonatype.org/content/repositories/releases \
|
||||||
-r https://oss.sonatype.org/content/repositories/snapshots \
|
-r https://oss.sonatype.org/content/repositories/snapshots \
|
||||||
-b \
|
-b \
|
||||||
-f -o coursier \
|
-f -o coursier \
|
||||||
-M coursier.cli.Coursier \
|
-M coursier.cli.Coursier \
|
||||||
-e COURSIER_CACHE="\$HOME/.coursier/cache/$CACHE_VERSION"
|
-P coursier.cache="\${user.home}/.coursier/cache/$CACHE_VERSION"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue