Add tasks / settings allowing using other SBT sources as a repository

This commit is contained in:
Alexandre Archambault 2016-05-06 13:54:03 +02:00
parent 0f914cf781
commit 16225d98e6
No known key found for this signature in database
GPG Key ID: 14640A6839C263A9
6 changed files with 300 additions and 13 deletions

View File

@ -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 } }

View File

@ -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")

View File

@ -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) = {
<dependency>
<groupId>{dep.module.organization}</groupId>
<artifactId>{dep.module.name}</artifactId>
{
if (dep.version.isEmpty)
Nil
else
Seq(<version>{dep.version}</version>)
}
{
if (config.isEmpty)
Nil
else
Seq(<scope>{config}</scope>)
}
</dependency>
}
<project>
// parent
<groupId>{proj.module.organization}</groupId>
<artifactId>{proj.module.name}</artifactId>
{
packaging
.map(p => <packaging>{p}</packaging>)
.toSeq
}
<description>{proj.info.description}</description>
<url>{proj.info.homePage}</url>
<version>{proj.version}</version>
// licenses
<name>{proj.module.name}</name>
<organization>
<name>{proj.module.name}</name>
<url>{proj.info.homePage}</url>
</organization>
// SCM
// developers
{
if (proj.dependencies.isEmpty)
Nil
else
<dependencies>{
proj.dependencies.map {
case (config, dep) =>
dependencyNode(config, dep)
}
}</dependencies>
}
{
if (proj.dependencyManagement.isEmpty)
Nil
else
<dependencyManagement>
<dependencies>{
proj.dependencyManagement.map {
case (config, dep) =>
dependencyNode(config, dep)
}
}</dependencies>
</dependencyManagement>
}
// properties
// repositories
</project>
}
}

View File

@ -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)

View File

@ -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"
)
}

View File

@ -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 = "<?xml version='1.0' encoding='UTF-8'?>\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)
}
}
}
}