mirror of https://github.com/sbt/sbt.git
Merge pull request #9291 from eed3si9n/bport2/avoid-update-full
This commit is contained in:
commit
aae1a980fa
|
|
@ -2737,16 +2737,38 @@ object Classpaths {
|
|||
dependencyMode.value match
|
||||
case DependencyMode.Transitive =>
|
||||
Def.task { dependencyClasspath.value }
|
||||
case _ =>
|
||||
case DependencyMode.Direct =>
|
||||
Def.task {
|
||||
ClasspathImpl.filterByDependencyMode(
|
||||
dependencyMode.value,
|
||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||
DependencyMode.Direct,
|
||||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByDirectDeps(
|
||||
allDependencies.value,
|
||||
externalDependencyClasspath.value,
|
||||
)
|
||||
internalFiltered ++ externalFiltered
|
||||
}
|
||||
case DependencyMode.PlusOne =>
|
||||
Def.task {
|
||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||
DependencyMode.PlusOne,
|
||||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByPlusOne(
|
||||
allDependencies.value,
|
||||
projectID.value,
|
||||
classpathConfiguration.value,
|
||||
updateFull.value,
|
||||
dependencyClasspath.value,
|
||||
externalDependencyClasspath.value,
|
||||
)
|
||||
internalFiltered ++ externalFiltered
|
||||
}
|
||||
})
|
||||
.value,
|
||||
|
|
@ -2807,23 +2829,46 @@ object Classpaths {
|
|||
// Note: invoking this task from shell would block indefinitely because it will
|
||||
// wait for the upstream compilation to start.
|
||||
dependencyPicklePath := Def.uncached {
|
||||
// This is a conditional task. Do not refactor.
|
||||
if (incOptions.value.pipelining) {
|
||||
val cp = concat(
|
||||
internalDependencyPicklePath,
|
||||
externalDependencyClasspath,
|
||||
).value
|
||||
ClasspathImpl.filterByDependencyMode(
|
||||
dependencyMode.value,
|
||||
allDependencies.value,
|
||||
projectID.value,
|
||||
classpathConfiguration.value,
|
||||
updateFull.value,
|
||||
cp,
|
||||
)
|
||||
} else {
|
||||
filteredDependencyClasspath.value
|
||||
}
|
||||
Def.taskDyn {
|
||||
(incOptions.value.pipelining, dependencyMode.value) match
|
||||
case (false, _) =>
|
||||
Def.task { filteredDependencyClasspath.value }
|
||||
case (true, DependencyMode.Transitive) =>
|
||||
Def.task { dependencyClasspath.value }
|
||||
case (true, DependencyMode.Direct) =>
|
||||
Def.task {
|
||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||
DependencyMode.Direct,
|
||||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByDirectDeps(
|
||||
allDependencies.value,
|
||||
externalDependencyClasspath.value,
|
||||
)
|
||||
internalFiltered ++ externalFiltered
|
||||
}
|
||||
case (true, DependencyMode.PlusOne) =>
|
||||
Def.task {
|
||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||
DependencyMode.PlusOne,
|
||||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByPlusOne(
|
||||
allDependencies.value,
|
||||
projectID.value,
|
||||
classpathConfiguration.value,
|
||||
updateFull.value,
|
||||
externalDependencyClasspath.value,
|
||||
)
|
||||
internalFiltered ++ externalFiltered
|
||||
}
|
||||
}.value
|
||||
},
|
||||
internalDependencyPicklePath := ClasspathImpl.internalDependencyPicklePathTask.value,
|
||||
exportedPickles := ClasspathImpl.exportedPicklesTask.value,
|
||||
|
|
|
|||
|
|
@ -559,4 +559,53 @@ private[sbt] object ClasspathImpl {
|
|||
case DependencyMode.Direct => filterByDirectDeps(directDeps, cp)
|
||||
case DependencyMode.PlusOne => filterByPlusOne(directDeps, projectId, config, fullReport, cp)
|
||||
|
||||
/**
|
||||
* Apply dependencyMode filtering to the *internal* classpath using the build's project graph.
|
||||
* `UpdateReport` only contains externally resolved modules, so it cannot answer
|
||||
* "is this internal project a direct dep of `projectRef`"; the `BuildDependencies` graph can.
|
||||
*
|
||||
* Direct -- entries from projects directly listed in `projectRef.dependsOn(...)`.
|
||||
* PlusOne -- direct + one more hop along the project graph.
|
||||
* Transitive -- unfiltered.
|
||||
*/
|
||||
def filterInternalByMode(
|
||||
mode: DependencyMode,
|
||||
projectRef: ProjectRef,
|
||||
data: Def.Settings,
|
||||
deps: BuildDependencies,
|
||||
internalCp: Classpath,
|
||||
): Classpath =
|
||||
mode match
|
||||
case DependencyMode.Transitive => internalCp
|
||||
case _ =>
|
||||
val allowed = allowedInternalKeys(mode, projectRef, data, deps)
|
||||
internalCp.filter: entry =>
|
||||
entry.get(Keys.moduleIDStr) match
|
||||
case Some(str) =>
|
||||
val mid = Classpaths.moduleIdJsonKeyFormat.read(str)
|
||||
allowed.contains((mid.organization, mid.name))
|
||||
case None => true
|
||||
|
||||
private def allowedInternalKeys(
|
||||
mode: DependencyMode,
|
||||
projectRef: ProjectRef,
|
||||
data: Def.Settings,
|
||||
deps: BuildDependencies,
|
||||
): Set[(String, String)] =
|
||||
def directRefs(p: ProjectRef): Set[ProjectRef] =
|
||||
deps
|
||||
.classpath(p)
|
||||
.collect:
|
||||
case ClasspathDep.ResolvedClasspathDependency(dep, _) => dep
|
||||
.toSet
|
||||
val refs: Set[ProjectRef] = mode match
|
||||
case DependencyMode.Direct =>
|
||||
directRefs(projectRef)
|
||||
case DependencyMode.PlusOne =>
|
||||
val direct = directRefs(projectRef)
|
||||
direct ++ direct.flatMap(directRefs)
|
||||
case DependencyMode.Transitive => Set.empty
|
||||
refs.flatMap: pr =>
|
||||
(pr / projectID).get(data).map(mid => (mid.organization, mid.name))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
// Regression test for sbt/sbt#9009. dependencyMode := Direct / PlusOne
|
||||
// must apply to *internal* project dependencies as well, walking the
|
||||
// project graph (UpdateReport only contains external/LM modules).
|
||||
//
|
||||
// Fixture: core <- libA <- libB <- libC (each depends on the previous).
|
||||
//
|
||||
// Direct on libA -- must include core
|
||||
// Direct on libB -- must *not* include core (core is transitive)
|
||||
// PlusOne on libB -- must include core (one hop through libA)
|
||||
// PlusOne on libC -- must include libA (one hop through libB)
|
||||
// must *not* include core (two hops away)
|
||||
|
||||
ThisBuild / scalaVersion := "3.7.4"
|
||||
ThisBuild / organization := "org.example"
|
||||
ThisBuild / version := "0.1.0-SNAPSHOT"
|
||||
|
||||
lazy val core = (project in file("core")).settings(name := "core")
|
||||
|
||||
lazy val libA = (project in file("libA"))
|
||||
.settings(name := "libA")
|
||||
.dependsOn(core)
|
||||
|
||||
lazy val libB = (project in file("libB"))
|
||||
.settings(name := "libB")
|
||||
.dependsOn(libA)
|
||||
|
||||
lazy val libC = (project in file("libC"))
|
||||
.settings(name := "libC")
|
||||
.dependsOn(libB)
|
||||
|
||||
def filteredIds(cp: Seq[Attributed[xsbti.HashedVirtualFileRef]]): Seq[String] =
|
||||
cp.map(_.data.id)
|
||||
|
||||
// Match against the lowercased jar filename (sbt lowercases project
|
||||
// names when building artifact filenames, e.g. `liba_3-...jar`).
|
||||
def assertIn(needle: String, haystack: Seq[String], label: String): Unit =
|
||||
val n = needle.toLowerCase
|
||||
assert(
|
||||
haystack.exists(_.toLowerCase.contains(n)),
|
||||
s"$label: expected `$needle` to appear in $haystack"
|
||||
)
|
||||
|
||||
def assertNotIn(needle: String, haystack: Seq[String], label: String): Unit =
|
||||
val n = needle.toLowerCase
|
||||
assert(
|
||||
!haystack.exists(_.toLowerCase.contains(n)),
|
||||
s"$label: expected `$needle` NOT to appear in $haystack"
|
||||
)
|
||||
|
||||
lazy val checkDirectLibA = taskKey[Unit]("Direct mode on libA includes core")
|
||||
lazy val checkDirectLibB = taskKey[Unit]("Direct mode on libB excludes core (transitive)")
|
||||
lazy val checkPlusOneLibB = taskKey[Unit]("PlusOne mode on libB includes core (one hop)")
|
||||
lazy val checkPlusOneLibC =
|
||||
taskKey[Unit]("PlusOne mode on libC includes libA (one hop) but not core (two hops)")
|
||||
|
||||
libA / checkDirectLibA := {
|
||||
val cp = filteredIds((libA / Compile / filteredDependencyClasspath).value)
|
||||
assertIn("core", cp, "libA/Direct")
|
||||
}
|
||||
|
||||
libB / checkDirectLibB := {
|
||||
val cp = filteredIds((libB / Compile / filteredDependencyClasspath).value)
|
||||
assertIn("libA", cp, "libB/Direct")
|
||||
assertNotIn("core", cp, "libB/Direct")
|
||||
}
|
||||
|
||||
libB / checkPlusOneLibB := {
|
||||
val cp = filteredIds((libB / Compile / filteredDependencyClasspath).value)
|
||||
assertIn("libA", cp, "libB/PlusOne")
|
||||
assertIn("core", cp, "libB/PlusOne")
|
||||
}
|
||||
|
||||
libC / checkPlusOneLibC := {
|
||||
val cp = filteredIds((libC / Compile / filteredDependencyClasspath).value)
|
||||
assertIn("libB", cp, "libC/PlusOne")
|
||||
assertIn("libA", cp, "libC/PlusOne")
|
||||
assertNotIn("core", cp, "libC/PlusOne")
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Direct mode: only the immediate dependsOn project is on the filtered
|
||||
# classpath. Transitive internal deps (core via libA) are stripped.
|
||||
> set ThisBuild / dependencyMode := DependencyMode.Direct
|
||||
> libA/checkDirectLibA
|
||||
> libB/checkDirectLibB
|
||||
|
||||
# PlusOne mode: direct + one more hop. libB sees core (one hop through
|
||||
# libA); libC sees libA but not core (two hops).
|
||||
> set ThisBuild / dependencyMode := DependencyMode.PlusOne
|
||||
> libB/checkPlusOneLibB
|
||||
> libC/checkPlusOneLibC
|
||||
Loading…
Reference in New Issue