mirror of https://github.com/sbt/sbt.git
Track generated .class files and clean leftovers after project load.
Fixes #1524 * Track generated .class files from Eval * While loading, join all classfiles throughout Load, lots of bookkeeping. * When a given build URI is done loading, we can look at its project/target/config-classes directory and clean out any extra items that are lingering from previous build definitions. * Add TODOs to handle the same thing in global directories. Right now, given the shared nature of these projects, it's a bit too dangerous to do so.
This commit is contained in:
parent
0bfb5a4118
commit
a1b3117a42
|
|
@ -153,8 +153,8 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
finally { unlinkAll() }
|
||||
}
|
||||
|
||||
val classFiles = getClassFiles(backing, moduleName)
|
||||
new EvalIntermediate(extra, loader, classFiles, moduleName)
|
||||
val generatedFiles = getGeneratedFiles(backing, moduleName)
|
||||
new EvalIntermediate(extra, loader, generatedFiles, moduleName)
|
||||
}
|
||||
// location of the cached type or definition information
|
||||
private[this] def cacheFile(base: File, moduleName: String): File = new File(base, moduleName + ".cache")
|
||||
|
|
@ -254,11 +254,20 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
|
||||
private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists
|
||||
// TODO: use the code from Analyzer
|
||||
private[this] def getGeneratedFiles(backing: Option[File], moduleName: String): Seq[File] =
|
||||
backing match {
|
||||
case None => Nil
|
||||
case Some(dir) => dir listFiles moduleFileFilter(moduleName)
|
||||
}
|
||||
private[this] def getClassFiles(backing: Option[File], moduleName: String): Seq[File] =
|
||||
backing match {
|
||||
case None => Nil
|
||||
case Some(dir) => dir listFiles moduleClassFilter(moduleName)
|
||||
}
|
||||
private[this] def moduleFileFilter(moduleName: String) = new java.io.FilenameFilter {
|
||||
def accept(dir: File, s: String) =
|
||||
(s contains moduleName)
|
||||
}
|
||||
private[this] def moduleClassFilter(moduleName: String) = new java.io.FilenameFilter {
|
||||
def accept(dir: File, s: String) =
|
||||
(s contains moduleName) && (s endsWith ".class")
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ val p = {
|
|||
|
||||
property("val test") = secure {
|
||||
val defs = (ValTestContent, 1 to 7) :: Nil
|
||||
val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", "scala.Int" :: Nil)
|
||||
val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", None, "scala.Int" :: Nil)
|
||||
label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ import scala.annotation.tailrec
|
|||
*
|
||||
*/
|
||||
object EvaluateConfigurations {
|
||||
|
||||
type LazyClassLoaded[T] = ClassLoader => T
|
||||
|
||||
private[sbt] case class TrackedEvalResult[T](generated: Seq[File], result: LazyClassLoaded[T])
|
||||
|
||||
/**
|
||||
* This represents the parsed expressions in a build sbt, as well as where they were defined.
|
||||
*/
|
||||
|
|
@ -38,7 +43,7 @@ object EvaluateConfigurations {
|
|||
* raw sbt-types that can be accessed and used.
|
||||
*/
|
||||
@deprecated("We no longer merge build.sbt files together unless they are in the same directory.", "0.13.6")
|
||||
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): ClassLoader => LoadedSbtFile =
|
||||
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): LazyClassLoaded[LoadedSbtFile] =
|
||||
{
|
||||
val loadFiles = srcs.sortBy(_.getName) map { src => evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) }
|
||||
loader => (LoadedSbtFile.empty /: loadFiles) { (loaded, load) => loaded merge load(loader) }
|
||||
|
|
@ -49,7 +54,7 @@ object EvaluateConfigurations {
|
|||
*
|
||||
* Note: This ignores any non-Setting[_] values in the file.
|
||||
*/
|
||||
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
|
||||
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): LazyClassLoaded[Seq[Setting[_]]] =
|
||||
evaluateConfiguration(eval, src, IO.readLines(src), imports, 0)
|
||||
|
||||
/**
|
||||
|
|
@ -76,7 +81,7 @@ object EvaluateConfigurations {
|
|||
*
|
||||
* @return Just the Setting[_] instances defined in the .sbt file.
|
||||
*/
|
||||
def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] =
|
||||
def evaluateConfiguration(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[Seq[Setting[_]]] =
|
||||
{
|
||||
val l = evaluateSbtFile(eval, file, lines, imports, offset)
|
||||
loader => l(loader).settings
|
||||
|
|
@ -93,7 +98,7 @@ object EvaluateConfigurations {
|
|||
* @return A function which can take an sbt classloader and return the raw types/configuratoin
|
||||
* which was compiled/parsed for the given file.
|
||||
*/
|
||||
private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile =
|
||||
private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): LazyClassLoaded[LoadedSbtFile] =
|
||||
{
|
||||
// TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do
|
||||
// detection for which project project manipulations should be applied.
|
||||
|
|
@ -112,13 +117,15 @@ object EvaluateConfigurations {
|
|||
evaluateDslEntry(eval, name, allImports, dslExpression, range)
|
||||
}
|
||||
eval.unlinkDeferred()
|
||||
// Tracks all the files we generated from evaluating the sbt file.
|
||||
val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated))
|
||||
loader => {
|
||||
val projects =
|
||||
definitions.values(loader).collect {
|
||||
case p: Project => resolveBase(file.getParentFile, p)
|
||||
}
|
||||
val (settingsRaw, manipulationsRaw) =
|
||||
dslEntries map (_ apply loader) partition {
|
||||
dslEntries map (_.result apply loader) partition {
|
||||
case internals.ProjectSettings(_) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -130,7 +137,7 @@ object EvaluateConfigurations {
|
|||
case internals.ProjectManipulation(f) => f
|
||||
}
|
||||
// TODO -get project manipulations.
|
||||
new LoadedSbtFile(settings, projects, importDefs, manipulations, definitions)
|
||||
new LoadedSbtFile(settings, projects, importDefs, manipulations, definitions, allGeneratedFiles)
|
||||
}
|
||||
}
|
||||
/** move a project to be relative to this file after we've evaluated it. */
|
||||
|
|
@ -161,18 +168,22 @@ object EvaluateConfigurations {
|
|||
* @param range The original position in source of the expression, for error messages.
|
||||
*
|
||||
* @return A method that given an sbt classloader, can return the actual [[DslEntry]] defined by
|
||||
* the expression.
|
||||
* the expression, and the sequence of .class files generated.
|
||||
*/
|
||||
private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): ClassLoader => internals.DslEntry = {
|
||||
private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): TrackedEvalResult[internals.DslEntry] = {
|
||||
// TODO - Should we try to namespace these between.sbt files? IF they hash to the same value, they may actually be
|
||||
// exactly the same setting, so perhaps we don't care?
|
||||
val result = try {
|
||||
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some(SettingsDefinitionName), line = range.start)
|
||||
} catch {
|
||||
case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage)
|
||||
}
|
||||
loader => {
|
||||
val pos = RangePosition(name, range shift 1)
|
||||
result.getValue(loader).asInstanceOf[internals.DslEntry].withPos(pos)
|
||||
}
|
||||
// TODO - keep track of configuration classes defined.
|
||||
TrackedEvalResult(result.generated,
|
||||
loader => {
|
||||
val pos = RangePosition(name, range shift 1)
|
||||
result.getValue(loader).asInstanceOf[internals.DslEntry].withPos(pos)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -189,9 +200,9 @@ object EvaluateConfigurations {
|
|||
* the expression.
|
||||
*/
|
||||
@deprecated("Build DSL now includes non-Setting[_] type settings.", "0.13.6") // Note: This method is used by the SET command, so we may want to evaluate that sucker a bit.
|
||||
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] =
|
||||
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): LazyClassLoaded[Seq[Setting[_]]] =
|
||||
{
|
||||
evaluateDslEntry(eval, name, imports, expression, range) andThen {
|
||||
evaluateDslEntry(eval, name, imports, expression, range).result andThen {
|
||||
case internals.ProjectSettings(values) => values
|
||||
case _ => Nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,13 @@ object Load {
|
|||
{
|
||||
val eval = mkEval(data(config.globalPluginClasspath), base, defaultEvalOptions)
|
||||
val imports = BuildUtil.baseImports ++ BuildUtil.importAllRoot(config.globalPluginNames)
|
||||
loader => EvaluateConfigurations(eval, files, imports)(loader).settings
|
||||
loader => {
|
||||
val loaded = EvaluateConfigurations(eval, files, imports)(loader)
|
||||
// TODO - We have a potential leak of config-classes in the global directory right now.
|
||||
// We need to find a way to clean these safely, or at least warn users about
|
||||
// unused class files that could be cleaned when multiple sbt instances are not running.
|
||||
loaded.settings
|
||||
}
|
||||
}
|
||||
def loadGlobal(state: State, base: File, global: File, config: sbt.LoadBuildConfiguration): sbt.LoadBuildConfiguration =
|
||||
if (base != global && global.exists) {
|
||||
|
|
@ -238,8 +244,34 @@ object Load {
|
|||
def mkEval(classpath: Seq[File], base: File, options: Seq[String]): Eval =
|
||||
new Eval(options, classpath, s => new ConsoleReporter(s), Some(evalOutputDirectory(base)))
|
||||
|
||||
/**
|
||||
* This will clean up left-over files in the config-classes directory if they are no longer used.
|
||||
*
|
||||
* @param base The base directory for the build, should match the one passed into `mkEval` method.
|
||||
*/
|
||||
def cleanEvalClasses(base: File, keep: Seq[File]): Unit = {
|
||||
val baseTarget = evalOutputDirectory(base)
|
||||
val keepSet = keep.map(_.getCanonicalPath).toSet
|
||||
// If there are no keeper files, this may be because cache was up-to-date and
|
||||
// the files aren't properly returned, even though they should be.
|
||||
// TODO - figure out where the caching of whether or not to generate classfiles occurs, and
|
||||
// put cleanups there, perhaps.
|
||||
if (!keepSet.isEmpty) {
|
||||
def keepFile(f: File) = keepSet(f.getCanonicalPath)
|
||||
import Path._
|
||||
val existing = (baseTarget.***.get).filterNot(_.isDirectory)
|
||||
val toDelete = existing.filterNot(keepFile)
|
||||
if (!toDelete.isEmpty) {
|
||||
System.err.println(s"Discovered unused files: ${toDelete.mkString("\n * ", "\n * ", "\n")}. Note: Please reload all other running sbt instances in $base")
|
||||
IO.delete(toDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated("This method is no longer used", "0.13.6")
|
||||
def configurations(srcs: Seq[File], eval: () => Eval, imports: Seq[String]): ClassLoader => LoadedSbtFile =
|
||||
if (srcs.isEmpty) const(LoadedSbtFile.empty) else EvaluateConfigurations(eval(), srcs, imports)
|
||||
if (srcs.isEmpty) const(LoadedSbtFile.empty)
|
||||
else EvaluateConfigurations(eval(), srcs, imports)
|
||||
|
||||
def load(file: File, s: State, config: sbt.LoadBuildConfiguration): sbt.PartBuild =
|
||||
load(file, builtinLoader(s, config.copy(pluginManagement = config.pluginManagement.shift, extraBuilds = Nil)), config.extraBuilds.toList)
|
||||
|
|
@ -400,6 +432,7 @@ object Load {
|
|||
val plugs = plugins(defDir, s, config.copy(pluginManagement = config.pluginManagement.forPlugin))
|
||||
val defsScala = plugs.detected.builds.values
|
||||
|
||||
// NOTE - because we create an eval here, we need a clean-eval later for this URI.
|
||||
lazy val eval = mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions)
|
||||
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase))
|
||||
|
||||
|
|
@ -407,25 +440,29 @@ object Load {
|
|||
|
||||
val memoSettings = new mutable.HashMap[File, LoadedSbtFile]
|
||||
def loadProjects(ps: Seq[Project], createRoot: Boolean) = {
|
||||
loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context)
|
||||
loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context, Nil)
|
||||
}
|
||||
|
||||
val loadedProjectsRaw = loadProjects(initialProjects, !hasRootAlreadyDefined)
|
||||
// TODO - As of sbt 0.13.6 we should always have a default root project from
|
||||
// here on, so the autogenerated build aggregated can be removed from this code. ( I think)
|
||||
// We may actually want to move it back here and have different flags in loadTransitive...
|
||||
val hasRoot = loadedProjectsRaw.exists(_.base == normBase) || defsScala.exists(_.rootProject.isDefined)
|
||||
val (loadedProjects, defaultBuildIfNone) =
|
||||
val hasRoot = loadedProjectsRaw.projects.exists(_.base == normBase) || defsScala.exists(_.rootProject.isDefined)
|
||||
val (loadedProjects, defaultBuildIfNone, keepClassFiles) =
|
||||
if (hasRoot)
|
||||
(loadedProjectsRaw, Build.defaultEmpty)
|
||||
(loadedProjectsRaw.projects, Build.defaultEmpty, loadedProjectsRaw.generatedConfigClassFiles)
|
||||
else {
|
||||
val existingIDs = loadedProjectsRaw.map(_.id)
|
||||
val existingIDs = loadedProjectsRaw.projects.map(_.id)
|
||||
val refs = existingIDs.map(id => ProjectRef(uri, id))
|
||||
val defaultID = autoID(normBase, config.pluginManagement.context, existingIDs)
|
||||
val b = Build.defaultAggregated(defaultID, refs)
|
||||
val defaultProjects = loadProjects(projectsFromBuild(b, normBase), false)
|
||||
(defaultProjects ++ loadedProjectsRaw, b)
|
||||
(defaultProjects.projects ++ loadedProjectsRaw.projects, b, defaultProjects.generatedConfigClassFiles ++ loadedProjectsRaw.generatedConfigClassFiles)
|
||||
}
|
||||
// Now we clean stale class files.
|
||||
// TODO - this may cause issues with multiple sbt clients, but that should be deprecated pending sbt-server anyway
|
||||
cleanEvalClasses(defDir, keepClassFiles)
|
||||
|
||||
val defs = if (defsScala.isEmpty) defaultBuildIfNone :: Nil else defsScala
|
||||
// HERE we pull out the defined vals from memoSettings and unify them all so
|
||||
// we can use them later.
|
||||
|
|
@ -458,6 +495,8 @@ object Load {
|
|||
private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] =
|
||||
b.projectDefinitions(base).map(resolveBase(base))
|
||||
|
||||
// Lame hackery to keep track of our state.
|
||||
private[this] case class LoadedProjects(projects: Seq[Project], generatedConfigClassFiles: Seq[File])
|
||||
/**
|
||||
* Loads a new set of projects, including any transitively defined projects underneath this one.
|
||||
*
|
||||
|
|
@ -498,10 +537,12 @@ object Load {
|
|||
log: Logger,
|
||||
makeOrDiscoverRoot: Boolean,
|
||||
buildUri: URI,
|
||||
context: PluginManagement.Context): Seq[Project] =
|
||||
context: PluginManagement.Context,
|
||||
generatedConfigClassFiles: Seq[File]): LoadedProjects =
|
||||
{
|
||||
// load all relevant configuration files (.sbt, as .scala already exists at this point)
|
||||
def discover(auto: AddSettings, base: File): DiscoveredProjects = discoverProjects(auto, base, plugins, eval, memoSettings)
|
||||
def discover(auto: AddSettings, base: File): DiscoveredProjects =
|
||||
discoverProjects(auto, base, plugins, eval, memoSettings)
|
||||
// Step two, Finalize a project with all its settings/configuration.
|
||||
def finalizeProject(p: Project, configFiles: Seq[File]): Project = {
|
||||
val loadedFiles = configFiles flatMap { f => memoSettings.get(f) }
|
||||
|
|
@ -509,48 +550,49 @@ object Load {
|
|||
}
|
||||
// Discover any new project definition for the base directory of this project, and load all settings.
|
||||
// Also return any newly discovered project instances.
|
||||
def discoverAndLoad(p: Project): (Project, Seq[Project]) = {
|
||||
val (root, discovered, files) = discover(p.auto, p.base) match {
|
||||
case DiscoveredProjects(Some(root), rest, files) =>
|
||||
def discoverAndLoad(p: Project): (Project, Seq[Project], Seq[File]) = {
|
||||
val (root, discovered, files, generated) = discover(p.auto, p.base) match {
|
||||
case DiscoveredProjects(Some(root), rest, files, generated) =>
|
||||
// TODO - We assume here the project defined in a build.sbt WINS because the original was
|
||||
// a phony. However, we may want to 'merge' the two, or only do this if the original was a default
|
||||
// generated project.
|
||||
(root, rest, files)
|
||||
case DiscoveredProjects(None, rest, files) => (p, rest, files)
|
||||
(root, rest, files, generated)
|
||||
case DiscoveredProjects(None, rest, files, generated) => (p, rest, files, generated)
|
||||
}
|
||||
val finalRoot = finalizeProject(root, files)
|
||||
finalRoot -> discovered
|
||||
(finalRoot, discovered, generated)
|
||||
}
|
||||
// Load all config files AND finalize the project at the root directory, if it exists.
|
||||
// Continue loading if we find any more.
|
||||
newProjects match {
|
||||
case Seq(next, rest @ _*) =>
|
||||
log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}")
|
||||
val (finished, discovered) = discoverAndLoad(next)
|
||||
loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context)
|
||||
val (finished, discovered, generated) = discoverAndLoad(next)
|
||||
loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context, generated)
|
||||
case Nil if makeOrDiscoverRoot =>
|
||||
log.debug(s"[Loading] Scanning directory ${buildBase}")
|
||||
discover(AddSettings.defaultSbtFiles, buildBase) match {
|
||||
case DiscoveredProjects(Some(root), discovered, files) =>
|
||||
case DiscoveredProjects(Some(root), discovered, files, generated) =>
|
||||
log.debug(s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}")
|
||||
val finalRoot = finalizeProject(root, files)
|
||||
loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context)
|
||||
loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated)
|
||||
// Here we need to create a root project...
|
||||
case DiscoveredProjects(None, discovered, files) =>
|
||||
case DiscoveredProjects(None, discovered, files, generated) =>
|
||||
log.debug(s"[Loading] Found non-root projects ${discovered.map(_.id).mkString(",")}")
|
||||
// Here we do something interesting... We need to create an aggregate root project
|
||||
val otherProjects = loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc, memoSettings, log, false, buildUri, context)
|
||||
val existingIds = otherProjects map (_.id)
|
||||
val otherProjects = loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc, memoSettings, log, false, buildUri, context, Nil)
|
||||
val otherGenerated = otherProjects.generatedConfigClassFiles
|
||||
val existingIds = otherProjects.projects map (_.id)
|
||||
val refs = existingIds map (id => ProjectRef(buildUri, id))
|
||||
val defaultID = autoID(buildBase, context, existingIds)
|
||||
val root = finalizeProject(Build.defaultAggregatedProject(defaultID, buildBase, refs), files)
|
||||
val result = root +: (acc ++ otherProjects)
|
||||
val result = root +: (acc ++ otherProjects.projects)
|
||||
log.debug(s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}")
|
||||
result
|
||||
LoadedProjects(result, generated ++ otherGenerated)
|
||||
}
|
||||
case Nil =>
|
||||
log.debug(s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}")
|
||||
acc
|
||||
LoadedProjects(acc, generatedConfigClassFiles)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -564,8 +606,13 @@ object Load {
|
|||
* @param root The project at "root" directory we were looking, or non if non was defined.
|
||||
* @param nonRoot Any sub-projects discovered from this directory
|
||||
* @param sbtFiles Any sbt file loaded during this discovery (used later to complete the project).
|
||||
* @param generatedFile Any .class file that was generated when compiling/discovering these projects.
|
||||
*/
|
||||
private[this] case class DiscoveredProjects(root: Option[Project], nonRoot: Seq[Project], sbtFiles: Seq[File])
|
||||
private[this] case class DiscoveredProjects(
|
||||
root: Option[Project],
|
||||
nonRoot: Seq[Project],
|
||||
sbtFiles: Seq[File],
|
||||
generatedFiles: Seq[File])
|
||||
|
||||
/**
|
||||
* This method attempts to resolve/apply all configuration loaded for a project. It is responsible for the following:
|
||||
|
|
@ -682,10 +729,11 @@ object Load {
|
|||
case _ => Seq.empty
|
||||
}
|
||||
val rawFiles = associatedFiles(auto)
|
||||
val rawProjects = loadFiles(rawFiles).projects
|
||||
val loadedFiles = loadFiles(rawFiles)
|
||||
val rawProjects = loadedFiles.projects
|
||||
val (root, nonRoot) = rawProjects.partition(_.base == projectBase)
|
||||
// TODO - good error message if more than one root project
|
||||
DiscoveredProjects(root.headOption, nonRoot, rawFiles)
|
||||
DiscoveredProjects(root.headOption, nonRoot, rawFiles, loadedFiles.generatedFiles)
|
||||
}
|
||||
|
||||
def globalPluginClasspath(globalPlugin: Option[GlobalPlugin]): Seq[Attributed[File]] =
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@ private[sbt] final class LoadedSbtFile(
|
|||
val manipulations: Seq[Project => Project],
|
||||
// TODO - we may want to expose a simpler interface on top of here for the set command,
|
||||
// rather than what we have now...
|
||||
val definitions: DefinedSbtValues) {
|
||||
@deprecated("LoadedSbtFiles are no longer directly merged.", "0.13.6")
|
||||
val definitions: DefinedSbtValues,
|
||||
val generatedFiles: Seq[File]) {
|
||||
// We still use merge for now. We track originating sbt file in an alternative manner.
|
||||
def merge(o: LoadedSbtFile): LoadedSbtFile =
|
||||
new LoadedSbtFile(
|
||||
settings ++ o.settings,
|
||||
projects ++ o.projects,
|
||||
importedDefs ++ o.importedDefs,
|
||||
manipulations,
|
||||
definitions zip o.definitions)
|
||||
definitions zip o.definitions,
|
||||
generatedFiles ++ o.generatedFiles)
|
||||
|
||||
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions)
|
||||
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions, generatedFiles)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,6 +76,6 @@ private[sbt] object DefinedSbtValues {
|
|||
|
||||
private[sbt] object LoadedSbtFile {
|
||||
/** Represents an empty .sbt file: no Projects, imports, or settings.*/
|
||||
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil, DefinedSbtValues.empty)
|
||||
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil, DefinedSbtValues.empty, Nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
[1489]: https://github.com/sbt/sbt/pull/1489
|
||||
[1494]: https://github.com/sbt/sbt/pull/1494
|
||||
[1516]: https://github.com/sbt/sbt/pull/1516
|
||||
[1465]: https://github.com/sbt/sbt/issues/1465
|
||||
[1524]: https://github.com/sbt/sbt/issues/1524
|
||||
|
||||
[@dansanduleac]: https://github.com/dansanduleac
|
||||
[@2m]: https://github.com/2m
|
||||
|
|
@ -104,6 +106,8 @@
|
|||
- set no longer removes any `++` scala version setting. [#856][856]/[#1489][1489] by [@jsuereth][@jsuereth]
|
||||
- Fixes `Scope.parseScopedKey`. [#1384][1384] by [@eed3si9n][@eed3si9n]
|
||||
- Fixes `build.sbt` errors causing `ArrayIndexOutOfBoundsException` due to invalid source in position. [#1181][1181] by [@eed3si9n][@eed3si9n]
|
||||
- Fixes config-classes leak in loading build files. [#1524][1524] by [@jsuereth][@jsuereth]
|
||||
- Fixes name-conflicts in hashed settings class files. [#1465][1465] by [@jsuereth][@jsuereth]
|
||||
|
||||
|
||||
### Maven Central Repository defaults to HTTPS
|
||||
|
|
|
|||
|
|
@ -2,4 +2,48 @@ lazy val common = project
|
|||
|
||||
lazy val boink = project
|
||||
|
||||
lazy val woof = project
|
||||
lazy val woof = project
|
||||
|
||||
|
||||
lazy val numConfigClasses = taskKey[Int]("counts number of config classes")
|
||||
|
||||
lazy val configClassCountFile = settingKey[File]("File where we write the # of config classes")
|
||||
|
||||
lazy val saveNumConfigClasses = taskKey[Unit]("Saves the number of config clases")
|
||||
|
||||
lazy val checkNumConfigClasses = taskKey[Unit]("Checks the number of config clases")
|
||||
|
||||
lazy val checkDifferentConfigClasses = taskKey[Unit]("Checks that the number of config classes are different.")
|
||||
|
||||
configClassCountFile := (target.value / "config-count")
|
||||
|
||||
numConfigClasses := {
|
||||
val cdir = (baseDirectory in ThisBuild).value / "project/target/config-classes"
|
||||
(cdir.*** --- cdir).get.length
|
||||
}
|
||||
|
||||
saveNumConfigClasses := {
|
||||
IO.write(configClassCountFile.value, numConfigClasses.value.toString)
|
||||
}
|
||||
|
||||
def previousConfigCount = Def.task {
|
||||
val previousString = IO.read(configClassCountFile.value)
|
||||
try Integer.parseInt(previousString)
|
||||
catch {
|
||||
case t: Throwable => throw new RuntimeException(s"Failed to parse previous config file value: $previousString", t)
|
||||
}
|
||||
}
|
||||
|
||||
checkDifferentConfigClasses := {
|
||||
val previousString = IO.read(configClassCountFile.value)
|
||||
val previous = previousConfigCount.value
|
||||
val current = numConfigClasses.value
|
||||
assert(previous != current, s"Failed to create new configuration classes. Expected: $previous, Found: $current")
|
||||
}
|
||||
|
||||
checkNumConfigClasses := {
|
||||
val previousString = IO.read(configClassCountFile.value)
|
||||
val previous = previousConfigCount.value
|
||||
val current = numConfigClasses.value
|
||||
assert(previous == current, s"Failed to delete extra configuration classes. Expected: $previous, Found: $current")
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
organization := "more"
|
||||
|
||||
version := "settings"
|
||||
|
|
@ -1 +1,8 @@
|
|||
> compile
|
||||
> compile
|
||||
> saveNumConfigClasses
|
||||
$ copy-file changes/extras.sbt extras.sbt
|
||||
> reload
|
||||
> checkDifferentConfigClasses
|
||||
$ delete extras.sbt
|
||||
> reload
|
||||
> checkNumConfigClasses
|
||||
Loading…
Reference in New Issue