diff --git a/main/actions/src/main/scala/sbt/compiler/Eval.scala b/main/actions/src/main/scala/sbt/compiler/Eval.scala index e530850db..1174b34d3 100644 --- a/main/actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main/actions/src/main/scala/sbt/compiler/Eval.scala @@ -91,12 +91,13 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se def extra(run: Run, unit: CompilationUnit) = atPhase(run.typerPhase.next) { (new TypeExtractor).getType(unit.body) } def read(file: File) = IO.read(file) def write(value: String, f: File) = IO.write(f, value) + def extraHash = "" } val i = evalCommon(expression :: Nil, imports, tpeName, ev) val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl)) new EvalResult(i.extra, value, i.generated, i.enclosingModule) } - def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, valTypes: Seq[String]): EvalDefinitions = + def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, file: Option[File], valTypes: Seq[String]): EvalDefinitions = { require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.") val ev = new EvalType[Seq[String]] { @@ -113,6 +114,10 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se } def read(file: File) = IO.readLines(file) def write(value: Seq[String], file: File) = IO.writeLines(file, value) + def extraHash = file match { + case Some(f) => f.getAbsolutePath + case None => "" + } } val i = evalCommon(definitions.map(_._1), imports, Some(""), ev) new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra) @@ -121,8 +126,12 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se private[this] def evalCommon[T](content: Seq[String], imports: EvalImports, tpeName: Option[String], ev: EvalType[T]): EvalIntermediate[T] = { import Eval._ + // TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting + // is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous + // value on the classpath when compiling. val hash = Hash.toHex(Hash(bytes(stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: - seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes(tpeName)(bytes) :: Nil))) + seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes(tpeName)(bytes) :: + bytes(ev.extraHash) :: Nil))) val moduleName = makeModuleName(hash) lazy val unit = { @@ -354,6 +363,9 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se * The Tree doesn't need to be parsed from the contents of `unit`. */ def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree + + /** Extra information to include in the hash'd object name to help avoid collisions. */ + def extraHash: String } val DefaultStartLine = 0 diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index a035fbd50..7fc398259 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -101,7 +101,7 @@ object EvaluateConfigurations { val parsed = parseConfiguration(lines, imports, offset) val (importDefs, definitions) = if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) else { - val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions) + val definitions = evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file)) val imp = BuildUtil.importAllRoot(definitions.enclosingModule :: Nil) val projs = (loader: ClassLoader) => definitions.values(loader).map(p => resolveBase(file.getParentFile, p.asInstanceOf[Project])) (imp, DefinedSbtValues(definitions)) @@ -237,10 +237,10 @@ object EvaluateConfigurations { } private[this] def extractedValTypes: Seq[String] = Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) - private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)]): compiler.EvalDefinitions = + private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)], file: Option[File]): compiler.EvalDefinitions = { val convertedRanges = definitions.map { case (s, r) => (s, r.start to r.end) } - eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, extractedValTypes) + eval.evalDefinitions(convertedRanges, new EvalImports(imports, name), name, file, extractedValTypes) } } object Index { diff --git a/sbt/src/sbt-test/actions/eval-is-safe-and-sound/boink/build.sbt b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/boink/build.sbt new file mode 100644 index 000000000..b7e193384 --- /dev/null +++ b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/boink/build.sbt @@ -0,0 +1,3 @@ +val commonP = LocalProject("common") + +name := "boink" \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/eval-is-safe-and-sound/build.sbt b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/build.sbt new file mode 100644 index 000000000..ab63c3ec3 --- /dev/null +++ b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/build.sbt @@ -0,0 +1,5 @@ +lazy val common = project + +lazy val boink = project + +lazy val woof = project \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/eval-is-safe-and-sound/test b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/test new file mode 100644 index 000000000..73a68203f --- /dev/null +++ b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/test @@ -0,0 +1 @@ +> compile \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/eval-is-safe-and-sound/woof/build.sbt b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/woof/build.sbt new file mode 100644 index 000000000..0a15a38fe --- /dev/null +++ b/sbt/src/sbt-test/actions/eval-is-safe-and-sound/woof/build.sbt @@ -0,0 +1,3 @@ +val commonP = LocalProject("common") + +name := "woof" \ No newline at end of file