Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support exclusion of certain projects from analysis #8

Merged
merged 4 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading