mirror of https://github.com/sbt/sbt.git
Merge pull request #124 from alexarchambault/develop
Latest developments
This commit is contained in:
commit
c9c6b39e49
|
|
@ -14,15 +14,9 @@ script:
|
|||
matrix:
|
||||
include:
|
||||
- env: TRAVIS_SCALA_VERSION=2.11.7 PUBLISH=1
|
||||
os: linux
|
||||
jdk: openjdk7
|
||||
- env: TRAVIS_SCALA_VERSION=2.10.6 PUBLISH=1
|
||||
os: linux
|
||||
jdk: openjdk7
|
||||
- env: TRAVIS_SCALA_VERSION=2.11.7
|
||||
os: linux
|
||||
jdk: oraclejdk8
|
||||
- env: TRAVIS_SCALA_VERSION=2.10.6
|
||||
- env: TRAVIS_SCALA_VERSION=2.10.6 PUBLISH=1
|
||||
os: linux
|
||||
jdk: oraclejdk8
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -1,15 +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.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
|
|
@ -40,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 {
|
||||
|
|
@ -54,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;
|
||||
|
|
@ -141,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
|
||||
|
|
@ -156,61 +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");
|
||||
|
||||
boolean prependClasspath = Boolean.parseBoolean(System.getProperty("bootstrap.prependClasspath", "false"));
|
||||
|
||||
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>() {
|
||||
|
|
@ -236,8 +182,6 @@ public class Bootstrap {
|
|||
return dest.toURI().toURL();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
localURLs.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,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();
|
||||
|
|
@ -267,7 +318,7 @@ public class Bootstrap {
|
|||
parentClassLoader = new IsolatedClassLoader(contextURLs, parentClassLoader, new String[]{ isolationID });
|
||||
}
|
||||
|
||||
URLClassLoader classLoader = new URLClassLoader(localURLs.toArray(new URL[localURLs.size()]), parentClassLoader);
|
||||
ClassLoader classLoader = new BootstrapClassLoader(localURLs.toArray(new URL[localURLs.size()]), parentClassLoader);
|
||||
|
||||
Class<?> mainClass = null;
|
||||
Method mainMethod = null;
|
||||
|
|
@ -288,14 +339,6 @@ public class Bootstrap {
|
|||
|
||||
List<String> userArgs0 = new ArrayList<>();
|
||||
|
||||
if (prependClasspath) {
|
||||
for (URL url : localURLs) {
|
||||
assert url.getProtocol().equals("file");
|
||||
userArgs0.add("-B");
|
||||
userArgs0.add(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.length; i++)
|
||||
userArgs0.add(args[i]);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package coursier;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class BootstrapClassLoader extends URLClassLoader {
|
||||
|
||||
public BootstrapClassLoader(
|
||||
URL[] urls,
|
||||
ClassLoader parent
|
||||
) {
|
||||
super(urls, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called by reflection by launched applications, to find the "main" `ClassLoader`
|
||||
* that loaded them, and possibly short-circuit it to load other things for example.
|
||||
*
|
||||
* The `launch` command of coursier does that.
|
||||
*/
|
||||
public boolean isBootstrapLoader() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -122,18 +122,23 @@ object Cache {
|
|||
logger.foreach(_.downloadingArtifact(url, file))
|
||||
|
||||
val res =
|
||||
try f
|
||||
catch { case e: Exception =>
|
||||
logger.foreach(_.downloadedArtifact(url, success = false))
|
||||
throw e
|
||||
try \/-(f)
|
||||
catch {
|
||||
case nfe: FileNotFoundException if nfe.getMessage != null =>
|
||||
logger.foreach(_.downloadedArtifact(url, success = false))
|
||||
-\/(-\/(FileError.NotFound(nfe.getMessage)))
|
||||
case e: Exception =>
|
||||
logger.foreach(_.downloadedArtifact(url, success = false))
|
||||
throw e
|
||||
}
|
||||
finally {
|
||||
urlLocks.remove(url)
|
||||
}
|
||||
|
||||
logger.foreach(_.downloadedArtifact(url, success = true))
|
||||
for (res0 <- res)
|
||||
logger.foreach(_.downloadedArtifact(url, success = res0.isRight))
|
||||
|
||||
res
|
||||
res.merge
|
||||
} else
|
||||
-\/(FileError.ConcurrentDownload(url))
|
||||
}
|
||||
|
|
@ -575,21 +580,21 @@ object FileError {
|
|||
def message = s"Download error: $message0"
|
||||
}
|
||||
case class NotFound(file: String) extends FileError {
|
||||
def message = s"$file: not found"
|
||||
def message = s"Not found: $file"
|
||||
}
|
||||
case class ChecksumNotFound(sumType: String, file: String) extends FileError {
|
||||
def message = s"$file: $sumType checksum not found"
|
||||
def message = s"$sumType checksum not found: $file"
|
||||
}
|
||||
case class WrongChecksum(sumType: String, got: String, expected: String, file: String, sumFile: String) extends FileError {
|
||||
def message = s"$file: $sumType checksum validation failed"
|
||||
def message = s"$sumType checksum validation failed: $file"
|
||||
}
|
||||
|
||||
sealed trait Recoverable extends FileError
|
||||
case class Locked(file: File) extends Recoverable {
|
||||
def message = s"$file: locked"
|
||||
def message = s"Locked: $file"
|
||||
}
|
||||
case class ConcurrentDownload(url: String) extends Recoverable {
|
||||
def message = s"$url: concurrent download"
|
||||
def message = s"Concurrent download: $url"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -9,7 +9,10 @@ import java.util.Properties
|
|||
import java.util.zip.{ ZipEntry, ZipOutputStream, ZipInputStream }
|
||||
|
||||
import caseapp.{ HelpMessage => Help, ValueDescription => Value, ExtraName => Short, _ }
|
||||
import coursier.util.{ Parse, ClasspathFilter }
|
||||
import coursier.util.Parse
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.language.reflectiveCalls
|
||||
|
||||
case class CommonOptions(
|
||||
@Help("Keep optional dependencies (Maven)")
|
||||
|
|
@ -61,7 +64,7 @@ case class Resolve(
|
|||
) extends CoursierCommand {
|
||||
|
||||
// the `val helper = ` part is needed because of DelayedInit it seems
|
||||
val helper = new Helper(common, remainingArgs)
|
||||
val helper = new Helper(common, remainingArgs, printResultStdout = true)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +164,32 @@ case class IsolatedLoaderOptions(
|
|||
|
||||
}
|
||||
|
||||
object Launch {
|
||||
|
||||
@tailrec
|
||||
def mainClassLoader(cl: ClassLoader): Option[ClassLoader] =
|
||||
if (cl == null)
|
||||
None
|
||||
else {
|
||||
val isMainLoader = try {
|
||||
val cl0 = cl.asInstanceOf[Object {
|
||||
def isBootstrapLoader: Boolean
|
||||
}]
|
||||
|
||||
cl0.isBootstrapLoader
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
false
|
||||
}
|
||||
|
||||
if (isMainLoader)
|
||||
Some(cl)
|
||||
else
|
||||
mainClassLoader(cl.getParent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class Launch(
|
||||
@Short("M")
|
||||
@Short("main")
|
||||
|
|
@ -188,12 +217,20 @@ case class Launch(
|
|||
|
||||
val files0 = helper.fetch(sources = false, javadoc = false)
|
||||
|
||||
val contextLoader = Thread.currentThread().getContextClassLoader
|
||||
|
||||
val parentLoader0: ClassLoader = new ClasspathFilter(
|
||||
Thread.currentThread().getContextClassLoader,
|
||||
Coursier.baseCp.map(new File(_)).toSet,
|
||||
exclude = true
|
||||
)
|
||||
val parentLoader0: ClassLoader = Launch.mainClassLoader(contextLoader)
|
||||
.flatMap(cl => Option(cl.getParent))
|
||||
.getOrElse {
|
||||
if (common.verbose0 >= 0)
|
||||
Console.err.println(
|
||||
"Warning: cannot find the main ClassLoader that launched coursier. " +
|
||||
"Was coursier launched by its main launcher? " +
|
||||
"The ClassLoader of the application that is about to be launched will be intertwined " +
|
||||
"with the one of coursier, which may be a problem if their dependencies conflict."
|
||||
)
|
||||
contextLoader
|
||||
}
|
||||
|
||||
val (parentLoader, filteredFiles) =
|
||||
if (isolated.isolated.isEmpty)
|
||||
|
|
@ -283,7 +320,7 @@ case class Launch(
|
|||
}
|
||||
val method =
|
||||
try cls.getMethod("main", classOf[Array[String]])
|
||||
catch { case e: NoSuchMethodError =>
|
||||
catch { case e: NoSuchMethodException =>
|
||||
Helper.errPrintln(s"Error: method main not found in $mainClass0")
|
||||
sys.exit(255)
|
||||
}
|
||||
|
|
@ -307,10 +344,10 @@ case class Bootstrap(
|
|||
downloadDir: String,
|
||||
@Short("f")
|
||||
force: Boolean,
|
||||
@Help(s"Internal use - prepend base classpath options to arguments (like -B jar1 -B jar2 etc.)")
|
||||
@Short("b")
|
||||
prependClasspath: Boolean,
|
||||
@Help("Set environment variables in the generated launcher. No escaping is done. Value is simply put between quotes in the launcher preamble.")
|
||||
@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")
|
||||
property: List[String],
|
||||
|
|
@ -327,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)
|
||||
|
|
@ -381,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")}")
|
||||
|
|
@ -423,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) {
|
||||
|
|
@ -430,17 +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)
|
||||
properties.setProperty("bootstrap.prependClasspath", prependClasspath.toString)
|
||||
if (!standalone)
|
||||
properties.setProperty("bootstrap.jarDir", downloadDir)
|
||||
|
||||
outputZip.putNextEntry(propsEntry)
|
||||
properties.store(outputZip, "")
|
||||
|
|
@ -487,27 +567,7 @@ case class Bootstrap(
|
|||
|
||||
}
|
||||
|
||||
case class BaseCommand(
|
||||
@Hidden
|
||||
@Short("B")
|
||||
baseCp: List[String]
|
||||
) extends Command {
|
||||
Coursier.baseCp = baseCp
|
||||
|
||||
// FIXME Should be in a trait in case-app
|
||||
override def setCommand(cmd: Option[Either[String, String]]): Unit = {
|
||||
if (cmd.isEmpty) {
|
||||
// FIXME Print available commands too?
|
||||
Console.err.println("Error: no command specified")
|
||||
sys.exit(255)
|
||||
}
|
||||
super.setCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
object Coursier extends CommandAppOfWithBase[BaseCommand, CoursierCommand] {
|
||||
object Coursier extends CommandAppOf[CoursierCommand] {
|
||||
override def appName = "Coursier"
|
||||
override def progName = "coursier"
|
||||
|
||||
private[coursier] var baseCp = Seq.empty[String]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ object Util {
|
|||
|
||||
class Helper(
|
||||
common: CommonOptions,
|
||||
rawDependencies: Seq[String]
|
||||
rawDependencies: Seq[String],
|
||||
printResultStdout: Boolean = false
|
||||
) {
|
||||
import common._
|
||||
import Helper.errPrintln
|
||||
|
|
@ -168,19 +169,13 @@ class Helper(
|
|||
print.flatMap(_ => fetchQuiet(modVers))
|
||||
}
|
||||
|
||||
def indent(s: String): String =
|
||||
if (s.isEmpty)
|
||||
s
|
||||
else
|
||||
s.split('\n').map(" "+_).mkString("\n")
|
||||
|
||||
if (verbose0 >= 0) {
|
||||
errPrintln(s"Dependencies:\n${indent(Print.dependenciesUnknownConfigs(dependencies))}")
|
||||
errPrintln(s" Dependencies:\n${Print.dependenciesUnknownConfigs(dependencies, Map.empty)}")
|
||||
|
||||
if (forceVersions.nonEmpty) {
|
||||
errPrintln("Force versions:")
|
||||
errPrintln(" Force versions:")
|
||||
for ((mod, ver) <- forceVersions.toVector.sortBy { case (mod, _) => mod.toString })
|
||||
errPrintln(s" $mod:$ver")
|
||||
errPrintln(s"$mod:$ver")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,20 +197,28 @@ class Helper(
|
|||
}
|
||||
|
||||
exitIf(res.errors.nonEmpty) {
|
||||
s"\n${res.errors.size} error(s):\n" +
|
||||
s"\nError:\n" +
|
||||
res.errors.map { case (dep, errs) =>
|
||||
s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}"
|
||||
}.mkString("\n")
|
||||
}
|
||||
|
||||
exitIf(res.conflicts.nonEmpty) {
|
||||
s"${res.conflicts.size} conflict(s):\n${Print.dependenciesUnknownConfigs(res.conflicts.toVector)}"
|
||||
s"\nConflict:\n${Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)}"
|
||||
}
|
||||
|
||||
val trDeps = res.minDependencies.toVector
|
||||
|
||||
if (verbose0 >= 0)
|
||||
errPrintln(s"Result:\n${indent(Print.dependenciesUnknownConfigs(trDeps))}")
|
||||
lazy val projCache = res.projectCache.mapValues { case (_, p) => p }
|
||||
|
||||
if (printResultStdout || verbose0 >= 0) {
|
||||
errPrintln(s" Result:")
|
||||
val depsStr = Print.dependenciesUnknownConfigs(trDeps, projCache)
|
||||
if (printResultStdout)
|
||||
println(depsStr)
|
||||
else
|
||||
errPrintln(depsStr)
|
||||
}
|
||||
|
||||
def fetch(
|
||||
sources: Boolean,
|
||||
|
|
@ -226,9 +229,9 @@ class Helper(
|
|||
if (verbose0 >= 0) {
|
||||
val msg = cachePolicies match {
|
||||
case Seq(CachePolicy.LocalOnly) =>
|
||||
"Checking artifacts"
|
||||
" Checking artifacts"
|
||||
case _ =>
|
||||
"Fetching artifacts"
|
||||
" Fetching artifacts"
|
||||
}
|
||||
|
||||
errPrintln(msg)
|
||||
|
|
@ -255,7 +258,7 @@ class Helper(
|
|||
None
|
||||
|
||||
if (verbose0 >= 1 && artifacts.nonEmpty)
|
||||
println(s"Found ${artifacts.length} artifacts")
|
||||
println(s" Found ${artifacts.length} artifacts")
|
||||
|
||||
val tasks = artifacts.map(artifact =>
|
||||
(Cache.file(artifact, caches, cachePolicies.head, logger = logger, pool = pool) /: cachePolicies.tail)(
|
||||
|
|
@ -274,9 +277,9 @@ class Helper(
|
|||
logger.foreach(_.stop())
|
||||
|
||||
exitIf(errors.nonEmpty) {
|
||||
s"${errors.size} error(s):\n" +
|
||||
s" Error:\n" +
|
||||
errors.map { case (artifact, error) =>
|
||||
s" ${artifact.url}: $error"
|
||||
s"${artifact.url}: $error"
|
||||
}.mkString("\n")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
package coursier
|
||||
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
import coursier.util.ClasspathFilter
|
||||
|
||||
class ResolutionClassLoader(
|
||||
val resolution: Resolution,
|
||||
val artifacts: Seq[(Dependency, Artifact, File)],
|
||||
parent: ClassLoader
|
||||
) extends URLClassLoader(
|
||||
artifacts.map { case (_, _, f) => f.toURI.toURL }.toArray,
|
||||
parent
|
||||
) {
|
||||
|
||||
/**
|
||||
* Filtered version of this `ClassLoader`, exposing only `dependencies` and their
|
||||
* their transitive dependencies, and filtering out the other dependencies from
|
||||
* `resolution` - for `ClassLoader` isolation.
|
||||
*
|
||||
* An application launched by `coursier launch -C` has `ResolutionClassLoader` set as its
|
||||
* context `ClassLoader` (can be obtain with `Thread.currentThread().getContextClassLoader`).
|
||||
* If it aims at doing `ClassLoader` isolation, exposing only a dependency `dep` to the isolated
|
||||
* things, `filter(dep)` provides a `ClassLoader` that loaded `dep` and all its transitive
|
||||
* dependencies through the same loader as the contextual one, but that "exposes" only
|
||||
* `dep` and its transitive dependencies, nothing more.
|
||||
*/
|
||||
def filter(dependencies: Set[Dependency]): ClassLoader = {
|
||||
val subRes = resolution.subset(dependencies)
|
||||
val subArtifacts = subRes.dependencyArtifacts.map { case (_, a) => a }.toSet
|
||||
val subFiles = artifacts.collect { case (_, a, f) if subArtifacts(a) => f }
|
||||
|
||||
new ClasspathFilter(this, subFiles.toSet, exclude = false)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package coursier.util
|
||||
|
||||
// Extracted and adapted from SBT
|
||||
|
||||
import java.io.File
|
||||
import java.net._
|
||||
|
||||
/**
|
||||
* Doesn't load any classes itself, but instead verifies that all classes loaded through `parent`
|
||||
* come from `classpath`.
|
||||
*
|
||||
* If `exclude` is `true`, does the opposite - exclude classes from `classpath`.
|
||||
*/
|
||||
class ClasspathFilter(parent: ClassLoader, classpath: Set[File], exclude: Boolean) extends ClassLoader(parent) {
|
||||
override def toString = s"ClasspathFilter(parent = $parent, cp = $classpath)"
|
||||
|
||||
private val directories = classpath.toSeq.filter(_.isDirectory)
|
||||
|
||||
|
||||
private def toFile(url: URL) =
|
||||
try { new File(url.toURI) }
|
||||
catch { case _: URISyntaxException => new File(url.getPath) }
|
||||
|
||||
private def uriToFile(uriString: String) = {
|
||||
val uri = new URI(uriString)
|
||||
assert(uri.getScheme == "file", s"Expected protocol to be 'file' in URI $uri")
|
||||
if (uri.getAuthority == null)
|
||||
new File(uri)
|
||||
else {
|
||||
/* https://github.com/sbt/sbt/issues/564
|
||||
* http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
|
||||
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086147
|
||||
* The specific problem here is that `uri` will have a defined authority component for UNC names like //foo/bar/some/path.jar
|
||||
* but the File constructor requires URIs with an undefined authority component.
|
||||
*/
|
||||
new File(uri.getSchemeSpecificPart)
|
||||
}
|
||||
}
|
||||
|
||||
private def urlAsFile(url: URL) =
|
||||
url.getProtocol match {
|
||||
case "file" => Some(toFile(url))
|
||||
case "jar" =>
|
||||
val path = url.getPath
|
||||
val end = path.indexOf('!')
|
||||
Some(uriToFile(if (end < 0) path else path.substring(0, end)))
|
||||
case _ => None
|
||||
}
|
||||
|
||||
private def baseFileString(baseFile: File) =
|
||||
Some(baseFile).filter(_.isDirectory).map { d =>
|
||||
val cp = d.getAbsolutePath.ensuring(_.nonEmpty)
|
||||
if (cp.last == File.separatorChar) cp else cp + File.separatorChar
|
||||
}
|
||||
|
||||
private def relativize(base: File, file: File) =
|
||||
baseFileString(base) .flatMap { baseString =>
|
||||
Some(file.getAbsolutePath).filter(_ startsWith baseString).map(_ substring baseString.length)
|
||||
}
|
||||
|
||||
private def fromClasspath(c: Class[_]): Boolean = {
|
||||
val codeSource = c.getProtectionDomain.getCodeSource
|
||||
(codeSource eq null) ||
|
||||
onClasspath(codeSource.getLocation) ||
|
||||
// workaround SBT classloader returning the target directory as sourcecode
|
||||
// make us keep more class than expected
|
||||
urlAsFile(codeSource.getLocation).exists(_.isDirectory)
|
||||
}
|
||||
|
||||
private val onClasspath: URL => Boolean = {
|
||||
if (exclude)
|
||||
src =>
|
||||
src == null || urlAsFile(src).forall(f =>
|
||||
!classpath(f) &&
|
||||
directories.forall(relativize(_, f).isEmpty)
|
||||
)
|
||||
else
|
||||
src =>
|
||||
src == null || urlAsFile(src).exists(f =>
|
||||
classpath(f) ||
|
||||
directories.exists(relativize(_, f).isDefined)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override def loadClass(className: String, resolve: Boolean): Class[_] = {
|
||||
val c =
|
||||
try super.loadClass(className, resolve)
|
||||
catch {
|
||||
case e: LinkageError =>
|
||||
// Happens when trying to derive a shapeless.Generic
|
||||
// from an Ammonite session launched like
|
||||
// ./coursier launch com.lihaoyi:ammonite-repl_2.11.7:0.5.2
|
||||
// For className == "shapeless.GenericMacros",
|
||||
// the super.loadClass above - which would be filtered out below anyway,
|
||||
// raises a NoClassDefFoundError.
|
||||
null
|
||||
}
|
||||
|
||||
if (c != null && fromClasspath(c))
|
||||
c
|
||||
else
|
||||
throw new ClassNotFoundException(className)
|
||||
}
|
||||
|
||||
override def getResource(name: String): URL = {
|
||||
val res = super.getResource(name)
|
||||
if (onClasspath(res)) res else null
|
||||
}
|
||||
|
||||
override def getResources(name: String): java.util.Enumeration[URL] = {
|
||||
import collection.JavaConverters._
|
||||
val res = super.getResources(name)
|
||||
if (res == null) null else res.asScala.filter(onClasspath).asJavaEnumeration
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,21 @@ object Parse {
|
|||
else Some(Version(s))
|
||||
}
|
||||
|
||||
def ivyLatestSubRevisionInterval(s: String): Option[VersionInterval] =
|
||||
if (s.endsWith(".+")) {
|
||||
for {
|
||||
from <- version(s.stripSuffix(".+"))
|
||||
if from.rawItems.nonEmpty
|
||||
last <- Some(from.rawItems.last).collect { case n: Version.Numeric => n }
|
||||
// a bit loose, but should do the job
|
||||
if from.repr.endsWith(last.repr)
|
||||
to <- version(from.repr.stripSuffix(last.repr) + last.next.repr)
|
||||
// the contrary would mean something went wrong in the loose substitution above
|
||||
if from.rawItems.init == to.rawItems.init
|
||||
} yield VersionInterval(Some(from), Some(to), fromIncluded = true, toIncluded = false)
|
||||
} else
|
||||
None
|
||||
|
||||
def versionInterval(s: String): Option[VersionInterval] = {
|
||||
for {
|
||||
fromIncluded <- if (s.startsWith("[")) Some(true) else if (s.startsWith("(")) Some(false) else None
|
||||
|
|
@ -28,6 +43,7 @@ object Parse {
|
|||
def noConstraint = if (s.isEmpty) Some(VersionConstraint.None) else None
|
||||
|
||||
noConstraint
|
||||
.orElse(ivyLatestSubRevisionInterval(s).map(VersionConstraint.Interval))
|
||||
.orElse(version(s).map(VersionConstraint.Preferred))
|
||||
.orElse(versionInterval(s).map(VersionConstraint.Interval))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -335,7 +335,14 @@ object Resolution {
|
|||
"project.groupId" -> project.module.organization,
|
||||
"project.artifactId" -> project.module.name,
|
||||
"project.version" -> project.version
|
||||
)
|
||||
) ++ project.parent.toSeq.flatMap {
|
||||
case (parModule, parVersion) =>
|
||||
Seq(
|
||||
"project.parent.groupId" -> parModule.organization,
|
||||
"project.parent.artifactId" -> parModule.name,
|
||||
"project.parent.version" -> parVersion
|
||||
)
|
||||
}
|
||||
|
||||
val properties = propertiesMap(properties0)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import coursier.core.compatibility._
|
|||
*/
|
||||
case class Version(repr: String) extends Ordered[Version] {
|
||||
lazy val items = Version.items(repr)
|
||||
lazy val rawItems: Seq[Version.Item] = {
|
||||
val (first, tokens) = Version.Tokenizer(repr)
|
||||
first +: tokens.toVector.map { case (_, item) => item }
|
||||
}
|
||||
def compare(other: Version) = Version.listCompare(items, other.items)
|
||||
def isEmpty = items.forall(_.isEmpty)
|
||||
}
|
||||
|
|
@ -39,13 +43,20 @@ object Version {
|
|||
def compareToEmpty: Int = 1
|
||||
}
|
||||
|
||||
sealed trait Numeric extends Item
|
||||
sealed trait Numeric extends Item {
|
||||
def repr: String
|
||||
def next: Numeric
|
||||
}
|
||||
case class Number(value: Int) extends Numeric {
|
||||
val order = 0
|
||||
def next: Number = Number(value + 1)
|
||||
def repr: String = value.toString
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class BigNumber(value: BigInt) extends Numeric {
|
||||
val order = 0
|
||||
def next: BigNumber = BigNumber(value + 1)
|
||||
def repr: String = value.toString
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class Qualifier(value: String, level: Int) extends Item {
|
||||
|
|
|
|||
|
|
@ -61,9 +61,10 @@ case class IvyRepository(
|
|||
case None =>
|
||||
project.publications.collect {
|
||||
case (conf, p)
|
||||
if conf == "*" ||
|
||||
conf == dependency.configuration ||
|
||||
project.allConfigurations.getOrElse(dependency.configuration, Set.empty).contains(conf) =>
|
||||
if (conf == "*" ||
|
||||
conf == dependency.configuration ||
|
||||
project.allConfigurations.getOrElse(dependency.configuration, Set.empty).contains(conf)
|
||||
) && p.classifier.isEmpty =>
|
||||
p
|
||||
}
|
||||
case Some(classifiers) =>
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ case class MavenRepository(
|
|||
): EitherT[F, String, (Artifact.Source, Project)] = {
|
||||
|
||||
Parse.versionInterval(version)
|
||||
.orElse(Parse.ivyLatestSubRevisionInterval(version))
|
||||
.filter(_.isValid) match {
|
||||
case None =>
|
||||
findNoInterval(module, version, fetch).map((source, _))
|
||||
|
|
|
|||
|
|
@ -1,20 +1,34 @@
|
|||
package coursier.util
|
||||
|
||||
import coursier.core.{ Orders, Dependency }
|
||||
import coursier.core.{Module, Project, Orders, Dependency}
|
||||
|
||||
object Print {
|
||||
|
||||
def dependency(dep: Dependency): String =
|
||||
s"${dep.module}:${dep.version}:${dep.configuration}"
|
||||
def dependency(dep: Dependency): String = {
|
||||
val exclusionsStr = dep.exclusions.toVector.sorted.map {
|
||||
case (org, name) =>
|
||||
s"\n exclude($org, $name)"
|
||||
}.mkString
|
||||
|
||||
def dependenciesUnknownConfigs(deps: Seq[Dependency]): String = {
|
||||
s"${dep.module}:${dep.version}:${dep.configuration}$exclusionsStr"
|
||||
}
|
||||
|
||||
def dependenciesUnknownConfigs(deps: Seq[Dependency], projects: Map[(Module, String), Project]): String = {
|
||||
|
||||
val deps0 = deps.map { dep =>
|
||||
dep.copy(
|
||||
version = projects
|
||||
.get(dep.moduleVersion)
|
||||
.fold(dep.version)(_.version)
|
||||
)
|
||||
}
|
||||
|
||||
val minDeps = Orders.minDependencies(
|
||||
deps.toSet,
|
||||
deps0.toSet,
|
||||
_ => Map.empty
|
||||
)
|
||||
|
||||
val deps0 = minDeps
|
||||
val deps1 = minDeps
|
||||
.groupBy(_.copy(configuration = ""))
|
||||
.toVector
|
||||
.map { case (k, l) =>
|
||||
|
|
@ -24,7 +38,7 @@ object Print {
|
|||
(dep.module.organization, dep.module.name, dep.module.toString, dep.version)
|
||||
}
|
||||
|
||||
deps0.map(dependency).mkString("\n")
|
||||
deps1.map(dependency).mkString("\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,8 @@ object Tasks {
|
|||
throw new Exception(s"Maximum number of iteration of dependency resolution reached")
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
println(s"${res.conflicts.size} conflict(s):\n ${Print.dependenciesUnknownConfigs(res.conflicts.toVector)}")
|
||||
val projCache = res.projectCache.mapValues { case (_, p) => p }
|
||||
println(s"${res.conflicts.size} conflict(s):\n ${Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)}")
|
||||
throw new Exception(s"Conflict(s) in dependency resolution")
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +343,8 @@ object Tasks {
|
|||
configs
|
||||
)
|
||||
|
||||
val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector)
|
||||
val projCache = res.projectCache.mapValues { case (_, p) => p }
|
||||
val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache)
|
||||
println(repr.split('\n').map(" "+_).mkString("\n"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ CACHE_VERSION=v1
|
|||
--no-default \
|
||||
-r central \
|
||||
-D "\${user.home}/.coursier/bootstrap/$VERSION" \
|
||||
-b \
|
||||
-f -o coursier \
|
||||
-M coursier.cli.Coursier \
|
||||
-P coursier.cache="\${user.home}/.coursier/cache/$CACHE_VERSION"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,41 @@
|
|||
package coursier.test
|
||||
|
||||
import coursier.{ Module, Cache }
|
||||
import coursier.{ Dependency, Module, Cache }
|
||||
import coursier.test.compatibility._
|
||||
|
||||
import scala.async.Async.{ async, await }
|
||||
|
||||
import utest._
|
||||
|
||||
object IvyLocalTests extends TestSuite {
|
||||
|
||||
val tests = TestSuite{
|
||||
'coursier{
|
||||
val module = Module("com.github.alexarchambault", "coursier_2.11")
|
||||
val version = "1.0.0-SNAPSHOT"
|
||||
|
||||
val extraRepo = Some(Cache.ivy2Local)
|
||||
|
||||
// Assume this module (and the sub-projects it depends on) is published locally
|
||||
CentralTests.resolutionCheck(
|
||||
Module("com.github.alexarchambault", "coursier_2.11"), "1.0.0-SNAPSHOT",
|
||||
Some(Cache.ivy2Local))
|
||||
module, version,
|
||||
extraRepo
|
||||
)
|
||||
|
||||
|
||||
async {
|
||||
val res = await(CentralTests.resolve(
|
||||
Set(Dependency(module, version)),
|
||||
extraRepo = extraRepo
|
||||
))
|
||||
|
||||
val artifacts = res.artifacts.map(_.url)
|
||||
val anyJavadoc = artifacts.exists(_.contains("-javadoc"))
|
||||
val anySources = artifacts.exists(_.contains("-sources"))
|
||||
|
||||
assert(!anyJavadoc)
|
||||
assert(!anySources)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
com.chuusai:shapeless_2.11:jar:2.2.5
|
||||
org.scala-lang:scala-library:jar:2.11.7
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
com.chuusai:shapeless_2.11:jar:2.2.5
|
||||
org.scala-lang:scala-library:jar:2.11.7
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
com.github.fommil.netlib:all:jar:1.1.2
|
||||
com.github.fommil.netlib:core:jar:1.1.2
|
||||
com.github.fommil.netlib:native_ref-java:jar:1.1
|
||||
com.github.fommil.netlib:native_system-java:jar:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-linux-armhf:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-linux-i686:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-linux-x86_64:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-osx-x86_64:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-win-i686:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_ref-win-x86_64:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-linux-armhf:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-linux-i686:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-linux-x86_64:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-osx-x86_64:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-win-i686:jar:natives:1.1
|
||||
com.github.fommil.netlib:netlib-native_system-win-x86_64:jar:natives:1.1
|
||||
com.github.fommil:jniloader:jar:1.1
|
||||
|
|
@ -0,0 +1 @@
|
|||
com.googlecode.libphonenumber:libphonenumber:jar:7.0.11
|
||||
|
|
@ -0,0 +1 @@
|
|||
com.googlecode.libphonenumber:libphonenumber:jar:7.0.11
|
||||
|
|
@ -61,12 +61,20 @@ object CentralTests extends TestSuite {
|
|||
val result = res
|
||||
.dependencies
|
||||
.toVector
|
||||
.map(repr)
|
||||
.map { dep =>
|
||||
val projOpt = res.projectCache
|
||||
.get(dep.moduleVersion)
|
||||
.map { case (_, proj) => proj }
|
||||
val dep0 = dep.copy(
|
||||
version = projOpt.fold(dep.version)(_.version)
|
||||
)
|
||||
repr(dep0)
|
||||
}
|
||||
.sorted
|
||||
.distinct
|
||||
|
||||
for (((e, r), idx) <- expected.zip(result).zipWithIndex if e != r)
|
||||
println(s"Line $idx:\n expected: $e\n got:$r")
|
||||
println(s"Line $idx:\n expected: $e\n got: $r")
|
||||
|
||||
assert(result == expected)
|
||||
}
|
||||
|
|
@ -143,6 +151,33 @@ object CentralTests extends TestSuite {
|
|||
extraRepo = Some(MavenRepository("https://oss.sonatype.org/content/repositories/public/"))
|
||||
)
|
||||
}
|
||||
'parentProjectProperties - {
|
||||
resolutionCheck(
|
||||
Module("com.github.fommil.netlib", "all"),
|
||||
"1.1.2"
|
||||
)
|
||||
}
|
||||
'latestRevision - {
|
||||
resolutionCheck(
|
||||
Module("com.chuusai", "shapeless_2.11"),
|
||||
"[2.2.0,2.3.0)"
|
||||
)
|
||||
|
||||
resolutionCheck(
|
||||
Module("com.chuusai", "shapeless_2.11"),
|
||||
"2.2.+"
|
||||
)
|
||||
|
||||
resolutionCheck(
|
||||
Module("com.googlecode.libphonenumber", "libphonenumber"),
|
||||
"[7.0,7.1)"
|
||||
)
|
||||
|
||||
resolutionCheck(
|
||||
Module("com.googlecode.libphonenumber", "libphonenumber"),
|
||||
"7.0.+"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue