From a9e06109a38d6b4683105b1e59adb2158d853378 Mon Sep 17 00:00:00 2001 From: Matthew Farwell Date: Mon, 13 Apr 2015 16:31:22 +0200 Subject: [PATCH 01/26] Added exclusions to testOnly and testQuick command line, indicated by a '-' prefix. --- main/src/main/scala/sbt/Defaults.scala | 17 ++++-- .../tests/test-exclude/project/Build.scala | 10 ++++ .../test-exclude/src/test/scala/Test.scala | 15 +++++ sbt/src/sbt-test/tests/test-exclude/test | 55 +++++++++++++++++++ 4 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 sbt/src/sbt-test/tests/test-exclude/project/Build.scala create mode 100644 sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala create mode 100644 sbt/src/sbt-test/tests/test-exclude/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 5dc348471..0c0e69821 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -566,11 +566,18 @@ object Defaults extends BuildCommon { def selectedFilter(args: Seq[String]): Seq[String => Boolean] = { - val filters = args map GlobFilter.apply - if (filters.isEmpty) + val (excludeArgs, includeArgs) = args.partition(s => s.startsWith("-")) + + val includeFilters = includeArgs map GlobFilter.apply + val excludeFilters = excludeArgs.map(_.substring(1)).map(GlobFilter.apply) + + if (includeFilters.isEmpty && excludeArgs.isEmpty) Seq(const(true)) - else - filters.map { f => (s: String) => f accept s } + else if (includeFilters.isEmpty) { + excludeFilters.map { f => (s: String) => !f.accept(s) } + } else { + includeFilters.map { f => (s: String) => (f.accept(s) && !excludeFilters.exists(x => x.accept(s))) } + } } def detectTests: Initialize[Task[Seq[TestDefinition]]] = (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) => Tests.discover(frameworkMap.values.toList, analysis, s.log)._1 @@ -892,7 +899,7 @@ object Defaults extends BuildCommon { selectTests ~ options } - def distinctParser(exs: Set[String], raw: Boolean): Parser[Seq[String]] = + private def distinctParser(exs: Set[String], raw: Boolean): Parser[Seq[String]] = { import DefaultParsers._ val base = token(Space) ~> token(NotSpace - "--" examples exs) diff --git a/sbt/src/sbt-test/tests/test-exclude/project/Build.scala b/sbt/src/sbt-test/tests/test-exclude/project/Build.scala new file mode 100644 index 000000000..7f2f30f93 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/project/Build.scala @@ -0,0 +1,10 @@ +import sbt._ +import Keys._ +import Defaults._ + +object B extends Build { + lazy val root = Project("root", file("."), settings = defaultSettings ++ Seq( + libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" % "test", + parallelExecution in test := false + )) +} diff --git a/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala b/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala new file mode 100644 index 000000000..c7b0235e2 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/src/test/scala/Test.scala @@ -0,0 +1,15 @@ +import java.io.File +import org.scalatest.FlatSpec +import org.scalatest.matchers.ShouldMatchers + +class Test1 extends FlatSpec with ShouldMatchers { + "a test" should "pass" in { + new File("target/Test1.run").createNewFile() + } +} + +class Test2 extends FlatSpec with ShouldMatchers { + "a test" should "pass" in { + new File("target/Test2.run").createNewFile() + } +} diff --git a/sbt/src/sbt-test/tests/test-exclude/test b/sbt/src/sbt-test/tests/test-exclude/test new file mode 100644 index 000000000..ec11c6a39 --- /dev/null +++ b/sbt/src/sbt-test/tests/test-exclude/test @@ -0,0 +1,55 @@ +# Test1 & Test2 create files Test1.run & Test2.run respectively + +# no parameters +> test-only +$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test1.run +$ delete target/Test2.run + + +# with explicit match +> test-only Test1* +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with explicit match and exclusion +> test-only Test* -Test1 +-$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test2.run + + +# with explicit match and exclusion +> test-only Test* -Test2 +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with only exclusion +> test-only -Test2 +$ exists target/Test1.run +-$ exists target/Test2.run + +$ delete target/Test1.run + + +# with only exclusion +> test-only -Test1 +-$ exists target/Test1.run +$ exists target/Test2.run + +$ delete target/Test2.run + + +# with only glob exclusion +> test-only -Test* +-$ exists target/Test1.run +-$ exists target/Test2.run From 0e568785340cad9abff087698d39ac7ce98d2272 Mon Sep 17 00:00:00 2001 From: Matthew Farwell Date: Tue, 14 Apr 2015 06:31:27 +0200 Subject: [PATCH 02/26] Added unit tests for Defaults.selectedFilter --- main/src/test/scala/DefaultsTest.scala | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 main/src/test/scala/DefaultsTest.scala diff --git a/main/src/test/scala/DefaultsTest.scala b/main/src/test/scala/DefaultsTest.scala new file mode 100644 index 000000000..43e8dd064 --- /dev/null +++ b/main/src/test/scala/DefaultsTest.scala @@ -0,0 +1,45 @@ +package sbt + +import java.io._ + +import org.specs2.mutable.Specification + +object DefaultsTest extends Specification { + "`selectedFilter`" should { + def assertFiltered(filter: List[String], expected: Map[String, Boolean]) = { + val actual = expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) + + actual must be equalTo (expected) + } + + "return all tests for an empty list" in { + assertFiltered(List(), Map("Test1" -> true, "Test2" -> true)) + } + + "work correctly with exact matches" in { + assertFiltered(List("Test1", "foo"), Map("Test1" -> true, "Test2" -> false, "Foo" -> false)) + } + + "work correctly with glob" in { + assertFiltered(List("Test*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + + "work correctly with excludes" in { + assertFiltered(List("Test*", "-Test2"), Map("Test1" -> true, "Test2" -> false, "Foo" -> false)) + } + + "work correctly without includes" in { + assertFiltered(List("-Test2"), Map("Test1" -> true, "Test2" -> false, "Foo" -> true)) + } + + "work correctly with excluded globs" in { + assertFiltered(List("Test*", "-F*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + + "cope with multiple filters" in { + assertFiltered(List("T*1", "T*2", "-F*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) + } + } + +} + From d4cffdddd99fb4bcb08b48f5a8899ba0e6806616 Mon Sep 17 00:00:00 2001 From: Matthew Farwell Date: Fri, 17 Apr 2015 09:28:58 +0200 Subject: [PATCH 03/26] corrected exclusion logic, added more unit tests & scripted tests --- main/src/main/scala/sbt/Defaults.scala | 12 ++++++----- main/src/test/scala/DefaultsTest.scala | 20 +++++++++++++------ .../mavenint/MavenRepositoryResolver.scala | 2 +- sbt/src/sbt-test/tests/test-exclude/test | 6 ++++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 0c0e69821..d0cf1145b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -566,17 +566,19 @@ object Defaults extends BuildCommon { def selectedFilter(args: Seq[String]): Seq[String => Boolean] = { - val (excludeArgs, includeArgs) = args.partition(s => s.startsWith("-")) + def matches(nfs: Seq[NameFilter], s: String) = nfs.exists(_.accept(s)) + + val (excludeArgs, includeArgs) = args.partition(_.startsWith("-")) val includeFilters = includeArgs map GlobFilter.apply val excludeFilters = excludeArgs.map(_.substring(1)).map(GlobFilter.apply) - if (includeFilters.isEmpty && excludeArgs.isEmpty) + if (includeFilters.isEmpty && excludeArgs.isEmpty) { Seq(const(true)) - else if (includeFilters.isEmpty) { - excludeFilters.map { f => (s: String) => !f.accept(s) } + } else if (includeFilters.isEmpty) { + Seq({ (s: String) => !matches(excludeFilters, s) }) } else { - includeFilters.map { f => (s: String) => (f.accept(s) && !excludeFilters.exists(x => x.accept(s))) } + includeFilters.map { f => (s: String) => (f.accept(s) && !matches(excludeFilters, s)) } } } def detectTests: Initialize[Task[Seq[TestDefinition]]] = (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) => diff --git a/main/src/test/scala/DefaultsTest.scala b/main/src/test/scala/DefaultsTest.scala index 43e8dd064..c09a806c0 100644 --- a/main/src/test/scala/DefaultsTest.scala +++ b/main/src/test/scala/DefaultsTest.scala @@ -5,13 +5,13 @@ import java.io._ import org.specs2.mutable.Specification object DefaultsTest extends Specification { + private def assertFiltered(filter: List[String], expected: Map[String, Boolean]) = { + val actual = expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) + + actual must be equalTo (expected) + } + "`selectedFilter`" should { - def assertFiltered(filter: List[String], expected: Map[String, Boolean]) = { - val actual = expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) - - actual must be equalTo (expected) - } - "return all tests for an empty list" in { assertFiltered(List(), Map("Test1" -> true, "Test2" -> true)) } @@ -39,6 +39,14 @@ object DefaultsTest extends Specification { "cope with multiple filters" in { assertFiltered(List("T*1", "T*2", "-F*"), Map("Test1" -> true, "Test2" -> true, "Foo" -> false)) } + + "cope with multiple exclusion filters, no includes" in { + assertFiltered(List("-A*", "-F*"), Map("Test1" -> true, "Test2" -> true, "AAA" -> false, "Foo" -> false)) + } + + "cope with multiple exclusion filters with includes" in { + assertFiltered(List("T*", "-T*1", "-T*2"), Map("Test1" -> false, "Test2" -> false, "Test3" -> true)) + } } } diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala index 6796efcd7..6ee17cced 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala @@ -259,7 +259,7 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe // Here we add in additional artifact requests, which ALLWAYS have to be explicit since // Maven/Aether doesn't include all known artifacts in a pom.xml // TODO - This does not appear to be working correctly. - if (dd.getAllDependencyArtifacts.isEmpty) { + if (dd.getAllDependencyArtifacts.isEmpty) { val artifactId = s"${drid.getName}-${drid.getRevision}" // Add the artifacts we know about the module packaging match { diff --git a/sbt/src/sbt-test/tests/test-exclude/test b/sbt/src/sbt-test/tests/test-exclude/test index ec11c6a39..913c97312 100644 --- a/sbt/src/sbt-test/tests/test-exclude/test +++ b/sbt/src/sbt-test/tests/test-exclude/test @@ -53,3 +53,9 @@ $ delete target/Test2.run > test-only -Test* -$ exists target/Test1.run -$ exists target/Test2.run + + +# with only glob exclusion +> test-only -T*1 -T*2 +-$ exists target/Test1.run +-$ exists target/Test2.run From 5e8907d1e8c144265b97c62bc4c8260bce85712c Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 11 May 2015 22:05:39 +0100 Subject: [PATCH 04/26] Add notes for #1970 - Test exclude filter. --- notes/0.13.9/test-exclude-filter.markdown | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 notes/0.13.9/test-exclude-filter.markdown diff --git a/notes/0.13.9/test-exclude-filter.markdown b/notes/0.13.9/test-exclude-filter.markdown new file mode 100644 index 000000000..d22b0efee --- /dev/null +++ b/notes/0.13.9/test-exclude-filter.markdown @@ -0,0 +1,12 @@ + + [@matthewfarwell]: http://github.com/matthewfarwell + [1970]: https://github.com/sbt/sbt/pull/1970 + +### Fixes with compatibility implications + +### Improvements + +- Support excluding tests in `testOnly`/`testQuick` with `-`, for example `-MySpec`. + [#1970][1970] by [@matthewfarwell][@matthewfarwell] + +### Bug fixes From 4ec092617f4d5471ada6c839c9307853a3031b17 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 13 May 2015 08:02:48 -0400 Subject: [PATCH 05/26] Add forceUpdateMs --- main/src/main/scala/sbt/Defaults.scala | 11 +++++++++-- main/src/main/scala/sbt/Keys.scala | 1 + notes/0.13.9/forceupdatems.markdown | 9 +++++++++ .../dependency-management/force-update-ms/build.sbt | 13 +++++++++++++ .../dependency-management/force-update-ms/test | 11 +++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 notes/0.13.9/forceupdatems.markdown create mode 100644 sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/force-update-ms/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index e75c97832..5b38a5b5b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -115,7 +115,8 @@ object Defaults extends BuildCommon { pomPostProcess :== idFun, pomAllRepositories :== false, pomIncludeRepository :== Classpaths.defaultRepositoryFilter, - updateOptions := UpdateOptions() + updateOptions := UpdateOptions(), + forceUpdateMs :== None ) /** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */ @@ -1324,7 +1325,13 @@ object Classpaths { def updateTask: Initialize[Task[UpdateReport]] = Def.task { val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached) val isRoot = executionRoots.value contains resolvedScoped.value + val forceUpdate = forceUpdateMs.value val s = streams.value + val fullUpdateOutput = s.cacheDirectory / "out" + val forceUpdateByTime = forceUpdate match { + case None => false + case Some(period) => fullUpdateOutput.exists() && fullUpdateOutput.lastModified() <= (System.currentTimeMillis() - period) + } val scalaProvider = appConfiguration.value.provider.scalaProvider // Only substitute unmanaged jars for managed jars when the major.minor parts of the versions the same for: @@ -1360,7 +1367,7 @@ object Classpaths { if (executionRoots.value exists { _.key == evicted.key }) EvictionWarningOptions.empty else (evictionWarningOptions in update).value cachedUpdate(s.cacheDirectory / updateCacheName.value, show, ivyModule.value, uc, transform, - skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, + skip = (skip in update).value, force = isRoot || forceUpdateByTime, depsUpdated = depsUpdated, uwConfig = uwConfig, logicalClock = logicalClock, depDir = Some(depDir), ewo = ewo, log = s.log) } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 59faafdae..93fce1a0c 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -324,6 +324,7 @@ object Keys { val publishArtifact = SettingKey[Boolean]("publish-artifact", "Enables (true) or disables (false) publishing an artifact.", AMinusSetting) val packagedArtifact = TaskKey[(Artifact, File)]("packaged-artifact", "Generates a packaged artifact, returning the Artifact and the produced File.", CTask) val checksums = SettingKey[Seq[String]]("checksums", "The list of checksums to generate and to verify for dependencies.", BSetting) + val forceUpdateMs = SettingKey[Option[Long]]("force-update-ms", "Amount of time in milliseconds after which to force a full update to occur", CSetting) val classifiersModule = TaskKey[GetClassifiersModule]("classifiers-module", rank = CTask) val conflictWarning = SettingKey[ConflictWarning]("conflict-warning", "Configures warnings for conflicts in dependency management.", CSetting) diff --git a/notes/0.13.9/forceupdatems.markdown b/notes/0.13.9/forceupdatems.markdown new file mode 100644 index 000000000..f506c8ad6 --- /dev/null +++ b/notes/0.13.9/forceupdatems.markdown @@ -0,0 +1,9 @@ + [@ajsquared]: https://github.com/ajsquared + + +### Changes with compatibility implications + +### Improvements +- Adds `forceUpdateMs` key, that takes values of `Option[Long]` representing a number of milliseconds. If set, a full `update` will occur after that amount of time without needing to explicitly run the `update` task. + +### Fixes diff --git a/sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt b/sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt new file mode 100644 index 000000000..ca910bdd9 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt @@ -0,0 +1,13 @@ +libraryDependencies += "log4j" % "log4j" % "1.2.16" % "compile" + +autoScalaLibrary := false + +TaskKey[Unit]("check-last-update-time") <<= streams map { (s) => + val fullUpdateOutput = s.cacheDirectory / "out" + val timeDiff = System.currentTimeMillis()-fullUpdateOutput.lastModified() + val exists = fullUpdateOutput.exists() + s.log.info(s"Amount of time since last full update: $timeDiff") + if (exists && timeDiff > 5000) { + sys.error("Full update not perfomed") + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/force-update-ms/test b/sbt/src/sbt-test/dependency-management/force-update-ms/test new file mode 100644 index 000000000..f88058665 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/force-update-ms/test @@ -0,0 +1,11 @@ +$ absent target/resolution-cache +> compile +$ exists target/resolution-cache +> checkLastUpdateTime +$ sleep 10000 +> compile +# This is expected to fail +-> checkLastUpdateTime +> set forceUpdateMs := Some(5000) +> compile +> checkLastUpdateTime \ No newline at end of file From 32b1f655032717bab86ce521550c2cf6e48be820 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 13 May 2015 08:04:44 -0400 Subject: [PATCH 06/26] Update note --- notes/0.13.9/forceupdatems.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/0.13.9/forceupdatems.markdown b/notes/0.13.9/forceupdatems.markdown index f506c8ad6..fd631b8b7 100644 --- a/notes/0.13.9/forceupdatems.markdown +++ b/notes/0.13.9/forceupdatems.markdown @@ -4,6 +4,6 @@ ### Changes with compatibility implications ### Improvements -- Adds `forceUpdateMs` key, that takes values of `Option[Long]` representing a number of milliseconds. If set, a full `update` will occur after that amount of time without needing to explicitly run the `update` task. +- Adds `forceUpdateMs` key, that takes values of `Option[Long]` representing a number of milliseconds. If set, a full `update` will occur after that amount of time without needing to explicitly run the `update` task. By [@ajsquared][@ajsquared] ### Fixes From 7c0f455c79a572f9f888f1b5ad6d43492bb0612d Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 13 May 2015 14:18:27 -0400 Subject: [PATCH 07/26] Use FiniteDuration to represent time rather than a long --- main/src/main/scala/sbt/Defaults.scala | 14 ++++++++------ main/src/main/scala/sbt/Keys.scala | 4 ++-- notes/0.13.9/forceupdatems.markdown | 9 --------- notes/0.13.9/forceupdateperiod.markdown | 9 +++++++++ .../build.sbt | 0 .../{force-update-ms => force-update-period}/test | 2 +- 6 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 notes/0.13.9/forceupdatems.markdown create mode 100644 notes/0.13.9/forceupdateperiod.markdown rename sbt/src/sbt-test/dependency-management/{force-update-ms => force-update-period}/build.sbt (100%) rename sbt/src/sbt-test/dependency-management/{force-update-ms => force-update-period}/test (54%) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 5b38a5b5b..cd05b9c77 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -3,7 +3,7 @@ */ package sbt -import scala.concurrent.duration.Duration +import scala.concurrent.duration.{ FiniteDuration, Duration } import Attributed.data import Scope.{ fillTaskAxis, GlobalScope, ThisScope } import sbt.Compiler.InputsWithPrevious @@ -27,7 +27,7 @@ import org.apache.ivy.core.module.{ descriptor, id } import descriptor.ModuleDescriptor, id.ModuleRevisionId import java.io.{ File, PrintWriter } import java.net.{ URI, URL, MalformedURLException } -import java.util.concurrent.Callable +import java.util.concurrent.{ TimeUnit, Callable } import sbinary.DefaultProtocol.StringFormat import Cache.seqFormat import CommandStrings.ExportStream @@ -116,7 +116,7 @@ object Defaults extends BuildCommon { pomAllRepositories :== false, pomIncludeRepository :== Classpaths.defaultRepositoryFilter, updateOptions := UpdateOptions(), - forceUpdateMs :== None + forceUpdatePeriod :== None ) /** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */ @@ -1325,12 +1325,14 @@ object Classpaths { def updateTask: Initialize[Task[UpdateReport]] = Def.task { val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached) val isRoot = executionRoots.value contains resolvedScoped.value - val forceUpdate = forceUpdateMs.value + val forceUpdate = forceUpdatePeriod.value val s = streams.value val fullUpdateOutput = s.cacheDirectory / "out" val forceUpdateByTime = forceUpdate match { - case None => false - case Some(period) => fullUpdateOutput.exists() && fullUpdateOutput.lastModified() <= (System.currentTimeMillis() - period) + case None => false + case Some(period) => + val elapsedDuration = new FiniteDuration(System.currentTimeMillis() - fullUpdateOutput.lastModified(), TimeUnit.MILLISECONDS) + fullUpdateOutput.exists() && elapsedDuration > period } val scalaProvider = appConfiguration.value.provider.scalaProvider diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 93fce1a0c..939dd9b77 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -5,7 +5,7 @@ package sbt import java.io.File import java.net.URL -import scala.concurrent.duration.Duration +import scala.concurrent.duration.{ FiniteDuration, Duration } import Def.ScopedKey import complete._ import inc.Analysis @@ -324,7 +324,7 @@ object Keys { val publishArtifact = SettingKey[Boolean]("publish-artifact", "Enables (true) or disables (false) publishing an artifact.", AMinusSetting) val packagedArtifact = TaskKey[(Artifact, File)]("packaged-artifact", "Generates a packaged artifact, returning the Artifact and the produced File.", CTask) val checksums = SettingKey[Seq[String]]("checksums", "The list of checksums to generate and to verify for dependencies.", BSetting) - val forceUpdateMs = SettingKey[Option[Long]]("force-update-ms", "Amount of time in milliseconds after which to force a full update to occur", CSetting) + val forceUpdatePeriod = SettingKey[Option[FiniteDuration]]("force-update-period", "Duration after which to force a full update to occur", CSetting) val classifiersModule = TaskKey[GetClassifiersModule]("classifiers-module", rank = CTask) val conflictWarning = SettingKey[ConflictWarning]("conflict-warning", "Configures warnings for conflicts in dependency management.", CSetting) diff --git a/notes/0.13.9/forceupdatems.markdown b/notes/0.13.9/forceupdatems.markdown deleted file mode 100644 index fd631b8b7..000000000 --- a/notes/0.13.9/forceupdatems.markdown +++ /dev/null @@ -1,9 +0,0 @@ - [@ajsquared]: https://github.com/ajsquared - - -### Changes with compatibility implications - -### Improvements -- Adds `forceUpdateMs` key, that takes values of `Option[Long]` representing a number of milliseconds. If set, a full `update` will occur after that amount of time without needing to explicitly run the `update` task. By [@ajsquared][@ajsquared] - -### Fixes diff --git a/notes/0.13.9/forceupdateperiod.markdown b/notes/0.13.9/forceupdateperiod.markdown new file mode 100644 index 000000000..49e171b5d --- /dev/null +++ b/notes/0.13.9/forceupdateperiod.markdown @@ -0,0 +1,9 @@ + [@ajsquared]: https://github.com/ajsquared + + +### Changes with compatibility implications + +### Improvements +- Adds `forceUpdatePeriod` key, that takes values of `Option[FiniteDuration]`. If set, a full `update` will occur after that amount of time without needing to explicitly run the `update` task. By [@ajsquared][@ajsquared] + +### Fixes diff --git a/sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt b/sbt/src/sbt-test/dependency-management/force-update-period/build.sbt similarity index 100% rename from sbt/src/sbt-test/dependency-management/force-update-ms/build.sbt rename to sbt/src/sbt-test/dependency-management/force-update-period/build.sbt diff --git a/sbt/src/sbt-test/dependency-management/force-update-ms/test b/sbt/src/sbt-test/dependency-management/force-update-period/test similarity index 54% rename from sbt/src/sbt-test/dependency-management/force-update-ms/test rename to sbt/src/sbt-test/dependency-management/force-update-period/test index f88058665..39e776d09 100644 --- a/sbt/src/sbt-test/dependency-management/force-update-ms/test +++ b/sbt/src/sbt-test/dependency-management/force-update-period/test @@ -6,6 +6,6 @@ $ sleep 10000 > compile # This is expected to fail -> checkLastUpdateTime -> set forceUpdateMs := Some(5000) +> set forceUpdatePeriod := Some(new scala.concurrent.duration.FiniteDuration(5000, java.util.concurrent.TimeUnit.MILLISECONDS)) > compile > checkLastUpdateTime \ No newline at end of file From 871979c806b54755339f1b987a0394ec6f8f6649 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 15 May 2015 22:04:51 -0400 Subject: [PATCH 08/26] use Scala 2.11.6 --- .../source-dependencies/macro-arg-dep-2-11/project/build.scala | 2 +- .../macro-arg-dep-nested-2-11/project/build.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala index 89cd91ef8..f823d8295 100644 --- a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-2-11/project/build.scala @@ -5,7 +5,7 @@ object build extends Build { val defaultSettings = Seq( libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), incOptions := incOptions.value.withNameHashing(true), - scalaVersion := "2.11.0-RC3" + scalaVersion := "2.11.6" ) lazy val root = Project( diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala index 89cd91ef8..f823d8295 100644 --- a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested-2-11/project/build.scala @@ -5,7 +5,7 @@ object build extends Build { val defaultSettings = Seq( libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), incOptions := incOptions.value.withNameHashing(true), - scalaVersion := "2.11.0-RC3" + scalaVersion := "2.11.6" ) lazy val root = Project( From 54081fb4e4a55a894751ac63a17f5c1c984c1ac3 Mon Sep 17 00:00:00 2001 From: Vitalii Voloshyn Date: Mon, 18 May 2015 13:33:31 +0300 Subject: [PATCH 09/26] Prevent history command(s) from going into an infinite loop [1562] --- notes/0.13.9/history-infinite-loop.markdown | 3 +++ .../complete/src/main/scala/sbt/complete/HistoryCommands.scala | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 notes/0.13.9/history-infinite-loop.markdown diff --git a/notes/0.13.9/history-infinite-loop.markdown b/notes/0.13.9/history-infinite-loop.markdown new file mode 100644 index 000000000..c2ae3eb51 --- /dev/null +++ b/notes/0.13.9/history-infinite-loop.markdown @@ -0,0 +1,3 @@ +### Bug fixes + +- Prevent history command(s) from going into an infinite loop [#1562][1562] by [@PanAeon][@PanAeon] \ No newline at end of file diff --git a/util/complete/src/main/scala/sbt/complete/HistoryCommands.scala b/util/complete/src/main/scala/sbt/complete/HistoryCommands.scala index 762f48c6d..1e124c583 100644 --- a/util/complete/src/main/scala/sbt/complete/HistoryCommands.scala +++ b/util/complete/src/main/scala/sbt/complete/HistoryCommands.scala @@ -61,7 +61,7 @@ object HistoryCommands { def execute(f: History => Option[String]): History => Option[List[String]] = (h: History) => { - val command = f(h) + val command = f(h).filterNot(_.startsWith(Start)) val lines = h.lines.toArray command.foreach(lines(lines.length - 1) = _) h.path foreach { h => IO.writeLines(h, lines) } From 1cb63c84e128c12ed52bad2eea454730498e4c06 Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Wed, 20 May 2015 03:26:30 +0200 Subject: [PATCH 10/26] Do not emit in the pom dependencies that are only sources or docs The pom generation code tries its best to map Ivy's configurations to Maven scopes; however, sources and javadoc artifacts cannot be properly mapped and they currently are emitted as dependencies in the default scope (compile). That may lead to the source/doc jars being erroneously processed like regular jars by automated tools. Arguably, the source/docs jars should not be included in the pom file as dependencies at all. This commit filters out the dependencies that only appear in the sources and/or javadoc Ivy configurations, thereby preventing them from appearing in the final pom file. --- ivy/src/main/scala/sbt/MakePom.scala | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index d0e1a2741..16bd6002a 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -233,28 +233,33 @@ class MakePom(val log: Logger) { val artifacts = dependency.getAllDependencyArtifacts val includeArtifacts = artifacts.filter(d => includeTypes(d.getType)) if (artifacts.isEmpty) { - val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) - makeDependencyElem(dependency, scope, optional, None, None) + val configs = dependency.getModuleConfigurations + if (configs.filterNot(Set("sources","docs")).nonEmpty) { + val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) + makeDependencyElem(dependency, scope, optional, None, None) + } else NodeSeq.Empty } else if (includeArtifacts.isEmpty) NodeSeq.Empty else - NodeSeq.fromSeq(artifacts.map(a => makeDependencyElem(dependency, a))) + NodeSeq.fromSeq(artifacts.flatMap(a => makeDependencyElem(dependency, a))) } - def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Elem = + def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Option[Elem] = { val configs = artifact.getConfigurations.toList match { case Nil | "*" :: Nil => dependency.getModuleConfigurations case x => x.toArray } - val (scope, optional) = getScopeAndOptional(configs) - val classifier = artifactClassifier(artifact) - val baseType = artifactType(artifact) - val tpe = (classifier, baseType) match { - case (Some(c), Some(tpe)) if Artifact.classifierType(c) == tpe => None - case _ => baseType - } - makeDependencyElem(dependency, scope, optional, classifier, tpe) + if (configs.filterNot(Set("sources","docs")).nonEmpty) { + val (scope, optional) = getScopeAndOptional(configs) + val classifier = artifactClassifier(artifact) + val baseType = artifactType(artifact) + val tpe = (classifier, baseType) match { + case (Some(c), Some(tpe)) if Artifact.classifierType(c) == tpe => None + case _ => baseType + } + Some(makeDependencyElem(dependency, scope, optional, classifier, tpe)) + } else None } def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String]): Elem = { From a6b9e1864713c7693adbe9f0ad055517fb2a1fb7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 21 May 2015 23:04:52 -0400 Subject: [PATCH 11/26] Fixes #1721/#1763. Cached resolution: summarize callers in graph.json - On some of the builds graph.json is reaching 250MB+ - JSON parsing alone takes hours - 97% of the content are caller info - This change summarizes all callers into one (zero caller would have correctness issues) --- ivy/src/main/scala/sbt/JsonUtil.scala | 28 ++++++++++++++++++- .../CachedResolutionResolveEngine.scala | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ivy/src/main/scala/sbt/JsonUtil.scala b/ivy/src/main/scala/sbt/JsonUtil.scala index 88d7e1e90..156c9aa44 100644 --- a/ivy/src/main/scala/sbt/JsonUtil.scala +++ b/ivy/src/main/scala/sbt/JsonUtil.scala @@ -25,8 +25,34 @@ private[sbt] object JsonUtil { } def toLite(ur: UpdateReport): UpdateReportLite = UpdateReportLite(ur.configurations map { cr => - ConfigurationReportLite(cr.configuration, cr.details) + ConfigurationReportLite(cr.configuration, cr.details map { oar => + new OrganizationArtifactReport(oar.organization, oar.name, oar.modules map { mr => + new ModuleReport( + mr.module, mr.artifacts, mr.missingArtifacts, mr.status, + mr.publicationDate, mr.resolver, mr.artifactResolver, + mr.evicted, mr.evictedData, mr.evictedReason, + mr.problem, mr.homepage, mr.extraAttributes, + mr.isDefault, mr.branch, mr.configurations, mr.licenses, + summarizeCallers(mr.callers)) + }) + }) }) + // #1763/#2030. Caller takes up 97% of space, so we need to shrink it down, + // but there are semantics associated with some of them. + def summarizeCallers(callers: Seq[Caller]): Seq[Caller] = + if (callers.isEmpty) callers + else { + // Use the first element to represent all callers + val head = callers.head + val caller = new Caller( + head.caller, head.callerConfigurations, head.callerExtraAttributes, + callers exists { _.isForceDependency }, + callers exists { _.isChangingDependency }, + callers exists { _.isTransitiveDependency }, + callers exists { _.isDirectlyForceDependency }) + Seq(caller) + } + def fromLite(lite: UpdateReportLite, cachedDescriptor: File): UpdateReport = { val stats = new UpdateStats(0L, 0L, 0L, false) diff --git a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala index 14d7af22a..5b0418abf 100644 --- a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala +++ b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala @@ -25,7 +25,7 @@ private[sbt] object CachedResolutionResolveCache { def createID(organization: String, name: String, revision: String) = ModuleRevisionId.newInstance(organization, name, revision) def sbtOrgTemp = "org.scala-sbt.temp" - def graphVersion = "0.13.8" + def graphVersion = "0.13.9" } private[sbt] class CachedResolutionResolveCache() { From af6c774f4662630f6c52daf3fec479a7a50d54eb Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 21 May 2015 23:19:42 -0400 Subject: [PATCH 12/26] notes --- notes/0.13.9/cached-resolution-fix.markdown | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 notes/0.13.9/cached-resolution-fix.markdown diff --git a/notes/0.13.9/cached-resolution-fix.markdown b/notes/0.13.9/cached-resolution-fix.markdown new file mode 100644 index 000000000..ad439b2da --- /dev/null +++ b/notes/0.13.9/cached-resolution-fix.markdown @@ -0,0 +1,26 @@ + [@cunei]: https://github.com/cunei + [@eed3si9n]: https://github.com/eed3si9n + [@gkossakowski]: https://github.com/gkossakowski + [@jsuereth]: https://github.com/jsuereth + + [1721]: https://github.com/sbt/sbt/issues/1721 + [2030]: https://github.com/sbt/sbt/pull/2030 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Fixes memory/performance issue with cached resolution. See below. + +### Cached resolution fixes + +On a larger dependency graph, the JSON file growing to be 100MB+ +with 97% of taken up by *caller* information. +The caller information is not useful once the graph is successfully resolved. +sbt 0.13.9 creates a single caller to represent all callers, +which fixes `OutOfMemoryException` seen on some builds, +and generally it should make JSON IO faster. + +[#2030][2030]/[#1721][1721] by [@eed3si9n][@eed3si9n] From e22d84e312541042eca94dcd145fc8963ec27c79 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 May 2015 03:25:26 -0400 Subject: [PATCH 13/26] Cached resolution: Clean up old dynamic minigraphs. Fixes #2014 Cached resolution saves dynamic mini graphs (including subproject graphs) timestamped to the logical clock (State). This enables graph caching across the subprojects. On the other hand, it creates garbage that becomes stale almost immediately. Prior to #2030 fix, this garbage would reach 1GB+. This fix timestamps these graphs using calendar date, and cleans them up after a day. --- .../CachedResolutionResolveEngine.scala | 23 ++++++++++++++++++- notes/0.13.9/cached-resolution-fix.markdown | 15 ++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala index 5b0418abf..0c09d9fc4 100644 --- a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala +++ b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala @@ -4,6 +4,7 @@ package ivyint import java.util.Date import java.net.URL import java.io.File +import java.text.SimpleDateFormat import collection.concurrent import collection.mutable import collection.immutable.ListMap @@ -20,12 +21,19 @@ import org.apache.ivy.plugins.latest.{ ArtifactInfo => IvyArtifactInfo } import org.apache.ivy.plugins.matcher.{ MapMatcher, PatternMatcher } import Configurations.{ System => _, _ } import annotation.tailrec +import scala.concurrent.duration._ private[sbt] object CachedResolutionResolveCache { def createID(organization: String, name: String, revision: String) = ModuleRevisionId.newInstance(organization, name, revision) def sbtOrgTemp = "org.scala-sbt.temp" def graphVersion = "0.13.9" + val buildStartup: Long = System.currentTimeMillis + lazy val todayStr: String = toYyyymmdd(buildStartup) + lazy val tomorrowStr: String = toYyyymmdd(buildStartup + (1 day).toMillis) + lazy val yesterdayStr: String = toYyyymmdd(buildStartup - (1 day).toMillis) + def toYyyymmdd(timeSinceEpoch: Long): String = yyyymmdd.format(new Date(timeSinceEpoch)) + lazy val yyyymmdd: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") } private[sbt] class CachedResolutionResolveCache() { @@ -137,7 +145,17 @@ private[sbt] class CachedResolutionResolveCache() { val staticGraphDirectory = miniGraphPath / "static" val dynamicGraphDirectory = miniGraphPath / "dynamic" val staticGraphPath = staticGraphDirectory / pathOrg / pathName / pathRevision / "graphs" / "graph.json" - val dynamicGraphPath = dynamicGraphDirectory / logicalClock.toString / pathOrg / pathName / pathRevision / "graphs" / "graph.json" + val dynamicGraphPath = dynamicGraphDirectory / todayStr / logicalClock.toString / pathOrg / pathName / pathRevision / "graphs" / "graph.json" + def cleanDynamicGraph(): Unit = + { + val list = (dynamicGraphDirectory listFiles DirectoryFilter).toList + list filterNot { d => + (d.getName == todayStr) || (d.getName == tomorrowStr) || (d.getName == yesterdayStr) + } foreach { d => + log.debug(s"deleting old graphs $d...") + IO.delete(d) + } + } def loadMiniGraphFromFile: Option[Either[ResolveException, UpdateReport]] = (if (staticGraphPath.exists) Some(staticGraphPath) else if (dynamicGraphPath.exists) Some(dynamicGraphPath) @@ -175,6 +193,9 @@ private[sbt] class CachedResolutionResolveCache() { val gp = if (changing) dynamicGraphPath else staticGraphPath log.debug(s"saving minigraph to $gp") + if (changing) { + cleanDynamicGraph() + } JsonUtil.writeUpdateReport(ur, gp) // limit the update cache size if (updateReportCache.size > maxUpdateReportCacheSize) { diff --git a/notes/0.13.9/cached-resolution-fix.markdown b/notes/0.13.9/cached-resolution-fix.markdown index ad439b2da..36b442b62 100644 --- a/notes/0.13.9/cached-resolution-fix.markdown +++ b/notes/0.13.9/cached-resolution-fix.markdown @@ -4,6 +4,7 @@ [@jsuereth]: https://github.com/jsuereth [1721]: https://github.com/sbt/sbt/issues/1721 + [2014]: https://github.com/sbt/sbt/issues/2014 [2030]: https://github.com/sbt/sbt/pull/2030 ### Fixes with compatibility implications @@ -12,15 +13,19 @@ ### Bug fixes -- Fixes memory/performance issue with cached resolution. See below. +- Fixes memory/performance issue with cached resolution. See below. ### Cached resolution fixes On a larger dependency graph, the JSON file growing to be 100MB+ with 97% of taken up by *caller* information. The caller information is not useful once the graph is successfully resolved. -sbt 0.13.9 creates a single caller to represent all callers, -which fixes `OutOfMemoryException` seen on some builds, -and generally it should make JSON IO faster. +To make the matter worse, these large JSON files were never cleaned up. -[#2030][2030]/[#1721][1721] by [@eed3si9n][@eed3si9n] +sbt 0.13.9 creates a single caller to represent all callers, +which fixes `OutOfMemoryException` seen on some builds. +This generally shrinks the size of JSON, so it should make the IO operations faster. +Dynamic graphs will be rotated with directories named after `yyyy-mm-dd`, +and stale JSON files will be cleaned up after few days. + +[#2030][2030]/[#1721][1721]/[#2014][2014] by [@eed3si9n][@eed3si9n] From da462aff1563600fe9365e02ac2a09a2d3c1aa0e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 May 2015 17:32:56 -0400 Subject: [PATCH 14/26] Adds null guard --- .../main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala index 0c09d9fc4..9f25d2172 100644 --- a/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala +++ b/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala @@ -148,7 +148,7 @@ private[sbt] class CachedResolutionResolveCache() { val dynamicGraphPath = dynamicGraphDirectory / todayStr / logicalClock.toString / pathOrg / pathName / pathRevision / "graphs" / "graph.json" def cleanDynamicGraph(): Unit = { - val list = (dynamicGraphDirectory listFiles DirectoryFilter).toList + val list = IO.listFiles(dynamicGraphDirectory, DirectoryFilter).toList list filterNot { d => (d.getName == todayStr) || (d.getName == tomorrowStr) || (d.getName == yesterdayStr) } foreach { d => From cd3af2f0cddef1b6c9743146ea8a549fda5ad594 Mon Sep 17 00:00:00 2001 From: Kamil Kloch Date: Wed, 20 May 2015 15:19:00 +0200 Subject: [PATCH 15/26] Updated ForkError.getMessage() to include exception's original name. --- notes/0.13.9/fork-error-get-message.markdown | 10 ++++++++++ testing/agent/src/main/java/sbt/ForkMain.java | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 notes/0.13.9/fork-error-get-message.markdown diff --git a/notes/0.13.9/fork-error-get-message.markdown b/notes/0.13.9/fork-error-get-message.markdown new file mode 100644 index 000000000..3f5176057 --- /dev/null +++ b/notes/0.13.9/fork-error-get-message.markdown @@ -0,0 +1,10 @@ + [@kamilkloch]: https://github.com/kamilkloch + [2028]: https://github.com/sbt/sbt/issues/2028 + +### Changes with compatibility implications + +### Improvements + +- Update ForkError.getMessage() to include exception's original name. [#2028][2028] by [@kamilkloch][@kamilkloch] + +### Fixes diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 15f53ce25..142bc6aa1 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -95,13 +95,15 @@ public class ForkMain { static class ForkError extends Exception { private String originalMessage; + private String originalName; private ForkError cause; ForkError(Throwable t) { originalMessage = t.getMessage(); + originalName = t.getClass().getName(); setStackTrace(t.getStackTrace()); if (t.getCause() != null) cause = new ForkError(t.getCause()); } - public String getMessage() { return originalMessage; } + public String getMessage() { return originalName + ": " + originalMessage; } public Exception getCause() { return cause; } } From d14afcf67d43101590f963480aa981cf8dd3eded Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Tue, 26 May 2015 23:48:05 +0200 Subject: [PATCH 16/26] Adding notes for 2001/2027 --- notes/0.13.9/source-pom.markdown | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 notes/0.13.9/source-pom.markdown diff --git a/notes/0.13.9/source-pom.markdown b/notes/0.13.9/source-pom.markdown new file mode 100644 index 000000000..fa906e711 --- /dev/null +++ b/notes/0.13.9/source-pom.markdown @@ -0,0 +1,21 @@ + + [@cunei]: http://github.com/cunei + [2001]: https://github.com/sbt/sbt/issues/2001 + [2027]: https://github.com/sbt/sbt/pull/2027 + +### Fixes with compatibility implications + +- Starting with 0.13.9, the generated POM files no longer include dependencies on source or javadoc jars + obtained via withSources() or withJavadoc() + +### Improvements + +### Bug fixes + +### POM files no longer include certain source and javadoc jars + +When declaring library dependencies using the withSources() or withJavadoc() options, sbt was also including +in the pom file, as dependencies, the source or javadoc jars using the default Maven scope. Such dependencies +might be erroneously processed as they were regular jars by automated tools + +[#2001][2001]/[#2027][2027] by [@cunei][@cunei] From 8276b6db123e3b34b41e8d56b23ca8d027096d57 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 27 May 2015 07:33:26 +0100 Subject: [PATCH 17/26] Fix intended formatting in README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 013b320d8..b398095e6 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ sbt === sbt is a build tool for Scala, Java, and more. + For general documentation, see http://www.scala-sbt.org/. Issues and Pull Requests ------------------------ Please read [CONTRIBUTING] carefully before opening a GitHub Issue. + The short version: try [StackOverflow] and [sbt-dev]. Don't open an Issue. sbt 0.13 From 827d0011ed1f6e59600c72250466da1e0387c349 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 May 2015 08:14:08 +0100 Subject: [PATCH 18/26] Add project-level exclusions in the POM. Closes #1877. --- ivy/src/main/scala/sbt/MakePom.scala | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index 16bd6002a..ffd189932 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -104,7 +104,7 @@ class MakePom(val log: Logger) { { val deps = depsInConfs(module, configurations) makeProperties(module, deps) ++ - makeDependencies(deps, includeTypes) + makeDependencies(deps, includeTypes, module.getAllExcludeRules) } { makeRepositories(ivy.getSettings, allRepositories, filterRepositories) } ) @@ -220,15 +220,15 @@ class MakePom(val log: Logger) { } val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType) - def makeDependencies(dependencies: Seq[DependencyDescriptor], includeTypes: Set[String]): NodeSeq = + def makeDependencies(dependencies: Seq[DependencyDescriptor], includeTypes: Set[String], excludes: Seq[ExcludeRule]): NodeSeq = if (dependencies.isEmpty) NodeSeq.Empty else - { dependencies.map(makeDependency(_, includeTypes)) } + { dependencies.map(makeDependency(_, includeTypes, excludes)) } - def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = + def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String], excludes: Seq[ExcludeRule]): NodeSeq = { val artifacts = dependency.getAllDependencyArtifacts val includeArtifacts = artifacts.filter(d => includeTypes(d.getType)) @@ -236,15 +236,15 @@ class MakePom(val log: Logger) { val configs = dependency.getModuleConfigurations if (configs.filterNot(Set("sources","docs")).nonEmpty) { val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) - makeDependencyElem(dependency, scope, optional, None, None) + makeDependencyElem(dependency, scope, optional, None, None, excludes) } else NodeSeq.Empty } else if (includeArtifacts.isEmpty) NodeSeq.Empty else - NodeSeq.fromSeq(artifacts.flatMap(a => makeDependencyElem(dependency, a))) + NodeSeq.fromSeq(artifacts.flatMap(a => makeDependencyElem(dependency, a, excludes))) } - def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Option[Elem] = + def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor, excludes: Seq[ExcludeRule]): Option[Elem] = { val configs = artifact.getConfigurations.toList match { case Nil | "*" :: Nil => dependency.getModuleConfigurations @@ -258,10 +258,10 @@ class MakePom(val log: Logger) { case (Some(c), Some(tpe)) if Artifact.classifierType(c) == tpe => None case _ => baseType } - Some(makeDependencyElem(dependency, scope, optional, classifier, tpe)) + Some(makeDependencyElem(dependency, scope, optional, classifier, tpe, excludes)) } else None } - def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String]): Elem = + def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String], excludes: Seq[ExcludeRule]): Elem = { val mrid = dependency.getDependencyRevisionId @@ -272,7 +272,7 @@ class MakePom(val log: Logger) { { optionalElem(optional) } { classifierElem(classifier) } { typeElem(tpe) } - { exclusions(dependency) } + { exclusions(dependency, excludes) } } @@ -322,9 +322,9 @@ class MakePom(val log: Logger) { (scope, opt.nonEmpty) } - def exclusions(dependency: DependencyDescriptor): NodeSeq = + def exclusions(dependency: DependencyDescriptor, excludes: Seq[ExcludeRule]): NodeSeq = { - val excl = dependency.getExcludeRules(dependency.getModuleConfigurations) + val excl = dependency.getExcludeRules(dependency.getModuleConfigurations) ++ excludes val (warns, excls) = IvyUtil.separate(excl.map(makeExclusion)) if (warns.nonEmpty) log.warn(warns.mkString(IO.Newline)) if (excls.nonEmpty) { excls } From 4be663191eab7d4cb08917ae065154a881420a78 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sun, 24 May 2015 21:55:10 +0100 Subject: [PATCH 19/26] Add a group+artifact test for excludeDependencies. --- .../exclude-dependencies/build.sbt | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt b/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt index 16bc85772..5390886f3 100644 --- a/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt +++ b/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt @@ -1,3 +1,6 @@ +import scala.xml.{ Node, _ } +import scala.xml.Utility.trim + lazy val check = taskKey[Unit]("check") val dispatch = "net.databinder.dispatch" %% "dispatch-core" % "0.11.2" @@ -31,5 +34,30 @@ lazy val root = (project in file(".")). if (bcp exists { _.data.getName contains "dispatch-core_2.11-0.11.1.jar" }) { sys.error("dispatch-core_2.11-0.11.1.jar found when it should NOT be included: " + bcp.toString) } + + val bPomXml = makePomXml(streams.value.log, (makePomConfiguration in b).value, (ivyModule in b).value) + + val repatchTwitterXml = bPomXml \ "dependencies" \ "dependency" find { d => + (d \ "groupId").text == "com.eed3si9n" && (d \ "artifactId").text == "repatch-twitter-core_2.11" + } getOrElse (sys error s"Missing repatch-twitter-core dependency: $bPomXml") + + val excludeDispatchCoreXml = + + net.databinder.dispatch + dispatch-core_2.11 + + + if (trim((repatchTwitterXml \ "exclusions" \ "exclusion").head) != trim(excludeDispatchCoreXml)) + sys error s"Missing dispatch-core exclusion: $repatchTwitterXml" + + () } - ) \ No newline at end of file + ) + +def makePomXml(log: Logger, makePomConfig: MakePomConfiguration, ivyModule: IvySbt#Module): Node = { + ivyModule.withModule[Node](log) { (ivy, md, default) => + import makePomConfig._ + new MakePom(log).toPom( + ivy, md, moduleInfo, configurations, includeTypes, extra, filterRepositories, allRepositories) + } +} From 87455f369d139309cf1da092e1d5fc8c08e905a5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 May 2015 23:25:08 +0100 Subject: [PATCH 20/26] Fix bincompat for project-level excludes in the POM. --- ivy/src/main/scala/sbt/MakePom.scala | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index ffd189932..4e0ed0118 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -220,6 +220,10 @@ class MakePom(val log: Logger) { } val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType) + @deprecated("Use `makeDependencies` variant which takes excludes", "0.13.9") + def makeDependencies(dependencies: Seq[DependencyDescriptor], includeTypes: Set[String]): NodeSeq = + makeDependencies(dependencies, includeTypes, Nil) + def makeDependencies(dependencies: Seq[DependencyDescriptor], includeTypes: Set[String], excludes: Seq[ExcludeRule]): NodeSeq = if (dependencies.isEmpty) NodeSeq.Empty @@ -228,6 +232,10 @@ class MakePom(val log: Logger) { { dependencies.map(makeDependency(_, includeTypes, excludes)) } + @deprecated("Use `makeDependency` variant which takes excludes", "0.13.9") + def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = + makeDependency(dependency, includeTypes, Nil) + def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String], excludes: Seq[ExcludeRule]): NodeSeq = { val artifacts = dependency.getAllDependencyArtifacts @@ -244,6 +252,10 @@ class MakePom(val log: Logger) { NodeSeq.fromSeq(artifacts.flatMap(a => makeDependencyElem(dependency, a, excludes))) } + @deprecated("Use `makeDependencyElem` variant which takes excludes", "0.13.9") + def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Option[Elem] = + makeDependencyElem(dependency, artifact, Nil) + def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor, excludes: Seq[ExcludeRule]): Option[Elem] = { val configs = artifact.getConfigurations.toList match { @@ -261,6 +273,11 @@ class MakePom(val log: Logger) { Some(makeDependencyElem(dependency, scope, optional, classifier, tpe, excludes)) } else None } + + @deprecated("Use `makeDependencyElem` variant which takes excludes", "0.13.9") + def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String]): Elem = + makeDependencyElem(dependency, scope, optional, classifier, tpe, Nil) + def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String], excludes: Seq[ExcludeRule]): Elem = { val mrid = dependency.getDependencyRevisionId @@ -322,6 +339,9 @@ class MakePom(val log: Logger) { (scope, opt.nonEmpty) } + @deprecated("Use `exclusions` variant which takes excludes", "0.13.9") + def exclusions(dependency: DependencyDescriptor): NodeSeq = exclusions(dependency, Nil) + def exclusions(dependency: DependencyDescriptor, excludes: Seq[ExcludeRule]): NodeSeq = { val excl = dependency.getExcludeRules(dependency.getModuleConfigurations) ++ excludes From bbfbdde427e0c8ea3de64c7325ebc0b7919df6a6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 May 2015 23:34:33 +0100 Subject: [PATCH 21/26] Add notes for project-level excludes in the POM. --- .../project-level-exclusions-in-the-pom.markdown | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 notes/0.13.9/project-level-exclusions-in-the-pom.markdown diff --git a/notes/0.13.9/project-level-exclusions-in-the-pom.markdown b/notes/0.13.9/project-level-exclusions-in-the-pom.markdown new file mode 100644 index 000000000..f534d989e --- /dev/null +++ b/notes/0.13.9/project-level-exclusions-in-the-pom.markdown @@ -0,0 +1,12 @@ + + [@dwijnand]: http://github.com/dwijnand + [#1877]: https://github.com/sbt/sbt/issues/1877 + [#2035]: https://github.com/sbt/sbt/pull/2035 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Add dependency-level exclusions in the POM for project-level exclusions. [#1877][]/[#2035][] by [@dwijnand][] From c47b28c0d9b1c007b70904e85f2e279c5b94acc1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 May 2015 08:27:55 +0100 Subject: [PATCH 22/26] Commit build auto-formatting changes for code in master. --- ivy/src/main/scala/sbt/MakePom.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index 4e0ed0118..1dfa5d1fe 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -242,7 +242,7 @@ class MakePom(val log: Logger) { val includeArtifacts = artifacts.filter(d => includeTypes(d.getType)) if (artifacts.isEmpty) { val configs = dependency.getModuleConfigurations - if (configs.filterNot(Set("sources","docs")).nonEmpty) { + if (configs.filterNot(Set("sources", "docs")).nonEmpty) { val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) makeDependencyElem(dependency, scope, optional, None, None, excludes) } else NodeSeq.Empty @@ -262,7 +262,7 @@ class MakePom(val log: Logger) { case Nil | "*" :: Nil => dependency.getModuleConfigurations case x => x.toArray } - if (configs.filterNot(Set("sources","docs")).nonEmpty) { + if (configs.filterNot(Set("sources", "docs")).nonEmpty) { val (scope, optional) = getScopeAndOptional(configs) val classifier = artifactClassifier(artifact) val baseType = artifactType(artifact) From 201135741093b7f8f3e553044c22dff2d956d5d5 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 11 May 2015 21:30:38 -0400 Subject: [PATCH 23/26] Adding a specific test for XML transforms done by CustomPomParser. --- .../pom-parent-pom/build.sbt | 17 +++++++++++++++++ .../dependency-management/pom-parent-pom/test | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt b/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt index 53d830e83..82e17b0ab 100644 --- a/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt +++ b/sbt/src/sbt-test/dependency-management/pom-parent-pom/build.sbt @@ -10,6 +10,8 @@ resolvers += libraryDependencies += "com.example" % "example-child" % "1.0-SNAPSHOT" +libraryDependencies += "org.apache.geronimo.specs" % "geronimo-jta_1.1_spec" % "1.1.1" + version := "1.0-SNAPSHOT" @@ -20,3 +22,18 @@ cleanExampleCache := { IO.delete(cacheDir / "com.example") } } + +val checkIvyXml = taskKey[Unit]("Checks the ivy.xml transform was correct") + +checkIvyXml := { + ivySbt.value.withIvy(streams.value.log) { ivy => + val cacheDir = ivy.getSettings.getDefaultRepositoryCacheBasedir + // TODO - Is this actually ok? + val xmlFile = + cacheDir / "org.apache.geronimo.specs" / "geronimo-jta_1.1_spec" / "ivy-1.1.1.xml" + //cacheDir / "com.example" / "example-child" / "ivy-1.0-SNAPSHOT.xml" + val lines = IO.read(xmlFile) + if(lines.isEmpty) sys.error(s"Unable to read $xmlFile, could not resolve geronimo...") + assert(lines contains "xmlns:e", s"Failed to appropriately modify ivy.xml file for sbt extra attributes!\n$lines") + } +} diff --git a/sbt/src/sbt-test/dependency-management/pom-parent-pom/test b/sbt/src/sbt-test/dependency-management/pom-parent-pom/test index c0f560f2f..368146c24 100644 --- a/sbt/src/sbt-test/dependency-management/pom-parent-pom/test +++ b/sbt/src/sbt-test/dependency-management/pom-parent-pom/test @@ -1,2 +1,4 @@ > cleanExampleCache -> update \ No newline at end of file +> update +> checkIvyXml +-> resolv \ No newline at end of file From 997037f7cd84afe332168aa0cb547afe04d4da48 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 11 May 2015 21:30:59 -0400 Subject: [PATCH 24/26] Fix corruption issues with sbt's CustomPomParser. --- ivy/src/main/scala/sbt/CustomPomParser.scala | 29 ++++++++++++++----- .../dependency-management/pom-parent-pom/test | 1 - 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ivy/src/main/scala/sbt/CustomPomParser.scala b/ivy/src/main/scala/sbt/CustomPomParser.scala index 7b5664daf..601f6e34d 100644 --- a/ivy/src/main/scala/sbt/CustomPomParser.scala +++ b/ivy/src/main/scala/sbt/CustomPomParser.scala @@ -51,7 +51,15 @@ object CustomPomParser { private[this] val TransformedHashKey = "e:sbtTransformHash" // A hash of the parameters transformation is based on. // If a descriptor has a different hash, we need to retransform it. - private[this] val TransformHash: String = hash((unqualifiedKeys ++ JarPackagings).toSeq.sorted) + private[this] def makeCoords(mrid: ModuleRevisionId): String = s"${mrid.getOrganisation}:${mrid.getName}:${mrid.getRevision}" + + // We now include the ModuleID in a hash, to ensure that parent-pom transformations don't corrupt child poms. + private[this] def MakeTransformHash(md: ModuleDescriptor): String = { + val coords: String = makeCoords(md.getModuleRevisionId) + + hash((unqualifiedKeys ++ JarPackagings ++ Set(coords)).toSeq.sorted) + } + private[this] def hash(ss: Seq[String]): String = Hash.toHex(Hash(ss.flatMap(_ getBytes "UTF-8").toArray)) // Unfortunately, ModuleDescriptorParserRegistry is add-only and is a singleton instance. @@ -65,11 +73,16 @@ object CustomPomParser { { val oldTransformedHashKey = "sbtTransformHash" val extraInfo = md.getExtraInfo + val MyHash = MakeTransformHash(md) + val h = MyHash // sbt 0.13.1 used "sbtTransformHash" instead of "e:sbtTransformHash" until #1192 so read both Option(extraInfo).isDefined && ((Option(extraInfo get TransformedHashKey) orElse Option(extraInfo get oldTransformedHashKey)) match { - case Some(TransformHash) => true - case _ => false + case x @ Some(MyHash) => + true + case Some(other) => + false + case _ => false }) } @@ -95,10 +108,10 @@ object CustomPomParser { val mergeDuplicates = IvySbt.hasDuplicateDependencies(md.getDependencies) val unqualify = toUnqualify(filtered) - if (unqualify.isEmpty && extraDepAttributes.isEmpty && !convertArtifacts && !mergeDuplicates) - md - else - addExtra(unqualify, extraDepAttributes, parser, md) + + // Here we always add extra attributes. There's a scenario where parent-pom information corrupts child-poms with "e:" namespaced xml elements + // and we have to force the every generated xml file to have the appropriate xml namespace + addExtra(unqualify, extraDepAttributes, parser, md) } // The element of the pom is used to store additional metadata, such as for sbt plugins or for the base URL for API docs. // This is done because the pom XSD does not appear to allow extra metadata anywhere else. @@ -185,7 +198,7 @@ object CustomPomParser { for (l <- md.getLicenses) dmd.addLicense(l) for ((key, value) <- md.getExtraInfo.asInstanceOf[java.util.Map[String, String]].asScala) dmd.addExtraInfo(key, value) - dmd.addExtraInfo(TransformedHashKey, TransformHash) // mark as transformed by this version, so we don't need to do it again + dmd.addExtraInfo(TransformedHashKey, MakeTransformHash(md)) // mark as transformed by this version, so we don't need to do it again for ((key, value) <- md.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String, String]].asScala) dmd.addExtraAttributeNamespace(key, value) IvySbt.addExtraNamespace(dmd) diff --git a/sbt/src/sbt-test/dependency-management/pom-parent-pom/test b/sbt/src/sbt-test/dependency-management/pom-parent-pom/test index 368146c24..68a7fec1c 100644 --- a/sbt/src/sbt-test/dependency-management/pom-parent-pom/test +++ b/sbt/src/sbt-test/dependency-management/pom-parent-pom/test @@ -1,4 +1,3 @@ > cleanExampleCache > update > checkIvyXml --> resolv \ No newline at end of file From 13b2a8ff4b4f0190c241477077b0983f65949d69 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 29 May 2015 09:49:47 -0400 Subject: [PATCH 25/26] Minor fixes of leftover codes from review. --- ivy/src/main/scala/sbt/CustomPomParser.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ivy/src/main/scala/sbt/CustomPomParser.scala b/ivy/src/main/scala/sbt/CustomPomParser.scala index 601f6e34d..d37804cbc 100644 --- a/ivy/src/main/scala/sbt/CustomPomParser.scala +++ b/ivy/src/main/scala/sbt/CustomPomParser.scala @@ -74,14 +74,10 @@ object CustomPomParser { val oldTransformedHashKey = "sbtTransformHash" val extraInfo = md.getExtraInfo val MyHash = MakeTransformHash(md) - val h = MyHash // sbt 0.13.1 used "sbtTransformHash" instead of "e:sbtTransformHash" until #1192 so read both Option(extraInfo).isDefined && ((Option(extraInfo get TransformedHashKey) orElse Option(extraInfo get oldTransformedHashKey)) match { - case x @ Some(MyHash) => - true - case Some(other) => - false + case Some(MyHash) => true case _ => false }) } From 5bf5fbee31cba3e213ba6d0e0742919a2046a96c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 29 May 2015 16:16:05 -0400 Subject: [PATCH 26/26] Adding notes --- notes/0.13.9/pom-corruption-fix.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 notes/0.13.9/pom-corruption-fix.md diff --git a/notes/0.13.9/pom-corruption-fix.md b/notes/0.13.9/pom-corruption-fix.md new file mode 100644 index 000000000..ab365e92d --- /dev/null +++ b/notes/0.13.9/pom-corruption-fix.md @@ -0,0 +1,14 @@ + [@jsuereth]: https://github.com/jsuereth + + [1856]: https://github.com/sbt/sbt/issues/1856 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Fixes a certain class of pom corruption that can occur in the presence of parent-poms. + + +[#1856][1856] by [@jsuereth][@jsuereth]