Skip to content

[2.x] feat: Add dependencyMode setting to control classpath transitivity#8960

Merged
eed3si9n merged 3 commits intosbt:developfrom
eureka928:feat/8942-dependency-mode
Mar 23, 2026
Merged

[2.x] feat: Add dependencyMode setting to control classpath transitivity#8960
eed3si9n merged 3 commits intosbt:developfrom
eureka928:feat/8942-dependency-mode

Conversation

@eureka928
Copy link
Copy Markdown
Contributor

@eureka928 eureka928 commented Mar 22, 2026

Summary

Implements the dependencyMode setting proposed in #8942, adding explicit dependency tracking similar to Bazel's rules_scala dependency tracking and sbt-explicit-dependencies.

  • DependencyMode.Transitive (default) — all transitive dependencies on the classpath (current behavior, zero overhead)
  • DependencyMode.Direct — only declared libraryDependencies plus scala-library
  • DependencyMode.PlusOne — declared dependencies plus their immediate transitive dependencies plus scala-library

Usage

// Only allow direct dependencies on the compile classpath
dependencyMode := DependencyMode.Direct

// Or allow one level of transitive deps
dependencyMode := DependencyMode.PlusOne

Design decisions

  • Filtering at managedClasspath level, not resolution levelupdate still resolves the full transitive graph. dependencyTree, eviction checks, and caching are unaffected.
  • Uses Def.taskDyn to avoid evaluating allDependencies/updateFull when mode is Transitive — zero overhead for the default case.
  • Cross-version aware matching — correctly matches cats-core (from libraryDependencies) against resolved cats-core_3 on the classpath.
  • scala-library always included regardless of mode, since the Scala standard library is a runtime requirement.

Files changed

  • main/src/main/scala/sbt/DependencyMode.scala — New Scala 3 enum
  • main/src/main/scala/sbt/Keys.scaladependencyMode setting key
  • main/src/main/scala/sbt/Defaults.scala — Default value, managedClasspath filtering logic, helper methods in Classpaths
  • sbt-app/src/sbt-test/dependency-management/dependency-mode/ — Scripted integration test

Test plan

  • Scripted test verifies all three modes with Scala (cats-core/cats-kernel) and Java (guava/failureaccess) dependencies
  • Test verifies Direct mode applies to both Compile and Test configurations
  • Default Transitive mode preserves existing behavior
  • Manual testing with publishLocalBin + real projects

Fixes #8942

Copy link
Copy Markdown
Member

@eed3si9n eed3si9n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor refactoring comments, but overall looking great!

**Problem**
sbt always includes all transitive dependencies on the classpath.
This makes it easy to accidentally depend on transitive dependencies
without declaring them, leading to fragile builds that break when
a library changes its own dependencies.

**Solution**
Add a `dependencyMode` setting with three modes:

- `DependencyMode.Transitive` (default) — current behavior, all
  transitive dependencies on the classpath
- `DependencyMode.Direct` — only declared dependencies plus
  scala-library on the classpath
- `DependencyMode.PlusOne` — declared dependencies plus their
  immediate transitive dependencies plus scala-library

The filtering is done at the `managedClasspath` level using
`Def.taskDyn` so that `allDependencies` and `updateFull` are only
evaluated when the mode is not `Transitive`, avoiding any overhead
for the default case.

Fixes sbt#8942
- Move DependencyMode enum to sbt.librarymanagement package (lm-core)
  so filtering logic can eventually move there too
- Add DependencyMode type/val to Import.scala for build file access
- Move private filter implementations (filterByDirectDeps, filterByPlusOne,
  isScalaLibraryModule, matchesDirectDep, directDepIndex) from
  Classpaths object to ClasspathImpl object
- Clean up ScalaArtifacts import and remove redundant test task
@eureka928 eureka928 force-pushed the feat/8942-dependency-mode branch from 03485c0 to f9b8501 Compare March 23, 2026 05:11
Adding DependencyMode type/val to the Import trait introduces new
abstract members, which is a binary-incompatible change. Add
exclusion filters for the two ReversedMissingMethodProblem entries.
@eureka928 eureka928 force-pushed the feat/8942-dependency-mode branch from cb72bf3 to 05e982e Compare March 23, 2026 06:21
@eureka928
Copy link
Copy Markdown
Contributor Author

