diff --git a/.travis.yml b/.travis.yml index 17d74437d..a2c012190 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: scala scala: -- 2.11.6 -- 2.10.5 +- 2.11.7 +- 2.10.6 jdk: - oraclejdk7 - oraclejdk8 diff --git a/appveyor.yml b/appveyor.yml index 102fc5cc6..a5ef6d67e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ install: build_script: - sbt clean compile publish-local test_script: - - sbt core-jvm/test # Would node be around for core-js/test? + - sbt coreJVM/test # Would node be around for coreJS/test? cache: - C:\sbt\ - C:\Users\appveyor\.m2 diff --git a/bootstrap/src/main/scala/coursier/Bootstrap.scala b/bootstrap/src/main/scala/coursier/Bootstrap.scala new file mode 100644 index 000000000..787d3d12f --- /dev/null +++ b/bootstrap/src/main/scala/coursier/Bootstrap.scala @@ -0,0 +1,143 @@ +package coursier + +import java.io.{ ByteArrayOutputStream, InputStream, File } +import java.net.{ URI, URLClassLoader } +import java.nio.file.Files +import java.util.concurrent.{ Executors, ThreadFactory } + +import scala.concurrent.duration.Duration +import scala.concurrent.{ ExecutionContext, Future, Await } + +import scala.util.{ Try, Success, Failure } + +object Bootstrap extends App { + + val concurrentDownloadCount = 6 + val threadFactory = new ThreadFactory { + // from scalaz Strategy.DefaultDaemonThreadFactory + val defaultThreadFactory = Executors.defaultThreadFactory() + def newThread(r: Runnable) = { + val t = defaultThreadFactory.newThread(r) + t.setDaemon(true) + t + } + } + val defaultPool = Executors.newFixedThreadPool(concurrentDownloadCount, threadFactory) + implicit val ec = ExecutionContext.fromExecutorService(defaultPool) + + private def readFullySync(is: InputStream) = { + val buffer = new ByteArrayOutputStream() + val data = Array.ofDim[Byte](16384) + + var nRead = is.read(data, 0, data.length) + while (nRead != -1) { + buffer.write(data, 0, nRead) + nRead = is.read(data, 0, data.length) + } + + buffer.flush() + buffer.toByteArray + } + + private def errPrintln(s: String): Unit = + Console.err.println(s) + + private def exit(msg: String = ""): Nothing = { + if (msg.nonEmpty) + errPrintln(msg) + sys.exit(255) + } + + args match { + case Array(mainClass0, jarDir0, remainingArgs @ _*) => + val jarDir = new File(jarDir0) + + if (jarDir.exists()) { + if (!jarDir.isDirectory) + exit(s"Error: $jarDir0 is not a directory") + } else if (!jarDir.mkdirs()) + errPrintln(s"Warning: cannot create $jarDir0, continuing anyway.") + + val splitIdx = remainingArgs.indexOf("--") + val (jarStrUrls, userArgs) = + if (splitIdx < 0) + (remainingArgs, Nil) + else + (remainingArgs.take(splitIdx), remainingArgs.drop(splitIdx + 1)) + + val tryUrls = jarStrUrls.map(urlStr => urlStr -> Try(URI.create(urlStr).toURL)) + + val failedUrls = tryUrls.collect { + case (strUrl, Failure(t)) => strUrl -> t + } + if (failedUrls.nonEmpty) + exit( + s"Error parsing ${failedUrls.length} URL(s):\n" + + failedUrls.map { case (s, t) => s"$s: ${t.getMessage}" }.mkString("\n") + ) + + val jarUrls = tryUrls.collect { + case (_, Success(url)) => url + } + + val jarLocalUrlFutures = jarUrls.map { url => + if (url.getProtocol == "file") + Future.successful(url) + else + Future { + val path = url.getPath + val idx = path.lastIndexOf('/') + // FIXME Add other components in path to prevent conflicts? + val fileName = path.drop(idx + 1) + val dest = new File(jarDir, fileName) + + // FIXME If dest exists, do a HEAD request and check that its size or last modified time is OK? + + if (!dest.exists()) { + Console.err.println(s"Downloading $url") + try { + val conn = url.openConnection() + val lastModified = conn.getLastModified + val s = conn.getInputStream + val b = readFullySync(s) + Files.write(dest.toPath, b) + dest.setLastModified(lastModified) + } catch { case e: Exception => + Console.err.println(s"Error while downloading $url: ${e.getMessage}, ignoring it") + } + } + + dest.toURI.toURL + } + } + + val jarLocalUrls = Await.result(Future.sequence(jarLocalUrlFutures), Duration.Inf) + + val thread = Thread.currentThread() + val parentClassLoader = thread.getContextClassLoader + + val classLoader = new URLClassLoader(jarLocalUrls.toArray, parentClassLoader) + + val mainClass = + try classLoader.loadClass(mainClass0) + catch { case e: ClassNotFoundException => + exit(s"Error: class $mainClass0 not found") + } + + val mainMethod = + try mainClass.getMethod("main", classOf[Array[String]]) + catch { case e: NoSuchMethodException => + exit(s"Error: main method not found in class $mainClass0") + } + + thread.setContextClassLoader(classLoader) + try mainMethod.invoke(null, userArgs.toArray) + finally { + thread.setContextClassLoader(parentClassLoader) + } + + case _ => + exit("Usage: bootstrap main-class JAR-directory JAR-URLs...") + } + +} \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..8ac735cbb --- /dev/null +++ b/build.sbt @@ -0,0 +1,174 @@ +import sbtrelease.ReleasePlugin.ReleaseKeys.{ publishArtifactsAction, versionBump } +import sbtrelease.Version.Bump + +lazy val publishingSettings = Seq( + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + licenses := Seq("Apache 2.0" -> url("http://opensource.org/licenses/Apache-2.0")), + homepage := Some(url("https://github.com/alexarchambault/coursier")), + developers := List( + Developer("alexarchambault", "Alexandre Archambault", "", url("https://github.com/alexarchambault")) + ), + pomExtra := { + + scm:git:github.com/alexarchambault/coursier.git + scm:git:git@github.com:alexarchambault/coursier.git + github.com/alexarchambault/coursier.git + + }, + credentials += { + Seq("SONATYPE_USER", "SONATYPE_PASS").map(sys.env.get) match { + case Seq(Some(user), Some(pass)) => + Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", user, pass) + case _ => + Credentials(Path.userHome / ".ivy2" / ".credentials") + } + }, + versionBump := Bump.Bugfix, + publishArtifactsAction := PgpKeys.publishSigned.value +) ++ releaseSettings + +lazy val noPublishSettings = Seq( + publish := (), + publishLocal := (), + publishArtifact := false +) + +lazy val commonSettings = Seq( + organization := "com.github.alexarchambault", + scalaVersion := "2.11.7", + crossScalaVersions := Seq("2.10.6", "2.11.7"), + resolvers ++= Seq( + "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", + Resolver.sonatypeRepo("releases"), + Resolver.sonatypeRepo("snapshots") + ), + libraryDependencies ++= { + if (scalaVersion.value startsWith "2.10.") + Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)) + else + Seq() + } +) + +lazy val core = crossProject + .settings(commonSettings: _*) + .settings(publishingSettings: _*) + .settings( + name := "coursier", + libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.1" % "provided", + unmanagedResourceDirectories in Compile += (baseDirectory in LocalRootProject).value / "core" / "shared" / "src" / "main" / "resources", + unmanagedResourceDirectories in Test += (baseDirectory in LocalRootProject).value / "core" / "shared" / "src" / "test" / "resources", + testFrameworks += new TestFramework("utest.runner.Framework") + ) + .jvmSettings( + libraryDependencies ++= Seq( + "org.scalaz" %% "scalaz-concurrent" % "7.1.2", + "com.lihaoyi" %% "utest" % "0.3.0" % "test" + ) ++ { + if (scalaVersion.value.startsWith("2.10.")) Seq() + else Seq( + "org.scala-lang.modules" %% "scala-xml" % "1.0.3" + ) + } + ) + .jsSettings( + libraryDependencies ++= Seq( + "org.scala-js" %%% "scalajs-dom" % "0.8.0", + "com.github.japgolly.fork.scalaz" %%% "scalaz-core" % (if (scalaVersion.value.startsWith("2.10.")) "7.1.1" else "7.1.2"), + "be.doeraene" %%% "scalajs-jquery" % "0.8.0", + "com.lihaoyi" %%% "utest" % "0.3.0" % "test" + ), + postLinkJSEnv := NodeJSEnv().value, + scalaJSStage in Global := FastOptStage + ) + +lazy val coreJvm = core.jvm +lazy val coreJs = core.js + +lazy val files = project + .dependsOn(coreJvm) + .settings(commonSettings) + .settings(publishingSettings) + .settings( + name := "coursier-files", + libraryDependencies ++= Seq( + "com.lihaoyi" %% "utest" % "0.3.0" % "test" + ), + testFrameworks += new TestFramework("utest.runner.Framework") + ) + +lazy val cli = project + .dependsOn(coreJvm, files) + .settings(commonSettings) + .settings(publishingSettings) + .settings(packAutoSettings ++ publishPackTxzArchive ++ publishPackZipArchive) + .settings( + packArchivePrefix := s"coursier-cli_${scalaBinaryVersion.value}", + packArchiveTxzArtifact := Artifact("coursier-cli", "arch", "tar.xz"), + packArchiveZipArtifact := Artifact("coursier-cli", "arch", "zip") + ) + .settings( + name := "coursier-cli", + libraryDependencies ++= Seq( + "com.github.alexarchambault" %% "case-app" % "1.0.0-SNAPSHOT", + "ch.qos.logback" % "logback-classic" % "1.1.3" + ), + resourceGenerators in Compile += assembly.in(bootstrap).in(assembly).map { jar => + Seq(jar) + }.taskValue + ) + +lazy val web = project + .enablePlugins(ScalaJSPlugin) + .dependsOn(coreJs) + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + libraryDependencies ++= { + if (scalaVersion.value startsWith "2.10.") + Seq() + else + Seq("com.github.japgolly.scalajs-react" %%% "core" % "0.9.0") + }, + sourceDirectory := { + val dir = sourceDirectory.value + + if (scalaVersion.value startsWith "2.10.") + dir / "dummy" + else + dir + }, + test in Test := (), + testOnly in Test := (), + resolvers += "Webjars Bintray" at "https://dl.bintray.com/webjars/maven/", + jsDependencies ++= Seq( + ("org.webjars.bower" % "bootstrap" % "3.3.4" intransitive()) / "bootstrap.min.js" commonJSName "Bootstrap", + ("org.webjars.bower" % "react" % "0.12.2" intransitive()) / "react-with-addons.js" commonJSName "React", + ("org.webjars.bower" % "bootstrap-treeview" % "1.2.0" intransitive()) / "bootstrap-treeview.min.js" commonJSName "Treeview", + ("org.webjars.bower" % "raphael" % "2.1.4" intransitive()) / "raphael-min.js" commonJSName "Raphael" + ) + ) + +lazy val bootstrap = project + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + name := "coursier-bootstrap", + assemblyJarName in assembly := s"bootstrap.jar" + ) + +lazy val `coursier` = project.in(file(".")) + .aggregate(coreJvm, coreJs, files, cli, web, bootstrap) + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + (unmanagedSourceDirectories in Compile) := Nil, + (unmanagedSourceDirectories in Test) := Nil + ) diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index 1e7b8620e..d67a99f98 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -1,330 +1,321 @@ package coursier package cli -import java.io.File +import java.io.{ File, IOException } +import java.net.URLClassLoader +import java.nio.file.{ Files => NIOFiles } import caseapp._ -import coursier.core.{ MavenRepository, Parse, CachePolicy } -import scalaz.{ \/-, -\/ } -import scalaz.concurrent.Task - -case class Coursier( +case class CommonOptions( @HelpMessage("Keep optional dependencies (Maven)") keepOptional: Boolean, - @HelpMessage("Fetch main artifacts (default: true if --classpath is specified and sources and javadoc are not fetched, else false)") - @ExtraName("J") - default: Boolean, - @HelpMessage("Fetch source artifacts") - @ExtraName("S") - sources: Boolean, - @HelpMessage("Fetch javadoc artifacts") - @ExtraName("D") - javadoc: Boolean, - @HelpMessage("Print java -cp compatible classpath (use like java -cp $(coursier -P ..dependencies..) )") - @ExtraName("P") - @ExtraName("cp") - classpath: Boolean, @HelpMessage("Off-line mode: only use cache and local repositories") - @ExtraName("c") + @ExtraName("c") offline: Boolean, @HelpMessage("Force download: for remote repositories only: re-download items, that is, don't use cache directly") - @ExtraName("f") + @ExtraName("f") force: Boolean, @HelpMessage("Quiet output") - @ExtraName("q") + @ExtraName("q") quiet: Boolean, @HelpMessage("Increase verbosity (specify several times to increase more)") - @ExtraName("v") + @ExtraName("v") verbose: List[Unit], @HelpMessage("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)") - @ExtraName("N") + @ExtraName("N") maxIterations: Int = 100, @HelpMessage("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)") - @ExtraName("r") + @ExtraName("r") repository: List[String], - @HelpMessage("Maximim number of parallel downloads (default: 6)") - @ExtraName("n") + @HelpMessage("Maximum number of parallel downloads (default: 6)") + @ExtraName("n") parallel: Int = 6 -) extends App { +) { + val verbose0 = verbose.length + (if (quiet) 1 else 0) +} - val verbose0 = { - verbose.length + - (if (quiet) 1 else 0) +@AppName("Coursier") +@ProgName("coursier") +sealed trait CoursierCommand extends Command + +case class Fetch( + @HelpMessage("Fetch source artifacts") + @ExtraName("S") + sources: Boolean, + @HelpMessage("Fetch javadoc artifacts") + @ExtraName("D") + javadoc: Boolean, + @Recurse + common: CommonOptions +) extends CoursierCommand { + + val helper = new Helper(common, remainingArgs) + + val files0 = helper.fetch(main = true, sources = false, javadoc = false) + + Console.out.println( + files0 + .map(_.toString) + .mkString("\n") + ) + +} + +case class Launch( + @ExtraName("M") + @ExtraName("main") + mainClass: String, + @Recurse + common: CommonOptions +) extends CoursierCommand { + + val (rawDependencies, extraArgs) = { + val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0) + idxOpt.fold((remainingArgs, Seq.empty[String])) { idx => + val (l, r) = remainingArgs.splitAt(idx) + assert(r.nonEmpty) + (l, r.tail) + } } - def fileRepr(f: File) = f.toString + val helper = new Helper(common, rawDependencies) - def println(s: String) = Console.err.println(s) + val files0 = helper.fetch(main = true, sources = false, javadoc = false) - if (force && offline) { - println("Error: --offline (-c) and --force (-f) options can't be specified at the same time.") + def printParents(cl: ClassLoader): Unit = + Option(cl.getParent) match { + case None => + case Some(cl0) => + println(cl0.toString) + printParents(cl0) + } + + printParents(Thread.currentThread().getContextClassLoader) + + import scala.collection.JavaConverters._ + val cl = new URLClassLoader( + files0.map(_.toURI.toURL).toArray, + // setting this to null provokes strange things (wrt terminal, ...) + // but this is far from perfect: this puts all our dependencies along with the user's, + // and with a higher priority + Thread.currentThread().getContextClassLoader + ) + + val mainClass0 = + if (mainClass.nonEmpty) + mainClass + else { + val metaInfs = cl.findResources("META-INF/MANIFEST.MF").asScala.toVector + val mainClasses = metaInfs.flatMap { url => + Option(new java.util.jar.Manifest(url.openStream()).getMainAttributes.getValue("Main-Class")) + } + + if (mainClasses.isEmpty) { + println(s"No main class found. Specify one with -M or --main.") + sys.exit(255) + } + + if (common.verbose0 >= 0) + println(s"Found ${mainClasses.length} main class(es):\n${mainClasses.map(" " + _).mkString("\n")}") + + mainClasses.head + } + + val cls = + try cl.loadClass(mainClass0) + catch { case e: ClassNotFoundException => + println(s"Error: class $mainClass0 not found") + sys.exit(255) + } + val method = + try cls.getMethod("main", classOf[Array[String]]) + catch { case e: NoSuchMethodError => + println(s"Error: method main not found in $mainClass0") + sys.exit(255) + } + + if (common.verbose0 >= 1) + println(s"Calling $mainClass0 ${extraArgs.mkString(" ")}") + + Thread.currentThread().setContextClassLoader(cl) + method.invoke(null, extraArgs.toArray) +} + +case class Classpath( + @Recurse + common: CommonOptions +) extends CoursierCommand { + + val helper = new Helper(common, remainingArgs) + + val files0 = helper.fetch(main = true, sources = false, javadoc = false) + + Console.out.println( + files0 + .map(_.toString) + .mkString(File.pathSeparator) + ) + +} + +// TODO: allow removing a repository (with confirmations, etc.) +case class Repository( + @ValueDescription("id:baseUrl") + @ExtraName("a") + add: List[String], + @ExtraName("L") + list: Boolean, + @ExtraName("l") + defaultList: Boolean, + ivyLike: Boolean +) extends CoursierCommand { + + if (add.exists(!_.contains(":"))) { + CaseApp.printUsage[Repository](err = true) sys.exit(255) } - if (parallel <= 0) { - println(s"Error: invalid --parallel (-n) value: $parallel") + val add0 = add + .map{ s => + val Seq(id, baseUrl) = s.split(":", 2).toSeq + id -> baseUrl + } + + if ( + add0.exists(_._1.contains("/")) || + add0.exists(_._1.startsWith(".")) || + add0.exists(_._1.isEmpty) + ) { + CaseApp.printUsage[Repository](err = true) + sys.exit(255) } - def defaultLogger: MavenRepository.Logger with Files.Logger = - new MavenRepository.Logger with Files.Logger { - def downloading(url: String) = - println(s"Downloading $url") - def downloaded(url: String, success: Boolean) = - if (!success) - println(s"Failed: $url") - def readingFromCache(f: File) = {} - def puttingInCache(f: File) = {} - - def foundLocally(f: File) = {} - def downloadingArtifact(url: String) = - println(s"Downloading $url") - def downloadedArtifact(url: String, success: Boolean) = - if (!success) - println(s"Failed: $url") - } - - def verboseLogger: MavenRepository.Logger with Files.Logger = - new MavenRepository.Logger with Files.Logger { - def downloading(url: String) = - println(s"Downloading $url") - def downloaded(url: String, success: Boolean) = - println( - if (success) s"Downloaded $url" - else s"Failed: $url" - ) - def readingFromCache(f: File) = { - println(s"Reading ${fileRepr(f)} from cache") - } - def puttingInCache(f: File) = - println(s"Writing ${fileRepr(f)} in cache") - - def foundLocally(f: File) = - println(s"Found locally ${fileRepr(f)}") - def downloadingArtifact(url: String) = - println(s"Downloading $url") - def downloadedArtifact(url: String, success: Boolean) = - println( - if (success) s"Downloaded $url" - else s"Failed: $url" - ) - } - - val logger = - if (verbose0 < 0) - None - else if (verbose0 == 0) - Some(defaultLogger) - else - Some(verboseLogger) - - implicit val cachePolicy = - if (offline) - CachePolicy.LocalOnly - else if (force) - CachePolicy.ForceDownload - else - CachePolicy.Default - val cache = Cache.default - cache.init(verbose = verbose0 >= 0) - - val repositoryIds = { - val repository0 = repository - .flatMap(_.split(',')) - .map(_.trim) - .filter(_.nonEmpty) - - if (repository0.isEmpty) - cache.default() - else - repository0 - } - - val repoMap = cache.map() - - if (repositoryIds.exists(!repoMap.contains(_))) { - val notFound = repositoryIds - .filter(!repoMap.contains(_)) - - Console.err.println( - (if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") + - " not found: " + - notFound.mkString(", ") - ) + if (cache.cache.exists() && !cache.cache.isDirectory) { + Console.err.println(s"Error: ${cache.cache} not a directory") sys.exit(1) } - val (repositories0, fileCaches) = repositoryIds - .map(repoMap) - .unzip + if (!cache.cache.exists()) + cache.init(verbose = true) - val repositories = repositories0 - .map(_.copy(logger = logger)) + val current = cache.list().map(_._1).toSet + val alreadyAdded = add0 + .map(_._1) + .filter(current) - val (splitDependencies, malformed) = remainingArgs.toList - .map(_.split(":", 3).toSeq) - .partition(_.length == 3) - - if (splitDependencies.isEmpty) { - CaseApp.printUsage[Coursier]() - sys exit 1 + if (alreadyAdded.nonEmpty) { + Console.err.println(s"Error: already added: ${alreadyAdded.mkString(", ")}") + sys.exit(1) } - if (malformed.nonEmpty) { - println(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}") - sys exit 1 + for ((id, baseUrl0) <- add0) { + val baseUrl = + if (baseUrl0.endsWith("/")) + baseUrl0 + else + baseUrl0 + "/" + + cache.add(id, baseUrl, ivyLike = ivyLike) } - val moduleVersions = splitDependencies.map{ - case Seq(org, name, version) => - (Module(org, name), version) + if (defaultList) { + val map = cache.repositoryMap() + + for (id <- cache.default(withNotFound = true)) + map.get(id) match { + case Some(repo) => + println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else "")) + case None => + println(s"$id (not found)") + } } - val deps = moduleVersions.map{case (mod, ver) => - Dependency(mod, ver, scope = Scope.Runtime) + if (list) + for ((id, repo, _) <- cache.list().sortBy(_._1)) { + println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else "")) + } + +} + +case class Bootstrap( + @ExtraName("M") + @ExtraName("main") + mainClass: String, + @ExtraName("o") + output: String, + @ExtraName("D") + downloadDir: String, + @ExtraName("f") + force: Boolean, + @Recurse + common: CommonOptions +) extends CoursierCommand { + + if (mainClass.isEmpty) { + Console.err.println(s"Error: no main class specified. Specify one with -M or --main") + sys.exit(255) } - val startRes = Resolution( - deps.toSet, - filter = Some(dep => keepOptional || !dep.optional) + if (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 downloadDir0 = + if (downloadDir.isEmpty) + "$HOME/" + else + downloadDir + + val bootstrapJar = + Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match { + case Some(is) => Files.readFullySync(is) + case None => + Console.err.println(s"Error: bootstrap JAR not found") + sys.exit(1) + } + + // scala-library version in the resulting JARs has to match the one in the bootstrap JAR + // This should be enforced more strictly (possibly by having one bootstrap JAR per scala version). + + val helper = new Helper( + common, + remainingArgs :+ s"org.scala-lang:scala-library:${scala.util.Properties.versionNumberString}" ) - val fetchQuiet = coursier.fetch(repositories) - val fetch0 = - if (verbose0 == 0) fetchQuiet - else { - modVers: Seq[(Module, String)] => - val print = Task{ - println(s"Getting ${modVers.length} project definition(s)") - } + val artifacts = helper.res.artifacts - print.flatMap(_ => fetchQuiet(modVers)) - } + val urls = artifacts.map(_.url) - if (verbose0 >= 0) - println(s"Resolving\n" + moduleVersions.map{case (mod, ver) => s" $mod:$ver"}.mkString("\n")) + 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")}") - val res = startRes - .process - .run(fetch0, maxIterations) - .run - - if (!res.isDone) { - println(s"Maximum number of iteration reached!") - 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 repr(dep: Dependency) = { - // dep.version can be an interval, whereas the one from project can't - val version = res - .projectCache - .get(dep.moduleVersion) - .map(_._2.version) - .getOrElse(dep.version) - val extra = - if (version == dep.version) "" - else s" ($version for ${dep.version})" + val shellPreamble = Seq( + "#!/usr/bin/env sh", + "exec java -jar \"$0\" \"" + mainClass + "\" \"" + downloadDir + "\" " + urls.map("\"" + _ + "\"").mkString(" ") + " -- \"$@\"", + "" + ).mkString("\n") - ( - Seq( - dep.module.organization, - dep.module.name, - dep.attributes.`type` - ) ++ - Some(dep.attributes.classifier) - .filter(_.nonEmpty) - .toSeq ++ - Seq( - version - ) - ).mkString(":") + extra + try NIOFiles.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ bootstrapJar) + catch { case e: IOException => + Console.err.println(s"Error while writing $output0: ${e.getMessage}") + sys.exit(1) } - val trDeps = res - .minDependencies - .toList - .sortBy(repr) - - if (verbose0 >= 0) { - println("") - println( - trDeps - .map(repr) - .distinct - .mkString("\n") - ) - } - - if (res.conflicts.nonEmpty) { - // Needs test - println(s"${res.conflicts.size} conflict(s):\n ${res.conflicts.toList.map(repr).sorted.mkString(" \n")}") - } - - val errors = res.errors - if (errors.nonEmpty) { - println(s"\n${errors.size} error(s):") - for ((dep, errs) <- errors) { - println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}") - } - } - - if (classpath || default || sources || javadoc) { - println("") - - val artifacts0 = res.artifacts - val default0 = default || (!sources && !javadoc) - val artifacts = artifacts0 - .flatMap{ artifact => - var l = List.empty[Artifact] - if (sources) - l = artifact.extra.get("sources").toList ::: l - if (javadoc) - l = artifact.extra.get("javadoc").toList ::: l - if (default0) - l = artifact :: l - - l - } - - val files = { - var files0 = cache - .files() - .copy(logger = logger) - files0 = files0.copy(concurrentDownloadCount = parallel) - files0 - } - - val tasks = artifacts.map(artifact => files.file(artifact).run.map(artifact.->)) - def printTask = Task{ - if (verbose0 >= 0 && artifacts.nonEmpty) - println(s"Found ${artifacts.length} artifacts") - } - val task = printTask.flatMap(_ => Task.gatherUnordered(tasks)) - - val results = task.run - val errors = results.collect{case (artifact, -\/(err)) => artifact -> err } - val files0 = results.collect{case (artifact, \/-(f)) => f } - - if (errors.nonEmpty) { - println(s"${errors.size} error(s):") - for ((artifact, error) <- errors) { - println(s" ${artifact.url}: $error") - } - } - - Console.out.println( - files0 - .map(_.toString) - .mkString(if (classpath) File.pathSeparator else "\n") - ) - } } -object Coursier extends AppOf[Coursier] { - val parser = default -} +object Coursier extends CommandAppOf[CoursierCommand] diff --git a/cli/src/main/scala/coursier/cli/Helper.scala b/cli/src/main/scala/coursier/cli/Helper.scala new file mode 100644 index 000000000..84593d4bf --- /dev/null +++ b/cli/src/main/scala/coursier/cli/Helper.scala @@ -0,0 +1,302 @@ +package coursier.cli + +import java.io.File + +import caseapp.CaseApp +import coursier._ +import coursier.core.{ CachePolicy, MavenRepository } + +import scalaz.{ \/-, -\/ } +import scalaz.concurrent.Task + +object Helper { + def validate(common: CommonOptions) = { + import common._ + + if (force && offline) { + Console.err.println("Error: --offline (-c) and --force (-f) options can't be specified at the same time.") + sys.exit(255) + } + + if (parallel <= 0) { + Console.err.println(s"Error: invalid --parallel (-n) value: $parallel") + sys.exit(255) + } + + ??? + } + + def fileRepr(f: File) = f.toString + + def errPrintln(s: String) = Console.err.println(s) + + def defaultLogger: MavenRepository.Logger with Files.Logger = + new MavenRepository.Logger with Files.Logger { + def downloading(url: String) = + errPrintln(s"Downloading $url") + def downloaded(url: String, success: Boolean) = + if (!success) + errPrintln(s"Failed: $url") + def readingFromCache(f: File) = {} + def puttingInCache(f: File) = {} + + def foundLocally(f: File) = {} + def downloadingArtifact(url: String) = + errPrintln(s"Downloading $url") + def downloadedArtifact(url: String, success: Boolean) = + if (!success) + errPrintln(s"Failed: $url") + } + + def verboseLogger: MavenRepository.Logger with Files.Logger = + new MavenRepository.Logger with Files.Logger { + def downloading(url: String) = + errPrintln(s"Downloading $url") + def downloaded(url: String, success: Boolean) = + errPrintln( + if (success) s"Downloaded $url" + else s"Failed: $url" + ) + def readingFromCache(f: File) = { + errPrintln(s"Reading ${fileRepr(f)} from cache") + } + def puttingInCache(f: File) = + errPrintln(s"Writing ${fileRepr(f)} in cache") + + def foundLocally(f: File) = + errPrintln(s"Found locally ${fileRepr(f)}") + def downloadingArtifact(url: String) = + errPrintln(s"Downloading $url") + def downloadedArtifact(url: String, success: Boolean) = + errPrintln( + if (success) s"Downloaded $url" + else s"Failed: $url" + ) + } + +} + +class Helper( + common: CommonOptions, + remainingArgs: Seq[String] +) { + import common._ + import Helper.errPrintln + + + val logger = + if (verbose0 < 0) + None + else if (verbose0 == 0) + Some(Helper.defaultLogger) + else + Some(Helper.verboseLogger) + + implicit val cachePolicy = + if (offline) + CachePolicy.LocalOnly + else if (force) + CachePolicy.ForceDownload + else + CachePolicy.Default + + val cache = Cache.default + cache.init(verbose = verbose0 >= 0) + + val repositoryIds = { + val repositoryIds0 = repository + .flatMap(_.split(',')) + .map(_.trim) + .filter(_.nonEmpty) + + if (repositoryIds0.isEmpty) + cache.default() + else + repositoryIds0 + } + + val repoMap = cache.map() + + if (repositoryIds.exists(!repoMap.contains(_))) { + val notFound = repositoryIds + .filter(!repoMap.contains(_)) + + errPrintln( + (if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") + + " not found: " + + notFound.mkString(", ") + ) + + sys.exit(1) + } + + val (repositories0, fileCaches) = repositoryIds + .map(repoMap) + .unzip + + val repositories = repositories0 + .map(_.copy(logger = logger)) + + val (rawDependencies, extraArgs) = { + val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0) + idxOpt.fold((remainingArgs, Seq.empty[String])) { idx => + val (l, r) = remainingArgs.splitAt(idx) + assert(r.nonEmpty) + (l, r.tail) + } + } + + val (splitDependencies, malformed) = rawDependencies.toList + .map(_.split(":", 3).toSeq) + .partition(_.length == 3) + + if (splitDependencies.isEmpty) { + ??? + // CaseApp.printUsage[Coursier]() + sys exit 1 + } + + if (malformed.nonEmpty) { + errPrintln(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}") + sys exit 1 + } + + val moduleVersions = splitDependencies.map{ + case Seq(org, name, version) => + (Module(org, name), version) + } + + val deps = moduleVersions.map{case (mod, ver) => + Dependency(mod, ver, scope = Scope.Runtime) + } + + val startRes = Resolution( + deps.toSet, + filter = Some(dep => keepOptional || !dep.optional) + ) + + val fetchQuiet = coursier.fetch(repositories) + val fetch0 = + if (verbose0 == 0) fetchQuiet + else { + modVers: Seq[(Module, String)] => + val print = Task{ + errPrintln(s"Getting ${modVers.length} project definition(s)") + } + + print.flatMap(_ => fetchQuiet(modVers)) + } + + if (verbose0 >= 0) + errPrintln(s"Resolving\n" + moduleVersions.map{case (mod, ver) => s" $mod:$ver"}.mkString("\n")) + + val res = startRes + .process + .run(fetch0, maxIterations) + .run + + if (!res.isDone) { + errPrintln(s"Maximum number of iteration reached!") + sys.exit(1) + } + + def repr(dep: Dependency) = { + // dep.version can be an interval, whereas the one from project can't + val version = res + .projectCache + .get(dep.moduleVersion) + .map(_._2.version) + .getOrElse(dep.version) + val extra = + if (version == dep.version) "" + else s" ($version for ${dep.version})" + + ( + Seq( + dep.module.organization, + dep.module.name, + dep.attributes.`type` + ) ++ + Some(dep.attributes.classifier) + .filter(_.nonEmpty) + .toSeq ++ + Seq( + version + ) + ).mkString(":") + extra + } + + val trDeps = res + .minDependencies + .toList + .sortBy(repr) + + if (verbose0 >= 0) { + println("") + println( + trDeps + .map(repr) + .distinct + .mkString("\n") + ) + } + + if (res.conflicts.nonEmpty) { + // Needs test + println(s"${res.conflicts.size} conflict(s):\n ${res.conflicts.toList.map(repr).sorted.mkString(" \n")}") + } + + val errors = res.errors + if (errors.nonEmpty) { + println(s"\n${errors.size} error(s):") + for ((dep, errs) <- errors) { + println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}") + } + } + + def fetch(main: Boolean, sources: Boolean, javadoc: Boolean): Seq[File] = { + println("") + + val artifacts0 = res.artifacts + val main0 = main || (!sources && !javadoc) + val artifacts = artifacts0.flatMap{ artifact => + var l = List.empty[Artifact] + if (sources) + l = artifact.extra.get("sources").toList ::: l + if (javadoc) + l = artifact.extra.get("javadoc").toList ::: l + if (main0) + l = artifact :: l + + l + } + + val files = { + var files0 = cache + .files() + .copy(logger = logger) + files0 = files0.copy(concurrentDownloadCount = parallel) + files0 + } + + val tasks = artifacts.map(artifact => files.file(artifact).run.map(artifact.->)) + def printTask = Task{ + if (verbose0 >= 0 && artifacts.nonEmpty) + println(s"Found ${artifacts.length} artifacts") + } + val task = printTask.flatMap(_ => Task.gatherUnordered(tasks)) + + val results = task.run + val errors = results.collect{case (artifact, -\/(err)) => artifact -> err } + val files0 = results.collect{case (artifact, \/-(f)) => f } + + if (errors.nonEmpty) { + println(s"${errors.size} error(s):") + for ((artifact, error) <- errors) { + println(s" ${artifact.url}: $error") + } + } + + files0 + } +} diff --git a/cli/src/main/scala/coursier/cli/Repositories.scala b/cli/src/main/scala/coursier/cli/Repositories.scala deleted file mode 100644 index 4e717fa03..000000000 --- a/cli/src/main/scala/coursier/cli/Repositories.scala +++ /dev/null @@ -1,87 +0,0 @@ -package coursier.cli - -import coursier.Cache -import caseapp._ - -// TODO: allow removing a repository (with confirmations, etc.) -case class Repositories( - @ValueDescription("id:baseUrl") @ExtraName("a") add: List[String], - @ExtraName("L") list: Boolean, - @ExtraName("l") defaultList: Boolean, - ivyLike: Boolean -) extends App { - - if (add.exists(!_.contains(":"))) { - CaseApp.printUsage[Repositories](err = true) - sys.exit(255) - } - - val add0 = add - .map{ s => - val Seq(id, baseUrl) = s.split(":", 2).toSeq - id -> baseUrl - } - - if ( - add0.exists(_._1.contains("/")) || - add0.exists(_._1.startsWith(".")) || - add0.exists(_._1.isEmpty) - ) { - CaseApp.printUsage[Repositories](err = true) - sys.exit(255) - } - - - val cache = Cache.default - - if (cache.cache.exists() && !cache.cache.isDirectory) { - Console.err.println(s"Error: ${cache.cache} not a directory") - sys.exit(1) - } - - if (!cache.cache.exists()) - cache.init(verbose = true) - - val current = cache.list().map(_._1).toSet - - val alreadyAdded = add0 - .map(_._1) - .filter(current) - - if (alreadyAdded.nonEmpty) { - Console.err.println(s"Error: already added: ${alreadyAdded.mkString(", ")}") - sys.exit(1) - } - - for ((id, baseUrl0) <- add0) { - val baseUrl = - if (baseUrl0.endsWith("/")) - baseUrl0 - else - baseUrl0 + "/" - - cache.add(id, baseUrl, ivyLike = ivyLike) - } - - if (defaultList) { - val map = cache.repositoryMap() - - for (id <- cache.default(withNotFound = true)) - map.get(id) match { - case Some(repo) => - println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else "")) - case None => - println(s"$id (not found)") - } - } - - if (list) - for ((id, repo, _) <- cache.list().sortBy(_._1)) { - println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else "")) - } - -} - -object Repositories extends AppOf[Repositories] { - val parser = default -} diff --git a/core-js/src/main/scala/coursier/core/MavenRepository.scala b/core/js/src/main/scala/coursier/core/MavenRepository.scala similarity index 100% rename from core-js/src/main/scala/coursier/core/MavenRepository.scala rename to core/js/src/main/scala/coursier/core/MavenRepository.scala diff --git a/core-js/src/main/scala/coursier/core/compatibility/package.scala b/core/js/src/main/scala/coursier/core/compatibility/package.scala similarity index 100% rename from core-js/src/main/scala/coursier/core/compatibility/package.scala rename to core/js/src/main/scala/coursier/core/compatibility/package.scala diff --git a/core-js/src/main/scala/scalaz/concurrent/package.scala b/core/js/src/main/scala/scalaz/concurrent/package.scala similarity index 100% rename from core-js/src/main/scala/scalaz/concurrent/package.scala rename to core/js/src/main/scala/scalaz/concurrent/package.scala diff --git a/core-js/src/test/scala/coursier/test/JsTests.scala b/core/js/src/test/scala/coursier/test/JsTests.scala similarity index 100% rename from core-js/src/test/scala/coursier/test/JsTests.scala rename to core/js/src/test/scala/coursier/test/JsTests.scala diff --git a/core-js/src/test/scala/coursier/test/compatibility/package.scala b/core/js/src/test/scala/coursier/test/compatibility/package.scala similarity index 90% rename from core-js/src/test/scala/coursier/test/compatibility/package.scala rename to core/js/src/test/scala/coursier/test/compatibility/package.scala index 87ef5860a..38099453c 100644 --- a/core-js/src/test/scala/coursier/test/compatibility/package.scala +++ b/core/js/src/test/scala/coursier/test/compatibility/package.scala @@ -13,7 +13,7 @@ package object compatibility { def textResource(path: String)(implicit ec: ExecutionContext): Future[String] = { val p = Promise[String]() - fs.readFile("core/src/test/resources/" + path, "utf-8", { + fs.readFile("core/shared/src/test/resources/" + path, "utf-8", { (err: js.Dynamic, data: js.Dynamic) => if (err == null) p.success(data.asInstanceOf[String]) else p.failure(new Exception(err.toString)) diff --git a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala b/core/jvm/src/main/scala/coursier/core/MavenRepository.scala similarity index 100% rename from core-jvm/src/main/scala/coursier/core/MavenRepository.scala rename to core/jvm/src/main/scala/coursier/core/MavenRepository.scala diff --git a/core-jvm/src/main/scala/coursier/core/compatibility/package.scala b/core/jvm/src/main/scala/coursier/core/compatibility/package.scala similarity index 100% rename from core-jvm/src/main/scala/coursier/core/compatibility/package.scala rename to core/jvm/src/main/scala/coursier/core/compatibility/package.scala diff --git a/core-jvm/src/test/scala/coursier/test/IvyLocalTests.scala b/core/jvm/src/test/scala/coursier/test/IvyLocalTests.scala similarity index 100% rename from core-jvm/src/test/scala/coursier/test/IvyLocalTests.scala rename to core/jvm/src/test/scala/coursier/test/IvyLocalTests.scala diff --git a/core-jvm/src/test/scala/coursier/test/compatibility/package.scala b/core/jvm/src/test/scala/coursier/test/compatibility/package.scala similarity index 100% rename from core-jvm/src/test/scala/coursier/test/compatibility/package.scala rename to core/jvm/src/test/scala/coursier/test/compatibility/package.scala diff --git a/core/src/main/scala/coursier/core/Definitions.scala b/core/shared/src/main/scala/coursier/core/Definitions.scala similarity index 100% rename from core/src/main/scala/coursier/core/Definitions.scala rename to core/shared/src/main/scala/coursier/core/Definitions.scala diff --git a/core/src/main/scala/coursier/core/Exclusions.scala b/core/shared/src/main/scala/coursier/core/Exclusions.scala similarity index 100% rename from core/src/main/scala/coursier/core/Exclusions.scala rename to core/shared/src/main/scala/coursier/core/Exclusions.scala diff --git a/core/src/main/scala/coursier/core/Orders.scala b/core/shared/src/main/scala/coursier/core/Orders.scala similarity index 100% rename from core/src/main/scala/coursier/core/Orders.scala rename to core/shared/src/main/scala/coursier/core/Orders.scala diff --git a/core/src/main/scala/coursier/core/Parse.scala b/core/shared/src/main/scala/coursier/core/Parse.scala similarity index 100% rename from core/src/main/scala/coursier/core/Parse.scala rename to core/shared/src/main/scala/coursier/core/Parse.scala diff --git a/core/src/main/scala/coursier/core/Repository.scala b/core/shared/src/main/scala/coursier/core/Repository.scala similarity index 100% rename from core/src/main/scala/coursier/core/Repository.scala rename to core/shared/src/main/scala/coursier/core/Repository.scala diff --git a/core/src/main/scala/coursier/core/Resolution.scala b/core/shared/src/main/scala/coursier/core/Resolution.scala similarity index 100% rename from core/src/main/scala/coursier/core/Resolution.scala rename to core/shared/src/main/scala/coursier/core/Resolution.scala diff --git a/core/src/main/scala/coursier/core/ResolutionProcess.scala b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala similarity index 100% rename from core/src/main/scala/coursier/core/ResolutionProcess.scala rename to core/shared/src/main/scala/coursier/core/ResolutionProcess.scala diff --git a/core/src/main/scala/coursier/core/Version.scala b/core/shared/src/main/scala/coursier/core/Version.scala similarity index 100% rename from core/src/main/scala/coursier/core/Version.scala rename to core/shared/src/main/scala/coursier/core/Version.scala diff --git a/core/src/main/scala/coursier/core/Versions.scala b/core/shared/src/main/scala/coursier/core/Versions.scala similarity index 100% rename from core/src/main/scala/coursier/core/Versions.scala rename to core/shared/src/main/scala/coursier/core/Versions.scala diff --git a/core/src/main/scala/coursier/core/Xml.scala b/core/shared/src/main/scala/coursier/core/Xml.scala similarity index 100% rename from core/src/main/scala/coursier/core/Xml.scala rename to core/shared/src/main/scala/coursier/core/Xml.scala diff --git a/core/src/main/scala/coursier/package.scala b/core/shared/src/main/scala/coursier/package.scala similarity index 100% rename from core/src/main/scala/coursier/package.scala rename to core/shared/src/main/scala/coursier/package.scala diff --git a/core/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0 b/core/shared/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0 similarity index 100% rename from core/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0 rename to core/shared/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0 diff --git a/core/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0.jcabi b/core/shared/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0.jcabi similarity index 100% rename from core/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0.jcabi rename to core/shared/src/test/resources/resolutions/com.github.alexarchambault/argonaut-shapeless_6.1_2.11/0.2.0.jcabi diff --git a/core/src/test/resources/resolutions/com.github.alexarchambault/coursier_2.11/0.1.0-SNAPSHOT b/core/shared/src/test/resources/resolutions/com.github.alexarchambault/coursier_2.11/0.1.0-SNAPSHOT similarity index 100% rename from core/src/test/resources/resolutions/com.github.alexarchambault/coursier_2.11/0.1.0-SNAPSHOT rename to core/shared/src/test/resources/resolutions/com.github.alexarchambault/coursier_2.11/0.1.0-SNAPSHOT diff --git a/core/src/test/resources/resolutions/com.github.fommil/java-logging/1.2-SNAPSHOT b/core/shared/src/test/resources/resolutions/com.github.fommil/java-logging/1.2-SNAPSHOT similarity index 100% rename from core/src/test/resources/resolutions/com.github.fommil/java-logging/1.2-SNAPSHOT rename to core/shared/src/test/resources/resolutions/com.github.fommil/java-logging/1.2-SNAPSHOT diff --git a/core/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1 b/core/shared/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1 similarity index 100% rename from core/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1 rename to core/shared/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1 diff --git a/core/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1.jcabi b/core/shared/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1.jcabi similarity index 100% rename from core/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1.jcabi rename to core/shared/src/test/resources/resolutions/org.apache.spark/spark-core_2.11/1.3.1.jcabi diff --git a/core/src/test/scala/coursier/test/CentralTests.scala b/core/shared/src/test/scala/coursier/test/CentralTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/CentralTests.scala rename to core/shared/src/test/scala/coursier/test/CentralTests.scala diff --git a/core/src/test/scala/coursier/test/ExclusionsTests.scala b/core/shared/src/test/scala/coursier/test/ExclusionsTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/ExclusionsTests.scala rename to core/shared/src/test/scala/coursier/test/ExclusionsTests.scala diff --git a/core/src/test/scala/coursier/test/PomParsingTests.scala b/core/shared/src/test/scala/coursier/test/PomParsingTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/PomParsingTests.scala rename to core/shared/src/test/scala/coursier/test/PomParsingTests.scala diff --git a/core/src/test/scala/coursier/test/ResolutionTests.scala b/core/shared/src/test/scala/coursier/test/ResolutionTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/ResolutionTests.scala rename to core/shared/src/test/scala/coursier/test/ResolutionTests.scala diff --git a/core/src/test/scala/coursier/test/TestRepository.scala b/core/shared/src/test/scala/coursier/test/TestRepository.scala similarity index 100% rename from core/src/test/scala/coursier/test/TestRepository.scala rename to core/shared/src/test/scala/coursier/test/TestRepository.scala diff --git a/core/src/test/scala/coursier/test/VersionConstraintTests.scala b/core/shared/src/test/scala/coursier/test/VersionConstraintTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/VersionConstraintTests.scala rename to core/shared/src/test/scala/coursier/test/VersionConstraintTests.scala diff --git a/core/src/test/scala/coursier/test/VersionIntervalTests.scala b/core/shared/src/test/scala/coursier/test/VersionIntervalTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/VersionIntervalTests.scala rename to core/shared/src/test/scala/coursier/test/VersionIntervalTests.scala diff --git a/core/src/test/scala/coursier/test/VersionTests.scala b/core/shared/src/test/scala/coursier/test/VersionTests.scala similarity index 100% rename from core/src/test/scala/coursier/test/VersionTests.scala rename to core/shared/src/test/scala/coursier/test/VersionTests.scala diff --git a/core/src/test/scala/coursier/test/package.scala b/core/shared/src/test/scala/coursier/test/package.scala similarity index 100% rename from core/src/test/scala/coursier/test/package.scala rename to core/shared/src/test/scala/coursier/test/package.scala diff --git a/project/Coursier.scala b/project/Coursier.scala deleted file mode 100644 index 80a4f8dd6..000000000 --- a/project/Coursier.scala +++ /dev/null @@ -1,183 +0,0 @@ -import org.scalajs.sbtplugin.ScalaJSPlugin -import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ - -import sbt._, Keys._ - -import sbtrelease.ReleasePlugin.releaseSettings -import sbtrelease.ReleasePlugin.ReleaseKeys.{ publishArtifactsAction, versionBump } -import sbtrelease.Version.Bump -import com.typesafe.sbt.pgp.PgpKeys - -import xerial.sbt.Pack._ - - -object CoursierBuild extends Build { - - lazy val publishingSettings = Seq[Setting[_]]( - publishMavenStyle := true, - publishTo := { - val nexus = "https://oss.sonatype.org/" - if (isSnapshot.value) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") - }, - pomExtra := { - https://github.com/alexarchambault/coursier - - - Apache 2.0 - http://opensource.org/licenses/Apache-2.0 - - - - scm:git:github.com/alexarchambault/coursier.git - scm:git:git@github.com:alexarchambault/coursier.git - github.com/alexarchambault/coursier.git - - - - alexarchambault - Alexandre Archambault - https://github.com/alexarchambault - - - }, - credentials += { - Seq("SONATYPE_USER", "SONATYPE_PASS").map(sys.env.get) match { - case Seq(Some(user), Some(pass)) => - Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", user, pass) - case _ => - Credentials(Path.userHome / ".ivy2" / ".credentials") - } - }, - versionBump := Bump.Bugfix, - publishArtifactsAction := PgpKeys.publishSigned.value - ) ++ releaseSettings - - lazy val commonSettings = Seq[Setting[_]]( - organization := "com.github.alexarchambault", - scalaVersion := "2.11.7", - crossScalaVersions := Seq("2.10.5", "2.11.7"), - resolvers ++= Seq( - "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", - Resolver.sonatypeRepo("releases") - ) - ) ++ publishingSettings - - private lazy val commonCoreSettings = commonSettings ++ Seq[Setting[_]]( - name := "coursier", - libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.1" % "provided", - unmanagedSourceDirectories in Compile += (baseDirectory in LocalRootProject).value / "core" / "src" / "main" / "scala", - unmanagedSourceDirectories in Test += (baseDirectory in LocalRootProject).value / "core" / "src" / "test" / "scala", - unmanagedResourceDirectories in Compile += (baseDirectory in LocalRootProject).value / "core" / "src" / "main" / "resources", - unmanagedResourceDirectories in Test += (baseDirectory in LocalRootProject).value / "core" / "src" / "test" / "resources", - testFrameworks += new TestFramework("utest.runner.Framework") - ) - - lazy val coreJvm = Project(id = "core-jvm", base = file("core-jvm")) - .settings(commonCoreSettings: _*) - .settings( - libraryDependencies ++= Seq( - "org.scalaz" %% "scalaz-concurrent" % "7.1.2", - "com.lihaoyi" %% "utest" % "0.3.0" % "test" - ) ++ { - if (scalaVersion.value.startsWith("2.10.")) Seq() - else Seq( - "org.scala-lang.modules" %% "scala-xml" % "1.0.3" - ) - } - ) - - lazy val coreJs = Project(id = "core-js", base = file("core-js")) - .settings(commonCoreSettings: _*) - .settings( - libraryDependencies ++= Seq( - "org.scala-js" %%% "scalajs-dom" % "0.8.0", - "com.github.japgolly.fork.scalaz" %%% "scalaz-core" % (if (scalaVersion.value.startsWith("2.10.")) "7.1.1" else "7.1.2"), - "be.doeraene" %%% "scalajs-jquery" % "0.8.0", - "com.lihaoyi" %%% "utest" % "0.3.0" % "test" - ), - postLinkJSEnv := NodeJSEnv().value, - scalaJSStage in Global := FastOptStage - ) - .enablePlugins(ScalaJSPlugin) - - lazy val files = Project(id = "files", base = file("files")) - .dependsOn(coreJvm) - .settings(commonSettings: _*) - .settings( - name := "coursier-files", - libraryDependencies ++= Seq( - // "org.http4s" %% "http4s-blazeclient" % "0.8.2", - "com.lihaoyi" %% "utest" % "0.3.0" % "test" - ), - testFrameworks += new TestFramework("utest.runner.Framework") - ) - - lazy val cli = Project(id = "cli", base = file("cli")) - .dependsOn(coreJvm, files) - .settings(commonSettings ++ packAutoSettings ++ publishPackTxzArchive ++ publishPackZipArchive: _*) - .settings( - packArchivePrefix := s"coursier-cli_${scalaBinaryVersion.value}", - packArchiveTxzArtifact := Artifact("coursier-cli", "arch", "tar.xz"), - packArchiveZipArtifact := Artifact("coursier-cli", "arch", "zip") - ) - .settings( - name := "coursier-cli", - libraryDependencies ++= Seq( - "com.github.alexarchambault" %% "case-app" % "0.3.0", - "ch.qos.logback" % "logback-classic" % "1.1.3" - ) ++ { - if (scalaVersion.value startsWith "2.10.") - Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)) - else - Seq() - } - ) - - lazy val web = Project(id = "web", base = file("web")) - .dependsOn(coreJs) - .settings(commonSettings: _*) - .settings( - libraryDependencies ++= { - if (scalaVersion.value startsWith "2.10.") - Seq() - else - Seq( - "com.github.japgolly.scalajs-react" %%% "core" % "0.9.0" - ) - }, - sourceDirectory := { - val dir = sourceDirectory.value - - if (scalaVersion.value startsWith "2.10.") - dir / "dummy" - else - dir - }, - publish := (), - publishLocal := (), - test in Test := (), - testOnly in Test := (), - resolvers += "Webjars Bintray" at "https://dl.bintray.com/webjars/maven/", - jsDependencies ++= Seq( - ("org.webjars.bower" % "bootstrap" % "3.3.4" intransitive()) / "bootstrap.min.js" commonJSName "Bootstrap", - ("org.webjars.bower" % "react" % "0.12.2" intransitive()) / "react-with-addons.js" commonJSName "React", - ("org.webjars.bower" % "bootstrap-treeview" % "1.2.0" intransitive()) / "bootstrap-treeview.min.js" commonJSName "Treeview", - ("org.webjars.bower" % "raphael" % "2.1.4" intransitive()) / "raphael-min.js" commonJSName "Raphael" - ) - ) - .enablePlugins(ScalaJSPlugin) - - lazy val root = Project(id = "root", base = file(".")) - .aggregate(coreJvm, coreJs, files, cli, web) - .settings(commonSettings: _*) - .settings( - (unmanagedSourceDirectories in Compile) := Nil, - (unmanagedSourceDirectories in Test) := Nil, - publish := (), - publishLocal := () - ) - -} diff --git a/project/plugins.sbt b/project/plugins.sbt index dcd3228a0..591cf84ea 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,9 +1,6 @@ addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.6.8") - -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.4") - +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") - addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5") - addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0") diff --git a/project/travis.sh b/project/travis.sh index 2424df814..ae2049176 100755 --- a/project/travis.sh +++ b/project/travis.sh @@ -35,7 +35,7 @@ else fi # Required for ~/.ivy2/local repo tests -sbt core-jvm/publish-local +sbt coreJVM/publish-local SBT_COMMANDS="$SBT_COMMANDS test" @@ -43,7 +43,7 @@ SBT_COMMANDS="$SBT_COMMANDS test" PUSH_GHPAGES=0 if isNotPr && isJdk7 && isMaster; then - SBT_COMMANDS="$SBT_COMMANDS core-jvm/publish core-js/publish files/publish cli/publish" + SBT_COMMANDS="$SBT_COMMANDS coreJVM/publish coreJS/publish files/publish cli/publish" fi if isNotPr && isJdk7 && isMasterOrDevelop; then