Merge pull request #1454 from sbt/wip/resolve-consolidation

Consolidated resolution
This commit is contained in:
eugene yokota 2014-07-24 02:13:01 -04:00
commit 5b070b9dcc
14 changed files with 308 additions and 21 deletions

View File

@ -4,6 +4,7 @@
package sbt
import Resolver.PluginPattern
import ivyint.{ ConsolidatedResolveEngine, ConsolidatedResolveCache }
import java.io.File
import java.net.URI
@ -14,12 +15,14 @@ import CS.singleton
import org.apache.ivy.Ivy
import org.apache.ivy.core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager, ModuleDescriptorWriter }
import org.apache.ivy.core.event.EventManager
import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact, DefaultArtifact, DefaultDependencyArtifactDescriptor, MDArtifact }
import org.apache.ivy.core.module.descriptor.{ DefaultDependencyDescriptor, DefaultModuleDescriptor, DependencyDescriptor, ModuleDescriptor, License }
import org.apache.ivy.core.module.descriptor.{ OverrideDependencyDescriptorMediator }
import org.apache.ivy.core.module.id.{ ArtifactId, ModuleId, ModuleRevisionId }
import org.apache.ivy.core.resolve.{ IvyNode, ResolveData, ResolvedModuleRevision }
import org.apache.ivy.core.resolve.{ IvyNode, ResolveData, ResolvedModuleRevision, ResolveEngine }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.sort.SortEngine
import org.apache.ivy.plugins.latest.LatestRevisionStrategy
import org.apache.ivy.plugins.matcher.PatternMatcher
import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser
@ -28,6 +31,7 @@ import org.apache.ivy.util.{ Message, MessageLogger }
import org.apache.ivy.util.extendable.ExtendableItem
import scala.xml.{ NodeSeq, Text }
import scala.collection.mutable
final class IvySbt(val configuration: IvyConfiguration) {
import configuration.baseDirectory
@ -76,7 +80,23 @@ final class IvySbt(val configuration: IvyConfiguration) {
}
private lazy val ivy: Ivy =
{
val i = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine }
val i = new Ivy() {
private val loggerEngine = new SbtMessageLoggerEngine
override def getLoggerEngine = loggerEngine
override def bind(): Unit = {
val prOpt = Option(getSettings.getResolver(ProjectResolver.InterProject)) map { case pr: ProjectResolver => pr }
// We inject the deps we need before we can hook our resolve engine.
setSortEngine(new SortEngine(getSettings))
setEventManager(new EventManager())
if (configuration.updateOptions.consolidatedResolution) {
setResolveEngine(new ResolveEngine(getSettings, getEventManager, getSortEngine) with ConsolidatedResolveEngine {
val consolidatedResolveCache = IvySbt.consolidatedResolveCache
val projectResolver = prOpt
})
} else setResolveEngine(new ResolveEngine(getSettings, getEventManager, getSortEngine))
super.bind()
}
}
i.setSettings(settings)
i.bind()
i.getLoggerEngine.pushLogger(new IvyLoggerInterface(configuration.log))
@ -103,6 +123,18 @@ final class IvySbt(val configuration: IvyConfiguration) {
}
}
/**
* Cleans consolidated resolution cache.
* @param md - module descriptor of the original Ivy graph.
*/
private[sbt] def cleanConsolidatedResolutionCache(md: ModuleDescriptor, log: Logger): Unit =
withIvy(log) { i =>
val prOpt = Option(i.getSettings.getResolver(ProjectResolver.InterProject)) map { case pr: ProjectResolver => pr }
if (configuration.updateOptions.consolidatedResolution) {
IvySbt.consolidatedResolveCache.clean(md, prOpt)
}
}
final class Module(rawModuleSettings: ModuleSettings) {
val moduleSettings: ModuleSettings = IvySbt.substituteCross(rawModuleSettings)
def owner = IvySbt.this
@ -204,6 +236,7 @@ private object IvySbt {
val DefaultIvyFilename = "ivy.xml"
val DefaultMavenFilename = "pom.xml"
val DefaultChecksums = Seq("sha1", "md5")
private[sbt] val consolidatedResolveCache: ConsolidatedResolveCache = new ConsolidatedResolveCache()
def defaultIvyFile(project: File) = new File(project, DefaultIvyFilename)
def defaultIvyConfiguration(project: File) = new File(project, DefaultIvyConfigFilename)

View File

@ -59,6 +59,15 @@ object IvyActions {
iv.getSettings.getRepositoryCacheManagers.foreach(_.clean())
}
/**
* Cleans the consolidated resolution cache, if any.
* This is called by clean.
*/
private[sbt] def cleanConsolidatedResolutionCache(module: IvySbt#Module, log: Logger): Unit =
module.withModule(log) { (ivy, md, default) =>
module.owner.cleanConsolidatedResolutionCache(md, log)
}
/** Creates a Maven pom from the given Ivy configuration*/
def makePom(module: IvySbt#Module, configuration: MakePomConfiguration, log: Logger) {
import configuration.{ allRepositories, moduleInfo, configurations, extra, file, filterRepositories, process, includeTypes }

View File

@ -16,27 +16,42 @@ sealed trait IvyConfiguration {
def baseDirectory: File
def log: Logger
def withBase(newBaseDirectory: File): This
def updateOptions: UpdateOptions
}
final class InlineIvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val otherResolvers: Seq[Resolver],
val moduleConfigurations: Seq[ModuleConfiguration], val localOnly: Boolean, val lock: Option[xsbti.GlobalLock],
val checksums: Seq[String], val resolutionCacheDir: Option[File], val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts the resolution cache location.", "0.13.0")
val checksums: Seq[String], val resolutionCacheDir: Option[File], val updateOptions: UpdateOptions,
val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts resolutionCacheDir and updateOptions.", "0.13.0")
def this(paths: IvyPaths, resolvers: Seq[Resolver], otherResolvers: Seq[Resolver],
moduleConfigurations: Seq[ModuleConfiguration], localOnly: Boolean, lock: Option[xsbti.GlobalLock],
checksums: Seq[String], log: Logger) =
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, None, log)
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, None, UpdateOptions(), log)
@deprecated("Use the variant that accepts updateOptions.", "0.13.6")
def this(paths: IvyPaths, resolvers: Seq[Resolver], otherResolvers: Seq[Resolver],
moduleConfigurations: Seq[ModuleConfiguration], localOnly: Boolean, lock: Option[xsbti.GlobalLock],
checksums: Seq[String], resolutionCacheDir: Option[File], log: Logger) =
this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, UpdateOptions(), log)
type This = InlineIvyConfiguration
def baseDirectory = paths.baseDirectory
def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, log)
def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, log)
def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums,
resolutionCacheDir, updateOptions, log)
def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums,
resolutionCacheDir, updateOptions, log)
}
final class ExternalIvyConfiguration(val baseDirectory: File, val uri: URI, val lock: Option[xsbti.GlobalLock], val extraResolvers: Seq[Resolver], val log: Logger) extends IvyConfiguration {
final class ExternalIvyConfiguration(val baseDirectory: File, val uri: URI, val lock: Option[xsbti.GlobalLock],
val extraResolvers: Seq[Resolver], val updateOptions: UpdateOptions, val log: Logger) extends IvyConfiguration {
@deprecated("Use the variant that accepts updateOptions.", "0.13.6")
def this(baseDirectory: File, uri: URI, lock: Option[xsbti.GlobalLock], extraResolvers: Seq[Resolver], log: Logger) =
this(baseDirectory, uri, lock, extraResolvers, UpdateOptions(), log)
type This = ExternalIvyConfiguration
def withBase(newBase: File) = new ExternalIvyConfiguration(newBase, uri, lock, extraResolvers, log)
}
object ExternalIvyConfiguration {
def apply(baseDirectory: File, file: File, lock: Option[xsbti.GlobalLock], log: Logger) = new ExternalIvyConfiguration(baseDirectory, file.toURI, lock, Nil, log)
def apply(baseDirectory: File, file: File, lock: Option[xsbti.GlobalLock], log: Logger) = new ExternalIvyConfiguration(baseDirectory, file.toURI, lock, Nil, UpdateOptions(), log)
}
object IvyConfiguration {

View File

@ -34,6 +34,8 @@ class ProjectResolver(name: String, map: Map[ModuleRevisionId, ModuleDescriptor]
map get revisionId map constructResult
}
private[sbt] def getModuleDescriptor(revisionId: ModuleRevisionId): Option[ModuleDescriptor] = map.get(revisionId)
def report(revisionId: ModuleRevisionId): MetadataArtifactDownloadReport =
{
val artifact = DefaultArtifact.newIvyArtifact(revisionId, new Date)
@ -87,3 +89,7 @@ class ProjectResolver(name: String, map: Map[ModuleRevisionId, ModuleDescriptor]
def setSettings(settings: ResolverSettings) { this.settings = Some(settings) }
def getRepositoryCacheManager = settings match { case Some(s) => s.getDefaultRepositoryCacheManager; case None => sys.error("No settings defined for ProjectResolver") }
}
object ProjectResolver {
private[sbt] val InterProject = "inter-project"
}

View File

@ -0,0 +1,27 @@
package sbt
import java.io.File
/**
* Represents configurable options for update task.
* While UpdateConfiguration is passed into update at runtime,
* UpdateOption is intended to be used while setting up the Ivy object.
*
* See also UpdateConfiguration in IvyActions.scala.
*/
final class UpdateOptions(
/** If set to true, use consolidated resolution. */
val consolidatedResolution: Boolean) {
def withConsolidatedResolution(consolidatedResolution: Boolean): UpdateOptions =
copy(consolidatedResolution = consolidatedResolution)
private[sbt] def copy(
consolidatedResolution: Boolean = this.consolidatedResolution): UpdateOptions =
new UpdateOptions(consolidatedResolution)
}
object UpdateOptions {
def apply(): UpdateOptions =
new UpdateOptions(false)
}

View File

@ -0,0 +1,120 @@
package sbt
package ivyint
import java.io.File
import collection.concurrent
import org.apache.ivy.core
import core.resolve._
import core.module.id.ModuleRevisionId
import core.report.ResolveReport
import core.module.descriptor.{ DefaultModuleDescriptor, ModuleDescriptor, DependencyDescriptor }
import core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.util.Message
private[sbt] object ConsolidatedResolveCache {
def createID(organization: String, name: String, revision: String) =
ModuleRevisionId.newInstance(organization, name, revision)
def sbtOrgTemp = "org.scala-sbt.temp"
}
private[sbt] class ConsolidatedResolveCache() {
import ConsolidatedResolveCache._
val resolveReportCache: concurrent.Map[ModuleRevisionId, ResolveReport] = concurrent.TrieMap()
val resolvePropertiesCache: concurrent.Map[ModuleRevisionId, String] = concurrent.TrieMap()
val directDependencyCache: concurrent.Map[ModuleDescriptor, Vector[DependencyDescriptor]] = concurrent.TrieMap()
def clean(md0: ModuleDescriptor, prOpt: Option[ProjectResolver]): Unit = {
val mrid0 = md0.getModuleRevisionId
val md1 = if (mrid0.getOrganisation == sbtOrgTemp) md0
else buildConsolidatedModuleDescriptor(md0, prOpt)
val mrid1 = md1.getModuleRevisionId
resolveReportCache.remove(mrid1)
resolvePropertiesCache.remove(mrid1)
}
def directDependencies(md0: ModuleDescriptor): Vector[DependencyDescriptor] =
directDependencyCache.getOrElseUpdate(md0, md0.getDependencies.toVector)
def buildConsolidatedModuleDescriptor(md0: ModuleDescriptor, prOpt: Option[ProjectResolver]): DefaultModuleDescriptor = {
def expandInternalDeps(dep: DependencyDescriptor): Vector[DependencyDescriptor] =
prOpt map {
_.getModuleDescriptor(dep.getDependencyRevisionId) match {
case Some(internal) => directDependencies(internal) flatMap expandInternalDeps
case _ => Vector(dep)
}
} getOrElse Vector(dep)
val expanded = directDependencies(md0) flatMap expandInternalDeps
val depStrings = expanded map { dep =>
val mrid = dep.getDependencyRevisionId
val confMap = (dep.getModuleConfigurations map { conf =>
conf + "->(" + dep.getDependencyConfigurations(conf).mkString(",") + ")"
})
mrid.toString + ";" + confMap.mkString(";")
}
val depsString = depStrings.distinct.sorted.mkString("\n")
val sha1 = Hash.toHex(Hash(depsString))
// println("sha1: " + sha1)
val md1 = new DefaultModuleDescriptor(createID(sbtOrgTemp, "temp-resolve-" + sha1, "1.0"), "release", null, false)
md1
}
}
private[sbt] trait ConsolidatedResolveEngine extends ResolveEngine {
import ConsolidatedResolveCache._
private[sbt] def consolidatedResolveCache: ConsolidatedResolveCache
private[sbt] def projectResolver: Option[ProjectResolver]
/**
* Resolve dependencies of a module described by a module descriptor.
*/
override def resolve(md0: ModuleDescriptor, options0: ResolveOptions): ResolveReport = {
val cache = consolidatedResolveCache
val cacheManager = getSettings.getResolutionCacheManager
val md1 = cache.buildConsolidatedModuleDescriptor(md0, projectResolver)
val md1mrid = md1.getModuleRevisionId
def doWork: (ResolveReport, String) = {
if (options0.getLog != LogOptions.LOG_QUIET) {
Message.info("Consolidating managed dependencies to " + md1mrid.toString + " ...")
}
md1.setLastModified(System.currentTimeMillis)
for {
x <- md0.getConfigurations
} yield md1.addConfiguration(x)
for {
x <- md0.getDependencies
} yield md1.addDependency(x)
val options1 = new ResolveOptions(options0)
options1.setOutputReport(false)
val report0 = super.resolve(md1, options1)
val ivyPropertiesInCache1 = cacheManager.getResolvedIvyPropertiesInCache(md1.getResolvedModuleRevisionId)
val prop0 =
if (ivyPropertiesInCache1.exists) IO.read(ivyPropertiesInCache1)
else ""
if (options0.isOutputReport) {
this.outputReport(report0, cacheManager, options0)
}
cache.resolveReportCache(md1mrid) = report0
cache.resolvePropertiesCache(md1mrid) = prop0
(report0, prop0)
}
val (report0, prop0) = (cache.resolveReportCache.get(md1mrid), cache.resolvePropertiesCache.get(md1mrid)) match {
case (Some(report), Some(prop)) =>
if (options0.getLog != LogOptions.LOG_QUIET) {
Message.info("Found consolidated dependency " + md1mrid.toString + " ...")
}
(report, prop)
case _ => doWork
}
cacheManager.saveResolvedModuleDescriptor(md0)
if (prop0 != "") {
val ivyPropertiesInCache0 = cacheManager.getResolvedIvyPropertiesInCache(md0.getResolvedModuleRevisionId)
IO.write(ivyPropertiesInCache0, prop0)
}
report0
}
}

View File

@ -108,7 +108,8 @@ object Defaults extends BuildCommon {
pomExtra :== NodeSeq.Empty,
pomPostProcess :== idFun,
pomAllRepositories :== false,
pomIncludeRepository :== Classpaths.defaultRepositoryFilter
pomIncludeRepository :== Classpaths.defaultRepositoryFilter,
updateOptions := UpdateOptions()
)
/** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */
@ -218,7 +219,11 @@ object Defaults extends BuildCommon {
sbt.inc.ClassfileManager.transactional(crossTarget.value / "classes.bak", sbt.Logger.Null)),
scalaInstance <<= scalaInstanceTask,
crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.Disabled),
crossTarget := makeCrossTarget(target.value, scalaBinaryVersion.value, sbtBinaryVersion.value, sbtPlugin.value, crossPaths.value)
crossTarget := makeCrossTarget(target.value, scalaBinaryVersion.value, sbtBinaryVersion.value, sbtPlugin.value, crossPaths.value),
clean := {
val _ = clean.value
IvyActions.cleanConsolidatedResolutionCache(ivyModule.value, streams.value.log)
}
)
// must be a val: duplication detected by object identity
private[this] lazy val compileBaseGlobal: Seq[Setting[_]] = globalDefaults(Seq(
@ -1075,6 +1080,7 @@ object Classpaths {
projectID <<= pluginProjectID,
projectDescriptors <<= depMap,
updateConfiguration := new UpdateConfiguration(retrieveConfiguration.value, false, ivyLoggingLevel.value),
updateOptions := (updateOptions in Global).value,
retrieveConfiguration := { if (retrieveManaged.value) Some(new RetrieveConfiguration(managedDirectory.value, retrievePattern.value)) else None },
ivyConfiguration <<= mkIvyConfiguration,
ivyConfigurations := {
@ -1162,7 +1168,8 @@ object Classpaths {
val explicit = buildStructure.value.units(thisProjectRef.value.build).unit.plugins.pluginData.resolvers
explicit orElse bootRepositories(appConfiguration.value) getOrElse externalResolvers.value
},
ivyConfiguration := new InlineIvyConfiguration(ivyPaths.value, externalResolvers.value, Nil, Nil, offline.value, Option(lock(appConfiguration.value)), checksums.value, Some(target.value / "resolution-cache"), streams.value.log),
ivyConfiguration := new InlineIvyConfiguration(ivyPaths.value, externalResolvers.value, Nil, Nil, offline.value, Option(lock(appConfiguration.value)),
checksums.value, Some(target.value / "resolution-cache"), UpdateOptions(), streams.value.log),
ivySbt <<= ivySbt0,
classifiersModule <<= (projectID, sbtDependency, transitiveClassifiers, loadedBuild, thisProjectRef) map { (pid, sbtDep, classifiers, lb, ref) =>
val pluginClasspath = lb.units(ref.build).unit.plugins.fullClasspath
@ -1312,7 +1319,7 @@ object Classpaths {
def projectResolverTask: Initialize[Task[Resolver]] =
projectDescriptors map { m =>
new RawRepository(new ProjectResolver("inter-project", m))
new RawRepository(new ProjectResolver(ProjectResolver.InterProject, m))
}
def analyzed[T](data: T, analysis: inc.Analysis) = Attributed.blank(data).put(Keys.analysis, analysis)
@ -1340,11 +1347,13 @@ object Classpaths {
def unmanagedDependencies: Initialize[Task[Classpath]] =
(thisProjectRef, configuration, settingsData, buildDependencies) flatMap unmanagedDependencies0
def mkIvyConfiguration: Initialize[Task[IvyConfiguration]] =
(fullResolvers, ivyPaths, otherResolvers, moduleConfigurations, offline, checksums in update, appConfiguration, target, streams) map { (rs, paths, other, moduleConfs, off, check, app, t, s) =>
warnResolversConflict(rs ++: other, s.log)
val resCacheDir = t / "resolution-cache"
new InlineIvyConfiguration(paths, rs, other, moduleConfs, off, Option(lock(app)), check, Some(resCacheDir), s.log)
}
(fullResolvers, ivyPaths, otherResolvers, moduleConfigurations, offline, checksums in update, appConfiguration,
target, updateOptions, streams) map { (rs, paths, other, moduleConfs, off, check, app, t, uo, s) =>
warnResolversConflict(rs ++: other, s.log)
val resCacheDir = t / "resolution-cache"
new InlineIvyConfiguration(paths, rs, other, moduleConfs, off, Option(lock(app)), check, Some(resCacheDir), uo, s.log)
}
import java.util.LinkedHashSet
import collection.JavaConversions.asScalaSet
@ -1648,13 +1657,13 @@ trait BuildExtra extends BuildCommon {
externalIvySettingsURI(Def.value(url.toURI), addMultiResolver)
def externalIvySettingsURI(uri: Initialize[URI], addMultiResolver: Boolean = true): Setting[Task[IvyConfiguration]] =
{
val other = (baseDirectory, appConfiguration, projectResolver, streams).identityMap
val other = (baseDirectory, appConfiguration, projectResolver, updateOptions, streams).identityMap
ivyConfiguration <<= (uri zipWith other) {
case (u, otherTask) =>
otherTask map {
case (base, app, pr, s) =>
case (base, app, pr, uo, s) =>
val extraResolvers = if (addMultiResolver) pr :: Nil else Nil
new ExternalIvyConfiguration(base, u, Option(lock(app)), extraResolvers, s.log)
new ExternalIvyConfiguration(base, u, Option(lock(app)), extraResolvers, uo, s.log)
}
}
}

View File

@ -242,6 +242,7 @@ object Keys {
val moduleSettings = TaskKey[ModuleSettings]("module-settings", "Module settings, which configure dependency management for a specific module, such as a project.", DTask)
val unmanagedBase = SettingKey[File]("unmanaged-base", "The default directory for manually managed libraries.", ASetting)
val updateConfiguration = SettingKey[UpdateConfiguration]("update-configuration", "Configuration for resolving and retrieving managed dependencies.", DSetting)
val updateOptions = SettingKey[UpdateOptions]("update-options", "Options for resolving managed dependencies.", DSetting)
val ivySbt = TaskKey[IvySbt]("ivy-sbt", "Provides the sbt interface to Ivy.", CTask)
val ivyModule = TaskKey[IvySbt#Module]("ivy-module", "Provides the sbt interface to a configured Ivy module.", CTask)
val updateCacheName = TaskKey[String]("updateCacheName", "Defines the directory name used to store the update cache files (inside the streams cacheDirectory).", DTask)

10
notes/0.13.6.md Normal file
View File

@ -0,0 +1,10 @@
[413]: https://github.com/sbt/sbt/issues/413
[1454]: https://github.com/sbt/sbt/pull/1454
### Consolidated resolution
sbt 0.13.6 adds a new setting key called `updateOptions`, which can be used to enable consolidated resolution for `update` task.
updateOptions := updateOptions.value.withConsolidatedResolution(true)
This feature is specifically targeted to address [Ivy resolution is beging slow for multi-module projects #413][413]. Consolidated resolution aims to fix this issue by artificially constructing an Ivy dependency graph for the unique managed dependencies. If two subprojects introduce identical external dependencies, both subprojects should consolidate to the same graph, and therefore resolve immediately for the second `update`. [#1454][1454]

1
notes/about.md Normal file
View File

@ -0,0 +1 @@
[sbt](http://www.scala-sbt.org/) is the interactive build tool.

View File

@ -0,0 +1,3 @@
public class A {
public static final int x = 3;
}

View File

@ -0,0 +1,5 @@
public final class C {
public static void main(String[] args) {
System.out.println(A.x);
}
}

View File

@ -0,0 +1,43 @@
lazy val check = taskKey[Unit]("Runs the check")
def commonSettings: Seq[Def.Setting[_]] =
Seq(
ivyPaths := new IvyPaths( (baseDirectory in ThisBuild).value, Some((target in LocalRootProject).value / "ivy-cache")),
libraryDependencies := Seq("net.sf.json-lib" % "json-lib" % "2.4" classifier "jdk15" intransitive()),
autoScalaLibrary := false, // avoid downloading fresh scala-library/scala-compiler
managedScalaInstance := false,
updateOptions := updateOptions.value.withConsolidatedResolution(true)
)
lazy val root = (project in file(".")).settings(
organization in ThisBuild := "org.example",
version in ThisBuild := "1.0"
)
lazy val a = project.
settings(commonSettings: _*).
settings(
artifact in (Compile, packageBin) := Artifact("demo")
)
lazy val b = project.
settings(commonSettings: _*).
settings(
check := {
val report = update.value
val configurationReport = (report.configurations find {_.configuration == "compile"}).head
val x = configurationReport.modules match {
case Seq(moduleReport) =>
moduleReport.module match {
case ModuleID("net.sf.json-lib", "json-lib", "2.4", _, _, _, _, _, _, _, _) => ()
case x => sys.error("Unexpected module: " + x.toString)
}
case x => sys.error("Unexpected modules: " + x.toString)
}
}
)
lazy val c = project.
settings(commonSettings: _*).
settings(
libraryDependencies := Seq(organization.value %% "a" % version.value)
)

View File

@ -0,0 +1,5 @@
> a/publishLocal
> b/check
> c/run