@eed3si9n The review feedback has been addressed:

  1. Moved DependencyMode to sbt.librarymanagement package in lm-core/
  2. Added DependencyMode type/val to Import.scala with MiMa exclusions
  3. Moved private filter implementations to ClasspathImpl

Regarding the remaining CI failure (test (windows-latest, 17, zulu)): this is a pre-existing flaky ExtendedRunnerTest — the sbt --jvm-client test gets AccessDeniedException on the portfile active.json. The same Windows launcher test also fails intermittently on develop. All other 14 checks pass.

@eureka928 eureka928 requested a review from eed3si9n March 23, 2026 06:50
@eed3si9n eed3si9n changed the title feat: Add dependencyMode setting to control classpath transitivity [2.x] feat: Add dependencyMode setting to control classpath transitivity Mar 23, 2026
Copy link
Copy Markdown
Member

@eed3si9n eed3si9n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

@eed3si9n eed3si9n merged commit 8132a39 into sbt:develop Mar 23, 2026
17 of 18 checks passed
@soc
Copy link
Copy Markdown
Contributor

soc commented Mar 23, 2026

Wow, thank you @eureka928! I will test and report back as soon as an artifact is available.

From my understanding

scala-library always included regardless of mode, since the Scala standard library is a runtime requirement.

should also be fine for us non-Scala users, because the mechanism in question does not actively add the dep, but simply does not filter it out.

So if we have configured the build to not use Scala to start with as before, this will not break anything, I think. :-)

eed3si9n pushed a commit to eed3si9n/sbt that referenced this pull request Mar 24, 2026
…ity (sbt#8960)

**Problem**
sbt always includes all transitive dependencies on the classpath.
This makes it easy to accidentally depend on transitive dependencies
without declaring them, leading to fragile builds that break when
a library changes its own dependencies.

**Solution**
Add a `dependencyMode` setting with three modes:

- DependencyMode.Transitive (default) — current behavior, all
  transitive dependencies on the classpath
- DependencyMode.Direct — only declared dependencies plus
  scala-library on the classpath
- DependencyMode.PlusOne — declared dependencies plus their
  immediate transitive dependencies plus scala-library

Fixes sbt#8942
eed3si9n added a commit that referenced this pull request Mar 24, 2026
…ivity (#8960) (#8972)

**Problem**
sbt always includes all transitive dependencies on the classpath.
This makes it easy to accidentally depend on transitive dependencies
without declaring them, leading to fragile builds that break when
a library changes its own dependencies.

**Solution**
Add a `dependencyMode` setting with three modes:

- DependencyMode.Transitive (default) — current behavior, all
  transitive dependencies on the classpath
- DependencyMode.Direct — only declared dependencies plus
  scala-library on the classpath
- DependencyMode.PlusOne — declared dependencies plus their
  immediate transitive dependencies plus scala-library

Fixes #8942

Co-authored-by: Dream <[email protected]>
@soc
Copy link
Copy Markdown
Contributor

soc commented Mar 28, 2026

I tried this with a very simple project and everything works fine on the CLI.

But it seems that any action in IntelliJ, like "build" or "test", fails with scala: No 'scala-library*.jar' in Scala compiler classpath in Scala SDK sbt: scala-sdk-0.0.0.
Not sure it is SBT's fault/responsibility to fix, but wanted to let you know.

Reproduction: https://codeberg.org/soc/base-uid/src/branch/dep-direct
Without this commit IntelliJ seems to be fine.

@eed3si9n
Copy link
Copy Markdown
Member

Thanks for testing. You can get past the initial error if you use autoScalaLibrary := false instead of managedScalaInstance := false, but I think there's an sbt bug here for the runtime classpath of test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.x] dependencyMode

3 participants