Skip to content

Commit

Permalink
Support exclusion of certain projects from analysis (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
notxcain authored Aug 8, 2024
1 parent 8655e7f commit 6c598b4
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 35 deletions.
100 changes: 73 additions & 27 deletions src/main/scala/com/elarib/PartialSbtPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package com.elarib

import com.elarib.model.{ChangeGetter, PartialSbParser}
import org.apache.logging.log4j.LogManager
import sbt.Keys._
import sbt.*
import sbt.Keys.*
import sbt.internal.BuildDependencies.DependencyMap
import sbt._
import sbt.internal.{BuildDependencies, LoadedBuild}

object BuildKeys {
val partialSbtExcludedFiles = sbt.settingKey[Seq[sbt.File]]("Files that should be excluded from analysis.")
val partialSbtOpaqueProject: SettingKey[Unit] =
sbt.settingKey[Unit]("Changes in this project will not contribute to invalidation of its dependent projects.")
}

object PartialSbtPlugin extends AutoPlugin {
Expand Down Expand Up @@ -36,6 +39,40 @@ object PartialSbtPlugin extends AutoPlugin {
BuildKeys.partialSbtExcludedFiles := Def.setting(List.empty[sbt.File]).value
)

private def hasCompileConfiguration[A <: ProjectReference](dep: ClasspathDep[A]): Boolean =
dep.configuration.forall(value => value.contains("compile->") || value == "compile")

private def changedProjectsCommand(name: String)(
buildDependencies: BuildDependencies,
baseDirectory: File,
loadedBuild: LoadedBuild,
partialSbtExcludedFiles: Seq[File],
dependencyFilter: ClasspathDep[ProjectRef] => Boolean
) =
Command(name)(_ => PartialSbParser.changeGetterParser)((st, changeGetter) => {

val compileDependencyMap: DependencyMap[ClasspathDep[ProjectRef]] =
buildDependencies.classpath.mapValues(_.filter(dependencyFilter))

val transitiveCompileDependencyMap =
BuildDependencies.transitive(compileDependencyMap, BuildDependencies.getID)

val changedProjects: Seq[ResolvedProject] =
findChangedModules(changeGetter)(
baseDirectory,
loadedBuild.allProjectRefs,
transitiveCompileDependencyMap,
partialSbtExcludedFiles
)

logger.debug(s"${changedProjects.size} projects have been changed")

changedProjects.foreach { resolvedProject =>
logger.debug(resolvedProject.id)
}
st
})

override def projectSettings: Seq[Def.Setting[_]] =
Seq(
commands += Command("metaBuildChangedFiles")(_ => PartialSbParser.changeGetterParser)((st, changeGetter) => {
Expand All @@ -49,22 +86,20 @@ object PartialSbtPlugin extends AutoPlugin {
}
st
}),
commands += Command("changedProjects")(_ => PartialSbParser.changeGetterParser)((st, changeGetter) => {
val changedProjects: Seq[ResolvedProject] =
findChangedModules(changeGetter)(
baseDirectory.value,
loadedBuild.value.allProjectRefs,
buildDependencies.value.classpathTransitive,
BuildKeys.partialSbtExcludedFiles.value
)

logger.debug(s"${changedProjects.size} projects have been changed")

changedProjects.foreach { resolvedProject =>
logger.debug(resolvedProject.id)
}
st
})
commands += changedProjectsCommand("changedProjects")(
buildDependencies.value,
baseDirectory.value,
loadedBuild.value,
BuildKeys.partialSbtExcludedFiles.value,
Function.const(true)
),
commands += changedProjectsCommand("changedProjectsInCompile")(
buildDependencies.value,
baseDirectory.value,
loadedBuild.value,
BuildKeys.partialSbtExcludedFiles.value,
hasCompileConfiguration
)
)

private def findChangedModules(changeGetter: ChangeGetter)(
Expand All @@ -82,6 +117,9 @@ object PartialSbtPlugin extends AutoPlugin {
projectMap.values.toSeq
.sortBy(_.id)
case Nil =>
def isTransparentProject(resolvedProject: ResolvedProject): Boolean =
!resolvedProject.settings.exists(_.key.key == BuildKeys.partialSbtOpaqueProject.key)

val reverseDependencyMap: DependencyMap[ResolvedProject] = buildDeps
.foldLeft[DependencyMap[ResolvedProject]](Map.empty) { (acc, dependency) =>
val (ref, dependsOnList) = dependency
Expand All @@ -94,18 +132,29 @@ object PartialSbtPlugin extends AutoPlugin {
.fold(resolvedProjects)(_ +: resolvedProjects)
dependencyMap + (key -> newValue)
}

}
.filterKeys { projectRef =>
projectMap.get(projectRef).exists(isTransparentProject)
}

val modulesWithPath: Seq[(ProjectRef, ResolvedProject)] =
allProjectRefs.filter(_._2.base != baseDir)
allProjectRefs.filter { case (_, resolvedProject) =>
resolvedProject.base != baseDir
}

val diffsFiles: Seq[sbt.File] = changeGetter.changes.filterNot(f => isFileExcluded(baseDir)(f, excludedFiles))

val modulesToBuild: Seq[ResolvedProject] = modulesWithPath
.filter { case (_, resolvedProject) =>
diffsFiles.exists(file => file.getAbsolutePath.contains(resolvedProject.base.getAbsolutePath))
}
def findContainingProject(file: File): Option[(ProjectRef, ResolvedProject)] =
modulesWithPath
.filter { case (_, resolvedProject) =>
file.getAbsolutePath.contains(resolvedProject.base.getAbsolutePath)
}
.sortBy(_._2.base.getAbsolutePath.length)
.lastOption

diffsFiles
.flatMap(findContainingProject)
.distinct
.flatMap { case (projectRef, resolvedProject) =>
reverseDependencyMap
.get(projectRef)
Expand All @@ -114,8 +163,6 @@ object PartialSbtPlugin extends AutoPlugin {
}
.distinct
.sortBy(_.id)

modulesToBuild
}

}
Expand Down Expand Up @@ -152,5 +199,4 @@ object PartialSbtPlugin extends AutoPlugin {
else
file.getCanonicalPath == ef.getCanonicalPath
}

}
11 changes: 10 additions & 1 deletion src/sbt-test/test-projects/multi-module-project/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ commands += Command("addEnvVar")(_ => sbt.internal.util.complete.Parsers.spaceDe
// Projects
// +-----------+ +-----------+
// | | | |
// | lib-1 +<---------+ +--------->+ lib-2 |
// | lib-1 +<---------+ +--------->+ lib-2 (X) |
// | | | | | |
// +-----^-----+ | | +---+-------+
// | | | ^
Expand Down Expand Up @@ -80,8 +80,13 @@ lazy val firstLib = sbt
lazy val secondLib = sbt
.Project("lib-2", libs / "lib-2")
.settings(settings("lib-2"))
.settings(com.elarib.BuildKeys.partialSbtOpaqueProject := ())

//Tools
lazy val toolsProject = sbt
.Project("tools", tools)
.settings(settings("tools"))

lazy val firstTool = sbt
.Project("tool-1", tools / "tool-1")
.dependsOn(firstLib)
Expand Down Expand Up @@ -116,4 +121,8 @@ lazy val thirdService = sbt
lazy val fourthService = sbt
.Project("service-4", service / "service-4")
.dependsOn(firstTool, secondTool)
.dependsOn(testKit % Test)
.settings(settings("service-4"))

lazy val testKit = sbt
.Project("testKit", libs / "testKit")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/libs/testKit/build.sbt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
9 projects have been changed
7 projects have been changed
lib-1
lib-2
service-1
service-2
service-3
service-4
tool-1
tool-2
tool-3
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
Metabuild files have changed. Need to reload all the 10 projects
10 projects have been changed
Metabuild files have changed. Need to reload all the 12 projects
12 projects have been changed
lib-1
lib-2
multi-module-project
service-1
service-2
service-3
service-4
testKit
tool-1
tool-2
tool-3
tool-3
tools
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1 projects have been changed
testKit
7 changes: 6 additions & 1 deletion src/sbt-test/test-projects/multi-module-project/test
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ $ must-mirror ./result4.log ./expected/4.log
> reload
> addEnvVar PARTIAL_SBT_LOG_PATH result5.log
> changedProjects dummyChanges ./changes/5.log
$ must-mirror ./result5.log ./expected/5.log
$ must-mirror ./result5.log ./expected/5.log
# 6th test
> reload
> addEnvVar PARTIAL_SBT_LOG_PATH result6.log
> changedProjectsInCompile dummyChanges ./changes/6.log
$ must-mirror ./result6.log ./expected/6.log

0 comments on commit 6c598b4

Please sign in to comment.