diff --git a/ivy/src/main/scala/sbt/CircularDependencyLevel.scala b/ivy/src/main/scala/sbt/CircularDependencyLevel.scala new file mode 100644 index 000000000..3c2f62bc6 --- /dev/null +++ b/ivy/src/main/scala/sbt/CircularDependencyLevel.scala @@ -0,0 +1,27 @@ +package sbt + +import org.apache.ivy.plugins.circular.{ CircularDependencyStrategy, WarnCircularDependencyStrategy, IgnoreCircularDependencyStrategy, ErrorCircularDependencyStrategy } + +/** + * Wrapper around circular dependency strategy. + */ +sealed trait CircularDependencyLevel { + private[sbt] def ivyStrategy: CircularDependencyStrategy + private[sbt] def name: String + override def toString: String = name +} + +object CircularDependencyLevel { + val Warn: CircularDependencyLevel = new CircularDependencyLevel { + def ivyStrategy: CircularDependencyStrategy = WarnCircularDependencyStrategy.getInstance + def name: String = "warn" + } + val Ignore: CircularDependencyLevel = new CircularDependencyLevel { + def ivyStrategy: CircularDependencyStrategy = IgnoreCircularDependencyStrategy.getInstance + def name: String = "ignore" + } + val Error: CircularDependencyLevel = new CircularDependencyLevel { + def ivyStrategy: CircularDependencyStrategy = ErrorCircularDependencyStrategy.getInstance + def name: String = "error" + } +} diff --git a/ivy/src/main/scala/sbt/Ivy.scala b/ivy/src/main/scala/sbt/Ivy.scala index 55a596bbc..cc39ab42e 100644 --- a/ivy/src/main/scala/sbt/Ivy.scala +++ b/ivy/src/main/scala/sbt/Ivy.scala @@ -69,6 +69,7 @@ final class IvySbt(val configuration: IvyConfiguration) { { val is = new IvySettings is.setBaseDir(baseDirectory) + is.setCircularDependencyStrategy(configuration.updateOptions.circularDependencyLevel.ivyStrategy) CustomPomParser.registerDefault configuration match { case e: ExternalIvyConfiguration => diff --git a/ivy/src/main/scala/sbt/UpdateOptions.scala b/ivy/src/main/scala/sbt/UpdateOptions.scala index dd4c83f47..d3bf28bfc 100644 --- a/ivy/src/main/scala/sbt/UpdateOptions.scala +++ b/ivy/src/main/scala/sbt/UpdateOptions.scala @@ -10,25 +10,33 @@ import java.io.File * See also UpdateConfiguration in IvyActions.scala. */ final class UpdateOptions private[sbt] ( + /** If set to CircularDependencyLevel.Error, halt the dependency resolution. */ + val circularDependencyLevel: CircularDependencyLevel, /** If set to true, check all resolvers for snapshots. */ val latestSnapshots: Boolean, /** If set to true, use consolidated resolution. */ val consolidatedResolution: Boolean) { + def withCircularDependencyLevel(circularDependencyLevel: CircularDependencyLevel): UpdateOptions = + copy(circularDependencyLevel = circularDependencyLevel) def withLatestSnapshots(latestSnapshots: Boolean): UpdateOptions = copy(latestSnapshots = latestSnapshots) def withConsolidatedResolution(consolidatedResolution: Boolean): UpdateOptions = copy(consolidatedResolution = consolidatedResolution) private[sbt] def copy( + circularDependencyLevel: CircularDependencyLevel = this.circularDependencyLevel, latestSnapshots: Boolean = this.latestSnapshots, consolidatedResolution: Boolean = this.consolidatedResolution): UpdateOptions = - new UpdateOptions(latestSnapshots, consolidatedResolution) + new UpdateOptions(circularDependencyLevel, + latestSnapshots, + consolidatedResolution) } object UpdateOptions { def apply(): UpdateOptions = new UpdateOptions( + circularDependencyLevel = CircularDependencyLevel.Warn, latestSnapshots = true, consolidatedResolution = false) } diff --git a/notes/0.13.7/circular-dependency.md b/notes/0.13.7/circular-dependency.md new file mode 100644 index 000000000..b0d9e391f --- /dev/null +++ b/notes/0.13.7/circular-dependency.md @@ -0,0 +1,7 @@ + [@eed3si9n]: https://github.com/eed3si9n + +### Circular dependency + +By default circular dependencies are warned, but they do not halt the dependency resolution. Using the following setting, circular dependencies can be treated as an error. + + updateOptions := updateOptions.value.withCircularDependencyLevel(CircularDependencyLevel.Error) diff --git a/sbt/src/sbt-test/dependency-management/circular-dependency/changes/multi.sbt b/sbt/src/sbt-test/dependency-management/circular-dependency/changes/multi.sbt new file mode 100644 index 000000000..e2a312aa6 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/circular-dependency/changes/multi.sbt @@ -0,0 +1,40 @@ +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")), + scalaVersion := "2.10.4", + updateOptions := updateOptions.value.withCircularDependencyLevel(CircularDependencyLevel.Error) + ) + +lazy val a = project. + settings(commonSettings: _*). + settings( + name := "a", + libraryDependencies := Seq( + "commons-io" % "commons-io" % "1.3", + organization.value %% "c" % version.value + ) + ) + +lazy val b = project. + settings(commonSettings: _*). + settings( + name := "b", + // this adds circular dependency + libraryDependencies := Seq(organization.value %% "c" % version.value) + ) + +lazy val c = project. + settings(commonSettings: _*). + settings( + name := "c", + libraryDependencies := Seq(organization.value %% "b" % version.value) + ) + +lazy val root = (project in file(".")). + settings(commonSettings: _*). + settings( + organization in ThisBuild := "org.example", + version in ThisBuild := "1.0-SNAPSHOT" + ) diff --git a/sbt/src/sbt-test/dependency-management/circular-dependency/multi.sbt b/sbt/src/sbt-test/dependency-management/circular-dependency/multi.sbt new file mode 100644 index 000000000..947217506 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/circular-dependency/multi.sbt @@ -0,0 +1,36 @@ +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")), + scalaVersion := "2.10.4" + ) + +lazy val a = project. + settings(commonSettings: _*). + settings( + name := "a", + libraryDependencies := Seq( + "commons-io" % "commons-io" % "1.3" + ) + ) + +lazy val b = project. + settings(commonSettings: _*). + settings( + name := "b" + ) + +lazy val c = project. + settings(commonSettings: _*). + settings( + name := "c", + libraryDependencies := Seq(organization.value %% "b" % version.value) + ) + +lazy val root = (project in file(".")). + settings(commonSettings: _*). + settings( + organization in ThisBuild := "org.example", + version in ThisBuild := "1.0-SNAPSHOT" + ) diff --git a/sbt/src/sbt-test/dependency-management/circular-dependency/test b/sbt/src/sbt-test/dependency-management/circular-dependency/test new file mode 100644 index 000000000..4feaf57a2 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/circular-dependency/test @@ -0,0 +1,13 @@ +> a/publishLocal + +> b/publishLocal + +> c/publishLocal + +$ copy-file changes/multi.sbt multi.sbt + +> reload + +-> b/publishLocal + +> a/publishLocal