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 c81650ae7..baa6c5069 100644
--- a/cli/src/main/scala-2.11/coursier/cli/Helper.scala
+++ b/cli/src/main/scala-2.11/coursier/cli/Helper.scala
@@ -99,6 +99,18 @@ class Helper(
MavenRepository("https://repo1.maven.org/maven2")
)
+ val sourceDirectories = common.sources.map { path =>
+ val subDir = "target/repository"
+ val dir = new File(path)
+ val repoDir = new File(dir, subDir)
+ if (!dir.exists())
+ Console.err.println(s"Warning: sources $path not found")
+ else if (!repoDir.exists())
+ Console.err.println(s"Warning: directory $subDir not found under sources path $path")
+
+ repoDir
+ }
+
val repositoriesValidation = CacheParse.repositories(common.repository).map { repos0 =>
var repos = (if (common.noDefault) Nil else defaultRepositories) ++ repos0
@@ -119,10 +131,14 @@ class Helper(
}
val repositories = repositoriesValidation match {
- case Success(repos) => repos
+ case Success(repos) =>
+ val sourceRepositories = sourceDirectories.map(dir =>
+ MavenRepository(dir.toURI.toString, changing = Some(true))
+ )
+ sourceRepositories ++ repos
case Failure(errors) =>
prematureExit(
- s"Error parsing repositories:\n${errors.list.map(" "+_).mkString("\n")}"
+ s"Error with repositories:\n${errors.list.map(" "+_).mkString("\n")}"
)
}
@@ -140,8 +156,36 @@ class Helper(
s"Cannot parse forced versions:\n" + forceVersionErrors.map(" "+_).mkString("\n")
}
+ val sourceRepositoryForceVersions = sourceDirectories.flatMap { base =>
+
+ // FIXME Also done in the plugin module
+
+ def pomDirComponents(f: File, components: Vector[String]): Stream[Vector[String]] =
+ if (f.isDirectory) {
+ val components0 = components :+ f.getName
+ Option(f.listFiles()).toStream.flatten.flatMap(pomDirComponents(_, components0))
+ } else if (f.getName.endsWith(".pom"))
+ Stream(components)
+ else
+ Stream.empty
+
+ Option(base.listFiles())
+ .toVector
+ .flatten
+ .flatMap(pomDirComponents(_, Vector()))
+ // at least 3 for org / name / version - the contrary should not happen, but who knows
+ .filter(_.length >= 3)
+ .map { components =>
+ val org = components.dropRight(2).mkString(".")
+ val name = components(components.length - 2)
+ val version = components.last
+
+ Module(org, name) -> version
+ }
+ }
+
val forceVersions = {
- val grouped = forceVersions0
+ val grouped = (forceVersions0 ++ sourceRepositoryForceVersions)
.groupBy { case (mod, _) => mod }
.map { case (mod, l) => mod -> l.map { case (_, version) => version } }
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 a0d635795..e790ad1c6 100644
--- a/cli/src/main/scala-2.11/coursier/cli/Options.scala
+++ b/cli/src/main/scala-2.11/coursier/cli/Options.scala
@@ -24,9 +24,12 @@ case class CommonOptions(
@Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
@Short("N")
maxIterations: Int = 100,
- @Help("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)")
+ @Help("Repository - for multiple repositories, separate with comma and/or add this option multiple times (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
@Short("r")
repository: List[String],
+ @Help("Source repository - for multiple repositories, separate with comma and/or add this option multiple times")
+ @Short("R")
+ sources: List[String],
@Help("Do not add default repositories (~/.ivy2/local, and Central)")
noDefault: Boolean = false,
@Help("Modify names in Maven repository paths for SBT plugins")
diff --git a/core/jvm/src/main/scala/coursier/maven/WritePom.scala b/core/jvm/src/main/scala/coursier/maven/WritePom.scala
new file mode 100644
index 000000000..26817608f
--- /dev/null
+++ b/core/jvm/src/main/scala/coursier/maven/WritePom.scala
@@ -0,0 +1,77 @@
+package coursier.maven
+
+import coursier.core.{ Dependency, Project }
+
+object WritePom {
+
+ def project(proj: Project, packaging: Option[String]) = {
+
+ def dependencyNode(config: String, dep: Dependency) = {
+
+ {dep.module.organization}
+ {dep.module.name}
+ {
+ if (dep.version.isEmpty)
+ Nil
+ else
+ Seq({dep.version})
+ }
+ {
+ if (config.isEmpty)
+ Nil
+ else
+ Seq({config})
+ }
+
+ }
+
+
+ // parent
+ {proj.module.organization}
+ {proj.module.name}
+ {
+ packaging
+ .map(p => {p})
+ .toSeq
+ }
+ {proj.info.description}
+ {proj.info.homePage}
+ {proj.version}
+ // licenses
+ {proj.module.name}
+
+ {proj.module.name}
+ {proj.info.homePage}
+
+ // SCM
+ // developers
+ {
+ if (proj.dependencies.isEmpty)
+ Nil
+ else
+ {
+ proj.dependencies.map {
+ case (config, dep) =>
+ dependencyNode(config, dep)
+ }
+ }
+ }
+ {
+ if (proj.dependencyManagement.isEmpty)
+ Nil
+ else
+
+ {
+ proj.dependencyManagement.map {
+ case (config, dep) =>
+ dependencyNode(config, dep)
+ }
+ }
+
+ }
+ // properties
+ // repositories
+
+ }
+
+}
diff --git a/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala b/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala
index 16db767ba..ce49d012c 100644
--- a/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala
+++ b/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala
@@ -16,6 +16,7 @@ object CoursierPlugin extends AutoPlugin {
val coursierArtifactsChecksums = Keys.coursierArtifactsChecksums
val coursierCachePolicies = Keys.coursierCachePolicies
val coursierVerbosity = Keys.coursierVerbosity
+ val coursierSourceRepositories = Keys.coursierSourceRepositories
val coursierResolvers = Keys.coursierResolvers
val coursierSbtResolvers = Keys.coursierSbtResolvers
val coursierFallbackDependencies = Keys.coursierFallbackDependencies
@@ -32,6 +33,11 @@ object CoursierPlugin extends AutoPlugin {
val coursierDependencyTree = Keys.coursierDependencyTree
val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree
+
+ val coursierExport = Keys.coursierExport
+ val coursierExportDirectory = Keys.coursierExportDirectory
+ val coursierExportJavadoc = Keys.coursierExportJavadoc
+ val coursierExportSources = Keys.coursierExportSources
}
import autoImport._
@@ -52,6 +58,7 @@ object CoursierPlugin extends AutoPlugin {
coursierArtifactsChecksums := Seq(None),
coursierCachePolicies := Settings.defaultCachePolicies,
coursierVerbosity := Settings.defaultVerbosityLevel,
+ coursierSourceRepositories := Nil,
coursierResolvers <<= Tasks.coursierResolversTask,
coursierSbtResolvers <<= externalResolvers in updateSbtClassifiers,
coursierFallbackDependencies <<= Tasks.coursierFallbackDependenciesTask,
@@ -74,7 +81,11 @@ object CoursierPlugin extends AutoPlugin {
coursierResolution <<= Tasks.resolutionTask(),
coursierSbtClassifiersResolution <<= Tasks.resolutionTask(
sbtClassifiers = true
- )
+ ),
+ coursierExport <<= Tasks.coursierExportTask,
+ coursierExportDirectory := baseDirectory.in(ThisBuild).value / "target" / "repository",
+ coursierExportJavadoc := false,
+ coursierExportSources := false
) ++
inConfig(Compile)(treeSettings) ++
inConfig(Test)(treeSettings)
diff --git a/plugin/src/main/scala-2.10/coursier/Keys.scala b/plugin/src/main/scala-2.10/coursier/Keys.scala
index e22dcfdf6..418534167 100644
--- a/plugin/src/main/scala-2.10/coursier/Keys.scala
+++ b/plugin/src/main/scala-2.10/coursier/Keys.scala
@@ -15,6 +15,7 @@ object Keys {
val coursierVerbosity = SettingKey[Int]("coursier-verbosity", "")
+ val coursierSourceRepositories = SettingKey[Seq[File]]("coursier-source-repositories")
val coursierResolvers = TaskKey[Seq[Resolver]]("coursier-resolvers", "")
val coursierSbtResolvers = TaskKey[Seq[Resolver]]("coursier-sbt-resolvers", "")
@@ -41,4 +42,21 @@ object Keys {
"coursier-dependency-inverse-tree",
"Prints dependencies and transitive dependencies as an inverted tree (dependees as children)"
)
+
+ val coursierExport = TaskKey[Option[File]](
+ "coursier-export",
+ "Generates files allowing using these sources as a source dependency repository"
+ )
+ val coursierExportDirectory = TaskKey[File](
+ "coursier-export-directory",
+ "Base directory for the products of coursierExport"
+ )
+ val coursierExportJavadoc = SettingKey[Boolean](
+ "coursier-export-javadoc",
+ "Build javadoc packages for the coursier source dependency repository"
+ )
+ val coursierExportSources = SettingKey[Boolean](
+ "coursier-export-sources",
+ "Build sources packages for the coursier source dependency repository"
+ )
}
diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala
index 021e46568..65e997e62 100644
--- a/plugin/src/main/scala-2.10/coursier/Tasks.scala
+++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala
@@ -9,6 +9,7 @@ import coursier.core.Publication
import coursier.ivy.IvyRepository
import coursier.Keys._
import coursier.Structure._
+import coursier.maven.WritePom
import coursier.util.{ Config, Print }
import org.apache.ivy.core.module.id.ModuleRevisionId
@@ -299,6 +300,40 @@ object Tasks {
else
coursierResolvers.value
+ val sourceRepositories = coursierSourceRepositories.value.map { dir =>
+ // FIXME Don't hardcode this path?
+ new File(dir, "target/repository")
+ }
+
+ val sourceRepositoriesForcedDependencies = sourceRepositories.flatMap {
+ base =>
+
+ def pomDirComponents(f: File, components: Vector[String]): Stream[Vector[String]] =
+ if (f.isDirectory) {
+ val components0 = components :+ f.getName
+ Option(f.listFiles()).toStream.flatten.flatMap(pomDirComponents(_, components0))
+ } else if (f.getName.endsWith(".pom"))
+ Stream(components)
+ else
+ Stream.empty
+
+ Option(base.listFiles())
+ .toVector
+ .flatten
+ .flatMap(pomDirComponents(_, Vector()))
+ // at least 3 for org / name / version - the contrary should not happen, but who knows
+ .filter(_.length >= 3)
+ .map { components =>
+ val org = components.dropRight(2).mkString(".")
+ val name = components(components.length - 2)
+ val version = components.last
+
+ Module(org, name) -> version
+ }
+ }
+
+ // TODO Warn about possible duplicated modules from source repositories?
+
val verbosityLevel = coursierVerbosity.value
@@ -308,7 +343,12 @@ object Tasks {
dep.copy(exclusions = dep.exclusions ++ exclusions)
}.toSet,
filter = Some(dep => !dep.optional),
- forceVersions = userForceVersions ++ forcedScalaModules(sv) ++ projects.map(_.moduleVersion)
+ forceVersions =
+ // order matters here
+ userForceVersions ++
+ sourceRepositoriesForcedDependencies ++
+ forcedScalaModules(sv) ++
+ projects.map(_.moduleVersion)
)
if (verbosityLevel >= 2) {
@@ -331,12 +371,12 @@ object Tasks {
"ivy.home" -> (new File(sys.props("user.home")).toURI.getPath + ".ivy2")
) ++ sys.props
- val repositories = Seq(
- globalPluginsRepo,
- interProjectRepo
- ) ++ resolvers.flatMap(
- FromSbt.repository(_, ivyProperties, log)
- ) ++ {
+ val sourceRepositories0 = sourceRepositories.map {
+ base =>
+ MavenRepository(base.toURI.toString, changing = Some(true))
+ }
+
+ val fallbackDependenciesRepositories =
if (fallbackDependencies.isEmpty)
Nil
else {
@@ -349,7 +389,12 @@ object Tasks {
FallbackDependenciesRepository(map)
)
}
- }
+
+ val repositories =
+ Seq(globalPluginsRepo, interProjectRepo) ++
+ sourceRepositories0 ++
+ resolvers.flatMap(FromSbt.repository(_, ivyProperties, log)) ++
+ fallbackDependenciesRepositories
def resolution = {
val pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory)
@@ -748,4 +793,93 @@ object Tasks {
)
}
+ def coursierExportTask =
+ (
+ sbt.Keys.state,
+ sbt.Keys.thisProjectRef,
+ sbt.Keys.projectID,
+ sbt.Keys.scalaVersion,
+ sbt.Keys.scalaBinaryVersion,
+ sbt.Keys.ivyConfigurations,
+ streams,
+ coursierProject,
+ coursierExportDirectory,
+ coursierExportJavadoc,
+ coursierExportSources
+ ).flatMap { (state, projectRef, projId, sv, sbv, ivyConfs, streams, proj, exportDir, exportJavadoc, exportSources) =>
+
+ val javadocPackageTasks =
+ if (exportJavadoc)
+ Seq(Some("javadoc") -> packageDoc)
+ else
+ Nil
+
+ val sourcesPackageTasks =
+ if (exportJavadoc)
+ Seq(Some("sources") -> packageSrc)
+ else
+ Nil
+
+ val packageTasks = Seq(None -> packageBin) ++ javadocPackageTasks ++ sourcesPackageTasks
+
+ val configs = Seq(None -> Compile, Some("tests") -> Test)
+
+ val productTasks =
+ for {
+ (classifierOpt, pkgTask) <- packageTasks
+ (classifierPrefixOpt, config) <- configs
+ if publishArtifact.in(projectRef).in(pkgTask).in(config).getOrElse(state, false)
+ } yield {
+ val classifier = (classifierPrefixOpt.toSeq ++ classifierOpt.toSeq).mkString("-")
+ pkgTask.in(projectRef).in(config).get(state).map((classifier, _))
+ }
+
+ val productTask = sbt.std.TaskExtra.joinTasks(productTasks).join
+
+ val dir = new File(
+ exportDir,
+ s"${proj.module.organization.replace('.', '/')}/${proj.module.name}/${proj.version}"
+ )
+
+ def pom = "\n" + WritePom.project(proj, Some("jar"))
+
+ val log = streams.log
+
+ productTask.map { products =>
+
+ if (products.isEmpty)
+ None
+ else {
+
+ dir.mkdirs()
+
+ val pomFile = new File(dir, s"${proj.module.name}-${proj.version}.pom")
+ Files.write(pomFile.toPath, pom.getBytes("UTF-8"))
+ log.info(s"Wrote POM file to $pomFile")
+
+ for ((classifier, f) <- products) {
+
+ val suffix = if (classifier.isEmpty) "" else "-" + classifier
+
+ val jarPath = new File(dir, s"${proj.module.name}-${proj.version}$suffix.jar")
+
+ if (jarPath.exists()) {
+ if (!jarPath.delete())
+ log.warn(s"Cannot remove $jarPath")
+ }
+
+ Files.createSymbolicLink(
+ jarPath.toPath,
+ dir.toPath.relativize(f.toPath)
+ )
+ log.info(s"Created symbolic link $jarPath -> $f")
+ }
+
+ // TODO Clean extra files in dir
+
+ Some(exportDir)
+ }
+ }
+ }
+
}