mirror of https://github.com/sbt/sbt.git
Simplify file management settings
I decided that there were too many settings related to the file management that did similar things and had similar names but did slightly different things. To improve this, I introduce the ChangedFiles class to sbt.nio.file and switch to having just two task for file input and output retrieval: all(Input|Output)Files and changed(Input|Output)Files. If, for example, changedInputFiles returns None that means that either the task has not yet been run or there were no changes. If there have been any changes, then it will return Some(changes) and the user can extract the relevant changes that they are interested in. The code may be slightly more verbose in a few places, but I think it's worth it for the conceptual clarity.
This commit is contained in:
parent
3319423369
commit
507346f3f6
|
|
@ -721,12 +721,14 @@ lazy val sbtIgnoredProblems = {
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.AnyPath"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.AnyPath"),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$**_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$**_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$*_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$*_="),
|
||||||
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$ChangedFiles_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$AnyPath_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$AnyPath_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$Glob_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$Glob_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RecursiveGlob_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RecursiveGlob_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RelativeGlob_="),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RelativeGlob_="),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.*"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.*"),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.**"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.**"),
|
||||||
|
exclude[ReversedMissingMethodProblem]("sbt.Import.ChangedFiles"),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.RecursiveGlob"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.RecursiveGlob"),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.Glob"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.Glob"),
|
||||||
exclude[ReversedMissingMethodProblem]("sbt.Import.RelativeGlob"),
|
exclude[ReversedMissingMethodProblem]("sbt.Import.RelativeGlob"),
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,7 @@ object Defaults extends BuildCommon {
|
||||||
}
|
}
|
||||||
unmanagedResourceDirectories.value.map(_ ** filter)
|
unmanagedResourceDirectories.value.map(_ ** filter)
|
||||||
},
|
},
|
||||||
unmanagedResources := (unmanagedResources / allInputPaths).value.map(_.toFile),
|
unmanagedResources := (unmanagedResources / allInputFiles).value.map(_.toFile),
|
||||||
resourceGenerators :== Nil,
|
resourceGenerators :== Nil,
|
||||||
resourceGenerators += Def.task {
|
resourceGenerators += Def.task {
|
||||||
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
|
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import sbt.internal.DynamicInput
|
||||||
import sbt.internal.nio.FileTreeRepository
|
import sbt.internal.nio.FileTreeRepository
|
||||||
import sbt.internal.util.AttributeKey
|
import sbt.internal.util.AttributeKey
|
||||||
import sbt.internal.util.complete.Parser
|
import sbt.internal.util.complete.Parser
|
||||||
import sbt.nio.file.{ FileAttributes, FileTreeView, Glob }
|
import sbt.nio.file.{ ChangedFiles, FileAttributes, FileTreeView, Glob }
|
||||||
import sbt.{ Def, InputKey, State, StateTransform }
|
import sbt.{ Def, InputKey, State, StateTransform }
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
@ -24,44 +24,27 @@ import scala.concurrent.duration.FiniteDuration
|
||||||
object Keys {
|
object Keys {
|
||||||
val allInputFiles =
|
val allInputFiles =
|
||||||
taskKey[Seq[Path]]("All of the file inputs for a task excluding directories and hidden files.")
|
taskKey[Seq[Path]]("All of the file inputs for a task excluding directories and hidden files.")
|
||||||
val allInputPaths = taskKey[Seq[Path]](
|
val changedInputFiles = taskKey[Option[ChangedFiles]]("The changed files for a task")
|
||||||
"All of the file inputs for a task with no filters applied. Regular files and directories are included. Excludes hidden files"
|
|
||||||
)
|
|
||||||
val changedInputFiles =
|
|
||||||
taskKey[Seq[Path]](
|
|
||||||
"All of the file inputs for a task that have changed since the last run. Includes new and modified files but excludes deleted files."
|
|
||||||
)
|
|
||||||
val modifiedInputFiles =
|
|
||||||
taskKey[Seq[Path]](
|
|
||||||
"All of the file inputs for a task that have changed since the last run. Excludes new files. Files are considered modified based on either the last modified time or the file stamp for the file."
|
|
||||||
)
|
|
||||||
val removedInputFiles =
|
|
||||||
taskKey[Seq[Path]]("All of the file inputs for a task that have changed since the last run.")
|
|
||||||
val fileInputs = settingKey[Seq[Glob]](
|
val fileInputs = settingKey[Seq[Glob]](
|
||||||
"The file globs that are used by a task. This setting will generally be scoped per task. It will also be used to determine the sources to watch during continuous execution."
|
"The file globs that are used by a task. This setting will generally be scoped per task. It will also be used to determine the sources to watch during continuous execution."
|
||||||
)
|
)
|
||||||
val fileOutputs = settingKey[Seq[Glob]]("Describes the output files of a task.")
|
|
||||||
val allOutputPaths =
|
|
||||||
taskKey[Seq[Path]]("All of the file output for a task with no filters applied.")
|
|
||||||
val changedOutputPaths =
|
|
||||||
taskKey[Seq[Path]]("All of the task file outputs that have changed since the last run.")
|
|
||||||
val modifiedOutputPaths =
|
|
||||||
taskKey[Seq[Path]](
|
|
||||||
"All of the task file outputs that have been modified since the last run. Excludes new files."
|
|
||||||
)
|
|
||||||
val removedOutputPaths =
|
|
||||||
taskKey[Seq[Path]](
|
|
||||||
"All of the output paths that have been removed since the last run."
|
|
||||||
)
|
|
||||||
|
|
||||||
val inputFileStamper = settingKey[FileStamper](
|
val inputFileStamper = settingKey[FileStamper](
|
||||||
"Toggles the file stamping implementation used to determine whether or not a file has been modified."
|
"Toggles the file stamping implementation used to determine whether or not a file has been modified."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val fileOutputs = settingKey[Seq[Glob]]("Describes the output files of a task.")
|
||||||
|
val allOutputFiles =
|
||||||
|
taskKey[Seq[Path]]("All of the file output for a task excluding directories and hidden files.")
|
||||||
|
val changedOutputFiles =
|
||||||
|
taskKey[Option[ChangedFiles]]("The files that have changed since the last task run.")
|
||||||
val outputFileStamper = settingKey[FileStamper](
|
val outputFileStamper = settingKey[FileStamper](
|
||||||
"Toggles the file stamping implementation used to determine whether or not a file has been modified."
|
"Toggles the file stamping implementation used to determine whether or not a file has been modified."
|
||||||
)
|
)
|
||||||
|
|
||||||
val fileTreeView =
|
val fileTreeView =
|
||||||
taskKey[FileTreeView.Nio[FileAttributes]]("A view of the local file system tree")
|
taskKey[FileTreeView.Nio[FileAttributes]]("A view of the local file system tree")
|
||||||
|
|
||||||
|
// watch related settings
|
||||||
val watchAntiEntropyRetentionPeriod = settingKey[FiniteDuration](
|
val watchAntiEntropyRetentionPeriod = settingKey[FiniteDuration](
|
||||||
"Wall clock Duration for which a FileEventMonitor will store anti-entropy events. This prevents spurious triggers when a task takes a long time to run. Higher values will consume more memory but make spurious triggers less likely."
|
"Wall clock Duration for which a FileEventMonitor will store anti-entropy events. This prevents spurious triggers when a task takes a long time to run. Higher values will consume more memory but make spurious triggers less likely."
|
||||||
).withRank(BMinusSetting)
|
).withRank(BMinusSetting)
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ import sbt.internal.{ Clean, Continuous, DynamicInput, SettingsGraph }
|
||||||
import sbt.nio.FileStamp.{ fileStampJsonFormatter, pathJsonFormatter, _ }
|
import sbt.nio.FileStamp.{ fileStampJsonFormatter, pathJsonFormatter, _ }
|
||||||
import sbt.nio.FileStamper.{ Hash, LastModified }
|
import sbt.nio.FileStamper.{ Hash, LastModified }
|
||||||
import sbt.nio.Keys._
|
import sbt.nio.Keys._
|
||||||
|
import sbt.nio.file.ChangedFiles
|
||||||
import sbt.std.TaskExtra._
|
import sbt.std.TaskExtra._
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
import scala.collection.immutable.VectorBuilder
|
||||||
|
|
||||||
private[sbt] object Settings {
|
private[sbt] object Settings {
|
||||||
private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
|
private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
|
||||||
|
|
@ -142,15 +144,8 @@ private[sbt] object Settings {
|
||||||
(transitiveClasspathDependency in scopedKey.scope := { () }) :: Nil
|
(transitiveClasspathDependency in scopedKey.scope := { () }) :: Nil
|
||||||
case allInputFiles.key => allFilesImpl(scopedKey) :: Nil
|
case allInputFiles.key => allFilesImpl(scopedKey) :: Nil
|
||||||
case changedInputFiles.key => changedInputFilesImpl(scopedKey)
|
case changedInputFiles.key => changedInputFilesImpl(scopedKey)
|
||||||
case changedOutputPaths.key =>
|
case changedOutputFiles.key =>
|
||||||
changedFilesImpl(scopedKey, changedOutputPaths, outputFileStamps)
|
changedFilesImpl(scopedKey, changedOutputFiles, outputFileStamps)
|
||||||
case modifiedInputFiles.key => modifiedInputFilesImpl(scopedKey)
|
|
||||||
case modifiedOutputPaths.key =>
|
|
||||||
modifiedFilesImpl(scopedKey, modifiedOutputPaths, outputFileStamps)
|
|
||||||
case removedInputFiles.key =>
|
|
||||||
removedFilesImpl(scopedKey, removedInputFiles, allInputPaths) :: Nil
|
|
||||||
case removedOutputPaths.key =>
|
|
||||||
removedFilesImpl(scopedKey, removedOutputPaths, allOutputPaths) :: Nil
|
|
||||||
case pathToFileStamp.key => stamper(scopedKey) :: Nil
|
case pathToFileStamp.key => stamper(scopedKey) :: Nil
|
||||||
case _ => Nil
|
case _ => Nil
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +206,7 @@ private[sbt] object Settings {
|
||||||
}
|
}
|
||||||
dynamicInputs.foreach(_ ++= inputs.map(g => DynamicInput(g, stamper, forceTrigger)))
|
dynamicInputs.foreach(_ ++= inputs.map(g => DynamicInput(g, stamper, forceTrigger)))
|
||||||
view.list(inputs)
|
view.list(inputs)
|
||||||
}) :: fileStamps(scopedKey) :: allPathsImpl(scopedKey) :: Nil
|
}) :: fileStamps(scopedKey) :: allFilesImpl(scopedKey) :: Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] val taskClass = classOf[Task[_]]
|
private[this] val taskClass = classOf[Task[_]]
|
||||||
|
|
@ -219,19 +214,6 @@ private[sbt] object Settings {
|
||||||
private[this] val fileClass = classOf[java.io.File]
|
private[this] val fileClass = classOf[java.io.File]
|
||||||
private[this] val pathClass = classOf[java.nio.file.Path]
|
private[this] val pathClass = classOf[java.nio.file.Path]
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all of the paths described by a glob with no additional filtering.
|
|
||||||
* No additional filtering is performed.
|
|
||||||
*
|
|
||||||
* @param scopedKey the key whose file inputs we are seeking
|
|
||||||
* @return a task definition that retrieves the input files and their attributes scoped to a
|
|
||||||
* particular task.
|
|
||||||
*/
|
|
||||||
private[this] def allPathsImpl(scopedKey: Def.ScopedKey[_]): Def.Setting[_] =
|
|
||||||
addTaskDefinition(Keys.allInputPaths in scopedKey.scope := {
|
|
||||||
(Keys.allInputPathsAndAttributes in scopedKey.scope).value.map(_._1)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all of the paths for the regular files described by a glob. Directories and hidden
|
* Returns all of the paths for the regular files described by a glob. Directories and hidden
|
||||||
* files are excluded.
|
* files are excluded.
|
||||||
|
|
@ -265,14 +247,39 @@ private[sbt] object Settings {
|
||||||
}) :: Nil
|
}) :: Nil
|
||||||
private[this] def changedFilesImpl(
|
private[this] def changedFilesImpl(
|
||||||
scopedKey: Def.ScopedKey[_],
|
scopedKey: Def.ScopedKey[_],
|
||||||
changeKey: TaskKey[Seq[Path]],
|
changeKey: TaskKey[Option[ChangedFiles]],
|
||||||
stampKey: TaskKey[Seq[(Path, FileStamp)]]
|
stampKey: TaskKey[Seq[(Path, FileStamp)]]
|
||||||
): Def.Setting[_] =
|
): Def.Setting[_] =
|
||||||
addTaskDefinition(changeKey in scopedKey.scope := {
|
addTaskDefinition(changeKey in scopedKey.scope := {
|
||||||
val current = (stampKey in scopedKey.scope).value
|
val current = (stampKey in scopedKey.scope).value
|
||||||
(stampKey in scopedKey.scope).previous match {
|
(stampKey in scopedKey.scope).previous match {
|
||||||
case Some(previous) => (current diff previous).map(_._1)
|
case Some(previous) =>
|
||||||
case None => current.map(_._1)
|
val createdBuilder = new VectorBuilder[Path]
|
||||||
|
val deletedBuilder = new VectorBuilder[Path]
|
||||||
|
val updatedBuilder = new VectorBuilder[Path]
|
||||||
|
val currentMap = current.toMap
|
||||||
|
val prevMap = previous.toMap
|
||||||
|
current.foreach {
|
||||||
|
case (path, currentStamp) =>
|
||||||
|
prevMap.get(path) match {
|
||||||
|
case Some(oldStamp) => if (oldStamp != currentStamp) updatedBuilder += path
|
||||||
|
case None => createdBuilder += path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous.foreach {
|
||||||
|
case (path, _) =>
|
||||||
|
if (currentMap.get(path).isEmpty) deletedBuilder += path
|
||||||
|
}
|
||||||
|
val created = createdBuilder.result()
|
||||||
|
val deleted = deletedBuilder.result()
|
||||||
|
val updated = updatedBuilder.result()
|
||||||
|
if (created.isEmpty && deleted.isEmpty && updated.isEmpty) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
val cf = ChangedFiles(created = created, deleted = deleted, updated = updated)
|
||||||
|
Some(cf)
|
||||||
|
}
|
||||||
|
case None => None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -326,7 +333,7 @@ private[sbt] object Settings {
|
||||||
Vector(allOutputPathsImpl(scope), outputFileStampsImpl(scope)) ++ cleanImpl(taskKey)
|
Vector(allOutputPathsImpl(scope), outputFileStampsImpl(scope)) ++ cleanImpl(taskKey)
|
||||||
}
|
}
|
||||||
private[this] def allOutputPathsImpl(scope: Scope): Def.Setting[_] =
|
private[this] def allOutputPathsImpl(scope: Scope): Def.Setting[_] =
|
||||||
addTaskDefinition(allOutputPaths in scope := {
|
addTaskDefinition(allOutputFiles in scope := {
|
||||||
val fileOutputGlobs = (fileOutputs in scope).value
|
val fileOutputGlobs = (fileOutputs in scope).value
|
||||||
val allFileOutputs = fileTreeView.value.list(fileOutputGlobs).map(_._1)
|
val allFileOutputs = fileTreeView.value.list(fileOutputGlobs).map(_._1)
|
||||||
val dynamicOutputs = (dynamicFileOutputs in scope).value
|
val dynamicOutputs = (dynamicFileOutputs in scope).value
|
||||||
|
|
@ -338,62 +345,7 @@ private[sbt] object Settings {
|
||||||
case LastModified => FileStamp.lastModified
|
case LastModified => FileStamp.lastModified
|
||||||
case Hash => FileStamp.hash
|
case Hash => FileStamp.hash
|
||||||
}
|
}
|
||||||
(allOutputPaths in scope).value.map(p => p -> stamper(p))
|
(allOutputFiles in scope).value.map(p => p -> stamper(p))
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all of the regular files whose stamp has changed since the last time the
|
|
||||||
* task was evaluated. The result includes modified files but neither new nor deleted
|
|
||||||
* files nor files whose stamp has not changed since the previous run. Directories and
|
|
||||||
* hidden files are excluded.
|
|
||||||
*
|
|
||||||
* @param scopedKey the key whose modified files we are seeking
|
|
||||||
* @return a task definition that retrieves the changed input files scoped to the key.
|
|
||||||
*/
|
|
||||||
private[this] def modifiedInputFilesImpl(scopedKey: Def.ScopedKey[_]): Seq[Def.Setting[_]] =
|
|
||||||
modifiedFilesImpl(scopedKey, modifiedInputFiles, inputFileStamps) ::
|
|
||||||
(watchForceTriggerOnAnyChange in scopedKey.scope := {
|
|
||||||
(watchForceTriggerOnAnyChange in scopedKey.scope).?.value match {
|
|
||||||
case Some(t) => t
|
|
||||||
case None => false
|
|
||||||
}
|
|
||||||
}) :: Nil
|
|
||||||
|
|
||||||
private[this] def modifiedFilesImpl(
|
|
||||||
scopedKey: Def.ScopedKey[_],
|
|
||||||
modifiedKey: TaskKey[Seq[Path]],
|
|
||||||
stampKey: TaskKey[Seq[(Path, FileStamp)]]
|
|
||||||
): Def.Setting[_] =
|
|
||||||
addTaskDefinition(modifiedKey in scopedKey.scope := {
|
|
||||||
val current = (stampKey in scopedKey.scope).value
|
|
||||||
(stampKey in scopedKey.scope).previous match {
|
|
||||||
case Some(previous) =>
|
|
||||||
val previousPathSet = previous.view.map(_._1).toSet
|
|
||||||
(current diff previous).collect { case (p, _) if previousPathSet(p) => p }
|
|
||||||
case None => current.map(_._1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all of the files that have been removed since the previous run.
|
|
||||||
* task was evaluated. The result includes modified files but neither new nor deleted
|
|
||||||
* files nor files whose stamp has not changed since the previous run. Directories and
|
|
||||||
* hidden files are excluded
|
|
||||||
*
|
|
||||||
* @param scopedKey the key whose removed files we are seeking
|
|
||||||
* @return a task definition that retrieves the changed input files scoped to the key.
|
|
||||||
*/
|
|
||||||
private[this] def removedFilesImpl(
|
|
||||||
scopedKey: Def.ScopedKey[_],
|
|
||||||
removeKey: TaskKey[Seq[Path]],
|
|
||||||
allKey: TaskKey[Seq[Path]]
|
|
||||||
): Def.Setting[_] =
|
|
||||||
addTaskDefinition(removeKey in scopedKey.scope := {
|
|
||||||
val current = (allKey in scopedKey.scope).value
|
|
||||||
(allKey in scopedKey.scope).previous match {
|
|
||||||
case Some(previous) => previous diff current
|
|
||||||
case None => Nil
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ trait Import {
|
||||||
val ** = sbt.nio.file.**
|
val ** = sbt.nio.file.**
|
||||||
val * = sbt.nio.file.*
|
val * = sbt.nio.file.*
|
||||||
val AnyPath = sbt.nio.file.AnyPath
|
val AnyPath = sbt.nio.file.AnyPath
|
||||||
|
type ChangedFiles = sbt.nio.file.ChangedFiles
|
||||||
|
val ChangedFiles = sbt.nio.file.ChangedFiles
|
||||||
type Glob = sbt.nio.file.Glob
|
type Glob = sbt.nio.file.Glob
|
||||||
val Glob = sbt.nio.file.Glob
|
val Glob = sbt.nio.file.Glob
|
||||||
type RelativeGlob = sbt.nio.file.RelativeGlob
|
type RelativeGlob = sbt.nio.file.RelativeGlob
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
import sjsonnew.BasicJsonProtocol._
|
import sjsonnew.BasicJsonProtocol._
|
||||||
|
|
||||||
val copyFile = taskKey[Int]("dummy task")
|
val copyFile = taskKey[Int]("dummy task")
|
||||||
copyFile / fileInputs += baseDirectory.value.toGlob / "base" / "*.txt"
|
copyFile / fileInputs += baseDirectory.value.toGlob / "base" / "*.txt"
|
||||||
copyFile / fileOutputs += baseDirectory.value.toGlob / "out" / "*.txt"
|
copyFile / fileOutputs += baseDirectory.value.toGlob / "out" / "*.txt"
|
||||||
|
copyFile / target := baseDirectory.value / "out"
|
||||||
|
|
||||||
copyFile := Def.task {
|
copyFile := Def.task {
|
||||||
val prev = copyFile.previous
|
val prev = copyFile.previous
|
||||||
|
val changes: Option[Seq[Path]] = (copyFile / changedInputFiles).value.map {
|
||||||
|
case ChangedFiles(c, _, u) => c ++ u
|
||||||
|
}
|
||||||
prev match {
|
prev match {
|
||||||
case Some(v: Int) if (copyFile / changedInputFiles).value.isEmpty => v
|
case Some(v: Int) if changes.isEmpty => v
|
||||||
case _ =>
|
case _ =>
|
||||||
(copyFile / changedInputFiles).value.foreach { p =>
|
changes.getOrElse((copyFile / allInputFiles).value).foreach { p =>
|
||||||
val outdir = baseDirectory.value / "out"
|
val outDir = baseDirectory.value / "out"
|
||||||
IO.createDirectory(baseDirectory.value / "out")
|
IO.createDirectory(outDir)
|
||||||
IO.copyFile(p.toFile, outdir / p.getFileName.toString)
|
IO.copyFile(p.toFile, outDir / p.getFileName.toString)
|
||||||
}
|
}
|
||||||
prev.map(_ + 1).getOrElse(1)
|
prev.map(_ + 1).getOrElse(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,9 @@ $ copy-file changes/Foo.txt base/Foo.txt
|
||||||
> checkOutDirectoryIsEmpty
|
> checkOutDirectoryIsEmpty
|
||||||
|
|
||||||
> checkCount 0
|
> checkCount 0
|
||||||
|
|
||||||
|
> copyFile / allOutputFiles
|
||||||
|
|
||||||
|
> checkCount 1
|
||||||
|
|
||||||
|
> checkOutDirectoryHasFile
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ val fileInputTask = taskKey[Unit]("task with file inputs")
|
||||||
fileInputTask / fileInputs += Glob(baseDirectory.value / "base", "*.md")
|
fileInputTask / fileInputs += Glob(baseDirectory.value / "base", "*.md")
|
||||||
|
|
||||||
fileInputTask := Def.taskDyn {
|
fileInputTask := Def.taskDyn {
|
||||||
if ((fileInputTask / changedInputFiles).value.nonEmpty) Def.task(assert(true))
|
if ((fileInputTask / changedInputFiles).value.fold(false)(_.updated.nonEmpty))
|
||||||
|
Def.task(assert(true))
|
||||||
else Def.task(assert(false))
|
else Def.task(assert(false))
|
||||||
}.value
|
}.value
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
> fileInputTask
|
|
||||||
|
|
||||||
-> fileInputTask
|
-> fileInputTask
|
||||||
|
|
||||||
$ copy-file changes/Bar.md base/Bar.md
|
$ copy-file changes/Bar.md base/Bar.md
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,19 @@ foo / fileInputs := Seq(
|
||||||
|
|
||||||
val checkModified = taskKey[Unit]("check that modified files are returned")
|
val checkModified = taskKey[Unit]("check that modified files are returned")
|
||||||
checkModified := Def.taskDyn {
|
checkModified := Def.taskDyn {
|
||||||
val changed = (foo / changedInputFiles).value
|
val modified = (foo / changedInputFiles).value.map(_.updated).getOrElse(Nil)
|
||||||
val modified = (foo / modifiedInputFiles).value
|
val allFiles = (foo / allInputFiles).value
|
||||||
if (modified.sameElements(changed)) Def.task(assert(true))
|
if (modified.isEmpty) Def.task(assert(true))
|
||||||
else Def.task {
|
else Def.task {
|
||||||
assert(modified != changed)
|
assert(modified != allFiles)
|
||||||
assert(modified == Seq((baseDirectory.value / "base" / "Bar.md").toPath))
|
assert(modified == Seq((baseDirectory.value / "base" / "Bar.md").toPath))
|
||||||
}
|
}
|
||||||
}.value
|
}.value
|
||||||
|
|
||||||
val checkRemoved = taskKey[Unit]("check that modified files are returned")
|
val checkRemoved = taskKey[Unit]("check that removed files are returned")
|
||||||
checkRemoved := Def.taskDyn {
|
checkRemoved := Def.taskDyn {
|
||||||
val files = (foo / allInputFiles).value
|
val files = (foo / allInputFiles).value
|
||||||
val removed = (foo / removedInputFiles).value
|
val removed = (foo / changedInputFiles).value.map(_.deleted).getOrElse(Nil)
|
||||||
if (removed.isEmpty) Def.task(assert(true))
|
if (removed.isEmpty) Def.task(assert(true))
|
||||||
else Def.task {
|
else Def.task {
|
||||||
assert(files == Seq((baseDirectory.value / "base" / "Foo.txt").toPath))
|
assert(files == Seq((baseDirectory.value / "base" / "Foo.txt").toPath))
|
||||||
|
|
@ -31,7 +31,7 @@ checkRemoved := Def.taskDyn {
|
||||||
val checkAdded = taskKey[Unit]("check that modified files are returned")
|
val checkAdded = taskKey[Unit]("check that modified files are returned")
|
||||||
checkAdded := Def.taskDyn {
|
checkAdded := Def.taskDyn {
|
||||||
val files = (foo / allInputFiles).value
|
val files = (foo / allInputFiles).value
|
||||||
val added = (foo / modifiedInputFiles).value
|
val added = (foo / changedInputFiles).value.map(_.created).getOrElse(Nil)
|
||||||
if (added.isEmpty || files.sameElements(added)) Def.task(assert(true))
|
if (added.isEmpty || files.sameElements(added)) Def.task(assert(true))
|
||||||
else Def.task {
|
else Def.task {
|
||||||
val base = baseDirectory.value / "base"
|
val base = baseDirectory.value / "base"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ val foo = taskKey[Seq[File]]("Retrieve Foo.txt")
|
||||||
|
|
||||||
foo / fileInputs += baseDirectory.value ** "*.txt"
|
foo / fileInputs += baseDirectory.value ** "*.txt"
|
||||||
|
|
||||||
foo := (foo / allInputPaths).value.map(_.toFile)
|
foo := (foo / allInputFiles).value.map(_.toFile)
|
||||||
|
|
||||||
val checkFoo = taskKey[Unit]("Check that the Foo.txt file is retrieved")
|
val checkFoo = taskKey[Unit]("Check that the Foo.txt file is retrieved")
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ val bar = taskKey[Seq[File]]("Retrieve Bar.md")
|
||||||
|
|
||||||
bar / fileInputs += baseDirectory.value / "base/subdir/nested-subdir" * "*.md"
|
bar / fileInputs += baseDirectory.value / "base/subdir/nested-subdir" * "*.md"
|
||||||
|
|
||||||
bar := (bar / allInputPaths).value.map(_.toFile)
|
bar := (bar / allInputFiles).value.map(_.toFile)
|
||||||
|
|
||||||
val checkBar = taskKey[Unit]("Check that the Bar.md file is retrieved")
|
val checkBar = taskKey[Unit]("Check that the Bar.md file is retrieved")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ fileInputTask / fileInputs += (baseDirectory.value / "base").toGlob / "*.md"
|
||||||
fileInputTask / inputFileStamper := sbt.nio.FileStamper.LastModified
|
fileInputTask / inputFileStamper := sbt.nio.FileStamper.LastModified
|
||||||
|
|
||||||
fileInputTask := Def.taskDyn {
|
fileInputTask := Def.taskDyn {
|
||||||
if ((fileInputTask / changedInputFiles).value.nonEmpty) Def.task(assert(true))
|
(fileInputTask / changedInputFiles).value match {
|
||||||
else Def.task(assert(false))
|
case Some(ChangedFiles(_, _, u)) if u.nonEmpty => Def.task(assert(true))
|
||||||
|
case None => Def.task(assert(false))
|
||||||
|
}
|
||||||
}.value
|
}.value
|
||||||
|
|
||||||
val setLastModified = taskKey[Unit]("Reset the last modified time")
|
val setLastModified = taskKey[Unit]("Reset the last modified time")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
> fileInputTask
|
|
||||||
|
|
||||||
-> fileInputTask
|
-> fileInputTask
|
||||||
|
|
||||||
$ touch base/Bar.md
|
$ touch base/Bar.md
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,13 @@ compileLib / fileInputs := {
|
||||||
val base: Glob = (compileLib / sourceDirectory).value.toGlob
|
val base: Glob = (compileLib / sourceDirectory).value.toGlob
|
||||||
base / ** / "*.c" :: base / "include" / "*.h" :: Nil
|
base / ** / "*.c" :: base / "include" / "*.h" :: Nil
|
||||||
}
|
}
|
||||||
compileLib / target := baseDirectory.value / "out" / "lib"
|
compileLib / target := baseDirectory.value / "out" / "objects"
|
||||||
compileLib := {
|
compileLib := {
|
||||||
val inputs: Seq[Path] = (compileLib / changedInputFiles).value
|
val allFiles: Seq[Path] = (compileLib / allInputFiles).value
|
||||||
|
val changedFiles: Option[Seq[Path]] = (compileLib / changedInputFiles).value match {
|
||||||
|
case Some(ChangedFiles(c, _, u)) => Some(c ++ u)
|
||||||
|
case None => None
|
||||||
|
}
|
||||||
val include = (compileLib / sourceDirectory).value / "include"
|
val include = (compileLib / sourceDirectory).value / "include"
|
||||||
val objectDir: Path = (compileLib / target).value.toPath / "objects"
|
val objectDir: Path = (compileLib / target).value.toPath / "objects"
|
||||||
val logger = streams.value.log
|
val logger = streams.value.log
|
||||||
|
|
@ -18,16 +22,15 @@ compileLib := {
|
||||||
name.substring(0, name.lastIndexOf('.')) + ".o"
|
name.substring(0, name.lastIndexOf('.')) + ".o"
|
||||||
}
|
}
|
||||||
compileLib.previous match {
|
compileLib.previous match {
|
||||||
case Some(outputs: Seq[Path]) if inputs.isEmpty =>
|
case Some(outputs: Seq[Path]) if changedFiles.isEmpty =>
|
||||||
logger.info("Not compiling libfoo: no inputs have changed.")
|
logger.info("Not compiling libfoo: no inputs have changed.")
|
||||||
outputs
|
outputs
|
||||||
case _ =>
|
case _ =>
|
||||||
Files.createDirectories(objectDir)
|
Files.createDirectories(objectDir)
|
||||||
def extensionFilter(ext: String): Path => Boolean = _.getFileName.toString.endsWith(s".$ext")
|
def extensionFilter(ext: String): Path => Boolean = _.getFileName.toString.endsWith(s".$ext")
|
||||||
val allInputs = (compileLib / allInputFiles).value
|
|
||||||
val cFiles: Seq[Path] =
|
val cFiles: Seq[Path] =
|
||||||
if (inputs.exists(extensionFilter("h"))) allInputs.filter(extensionFilter("c"))
|
if (changedFiles.fold(false)(_.exists(extensionFilter("h")))) allFiles.filter(extensionFilter("c"))
|
||||||
else inputs.filter(extensionFilter("c"))
|
else changedFiles.getOrElse(allFiles).filter(extensionFilter("c"))
|
||||||
cFiles.map { file =>
|
cFiles.map { file =>
|
||||||
val outFile = objectDir.resolve(objectFileName(file))
|
val outFile = objectDir.resolve(objectFileName(file))
|
||||||
logger.info(s"Compiling $file to $outFile")
|
logger.info(s"Compiling $file to $outFile")
|
||||||
|
|
@ -38,13 +41,14 @@ compileLib := {
|
||||||
}
|
}
|
||||||
|
|
||||||
val linkLib = taskKey[Path]("")
|
val linkLib = taskKey[Path]("")
|
||||||
|
linkLib / target := baseDirectory.value / "out" / "lib"
|
||||||
linkLib := {
|
linkLib := {
|
||||||
val objects = (compileLib / changedOutputPaths).value
|
val changedObjects = (compileLib / changedOutputFiles).value
|
||||||
val outPath = (compileLib / target).value.toPath
|
val outPath = (linkLib / target).value.toPath
|
||||||
val allObjects = (compileLib / allOutputPaths).value.map(_.toString)
|
val allObjects = (compileLib / allOutputFiles).value.map(_.toString)
|
||||||
val logger = streams.value.log
|
val logger = streams.value.log
|
||||||
linkLib.previous match {
|
linkLib.previous match {
|
||||||
case Some(p: Path) if objects.isEmpty =>
|
case Some(p: Path) if changedObjects.isEmpty =>
|
||||||
logger.info("Not running linker: no outputs have changed.")
|
logger.info("Not running linker: no outputs have changed.")
|
||||||
p
|
p
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -53,9 +57,10 @@ linkLib := {
|
||||||
(Seq("-dynamiclib", "-o", path.toString), path)
|
(Seq("-dynamiclib", "-o", path.toString), path)
|
||||||
} else {
|
} else {
|
||||||
val path = outPath.resolve("libfoo.so")
|
val path = outPath.resolve("libfoo.so")
|
||||||
(Seq("-shared", "-o", path.toString), path)
|
(Seq("-shared", "-fPIC", "-o", path.toString), path)
|
||||||
}
|
}
|
||||||
logger.info(s"Linking $libraryPath")
|
logger.info(s"Linking $libraryPath")
|
||||||
|
Files.createDirectories(outPath)
|
||||||
("gcc" +: (linkOptions ++ allObjects)).!!
|
("gcc" +: (linkOptions ++ allObjects)).!!
|
||||||
libraryPath
|
libraryPath
|
||||||
}
|
}
|
||||||
|
|
@ -67,13 +72,14 @@ compileMain / fileInputs := (compileMain / sourceDirectory).value.toGlob / "main
|
||||||
compileMain / target := baseDirectory.value / "out" / "main"
|
compileMain / target := baseDirectory.value / "out" / "main"
|
||||||
compileMain := {
|
compileMain := {
|
||||||
val library = linkLib.value
|
val library = linkLib.value
|
||||||
val changed = (compileMain / changedInputFiles).value ++ (linkLib / changedOutputPaths).value
|
val changed: Boolean = (compileMain / changedInputFiles).value.nonEmpty ||
|
||||||
|
(linkLib / changedOutputFiles).value.nonEmpty
|
||||||
val include = (compileLib / sourceDirectory).value / "include"
|
val include = (compileLib / sourceDirectory).value / "include"
|
||||||
val logger = streams.value.log
|
val logger = streams.value.log
|
||||||
val outDir = (compileMain / target).value.toPath
|
val outDir = (compileMain / target).value.toPath
|
||||||
val outPath = outDir.resolve("main.out")
|
val outPath = outDir.resolve("main.out")
|
||||||
compileMain.previous match {
|
compileMain.previous match {
|
||||||
case Some(p: Path) if changed.isEmpty =>
|
case Some(p: Path) if changed =>
|
||||||
logger.info(s"Not building $outPath: no dependencies have changed")
|
logger.info(s"Not building $outPath: no dependencies have changed")
|
||||||
p
|
p
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -100,17 +106,27 @@ compileMain := {
|
||||||
val executeMain = inputKey[Unit]("run the main method")
|
val executeMain = inputKey[Unit]("run the main method")
|
||||||
executeMain := {
|
executeMain := {
|
||||||
val args = Def.spaceDelimited("<arguments>").parsed
|
val args = Def.spaceDelimited("<arguments>").parsed
|
||||||
val binary = (compileMain / allOutputPaths).value
|
val binary: Seq[Path] = (compileMain / allOutputFiles).value
|
||||||
val logger = streams.value.log
|
val logger = streams.value.log
|
||||||
binary match {
|
binary match {
|
||||||
case Seq(b) =>
|
case Seq(b) =>
|
||||||
val argString =
|
val argString =
|
||||||
if (args.nonEmpty) s" with arguments: ${args.mkString("'", "', '", "'")}" else ""
|
if (args.nonEmpty) s" with arguments: ${args.mkString("'", "', '", "'")}" else ""
|
||||||
logger.info(s"Running $b$argString")
|
logger.info(s"Running $b$argString")
|
||||||
logger.info((b.toString +: args).!!)
|
logger.info(RunBinary(b, args, linkLib.value).mkString("\n"))
|
||||||
|
|
||||||
case b =>
|
case b =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"compileMain generated multiple binaries: ${b.mkString(", ")}"
|
s"compileMain generated multiple binaries: ${b.mkString(", ")}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val checkOutput = inputKey[Unit]("check the output value")
|
||||||
|
checkOutput := {
|
||||||
|
val args @ Seq(arg, res) = Def.spaceDelimited("").parsed
|
||||||
|
val binary: Path = (compileMain / allOutputFiles).value.head
|
||||||
|
val output = RunBinary(binary, args, linkLib.value)
|
||||||
|
assert(output.contains(s"f($arg) = $res"))
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#include "lib.h"
|
||||||
|
|
||||||
|
#define __STR(x) #x
|
||||||
|
#define STR(x) __STR(x)
|
||||||
|
|
||||||
|
#define BODY(x, op) x op x
|
||||||
|
#define OP *
|
||||||
|
#define ARG x
|
||||||
|
|
||||||
|
const int func(const int x) {
|
||||||
|
return BODY(ARG, OP);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* func_str() {
|
||||||
|
return BODY(STR(ARG), " "STR(OP)" ");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object RunBinary {
|
||||||
|
def apply(binary: Path, args: Seq[String], libraryPath: Path): Seq[String] = {
|
||||||
|
val builder = new java.lang.ProcessBuilder(binary.toString +: args :_*)
|
||||||
|
if (scala.util.Properties.isLinux) {
|
||||||
|
builder.environment.put("LD_LIBRARY_PATH", libraryPath.getParent.toString)
|
||||||
|
}
|
||||||
|
val process = builder.start()
|
||||||
|
process.waitFor(5, TimeUnit.SECONDS)
|
||||||
|
scala.io.Source.fromInputStream(process.getInputStream).getLines.toVector ++
|
||||||
|
scala.io.Source.fromInputStream(process.getErrorStream).getLines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
> executeMain 1
|
> executeMain 1
|
||||||
|
|
||||||
#> executeMain 1
|
> checkDirectoryContents out/main main.out
|
||||||
|
|
||||||
#> compileLib / clean
|
> compileMain / clean
|
||||||
|
|
||||||
#> linkLib / clean
|
> checkDirectoryContents out/main empty
|
||||||
|
|
||||||
#> executeMain 1 2 3
|
> checkDirectoryContents out/lib libfoo*
|
||||||
|
|
||||||
#> compileLib / clean
|
> linkLib / clean
|
||||||
|
|
||||||
#> executeMain 2 3 4
|
> checkDirectoryContents out/lib empty
|
||||||
|
|
||||||
#> compileMain / clean
|
> executeMain 1
|
||||||
|
|
||||||
#> executeMain 4 5 6
|
> checkDirectoryContents out/main main.out
|
||||||
|
|
||||||
#> clean
|
> checkDirectoryContents out/lib libfoo*
|
||||||
|
|
||||||
#> executeMain 4
|
> checkOutput 2 8
|
||||||
|
|
||||||
#> compileLib / clean
|
$ copy-file changes/lib.c src/lib/lib.c
|
||||||
|
|
||||||
#> executeMain 3
|
> checkOutput 2 4
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
val checkDirectoryContents = inputKey[Unit]("Validates that a directory has the expected files")
|
||||||
|
checkDirectoryContents := {
|
||||||
|
val arguments = Def.spaceDelimited("").parsed
|
||||||
|
val directory = (baseDirectory.value / arguments.head).toPath
|
||||||
|
val view = fileTreeView.value
|
||||||
|
val expected = arguments.tail
|
||||||
|
expected match {
|
||||||
|
case s if s.isEmpty => assert(view.list(directory.toGlob / **).isEmpty)
|
||||||
|
case Seq("empty") => assert(view.list(directory.toGlob / **).isEmpty)
|
||||||
|
case globStrings =>
|
||||||
|
val globs = globStrings.map(Glob.apply)
|
||||||
|
val actual: Seq[Path] = view.list(directory.toGlob / **).map {
|
||||||
|
case (p, _) => directory.relativize(p)
|
||||||
|
}
|
||||||
|
assert(actual.forall(f => globs.exists(_.matches(f))))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue