From f61a25857460758d7c2e1eb56e526f309ad00993 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 4 Aug 2016 17:42:16 -0400 Subject: [PATCH 1/4] Accept single version interval syntax Fixes https://github.com/alexarchambault/coursier/issues/310 --- .../src/main/scala/coursier/core/Parse.scala | 29 +++++++++++---- .../resolutions/io.grpc/grpc-netty/0.14.1 | 12 +++++++ .../scala/coursier/test/CentralTests.scala | 7 ++++ .../coursier/test/VersionIntervalTests.scala | 36 ++++++++++++++++--- 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 tests/shared/src/test/resources/resolutions/io.grpc/grpc-netty/0.14.1 diff --git a/core/shared/src/main/scala/coursier/core/Parse.scala b/core/shared/src/main/scala/coursier/core/Parse.scala index 83bcabd6e..179903f14 100644 --- a/core/shared/src/main/scala/coursier/core/Parse.scala +++ b/core/shared/src/main/scala/coursier/core/Parse.scala @@ -29,17 +29,32 @@ object Parse { None def versionInterval(s: String): Option[VersionInterval] = { + + def parseBounds(fromIncluded: Boolean, toIncluded: Boolean, s: String) = { + + val commaIdx = s.indexOf(',') + + if (commaIdx >= 0) { + val strFrom = s.take(commaIdx) + val strTo = s.drop(commaIdx + 1) + + for { + from <- if (strFrom.isEmpty) Some(None) else version(strFrom).map(Some(_)) + to <- if (strTo.isEmpty) Some(None) else version(strTo).map(Some(_)) + } yield VersionInterval(from.filterNot(_.isEmpty), to.filterNot(_.isEmpty), fromIncluded, toIncluded) + } else if (s.nonEmpty && fromIncluded && toIncluded) + for (v <- version(s) if !v.isEmpty) + yield VersionInterval(Some(v), Some(v), fromIncluded, toIncluded) + else + None + } + for { fromIncluded <- if (s.startsWith("[")) Some(true) else if (s.startsWith("(")) Some(false) else None toIncluded <- if (s.endsWith("]")) Some(true) else if (s.endsWith(")")) Some(false) else None s0 = s.drop(1).dropRight(1) - commaIdx = s0.indexOf(',') - if commaIdx >= 0 - strFrom = s0.take(commaIdx) - strTo = s0.drop(commaIdx + 1) - from <- if (strFrom.isEmpty) Some(None) else version(strFrom).map(Some(_)) - to <- if (strTo.isEmpty) Some(None) else version(strTo).map(Some(_)) - } yield VersionInterval(from.filterNot(_.isEmpty), to.filterNot(_.isEmpty), fromIncluded, toIncluded) + itv <- parseBounds(fromIncluded, toIncluded, s0) + } yield itv } def versionConstraint(s: String): Option[VersionConstraint] = { diff --git a/tests/shared/src/test/resources/resolutions/io.grpc/grpc-netty/0.14.1 b/tests/shared/src/test/resources/resolutions/io.grpc/grpc-netty/0.14.1 new file mode 100644 index 000000000..25ac86f48 --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/io.grpc/grpc-netty/0.14.1 @@ -0,0 +1,12 @@ +com.google.code.findbugs:jsr305:3.0.0:compile +com.google.guava:guava:19.0:compile +io.grpc:grpc-core:0.14.1:compile +io.grpc:grpc-netty:0.14.1:compile +io.netty:netty-buffer:4.1.1.Final:compile +io.netty:netty-codec:4.1.1.Final:compile +io.netty:netty-codec-http:4.1.1.Final:compile +io.netty:netty-codec-http2:4.1.1.Final:compile +io.netty:netty-common:4.1.1.Final:compile +io.netty:netty-handler:4.1.1.Final:compile +io.netty:netty-resolver:4.1.1.Final:compile +io.netty:netty-transport:4.1.1.Final:compile diff --git a/tests/shared/src/test/scala/coursier/test/CentralTests.scala b/tests/shared/src/test/scala/coursier/test/CentralTests.scala index afab031d7..1be4109c9 100644 --- a/tests/shared/src/test/scala/coursier/test/CentralTests.scala +++ b/tests/shared/src/test/scala/coursier/test/CentralTests.scala @@ -280,6 +280,13 @@ object CentralTests extends TestSuite { } } + 'fixedVersionDependency - { + val mod = Module("io.grpc", "grpc-netty") + val version = "0.14.1" + + resolutionCheck(mod, version) + } + 'mavenScopes - { def check(config: String) = resolutionCheck( Module("com.android.tools", "sdklib"), diff --git a/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala b/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala index 95118627c..09c906ef7 100644 --- a/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala +++ b/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala @@ -163,10 +163,6 @@ object VersionIntervalTests extends TestSuite { 'parse{ 'malformed{ - val s1 = "[1.1]" - val itv1 = Parse.versionInterval(s1) - assert(itv1 == None) - val s2 = "(1.1)" val itv2 = Parse.versionInterval(s2) assert(itv2 == None) @@ -263,6 +259,38 @@ object VersionIntervalTests extends TestSuite { assert(itv4 == Some(VersionInterval(None, None, false, true))) assert(!itv4.get.isValid) } + + 'fixedVersion - { + * - { + val itv = Parse.versionInterval("[1.2]") + assert(itv == Some(VersionInterval(Some(Version("1.2")), Some(Version("1.2")), true, true))) + } + + * - { + val itv = Parse.versionInterval("[1.2)") + assert(itv.isEmpty) + } + + * - { + val itv = Parse.versionInterval("(1.2]") + assert(itv.isEmpty) + } + + * - { + val itv = Parse.versionInterval("(1.2)") + assert(itv.isEmpty) + } + + * - { + val itv = Parse.versionInterval("[]") + assert(itv.isEmpty) + } + + * - { + val itv = Parse.versionInterval("[0.0]") + assert(itv.isEmpty) + } + } } 'constraint{ From 419ff74a9829cd4c5c256f65372929c9ed8d0694 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 4 Aug 2016 17:42:21 -0400 Subject: [PATCH 2/4] Switch to scala-js 0.6.11 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a2cc88871..0cdccad74 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.8.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.10") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.11") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.2") From c89dc684c191c19d49fa1031e2d3fa9952774d00 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 4 Aug 2016 18:41:56 -0400 Subject: [PATCH 3/4] Detect main class in bootstrap command --- .../scala-2.11/coursier/cli/Bootstrap.scala | 20 +-- .../main/scala-2.11/coursier/cli/Helper.scala | 119 ++++++++++++++++- .../main/scala-2.11/coursier/cli/Launch.scala | 124 +----------------- .../scala-2.11/coursier/cli/Options.scala | 4 +- 4 files changed, 137 insertions(+), 130 deletions(-) diff --git a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala index 024d87eef..d3e14e981 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala @@ -16,11 +16,6 @@ case class Bootstrap( import scala.collection.JavaConverters._ - if (options.mainClass.isEmpty) { - Console.err.println(s"Error: no main class specified. Specify one with -M or --main") - sys.exit(255) - } - if (!options.standalone && options.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\"") @@ -73,7 +68,12 @@ case class Bootstrap( } - val helper = new Helper(options.common, remainingArgs) + val helper = new Helper( + options.common, + remainingArgs, + isolated = options.isolated, + warnBaseLoaderNotFound = false + ) val isolatedDeps = options.isolated.isolatedDeps(options.common.defaultArtifactType) @@ -121,7 +121,9 @@ case class Bootstrap( if (nonHttpUrls.nonEmpty) Console.err.println(s"Warning: non HTTP URLs:\n${nonHttpUrls.mkString("\n")}") - val buffer = new ByteArrayOutputStream() + val mainClass = helper.retainedMainClass + + val buffer = new ByteArrayOutputStream val bootstrapZip = new ZipInputStream(new ByteArrayInputStream(bootstrapJar)) val outputZip = new ZipOutputStream(buffer) @@ -176,8 +178,8 @@ case class Bootstrap( val propsEntry = new ZipEntry("bootstrap.properties") propsEntry.setTime(time) - val properties = new Properties() - properties.setProperty("bootstrap.mainClass", options.mainClass) + val properties = new Properties + properties.setProperty("bootstrap.mainClass", mainClass) if (!options.standalone) properties.setProperty("bootstrap.jarDir", options.downloadDir) diff --git a/cli/src/main/scala-2.11/coursier/cli/Helper.scala b/cli/src/main/scala-2.11/coursier/cli/Helper.scala index cee03e086..922df15ae 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Helper.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Helper.scala @@ -2,7 +2,7 @@ package coursier package cli import java.io.{ OutputStreamWriter, File } -import java.net.URL +import java.net.{ URL, URLClassLoader } import java.util.jar.{ Manifest => JManifest } import java.util.concurrent.Executors @@ -11,6 +11,7 @@ import coursier.util.{Print, Parse} import scala.annotation.tailrec import scala.concurrent.duration.Duration +import scala.util.Try import scalaz.{Failure, Success, \/-, -\/} import scalaz.concurrent.{ Task, Strategy } @@ -75,7 +76,9 @@ class Helper( common: CommonOptions, rawDependencies: Seq[String], printResultStdout: Boolean = false, - ignoreErrors: Boolean = false + ignoreErrors: Boolean = false, + isolated: IsolatedLoaderOptions = IsolatedLoaderOptions(), + warnBaseLoaderNotFound: Boolean = true ) { import common._ import Helper.errPrintln @@ -577,4 +580,116 @@ class Helper( files0 } + + lazy val (parentLoader, filteredFiles) = { + + val contextLoader = Thread.currentThread().getContextClassLoader + + val files0 = fetch(sources = false, javadoc = false) + + val parentLoader0: ClassLoader = + Launch.mainClassLoader(contextLoader) + .flatMap(cl => Option(cl.getParent)) + .getOrElse { + // proguarded -> no risk of conflicts, no absolute need to find a specific ClassLoader + val isProguarded = Try(contextLoader.loadClass("coursier.cli.Launch")).isFailure + if (warnBaseLoaderNotFound && !isProguarded && common.verbosityLevel >= 0) + Console.err.println( + "Warning: cannot find the main ClassLoader that launched coursier.\n" + + "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 + } + + if (isolated.isolated.isEmpty) + (parentLoader0, files0) + else { + + val isolatedDeps = isolated.isolatedDeps(common.defaultArtifactType) + + val (isolatedLoader, filteredFiles0) = isolated.targets.foldLeft((parentLoader0, files0)) { + case ((parent, files0), target) => + + // FIXME These were already fetched above + val isolatedFiles = fetch( + sources = false, + javadoc = false, + subset = isolatedDeps.getOrElse(target, Seq.empty).toSet + ) + + if (common.verbosityLevel >= 2) { + Console.err.println(s"Isolated loader files:") + for (f <- isolatedFiles.map(_.toString).sorted) + Console.err.println(s" $f") + } + + val isolatedLoader = new IsolatedClassLoader( + isolatedFiles.map(_.toURI.toURL).toArray, + parent, + Array(target) + ) + + val filteredFiles0 = files0.filterNot(isolatedFiles.toSet) + + (isolatedLoader, filteredFiles0) + } + + if (common.verbosityLevel >= 2) { + Console.err.println(s"Remaining files:") + for (f <- filteredFiles0.map(_.toString).sorted) + Console.err.println(s" $f") + } + + (isolatedLoader, filteredFiles0) + } + } + + lazy val loader = new URLClassLoader( + filteredFiles.map(_.toURI.toURL).toArray, + parentLoader + ) + + + lazy val retainedMainClass = { + + val mainClasses = Helper.mainClasses(loader) + + if (common.verbosityLevel >= 2) { + Console.err.println("Found main classes:") + for (((vendor, title), mainClass) <- mainClasses) + Console.err.println(s" $mainClass (vendor: $vendor, title: $title)") + Console.err.println("") + } + + val mainClass = + if (mainClasses.isEmpty) { + Helper.errPrintln("No main class found. Specify one with -M or --main.") + sys.exit(255) + } else if (mainClasses.size == 1) { + val (_, mainClass) = mainClasses.head + mainClass + } else { + // Trying to get the main class of the first artifact + val mainClassOpt = for { + (module, _, _) <- moduleVersionConfigs.headOption + mainClass <- mainClasses.collectFirst { + case ((org, name), mainClass) + if org == module.organization && ( + module.name == name || + module.name.startsWith(name + "_") // Ignore cross version suffix + ) => + mainClass + } + } yield mainClass + + mainClassOpt.getOrElse { + Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.") + sys.exit(255) + } + } + + mainClass + } } diff --git a/cli/src/main/scala-2.11/coursier/cli/Launch.scala b/cli/src/main/scala-2.11/coursier/cli/Launch.scala index 8b73ed099..902d8ab81 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Launch.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Launch.scala @@ -76,140 +76,30 @@ case class Launch( val helper = new Helper( options.common, - remainingArgs ++ options.isolated.rawIsolated.map { case (_, dep) => dep } + remainingArgs ++ options.isolated.rawIsolated.map { case (_, dep) => dep }, + isolated = options.isolated ) - - val files0 = helper.fetch(sources = false, javadoc = false) - - val contextLoader = Thread.currentThread().getContextClassLoader - - val parentLoader0: ClassLoader = - Launch.mainClassLoader(contextLoader) - .flatMap(cl => Option(cl.getParent)) - .getOrElse { - // proguarded -> no risk of conflicts, no absolute need to find a specific ClassLoader - val isProguarded = Try(contextLoader.loadClass("coursier.cli.Launch")).isFailure - if (!isProguarded && options.common.verbosityLevel >= 0) - Console.err.println( - "Warning: cannot find the main ClassLoader that launched coursier.\n" + - "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 (options.isolated.isolated.isEmpty) - (parentLoader0, files0) - else { - - val isolatedDeps = options.isolated.isolatedDeps(options.common.defaultArtifactType) - - val (isolatedLoader, filteredFiles0) = options.isolated.targets.foldLeft((parentLoader0, files0)) { - case ((parent, files0), target) => - - // FIXME These were already fetched above - val isolatedFiles = helper.fetch( - sources = false, - javadoc = false, - subset = isolatedDeps.getOrElse(target, Seq.empty).toSet - ) - - if (options.common.verbosityLevel >= 2) { - Console.err.println(s"Isolated loader files:") - for (f <- isolatedFiles.map(_.toString).sorted) - Console.err.println(s" $f") - } - - val isolatedLoader = new IsolatedClassLoader( - isolatedFiles.map(_.toURI.toURL).toArray, - parent, - Array(target) - ) - - val filteredFiles0 = files0.filterNot(isolatedFiles.toSet) - - (isolatedLoader, filteredFiles0) - } - - if (options.common.verbosityLevel >= 2) { - Console.err.println(s"Remaining files:") - for (f <- filteredFiles0.map(_.toString).sorted) - Console.err.println(s" $f") - } - - (isolatedLoader, filteredFiles0) - } - - val loader = new URLClassLoader( - filteredFiles.map(_.toURI.toURL).toArray, - parentLoader - ) - - val mainClass0 = - if (options.mainClass.nonEmpty) options.mainClass - else { - val mainClasses = Helper.mainClasses(loader) - - if (options.common.verbosityLevel >= 2) { - Console.err.println("Found main classes:") - for (((vendor, title), mainClass) <- mainClasses) - Console.err.println(s" $mainClass (vendor: $vendor, title: $title)") - Console.err.println("") - } - - val mainClass = - if (mainClasses.isEmpty) { - Helper.errPrintln("No main class found. Specify one with -M or --main.") - sys.exit(255) - } else if (mainClasses.size == 1) { - val (_, mainClass) = mainClasses.head - mainClass - } else { - // Trying to get the main class of the first artifact - val mainClassOpt = for { - (module, _, _) <- helper.moduleVersionConfigs.headOption - mainClass <- mainClasses.collectFirst { - case ((org, name), mainClass) - if org == module.organization && ( - module.name == name || - module.name.startsWith(name + "_") // Ignore cross version suffix - ) => - mainClass - } - } yield mainClass - - mainClassOpt.getOrElse { - Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.") - sys.exit(255) - } - } - - mainClass - } - val cls = - try loader.loadClass(mainClass0) + try helper.loader.loadClass(helper.retainedMainClass) catch { case e: ClassNotFoundException => - Helper.errPrintln(s"Error: class $mainClass0 not found") + Helper.errPrintln(s"Error: class ${helper.retainedMainClass} not found") sys.exit(255) } val method = try cls.getMethod("main", classOf[Array[String]]) catch { case e: NoSuchMethodException => - Helper.errPrintln(s"Error: method main not found in $mainClass0") + Helper.errPrintln(s"Error: method main not found in ${helper.retainedMainClass}") sys.exit(255) } method.setAccessible(true) if (options.common.verbosityLevel >= 2) - Helper.errPrintln(s"Launching $mainClass0 ${userArgs.mkString(" ")}") + Helper.errPrintln(s"Launching ${helper.retainedMainClass} ${userArgs.mkString(" ")}") else if (options.common.verbosityLevel == 1) Helper.errPrintln(s"Launching") - Thread.currentThread().setContextClassLoader(loader) + Thread.currentThread().setContextClassLoader(helper.loader) try method.invoke(null, userArgs.toArray) catch { case e: java.lang.reflect.InvocationTargetException => diff --git a/cli/src/main/scala-2.11/coursier/cli/Options.scala b/cli/src/main/scala-2.11/coursier/cli/Options.scala index 905368710..d084f3e8c 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Options.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Options.scala @@ -98,10 +98,10 @@ case class CacheOptions( case class IsolatedLoaderOptions( @Value("target:dependency") @Short("I") - isolated: List[String], + isolated: List[String] = Nil, @Help("Comma-separated isolation targets") @Short("i") - isolateTarget: List[String] + isolateTarget: List[String] = Nil ) { def anyIsolatedDep = isolateTarget.nonEmpty || isolated.nonEmpty From c0f1ac860281cd91d04e22d405fd7a9b3152e9b1 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 4 Aug 2016 18:41:58 -0400 Subject: [PATCH 4/4] Set default value for download dir option of boostrap command --- .../scala-2.11/coursier/cli/Bootstrap.scala | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala index d3e14e981..1288cc0b5 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala @@ -16,11 +16,24 @@ case class Bootstrap( import scala.collection.JavaConverters._ - if (!options.standalone && options.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) - } + val helper = new Helper( + options.common, + remainingArgs, + isolated = options.isolated, + warnBaseLoaderNotFound = false + ) + + lazy val downloadDir = + if (options.downloadDir.isEmpty) + helper.baseDependencies.headOption match { + case Some(dep) => + s"\\$$HOME/.coursier/bootstrap/${dep.module.organization}/${dep.module.name}" + case None => + Console.err.println("Error: no dependencies specified.") + sys.exit(255) + } + else + options.downloadDir val (validProperties, wrongProperties) = options.property.partition(_.contains("=")) if (wrongProperties.nonEmpty) { @@ -68,13 +81,6 @@ case class Bootstrap( } - val helper = new Helper( - options.common, - remainingArgs, - isolated = options.isolated, - warnBaseLoaderNotFound = false - ) - val isolatedDeps = options.isolated.isolatedDeps(options.common.defaultArtifactType) val (_, isolatedArtifactFiles) = @@ -181,7 +187,7 @@ case class Bootstrap( val properties = new Properties properties.setProperty("bootstrap.mainClass", mainClass) if (!options.standalone) - properties.setProperty("bootstrap.jarDir", options.downloadDir) + properties.setProperty("bootstrap.jarDir", downloadDir) outputZip.putNextEntry(propsEntry) properties.store(outputZip, "")