From e21c871d71865c0c246b46f711122cd539e4b69d Mon Sep 17 00:00:00 2001 From: James Roper Date: Fri, 13 May 2016 13:32:34 +1000 Subject: [PATCH] Replaced cross building support with sbt-doge --- main/src/main/scala/sbt/Cross.scala | 339 +++++++++++++----- main/src/main/scala/sbt/Main.scala | 2 +- .../scala/sbt/internal/CommandStrings.scala | 38 +- notes/1.0.0/sbt-doge.markdown | 10 + .../actions/cross-multiproject/build.sbt | 18 + .../actions/cross-multiproject/lib/A.scala | 5 + .../cross-multiproject/sbt-foo/B.scala | 5 + .../sbt-test/actions/cross-multiproject/test | 46 +++ sbt/src/sbt-test/actions/cross/test | 4 +- .../source-dependencies/cross-source/test | 4 +- 10 files changed, 359 insertions(+), 112 deletions(-) create mode 100644 notes/1.0.0/sbt-doge.markdown create mode 100644 sbt/src/sbt-test/actions/cross-multiproject/build.sbt create mode 100644 sbt/src/sbt-test/actions/cross-multiproject/lib/A.scala create mode 100644 sbt/src/sbt-test/actions/cross-multiproject/sbt-foo/B.scala create mode 100644 sbt/src/sbt-test/actions/cross-multiproject/test diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 174e2f248..53ac58ba4 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -4,128 +4,269 @@ package sbt import Keys._ -import sbt.internal.{ GCUtil, CommandStrings } import sbt.internal.util.complete.{ DefaultParsers, Parser } import sbt.internal.util.AttributeKey import DefaultParsers._ import Def.{ ScopedKey, Setting } -import Scope.GlobalScope -import sbt.internal.CommandStrings.{ CrossCommand, crossHelp, SwitchCommand, switchHelp } +import sbt.internal.CommandStrings.{ CrossCommand, CrossRestoreSessionCommand, SwitchCommand, crossHelp, crossRestoreSessionHelp, switchHelp } import java.io.File -import sbt.internal.inc.ScalaInstance +import sbt.internal.inc.ScalaInstance import sbt.io.IO +import sbt.librarymanagement.CrossVersion object Cross { - @deprecated("Moved to CommandStrings.Switch", "0.13.0") - final val Switch = CommandStrings.SwitchCommand - @deprecated("Moved to CommandStrings.Cross", "0.13.0") - final val Cross = CommandStrings.CrossCommand + private def spacedFirst(name: String) = opOrIDSpaced(name) ~ any.+ - def switchParser(state: State): Parser[(String, String)] = - { - def versionAndCommand(spacePresent: Boolean) = { - val knownVersions = crossVersions(state) - val version = token(StringBasic.examples(knownVersions: _*)) - val spacedVersion = if (spacePresent) version else version & spacedFirst(SwitchCommand) - val optionalCommand = token(Space ~> matched(state.combinedParser)) ?? "" - spacedVersion ~ optionalCommand - } - token(SwitchCommand ~> OptSpace) flatMap { sp => versionAndCommand(sp.nonEmpty) } - } - def spacedFirst(name: String) = opOrIDSpaced(name) ~ any.+ + private case class Switch(version: ScalaVersion, verbose: Boolean, command: Option[String]) + private trait ScalaVersion { + def force: Boolean + } + private case class NamedScalaVersion(name: String, force: Boolean) extends ScalaVersion + private case class ScalaHomeVersion(home: File, resolveVersion: Option[String], force: Boolean) extends ScalaVersion - lazy val switchVersion = Command.arb(requireSession(switchParser), switchHelp) { - case (state, (arg, command)) => + private def switchParser(state: State): Parser[Switch] = { + import DefaultParsers._ + def versionAndCommand(spacePresent: Boolean) = { val x = Project.extract(state) import x._ - - val (resolveVersion, homePath) = arg.split("=") match { - case Array(v, h) => (v, h) - case _ => ("", arg) - } - val home = IO.resolve(x.currentProject.base, new File(homePath)) - // Basic Algorithm. - // 1. First we figure out what the new scala instances should be, create settings for them. - // 2. Find any non-overridden scalaVersion setting in the whole build and force it to delegate - // to the new global settings. - // 3. Append these to the session, so that the session is up-to-date and - // things like set/session clear, etc. work. - val (add, exclude) = - if (home.exists) { - val instance = ScalaInstance(home)(state.classLoaderCache.apply _) - state.log.info("Setting Scala home to " + home + " with actual version " + instance.actualVersion) - val version = if (resolveVersion.isEmpty) instance.actualVersion else resolveVersion - state.log.info("\tand using " + version + " for resolving dependencies.") - val settings = Seq( - scalaVersion in GlobalScope :== version, - scalaHome in GlobalScope :== Some(home), - scalaInstance in GlobalScope :== instance - ) - (settings, excludeKeys(Set(scalaVersion.key, scalaHome.key, scalaInstance.key))) - } else if (!resolveVersion.isEmpty) { - sys.error("Scala home directory did not exist: " + home) - } else { - state.log.info("Setting version to " + arg) - val settings = Seq( - scalaVersion in GlobalScope :== arg, - scalaHome in GlobalScope :== None - ) - (settings, excludeKeys(Set(scalaVersion.key, scalaHome.key))) + val knownVersions = crossVersions(x, currentRef) + val version = token(StringBasic.examples(knownVersions: _*)).map { arg => + val force = arg.endsWith("!") + val versionArg = if (force) arg.dropRight(1) else arg + versionArg.split("=", 2) match { + case Array(home) if new File(home).exists() => ScalaHomeVersion(new File(home), None, force) + case Array(v) => NamedScalaVersion(v, force) + case Array(v, home) => ScalaHomeVersion(new File(home), Some(v).filterNot(_.isEmpty), force) } - - val isForceGc = getOpt(Keys.forcegc in Global) getOrElse GCUtil.defaultForceGarbageCollection - // This is how to get the interval, but ignore it, and just forcegc - // val gcInterval = getOpt(Keys.minForcegcInterval in Global) getOrElse GCUtil.defaultMinForcegcInterval - if (isForceGc) { - GCUtil.forceGc(state.log) } - - // TODO - Track delegates and avoid regenerating. - val delegates: Seq[Setting[_]] = session.mergeSettings collect { - case x if exclude(x) => delegateToGlobal(x.key) + val spacedVersion = if (spacePresent) version else version & spacedFirst(SwitchCommand) + val verbose = Parser.opt(token(Space ~> "-v")) + val optionalCommand = Parser.opt(token(Space ~> matched(state.combinedParser))) + (spacedVersion ~ verbose ~ optionalCommand).map { + case v ~ verbose ~ command => + Switch(v, verbose.isDefined, command) } - val fixedSession = session.appendRaw(add ++ delegates) - val fixedState = BuiltinCommands.reapply(fixedSession, structure, state) - if (!command.isEmpty) command :: fixedState - else fixedState + } + + token(SwitchCommand ~> OptSpace) flatMap { sp => versionAndCommand(sp.nonEmpty) } } - // Creates a delegate for a scoped key that pulls the setting from the global scope. - private[this] def delegateToGlobal[T](key: ScopedKey[T]): Setting[_] = - SettingKey[T](key.key) in key.scope := (SettingKey[T](key.key) in GlobalScope).value + private case class CrossArgs(command: String, verbose: Boolean) - @deprecated("No longer used.", "0.13.0") - def crossExclude(s: Setting[_]): Boolean = excludeKeys(Set(scalaVersion.key, scalaHome.key))(s) - - private[this] def excludeKeys(keys: Set[AttributeKey[_]]): Setting[_] => Boolean = - _.key match { - case ScopedKey(Scope(_, Global, Global, _), key) if keys.contains(key) => true - case _ => false + private def crossParser(state: State): Parser[CrossArgs] = + token(CrossCommand <~ OptSpace) flatMap { _ => + (token(Parser.opt("-v" <~ Space)) ~ token(matched(state.combinedParser))).map { + case (verbose, command) => CrossArgs(command, verbose.isDefined) + } & spacedFirst(CrossCommand) } - def crossParser(state: State): Parser[String] = - token(CrossCommand <~ OptSpace) flatMap { _ => token(matched(state.combinedParser & spacedFirst(CrossCommand))) } + private def crossRestoreSessionParser(state: State): Parser[String] = token(CrossRestoreSessionCommand) - lazy val crossBuild = Command.arb(requireSession(crossParser), crossHelp) { (state, command) => - val x = Project.extract(state) - import x._ - val versions = crossVersions(state) - val current = scalaVersion in currentRef get structure.data map (SwitchCommand + " " + _) toList; - if (versions.isEmpty) command :: state - else { - versions.map(v => s"$SwitchCommand $v $command") ::: current ::: state - } - } - def crossVersions(state: State): Seq[String] = - { - val x = Project.extract(state) - import x._ - crossScalaVersions in currentRef get structure.data getOrElse Nil - } - - def requireSession[T](p: State => Parser[T]): State => Parser[T] = s => + private def requireSession[T](p: State => Parser[T]): State => Parser[T] = s => if (s get sessionSettings isEmpty) failure("No project loaded") else p(s) + private def resolveAggregates(extracted: Extracted): Seq[ProjectRef] = { + import extracted._ + + def findAggregates(project: ProjectRef): List[ProjectRef] = { + project :: (structure.allProjects(project.build).find(_.id == project.project) match { + case Some(resolved) => resolved.aggregate.toList.flatMap(findAggregates) + case None => Nil + }) + } + + (currentRef :: currentProject.aggregate.toList.flatMap(findAggregates)).distinct + } + + private def crossVersions(extracted: Extracted, proj: ProjectRef): Seq[String] = { + import extracted._ + (crossScalaVersions in proj get structure.data) getOrElse { + // reading scalaVersion is a one-time deal + (scalaVersion in proj get structure.data).toSeq + } + } + + /** + * Parse the given command into either an aggregate command or a command for a project + */ + private def parseCommand(command: String): Either[String, (String, String)] = { + import DefaultParsers._ + val parser = (OpOrID <~ charClass(_ == '/', "/")) ~ any.* map { + case project ~ cmd => (project, cmd.mkString) + } + Parser.parse(command, parser).left.map(_ => command) + } + + def crossBuild: Command = + Command.arb(requireSession(crossParser), crossHelp)(crossBuildCommandImpl) + + private def crossBuildCommandImpl(state: State, args: CrossArgs): State = { + val x = Project.extract(state) + import x._ + + val (aggs, aggCommand) = parseCommand(args.command) match { + case Right((project, cmd)) => + (structure.allProjectRefs.filter(_.project == project), cmd) + case Left(cmd) => (resolveAggregates(x), cmd) + } + + // if we support scalaVersion, projVersions should be cached somewhere since + // running ++2.11.1 is at the root level is going to mess with the scalaVersion for the aggregated subproj + val projVersions = (aggs flatMap { proj => + crossVersions(x, proj) map { (proj.project, _) } + }).toList + + val verbose = if (args.verbose) "-v" else "" + + if (projVersions.isEmpty) { + state + } else { + // Group all the projects by scala version + val allCommands = projVersions.groupBy(_._2).mapValues(_.map(_._1)).toSeq.flatMap { + case (version, Seq(project)) => + // If only one project for a version, issue it directly + Seq(s"$SwitchCommand $verbose $version $project/$aggCommand") + case (version, projects) if aggCommand.contains(" ") => + // If the command contains a space, then the all command won't work because it doesn't support issuing + // commands with spaces, so revert to running the command on each project one at a time + s"$SwitchCommand $verbose $version" :: projects.map(project => s"$project/$aggCommand") + case (version, projects) => + // First switch scala version, then use the all command to run the command on each project concurrently + Seq(s"$SwitchCommand $verbose $version", projects.map(_ + "/" + aggCommand).mkString("all ", " ", "")) + } + + allCommands ::: CrossRestoreSessionCommand :: captureCurrentSession(state, x) + } + } + + def crossRestoreSession: Command = + Command.arb(crossRestoreSessionParser, crossRestoreSessionHelp)(crossRestoreSessionImpl) + + private def crossRestoreSessionImpl(state: State, arg: String): State = { + restoreCapturedSession(state, Project.extract(state)) + } + + private val CapturedSession = AttributeKey[Seq[Setting[_]]]("crossCapturedSession") + + private def captureCurrentSession(state: State, extracted: Extracted): State = { + state.put(CapturedSession, extracted.session.rawAppend) + } + + private def restoreCapturedSession(state: State, extracted: Extracted): State = { + state.get(CapturedSession) match { + case Some(rawAppend) => + val restoredSession = extracted.session.copy(rawAppend = rawAppend) + BuiltinCommands.reapply(restoredSession, extracted.structure, state).remove(CapturedSession) + case None => state + } + } + + def switchVersion: Command = + Command.arb(requireSession(switchParser), switchHelp)(switchCommandImpl) + + private def switchCommandImpl(state: State, args: Switch): State = { + val switchedState = switchScalaVersion(args, state) + + args.command.toSeq ::: switchedState + } + + private def switchScalaVersion(switch: Switch, state: State): State = { + val x = Project.extract(state) + import x._ + + val (version, instance) = switch.version match { + case ScalaHomeVersion(homePath, resolveVersion, _) => + val home = IO.resolve(x.currentProject.base, homePath) + if (home.exists()) { + val instance = ScalaInstance(home)(state.classLoaderCache.apply _) + val version = resolveVersion.getOrElse(instance.actualVersion) + (version, Some((home, instance))) + } else { + sys.error(s"Scala home directory did not exist: $home") + } + case NamedScalaVersion(v, _) => (v, None) + } + + val binaryVersion = CrossVersion.binaryScalaVersion(version) + + def logSwitchInfo(included: Seq[(ProjectRef, Seq[String])], excluded: Seq[(ProjectRef, Seq[String])]) = { + + instance.foreach { + case (home, instance) => + state.log.info(s"Using Scala home $home with actual version ${instance.actualVersion}") + } + if (switch.version.force) { + state.log.info(s"Forcing Scala version to $version on all projects.") + } else { + state.log.info(s"Setting Scala version to $version on ${included.size} projects.") + } + if (excluded.nonEmpty && !switch.verbose) { + state.log.info(s"Excluded ${excluded.size} projects, run ++ $version -v for more details.") + } + + def detailedLog(msg: => String) = if (switch.verbose) state.log.info(msg) else state.log.debug(msg) + + def logProject: (ProjectRef, Seq[String]) => Unit = (proj, scalaVersions) => { + val current = if (proj == currentRef) "*" else " " + detailedLog(s" $current ${proj.project} ${scalaVersions.mkString("(", ", ", ")")}") + } + detailedLog("Switching Scala version on:") + included.foreach(logProject.tupled) + detailedLog("Excluding projects:") + excluded.foreach(logProject.tupled) + } + + val projects: Seq[Reference] = { + val projectScalaVersions = structure.allProjectRefs.map(proj => proj -> crossVersions(x, proj)) + if (switch.version.force) { + logSwitchInfo(projectScalaVersions, Nil) + structure.allProjectRefs ++ structure.units.keys.map(BuildRef.apply) + } else { + + val (included, excluded) = projectScalaVersions.partition { + case (proj, scalaVersions) => scalaVersions.exists(v => CrossVersion.binaryScalaVersion(v) == binaryVersion) + } + logSwitchInfo(included, excluded) + included.map(_._1) + } + } + + setScalaVersionForProjects(version, instance, projects, state, x) + } + + private def setScalaVersionForProjects(version: String, instance: Option[(File, ScalaInstance)], + projects: Seq[Reference], state: State, extracted: Extracted): State = { + import extracted._ + + val newSettings = projects.flatMap { project => + val scope = Scope(Select(project), Global, Global, Global) + + instance match { + case Some((home, inst)) => Seq( + scalaVersion in scope := version, + scalaHome in scope := Some(home), + scalaInstance in scope := inst + ) + case None => Seq( + scalaVersion in scope := version, + scalaHome in scope := None + ) + } + } + + val filterKeys: Set[AttributeKey[_]] = Set(scalaVersion, scalaHome, scalaInstance).map(_.key) + + // Filter out any old scala version settings that were added, this is just for hygiene. + val filteredRawAppend = session.rawAppend.filter(_.key match { + case ScopedKey(Scope(Select(ref), Global, Global, Global), key) if filterKeys.contains(key) && projects.contains(ref) => false + case _ => true + }) + + val newSession = session.copy(rawAppend = filteredRawAppend ++ newSettings) + + BuiltinCommands.reapply(newSession, structure, state) + } + } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index b3e2f2901..59fa0d008 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -92,7 +92,7 @@ object BuiltinCommands { def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, setLogLevel, early, act, nop) def DefaultCommands: Seq[Command] = Seq(ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject, projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion, - setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins, + Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins, ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++ compatCommands def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 18a85f657..0f6ed2a0f 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -293,31 +293,47 @@ defaults Nil val CrossCommand = "+" + val CrossRestoreSessionCommand = "+-" val SwitchCommand = "++" def crossHelp: Help = Help.more(CrossCommand, CrossDetailed) + def crossRestoreSessionHelp = Help.more(CrossRestoreSessionCommand, CrossRestoreSessionDetailed) def switchHelp: Help = Help.more(SwitchCommand, SwitchDetailed) def CrossDetailed = - s"""$CrossCommand + s"""$CrossCommand [-v] Runs for each Scala version specified for cross-building. - For each string in `crossScalaVersions` in the current project, this command sets the - `scalaVersion` of all projects to that version, reloads the build, and - executes . When finished, it reloads the build with the original - Scala version. + For each string in `crossScalaVersions` in each project project, this command sets + the `scalaVersion` of all projects that list that Scala version with that Scala + version reloads the build, and then executes for those projects. When + finished, it resets the build to its original state. + + If -v is supplied, verbose logging of the Scala version switching is done. See also `help $SwitchCommand` """ + def CrossRestoreSessionDetailed = + s"""$CrossRestoreSessionCommand + + Restores a session that was captured by the cross command, +. +""" + def SwitchDetailed = - s"""$SwitchCommand [] + s"""$SwitchCommand [!] [-v] [] Changes the Scala version and runs a command. - Sets the `scalaVersion` of all projects to and reloads the build. + Sets the `scalaVersion` of all projects that define a Scala cross version that is binary + compatible with and reloads the build. If ! is supplied, then the + version is forced on all projects regardless of whether they are binary compatible or + not. + + If -v is supplied, verbose logging of the Scala version switching is done. + If is provided, it is then executed. -$SwitchCommand [=] [] +$SwitchCommand [=][!] [-v] [] Uses the Scala installation at by configuring the scalaHome setting for all projects. @@ -325,6 +341,12 @@ $SwitchCommand [=] [] This is important when using managed dependencies. This version will determine the cross-version used as well as transitive dependencies. + Only projects that are listed to be binary compatible with the selected Scala version + have their Scala version switched. If ! is supplied, then all projects projects have + their Scala version switched. + + If -v is supplied, verbose logging of the Scala version switching is done. + If is provided, it is then executed. See also `help $CrossCommand` diff --git a/notes/1.0.0/sbt-doge.markdown b/notes/1.0.0/sbt-doge.markdown new file mode 100644 index 000000000..060f72699 --- /dev/null +++ b/notes/1.0.0/sbt-doge.markdown @@ -0,0 +1,10 @@ +[@jroper]: https://github.com/jroper +[2613]: https://github.com/sbt/sbt/pull/2613 + +### Fixes with compatibility implications + +### Improvements + +- Replace cross building support with sbt-doge. This allows builds with projects that have multiple different combinations of cross scala versions to be cross built correctly. The behaviour of ++ is changed so that it only updates the Scala version of projects that support that Scala version, but the Scala version can be post fixed with ! to force it to change for all projects. A -v argument has been added that prints verbose information about which projects are having their settings changed along with their cross scala versions. [#2613][2613] by [@jroper][@jroper]. + +### Bug fixes diff --git a/sbt/src/sbt-test/actions/cross-multiproject/build.sbt b/sbt/src/sbt-test/actions/cross-multiproject/build.sbt new file mode 100644 index 000000000..6414c5417 --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-multiproject/build.sbt @@ -0,0 +1,18 @@ + +lazy val rootProj = (project in file(".")). + aggregate(libProj, fooPlugin) + +lazy val libProj = (project in file("lib")). + settings( + name := "foo-lib", + scalaVersion := "2.11.8", + crossScalaVersions := Seq("2.11.8", "2.10.4") + ) + +lazy val fooPlugin =(project in file("sbt-foo")). + settings( + name := "sbt-foo", + sbtPlugin := true, + scalaVersion := "2.10.4", + crossScalaVersions := Seq("2.10.4") + ) diff --git a/sbt/src/sbt-test/actions/cross-multiproject/lib/A.scala b/sbt/src/sbt-test/actions/cross-multiproject/lib/A.scala new file mode 100644 index 000000000..6c542aa30 --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-multiproject/lib/A.scala @@ -0,0 +1,5 @@ +package foo + +object Foo { + +} diff --git a/sbt/src/sbt-test/actions/cross-multiproject/sbt-foo/B.scala b/sbt/src/sbt-test/actions/cross-multiproject/sbt-foo/B.scala new file mode 100644 index 000000000..4fc020221 --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-multiproject/sbt-foo/B.scala @@ -0,0 +1,5 @@ +package foo.sbt + +object SbtFoo { + +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/cross-multiproject/test b/sbt/src/sbt-test/actions/cross-multiproject/test new file mode 100644 index 000000000..6c4f8282c --- /dev/null +++ b/sbt/src/sbt-test/actions/cross-multiproject/test @@ -0,0 +1,46 @@ +> + compile + +$ exists lib/target/scala-2.11 +$ exists lib/target/scala-2.10 +$ exists sbt-foo/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.11 + +> clean +> + libProj/compile + +$ exists lib/target/scala-2.11 +$ exists lib/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.11 + +> clean +> ++ 2.11.1 compile + +$ exists lib/target/scala-2.11 +-$ exists lib/target/scala-2.10 +$ exists sbt-foo/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.11 + +> clean +> ++ 2.10.4 compile + +-$ exists lib/target/scala-2.11 +$ exists lib/target/scala-2.10 +$ exists sbt-foo/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.11 + +> clean +> ++ 2.11.5 compile + +$ exists lib/target/scala-2.11 +-$ exists lib/target/scala-2.10 +$ exists sbt-foo/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.11 + +> clean +> ++ 2.11.5! compile + +$ exists lib/target/scala-2.11 +-$ exists lib/target/scala-2.10 +-$ exists sbt-foo/target/scala-2.10 +$ exists sbt-foo/target/scala-2.11 diff --git a/sbt/src/sbt-test/actions/cross/test b/sbt/src/sbt-test/actions/cross/test index ee74f0f8a..0ea962b97 100644 --- a/sbt/src/sbt-test/actions/cross/test +++ b/sbt/src/sbt-test/actions/cross/test @@ -1,7 +1,7 @@ > check 2.7.7 2.9.1 2.9.0-1 -> ++ 2.8.2 +> ++ 2.8.2! > check 2.8.2 2.8.2 2.8.1 -> ++ 2.10.4 +> ++ 2.10.4! > set resolvers ++= Nil > check 2.10.4 2.10.4 2.10.4 > session clear-all diff --git a/sbt/src/sbt-test/source-dependencies/cross-source/test b/sbt/src/sbt-test/source-dependencies/cross-source/test index a0fa20837..ddea251c9 100644 --- a/sbt/src/sbt-test/source-dependencies/cross-source/test +++ b/sbt/src/sbt-test/source-dependencies/cross-source/test @@ -1,7 +1,7 @@ # A.scala needs B.scala, it won't be in source list -> ++2.11.4 +> ++2.11.4! -> compile # A.scala needs B.scala, it would be in source list -> ++2.10.4 +> ++2.10.4! > compile