mirror of https://github.com/sbt/sbt.git
Add support for standalone launchers in bootstrap command
This commit is contained in:
parent
63d0d7d1c3
commit
58e6375c33
|
|
@ -1,14 +1,9 @@
|
|||
package coursier;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
|
|
@ -39,6 +34,7 @@ public class Bootstrap {
|
|||
}
|
||||
|
||||
final static String defaultURLResource = "bootstrap-jar-urls";
|
||||
final static String defaultJarResource = "bootstrap-jar-resources";
|
||||
final static String isolationIDsResource = "bootstrap-isolation-ids";
|
||||
|
||||
static String[] readStringSequence(String resource) throws IOException {
|
||||
|
|
@ -53,21 +49,16 @@ public class Bootstrap {
|
|||
return content.split("\n");
|
||||
}
|
||||
|
||||
static Map<String, URL[]> readIsolationContexts(File jarDir, String[] isolationIDs) throws IOException {
|
||||
static Map<String, URL[]> readIsolationContexts(File jarDir, String[] isolationIDs, String bootstrapProtocol, ClassLoader loader) throws IOException {
|
||||
final Map<String, URL[]> perContextURLs = new LinkedHashMap<>();
|
||||
|
||||
for (String isolationID: isolationIDs) {
|
||||
String[] contextURLs = readStringSequence("bootstrap-isolation-" + isolationID + "-jar-urls");
|
||||
List<URL> urls = new ArrayList<>();
|
||||
for (String strURL : contextURLs) {
|
||||
URL url = new URL(strURL);
|
||||
File local = localFile(jarDir, url);
|
||||
if (local.exists())
|
||||
urls.add(local.toURI().toURL());
|
||||
else
|
||||
System.err.println("Warning: " + local + " not found.");
|
||||
}
|
||||
perContextURLs.put(isolationID, urls.toArray(new URL[urls.size()]));
|
||||
String[] strUrls = readStringSequence("bootstrap-isolation-" + isolationID + "-jar-urls");
|
||||
String[] resources = readStringSequence("bootstrap-isolation-" + isolationID + "-jar-resources");
|
||||
List<URL> urls = getURLs(strUrls, resources, bootstrapProtocol, loader);
|
||||
List<URL> localURLs = getLocalURLs(urls, jarDir, bootstrapProtocol);
|
||||
|
||||
perContextURLs.put(isolationID, localURLs.toArray(new URL[localURLs.size()]));
|
||||
}
|
||||
|
||||
return perContextURLs;
|
||||
|
|
@ -140,8 +131,7 @@ public class Bootstrap {
|
|||
return new File(jarDir, fileName);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
static List<URL> getLocalURLs(List<URL> urls, final File jarDir, String bootstrapProtocol) {
|
||||
|
||||
ThreadFactory threadFactory = new ThreadFactory() {
|
||||
// from scalaz Strategy.DefaultDaemonThreadFactory
|
||||
|
|
@ -155,59 +145,18 @@ public class Bootstrap {
|
|||
|
||||
ExecutorService pool = Executors.newFixedThreadPool(concurrentDownloadCount, threadFactory);
|
||||
|
||||
System.setProperty("coursier.mainJar", mainJarPath());
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.setProperty("coursier.main.arg-" + i, args[i]);
|
||||
}
|
||||
|
||||
Map<String,String> properties = loadPropertiesMap(Thread.currentThread().getContextClassLoader().getResourceAsStream("bootstrap.properties"));
|
||||
for (Map.Entry<String, String> ent : properties.entrySet()) {
|
||||
System.setProperty(ent.getKey(), ent.getValue());
|
||||
}
|
||||
|
||||
String mainClass0 = System.getProperty("bootstrap.mainClass");
|
||||
String jarDir0 = System.getProperty("bootstrap.jarDir");
|
||||
|
||||
final File jarDir = new File(jarDir0);
|
||||
|
||||
if (jarDir.exists()) {
|
||||
if (!jarDir.isDirectory())
|
||||
exit("Error: " + jarDir0 + " is not a directory");
|
||||
} else if (!jarDir.mkdirs())
|
||||
System.err.println("Warning: cannot create " + jarDir0 + ", continuing anyway.");
|
||||
|
||||
String[] jarStrUrls = readStringSequence(defaultURLResource);
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<URL> urls = new ArrayList<>();
|
||||
|
||||
for (String urlStr : jarStrUrls) {
|
||||
try {
|
||||
URL url = URI.create(urlStr).toURL();
|
||||
urls.add(url);
|
||||
} catch (Exception ex) {
|
||||
String message = urlStr + ": " + ex.getMessage();
|
||||
errors.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
StringBuilder builder = new StringBuilder("Error parsing " + errors.size() + " URL(s):");
|
||||
for (String error: errors) {
|
||||
builder.append('\n');
|
||||
builder.append(error);
|
||||
}
|
||||
exit(builder.toString());
|
||||
}
|
||||
|
||||
CompletionService<URL> completionService =
|
||||
new ExecutorCompletionService<>(pool);
|
||||
|
||||
List<URL> localURLs = new ArrayList<>();
|
||||
|
||||
for (URL url : urls) {
|
||||
if (!url.getProtocol().equals("file")) {
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
|
||||
if (protocol.equals("file") || protocol.equals(bootstrapProtocol)) {
|
||||
localURLs.add(url);
|
||||
} else {
|
||||
final URL url0 = url;
|
||||
|
||||
completionService.submit(new Callable<URL>() {
|
||||
|
|
@ -233,8 +182,6 @@ public class Bootstrap {
|
|||
return dest.toURI().toURL();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
localURLs.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,8 +200,115 @@ public class Bootstrap {
|
|||
exit("Interrupted");
|
||||
}
|
||||
|
||||
final String[] isolationIDs = readStringSequence(isolationIDsResource);
|
||||
final Map<String, URL[]> perIsolationContextURLs = readIsolationContexts(jarDir, isolationIDs);
|
||||
return localURLs;
|
||||
}
|
||||
|
||||
static void setMainProperties(String mainJarPath, String[] args) {
|
||||
System.setProperty("coursier.mainJar", mainJarPath);
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.setProperty("coursier.main.arg-" + i, args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void setExtraProperties(String resource) throws IOException {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
Map<String,String> properties = loadPropertiesMap(loader.getResourceAsStream(resource));
|
||||
for (Map.Entry<String, String> ent : properties.entrySet()) {
|
||||
System.setProperty(ent.getKey(), ent.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
static List<URL> getURLs(String[] rawURLs, String[] resources, String bootstrapProtocol, ClassLoader loader) throws MalformedURLException {
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<URL> urls = new ArrayList<>();
|
||||
|
||||
for (String urlStr : rawURLs) {
|
||||
try {
|
||||
URL url = URI.create(urlStr).toURL();
|
||||
urls.add(url);
|
||||
} catch (Exception ex) {
|
||||
String message = urlStr + ": " + ex.getMessage();
|
||||
errors.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
for (String resource : resources) {
|
||||
URL url = loader.getResource(resource);
|
||||
if (url == null) {
|
||||
String message = "Resource " + resource + " not found";
|
||||
errors.add(message);
|
||||
} else {
|
||||
URL url0 = new URL(bootstrapProtocol, null, resource);
|
||||
urls.add(url0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
StringBuilder builder = new StringBuilder("Error:");
|
||||
for (String error: errors) {
|
||||
builder.append("\n ");
|
||||
builder.append(error);
|
||||
}
|
||||
exit(builder.toString());
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
// JARs from JARs can't be used directly, see:
|
||||
// http://stackoverflow.com/questions/183292/classpath-including-jar-within-a-jar/2326775#2326775
|
||||
// Loading them via a custom protocol, inspired by:
|
||||
// http://stackoverflow.com/questions/26363573/registering-and-using-a-custom-java-net-url-protocol/26409796#26409796
|
||||
static void registerBootstrapUnder(final String bootstrapProtocol, final ClassLoader loader) {
|
||||
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
return bootstrapProtocol.equals(protocol) ? new URLStreamHandler() {
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
String path = url.getPath();
|
||||
URL resURL = loader.getResource(path);
|
||||
if (resURL == null)
|
||||
throw new FileNotFoundException("Resource " + path);
|
||||
return resURL.openConnection();
|
||||
}
|
||||
} : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
|
||||
setMainProperties(mainJarPath(), args);
|
||||
setExtraProperties("bootstrap.properties");
|
||||
|
||||
String mainClass0 = System.getProperty("bootstrap.mainClass");
|
||||
String jarDir0 = System.getProperty("bootstrap.jarDir");
|
||||
|
||||
final File jarDir = new File(jarDir0);
|
||||
|
||||
if (jarDir.exists()) {
|
||||
if (!jarDir.isDirectory())
|
||||
exit("Error: " + jarDir0 + " is not a directory");
|
||||
} else if (!jarDir.mkdirs())
|
||||
System.err.println("Warning: cannot create " + jarDir0 + ", continuing anyway.");
|
||||
|
||||
Random rng = new Random();
|
||||
String protocol = "bootstrap" + rng.nextLong();
|
||||
|
||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
registerBootstrapUnder(protocol, contextLoader);
|
||||
|
||||
String[] strUrls = readStringSequence(defaultURLResource);
|
||||
String[] resources = readStringSequence(defaultJarResource);
|
||||
List<URL> urls = getURLs(strUrls, resources, protocol, contextLoader);
|
||||
List<URL> localURLs = getLocalURLs(urls, jarDir, protocol);
|
||||
|
||||
String[] isolationIDs = readStringSequence(isolationIDsResource);
|
||||
Map<String, URL[]> perIsolationContextURLs = readIsolationContexts(jarDir, isolationIDs, protocol, contextLoader);
|
||||
|
||||
Thread thread = Thread.currentThread();
|
||||
ClassLoader parentClassLoader = thread.getContextClassLoader();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package coursier
|
||||
package cli
|
||||
|
||||
import java.io.{ ByteArrayOutputStream, File, IOException }
|
||||
import java.io.{ FileInputStream, ByteArrayOutputStream, File, IOException }
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.{ Files => NIOFiles }
|
||||
import java.nio.file.attribute.PosixFilePermission
|
||||
|
|
@ -344,6 +344,9 @@ case class Bootstrap(
|
|||
downloadDir: String,
|
||||
@Short("f")
|
||||
force: Boolean,
|
||||
@Help("Generate a standalone launcher, with all JARs included, instead of one downloading its dependencies on startup.")
|
||||
@Short("s")
|
||||
standalone: Boolean,
|
||||
@Help("Set Java properties in the generated launcher.")
|
||||
@Value("key=value")
|
||||
@Short("P")
|
||||
|
|
@ -361,7 +364,7 @@ case class Bootstrap(
|
|||
sys.exit(255)
|
||||
}
|
||||
|
||||
if (downloadDir.isEmpty) {
|
||||
if (!standalone && downloadDir.isEmpty) {
|
||||
Console.err.println(s"Error: no download dir specified. Specify one with -D or --download-dir")
|
||||
Console.err.println("E.g. -D \"\\$HOME/.app-name/jars\"")
|
||||
sys.exit(255)
|
||||
|
|
@ -415,21 +418,46 @@ case class Bootstrap(
|
|||
|
||||
val helper = new Helper(common, remainingArgs)
|
||||
|
||||
val urls = helper.res.artifacts.map(_.url)
|
||||
|
||||
val (_, isolatedUrls) =
|
||||
isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, Seq[String]])) {
|
||||
val (_, isolatedArtifactFiles) =
|
||||
isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) {
|
||||
case ((done, acc), target) =>
|
||||
val subRes = helper.res.subset(isolated.isolatedDeps.getOrElse(target, Nil).toSet)
|
||||
val subUrls = subRes.artifacts.map(_.url)
|
||||
val subArtifacts = subRes.artifacts.map(_.url)
|
||||
|
||||
val filteredSubUrls = subUrls.diff(done)
|
||||
val filteredSubArtifacts = subArtifacts.diff(done)
|
||||
|
||||
val updatedAcc = acc + (target -> filteredSubUrls)
|
||||
def subFiles0 = helper.fetch(
|
||||
sources = false,
|
||||
javadoc = false,
|
||||
subset = isolated.isolatedDeps.getOrElse(target, Seq.empty).toSet
|
||||
)
|
||||
|
||||
(done ++ filteredSubUrls, updatedAcc)
|
||||
val (subUrls, subFiles) =
|
||||
if (standalone)
|
||||
(Nil, subFiles0)
|
||||
else
|
||||
(filteredSubArtifacts, Nil)
|
||||
|
||||
val updatedAcc = acc + (target -> (subUrls, subFiles))
|
||||
|
||||
(done ++ filteredSubArtifacts, updatedAcc)
|
||||
}
|
||||
|
||||
val (urls, files) =
|
||||
if (standalone)
|
||||
(
|
||||
Seq.empty[String],
|
||||
helper.fetch(sources = false, javadoc = false)
|
||||
)
|
||||
else
|
||||
(
|
||||
helper.res.artifacts.map(_.url),
|
||||
Seq.empty[File]
|
||||
)
|
||||
|
||||
val isolatedUrls = isolatedArtifactFiles.map { case (k, (v, _)) => k -> v }
|
||||
val isolatedFiles = isolatedArtifactFiles.map { case (k, (_, v)) => k -> v }
|
||||
|
||||
val unrecognized = urls.filter(s => !s.startsWith("http://") && !s.startsWith("https://"))
|
||||
if (unrecognized.nonEmpty)
|
||||
Console.err.println(s"Warning: non HTTP URLs:\n${unrecognized.mkString("\n")}")
|
||||
|
|
@ -457,6 +485,15 @@ case class Bootstrap(
|
|||
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 (isolated.anyIsolatedDep) {
|
||||
|
|
@ -464,16 +501,26 @@ case class Bootstrap(
|
|||
|
||||
for (target <- 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)
|
||||
properties.setProperty("bootstrap.jarDir", downloadDir)
|
||||
if (!standalone)
|
||||
properties.setProperty("bootstrap.jarDir", downloadDir)
|
||||
|
||||
outputZip.putNextEntry(propsEntry)
|
||||
properties.store(outputZip, "")
|
||||
|
|
|
|||
Loading…
Reference in New Issue