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.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_$Glob_="),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RecursiveGlob_="),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$RelativeGlob_="),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.*"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.**"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.ChangedFiles"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.RecursiveGlob"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.Glob"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.Import.RelativeGlob"),
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ object Defaults extends BuildCommon {
|
|||
}
|
||||
unmanagedResourceDirectories.value.map(_ ** filter)
|
||||
},
|
||||
unmanagedResources := (unmanagedResources / allInputPaths).value.map(_.toFile),
|
||||
unmanagedResources := (unmanagedResources / allInputFiles).value.map(_.toFile),
|
||||
resourceGenerators :== Nil,
|
||||
resourceGenerators += Def.task {
|
||||
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import sbt.internal.DynamicInput
|
|||
import sbt.internal.nio.FileTreeRepository
|
||||
import sbt.internal.util.AttributeKey
|
||||
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 scala.concurrent.duration.FiniteDuration
|
||||
|
|
@ -24,44 +24,27 @@ import scala.concurrent.duration.FiniteDuration
|
|||
object Keys {
|
||||
val allInputFiles =
|
||||
taskKey[Seq[Path]]("All of the file inputs for a task excluding directories and hidden files.")
|
||||
val allInputPaths = taskKey[Seq[Path]](
|
||||
"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 changedInputFiles = taskKey[Option[ChangedFiles]]("The changed files for a task")
|
||||
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."
|
||||
)
|
||||
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](
|
||||
"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](
|
||||
"Toggles the file stamping implementation used to determine whether or not a file has been modified."
|
||||
)
|
||||
|
||||
val fileTreeView =
|
||||
taskKey[FileTreeView.Nio[FileAttributes]]("A view of the local file system tree")
|
||||
|
||||
// watch related settings
|
||||
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."
|
||||
).withRank(BMinusSetting)
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ import sbt.internal.{ Clean, Continuous, DynamicInput, SettingsGraph }
|
|||
import sbt.nio.FileStamp.{ fileStampJsonFormatter, pathJsonFormatter, _ }
|
||||
import sbt.nio.FileStamper.{ Hash, LastModified }
|
||||
import sbt.nio.Keys._
|
||||
import sbt.nio.file.ChangedFiles
|
||||
import sbt.std.TaskExtra._
|
||||
import sjsonnew.JsonFormat
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
import scala.collection.immutable.VectorBuilder
|
||||
|
||||
private[sbt] object Settings {
|
||||
private[sbt] def inject(transformed: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
|
||||
|
|
@ -142,15 +144,8 @@ private[sbt] object Settings {
|
|||
(transitiveClasspathDependency in scopedKey.scope := { () }) :: Nil
|
||||
case allInputFiles.key => allFilesImpl(scopedKey) :: Nil
|
||||
case changedInputFiles.key => changedInputFilesImpl(scopedKey)
|
||||
case changedOutputPaths.key =>
|
||||
changedFilesImpl(scopedKey, changedOutputPaths, 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 changedOutputFiles.key =>
|
||||
changedFilesImpl(scopedKey, changedOutputFiles, outputFileStamps)
|
||||
case pathToFileStamp.key => stamper(scopedKey) :: Nil
|
||||
case _ => Nil
|
||||
}
|
||||
|
|
@ -211,7 +206,7 @@ private[sbt] object Settings {
|
|||
}
|
||||
dynamicInputs.foreach(_ ++= inputs.map(g => DynamicInput(g, stamper, forceTrigger)))
|
||||
view.list(inputs)
|
||||
}) :: fileStamps(scopedKey) :: allPathsImpl(scopedKey) :: Nil
|
||||
}) :: fileStamps(scopedKey) :: allFilesImpl(scopedKey) :: Nil
|
||||
}
|
||||
|
||||
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 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
|
||||
* files are excluded.
|
||||
|
|
@ -265,14 +247,39 @@ private[sbt] object Settings {
|
|||
}) :: Nil
|
||||
private[this] def changedFilesImpl(
|
||||
scopedKey: Def.ScopedKey[_],
|
||||
changeKey: TaskKey[Seq[Path]],
|
||||
changeKey: TaskKey[Option[ChangedFiles]],
|
||||
stampKey: TaskKey[Seq[(Path, FileStamp)]]
|
||||
): Def.Setting[_] =
|
||||
addTaskDefinition(changeKey in scopedKey.scope := {
|
||||
val current = (stampKey in scopedKey.scope).value
|
||||
(stampKey in scopedKey.scope).previous match {
|
||||
case Some(previous) => (current diff previous).map(_._1)
|
||||
case None => current.map(_._1)
|
||||
case Some(previous) =>
|
||||
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)
|
||||
}
|
||||
private[this] def allOutputPathsImpl(scope: Scope): Def.Setting[_] =
|
||||
addTaskDefinition(allOutputPaths in scope := {
|
||||
addTaskDefinition(allOutputFiles in scope := {
|
||||
val fileOutputGlobs = (fileOutputs in scope).value
|
||||
val allFileOutputs = fileTreeView.value.list(fileOutputGlobs).map(_._1)
|
||||
val dynamicOutputs = (dynamicFileOutputs in scope).value
|
||||
|
|
@ -338,62 +345,7 @@ private[sbt] object Settings {
|
|||
case LastModified => FileStamp.lastModified
|
||||
case Hash => FileStamp.hash
|
||||
}
|
||||
(allOutputPaths 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
|
||||
}
|
||||
(allOutputFiles in scope).value.map(p => p -> stamper(p))
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ trait Import {
|
|||
val ** = sbt.nio.file.**
|
||||
val * = sbt.nio.file.*
|
||||
val AnyPath = sbt.nio.file.AnyPath
|
||||
type ChangedFiles = sbt.nio.file.ChangedFiles
|
||||
val ChangedFiles = sbt.nio.file.ChangedFiles
|
||||
type Glob = sbt.nio.file.Glob
|
||||
val Glob = sbt.nio.file.Glob
|
||||
type RelativeGlob = sbt.nio.file.RelativeGlob
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
import java.nio.file.Path
|
||||
|
||||
import sjsonnew.BasicJsonProtocol._
|
||||
|
||||
val copyFile = taskKey[Int]("dummy task")
|
||||
copyFile / fileInputs += baseDirectory.value.toGlob / "base" / "*.txt"
|
||||
copyFile / fileOutputs += baseDirectory.value.toGlob / "out" / "*.txt"
|
||||
copyFile / target := baseDirectory.value / "out"
|
||||
|
||||
copyFile := Def.task {
|
||||
val prev = copyFile.previous
|
||||
val changes: Option[Seq[Path]] = (copyFile / changedInputFiles).value.map {
|
||||
case ChangedFiles(c, _, u) => c ++ u
|
||||
}
|
||||
prev match {
|
||||
case Some(v: Int) if (copyFile / changedInputFiles).value.isEmpty => v
|
||||
case Some(v: Int) if changes.isEmpty => v
|
||||
case _ =>
|
||||
(copyFile / changedInputFiles).value.foreach { p =>
|
||||
val outdir = baseDirectory.value / "out"
|
||||
IO.createDirectory(baseDirectory.value / "out")
|
||||
IO.copyFile(p.toFile, outdir / p.getFileName.toString)
|
||||
changes.getOrElse((copyFile / allInputFiles).value).foreach { p =>
|
||||
val outDir = baseDirectory.value / "out"
|
||||
IO.createDirectory(outDir)
|
||||
IO.copyFile(p.toFile, outDir / p.getFileName.toString)
|
||||
}
|
||||
prev.map(_ + 1).getOrElse(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,3 +51,9 @@ $ copy-file changes/Foo.txt base/Foo.txt
|
|||
> checkOutDirectoryIsEmpty
|
||||
|
||||
> 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 := 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))
|
||||
}.value
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
> fileInputTask
|
||||
|
||||
-> fileInputTask
|
||||
|
||||
$ 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")
|
||||
checkModified := Def.taskDyn {
|
||||
val changed = (foo / changedInputFiles).value
|
||||
val modified = (foo / modifiedInputFiles).value
|
||||
if (modified.sameElements(changed)) Def.task(assert(true))
|
||||
val modified = (foo / changedInputFiles).value.map(_.updated).getOrElse(Nil)
|
||||
val allFiles = (foo / allInputFiles).value
|
||||
if (modified.isEmpty) Def.task(assert(true))
|
||||
else Def.task {
|
||||
assert(modified != changed)
|
||||
assert(modified != allFiles)
|
||||
assert(modified == Seq((baseDirectory.value / "base" / "Bar.md").toPath))
|
||||
}
|
||||
}.value
|
||||
|
||||
val checkRemoved = taskKey[Unit]("check that modified files are returned")
|
||||
val checkRemoved = taskKey[Unit]("check that removed files are returned")
|
||||
checkRemoved := Def.taskDyn {
|
||||
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))
|
||||
else Def.task {
|
||||
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")
|
||||
checkAdded := Def.taskDyn {
|
||||
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))
|
||||
else Def.task {
|
||||
val base = baseDirectory.value / "base"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ val foo = taskKey[Seq[File]]("Retrieve Foo.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")
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ val bar = taskKey[Seq[File]]("Retrieve Bar.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")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ fileInputTask / fileInputs += (baseDirectory.value / "base").toGlob / "*.md"
|
|||
fileInputTask / inputFileStamper := sbt.nio.FileStamper.LastModified
|
||||
|
||||
fileInputTask := Def.taskDyn {
|
||||
if ((fileInputTask / changedInputFiles).value.nonEmpty) Def.task(assert(true))
|
||||
else Def.task(assert(false))
|
||||
(fileInputTask / changedInputFiles).value match {
|
||||
case Some(ChangedFiles(_, _, u)) if u.nonEmpty => Def.task(assert(true))
|
||||
case None => Def.task(assert(false))
|
||||
}
|
||||
}.value
|
||||
|
||||
val setLastModified = taskKey[Unit]("Reset the last modified time")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
> fileInputTask
|
||||
|
||||
-> fileInputTask
|
||||
|
||||
$ touch base/Bar.md
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@ compileLib / fileInputs := {
|
|||
val base: Glob = (compileLib / sourceDirectory).value.toGlob
|
||||
base / ** / "*.c" :: base / "include" / "*.h" :: Nil
|
||||
}
|
||||
compileLib / target := baseDirectory.value / "out" / "lib"
|
||||
compileLib / target := baseDirectory.value / "out" / "objects"
|
||||
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 objectDir: Path = (compileLib / target).value.toPath / "objects"
|
||||
val logger = streams.value.log
|
||||
|
|
@ -18,16 +22,15 @@ compileLib := {
|
|||
name.substring(0, name.lastIndexOf('.')) + ".o"
|
||||
}
|
||||
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.")
|
||||
outputs
|
||||
case _ =>
|
||||
Files.createDirectories(objectDir)
|
||||
def extensionFilter(ext: String): Path => Boolean = _.getFileName.toString.endsWith(s".$ext")
|
||||
val allInputs = (compileLib / allInputFiles).value
|
||||
val cFiles: Seq[Path] =
|
||||
if (inputs.exists(extensionFilter("h"))) allInputs.filter(extensionFilter("c"))
|
||||
else inputs.filter(extensionFilter("c"))
|
||||
if (changedFiles.fold(false)(_.exists(extensionFilter("h")))) allFiles.filter(extensionFilter("c"))
|
||||
else changedFiles.getOrElse(allFiles).filter(extensionFilter("c"))
|
||||
cFiles.map { file =>
|
||||
val outFile = objectDir.resolve(objectFileName(file))
|
||||
logger.info(s"Compiling $file to $outFile")
|
||||
|
|
@ -38,13 +41,14 @@ compileLib := {
|
|||
}
|
||||
|
||||
val linkLib = taskKey[Path]("")
|
||||
linkLib / target := baseDirectory.value / "out" / "lib"
|
||||
linkLib := {
|
||||
val objects = (compileLib / changedOutputPaths).value
|
||||
val outPath = (compileLib / target).value.toPath
|
||||
val allObjects = (compileLib / allOutputPaths).value.map(_.toString)
|
||||
val changedObjects = (compileLib / changedOutputFiles).value
|
||||
val outPath = (linkLib / target).value.toPath
|
||||
val allObjects = (compileLib / allOutputFiles).value.map(_.toString)
|
||||
val logger = streams.value.log
|
||||
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.")
|
||||
p
|
||||
case _ =>
|
||||
|
|
@ -53,9 +57,10 @@ linkLib := {
|
|||
(Seq("-dynamiclib", "-o", path.toString), path)
|
||||
} else {
|
||||
val path = outPath.resolve("libfoo.so")
|
||||
(Seq("-shared", "-o", path.toString), path)
|
||||
(Seq("-shared", "-fPIC", "-o", path.toString), path)
|
||||
}
|
||||
logger.info(s"Linking $libraryPath")
|
||||
Files.createDirectories(outPath)
|
||||
("gcc" +: (linkOptions ++ allObjects)).!!
|
||||
libraryPath
|
||||
}
|
||||
|
|
@ -67,13 +72,14 @@ compileMain / fileInputs := (compileMain / sourceDirectory).value.toGlob / "main
|
|||
compileMain / target := baseDirectory.value / "out" / "main"
|
||||
compileMain := {
|
||||
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 logger = streams.value.log
|
||||
val outDir = (compileMain / target).value.toPath
|
||||
val outPath = outDir.resolve("main.out")
|
||||
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")
|
||||
p
|
||||
case _ =>
|
||||
|
|
@ -100,17 +106,27 @@ compileMain := {
|
|||
val executeMain = inputKey[Unit]("run the main method")
|
||||
executeMain := {
|
||||
val args = Def.spaceDelimited("<arguments>").parsed
|
||||
val binary = (compileMain / allOutputPaths).value
|
||||
val binary: Seq[Path] = (compileMain / allOutputFiles).value
|
||||
val logger = streams.value.log
|
||||
binary match {
|
||||
case Seq(b) =>
|
||||
val argString =
|
||||
if (args.nonEmpty) s" with arguments: ${args.mkString("'", "', '", "'")}" else ""
|
||||
logger.info(s"Running $b$argString")
|
||||
logger.info((b.toString +: args).!!)
|
||||
logger.info(RunBinary(b, args, linkLib.value).mkString("\n"))
|
||||
|
||||
case b =>
|
||||
throw new IllegalArgumentException(
|
||||
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
|
||||
> 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