From 16225d98e675dbbece5bd33df390d2407ba2d850 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Fri, 6 May 2016 13:54:03 +0200 Subject: [PATCH] Add tasks / settings allowing using other SBT sources as a repository --- .../main/scala-2.11/coursier/cli/Helper.scala | 50 +++++- .../scala-2.11/coursier/cli/Options.scala | 5 +- .../main/scala/coursier/maven/WritePom.scala | 77 +++++++++ .../scala-2.10/coursier/CoursierPlugin.scala | 13 +- .../src/main/scala-2.10/coursier/Keys.scala | 18 +++ .../src/main/scala-2.10/coursier/Tasks.scala | 150 +++++++++++++++++- 6 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 core/jvm/src/main/scala/coursier/maven/WritePom.scala 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) + } + } + } + }