Use transitive header jars in classpaths

Skip combining jars into turbine-combined, combined, and withres jars
and instead collect transitive jars to use in the classpath and to
produce the final dexed jar.

This reduces the size of a `m checkbuild` in git_main by 11%, from
1300 KiB to 1154 KiB.  It may also improve caching and reduce uplink
network bandwidth when building with RBE, as now the classpath inputs
to rules are themselves outputs of previous rules and so already in
the RBE CAS.

The downside is that the classpath inputs to each rule are now much
longer, increasing the Soong ninja file size 11%, from 4.6 GiB to
5.1 GiB.  This could be mitigated in the future by supporting something
like depsets in the generated ninja file to reduce duplication.

Bug: 308016794
Test: TestSimple, TestKotlin, TestClasspath
Flag: build.RELEASE_USE_TRANSITIVE_JARS_IN_CLASSPATH
Change-Id: I2b7b4261375494370da70f98597c8719f1d561cf
diff --git a/java/aar.go b/java/aar.go
index 8ceeace..bffe88b 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -1297,6 +1297,10 @@
 	var staticJars android.Paths
 	var staticHeaderJars android.Paths
 	var staticResourceJars android.Paths
+	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsResourceJars []*android.DepSet[android.Path]
+
 	ctx.VisitDirectDeps(func(module android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			tag := ctx.OtherModuleDependencyTag(module)
@@ -1305,66 +1309,111 @@
 				staticJars = append(staticJars, dep.ImplementationJars...)
 				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
 				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
+				if dep.TransitiveStaticLibsImplementationJars != nil {
+					transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+				}
+				if dep.TransitiveStaticLibsResourceJars != nil {
+					transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
+				}
 			}
 		}
 		addCLCFromDep(ctx, module, a.classLoaderContexts)
 		addMissingOptionalUsesLibsFromDep(ctx, module, &a.usesLibrary)
 	})
 
+	completeStaticLibsHeaderJars := android.NewDepSet(android.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsHeaderJars)
+	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsImplementationJars)
+	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsResourceJars)
+
 	var implementationJarFile android.Path
-	if len(staticJars) > 0 {
-		combineJars := append(android.Paths{classpathFile}, staticJars...)
-		combinedImplementationJar := android.PathForModuleOut(ctx, "combined", jarName).OutputPath
-		TransformJarsToJar(ctx, combinedImplementationJar, "combine", combineJars, android.OptionalPath{}, false, nil, nil)
-		implementationJarFile = combinedImplementationJar
+	var combineJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		combineJars = completeStaticLibsImplementationJars.ToList()
+	} else {
+		combineJars = append(android.Paths{classpathFile}, staticJars...)
+	}
+
+	if len(combineJars) > 1 {
+		implementationJarOutputPath := android.PathForModuleOut(ctx, "combined", jarName)
+		TransformJarsToJar(ctx, implementationJarOutputPath, "combine", combineJars, android.OptionalPath{}, false, nil, nil)
+		implementationJarFile = implementationJarOutputPath
 	} else {
 		implementationJarFile = classpathFile
 	}
 
 	var resourceJarFile android.Path
-	if len(staticResourceJars) > 1 {
+	var resourceJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		resourceJars = completeStaticLibsResourceJars.ToList()
+	} else {
+		resourceJars = staticResourceJars
+	}
+	if len(resourceJars) > 1 {
 		combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName)
-		TransformJarsToJar(ctx, combinedJar, "for resources", staticResourceJars, android.OptionalPath{},
+		TransformJarsToJar(ctx, combinedJar, "for resources", resourceJars, android.OptionalPath{},
 			false, nil, nil)
 		resourceJarFile = combinedJar
-	} else if len(staticResourceJars) == 1 {
-		resourceJarFile = staticResourceJars[0]
+	} else if len(resourceJars) == 1 {
+		resourceJarFile = resourceJars[0]
 	}
 
 	// merge implementation jar with resources if necessary
-	implementationAndResourcesJar := implementationJarFile
-	if resourceJarFile != nil {
-		jars := android.Paths{resourceJarFile, implementationAndResourcesJar}
+	var implementationAndResourcesJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		implementationAndResourcesJars = append(slices.Clone(resourceJars), combineJars...)
+	} else {
+		implementationAndResourcesJars = android.PathsIfNonNil(resourceJarFile, implementationJarFile)
+	}
+	var implementationAndResourcesJar android.Path
+	if len(implementationAndResourcesJars) > 1 {
 		combinedJar := android.PathForModuleOut(ctx, "withres", jarName)
-		TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{},
+		TransformJarsToJar(ctx, combinedJar, "for resources", implementationAndResourcesJars, android.OptionalPath{},
 			false, nil, nil)
 		implementationAndResourcesJar = combinedJar
+	} else {
+		implementationAndResourcesJar = implementationAndResourcesJars[0]
 	}
 
 	a.implementationJarFile = implementationJarFile
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
 	a.implementationAndResourcesJarFile = implementationAndResourcesJar.WithoutRel()
 
-	if len(staticHeaderJars) > 0 {
-		combineJars := append(android.Paths{classpathFile}, staticHeaderJars...)
+	var headerJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		headerJars = completeStaticLibsHeaderJars.ToList()
+	} else {
+		headerJars = append(android.Paths{classpathFile}, staticHeaderJars...)
+	}
+	if len(headerJars) > 1 {
 		headerJarFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
-		TransformJarsToJar(ctx, headerJarFile, "combine header jars", combineJars, android.OptionalPath{}, false, nil, nil)
+		TransformJarsToJar(ctx, headerJarFile, "combine header jars", headerJars, android.OptionalPath{}, false, nil, nil)
 		a.headerJarFile = headerJarFile
 	} else {
-		a.headerJarFile = classpathFile
+		a.headerJarFile = headerJars[0]
 	}
 
-	ctx.CheckbuildFile(a.headerJarFile)
-	ctx.CheckbuildFile(a.implementationJarFile)
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		ctx.CheckbuildFile(classpathFile)
+	} else {
+		ctx.CheckbuildFile(a.headerJarFile)
+		ctx.CheckbuildFile(a.implementationJarFile)
+	}
 
 	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
-		HeaderJars:                          android.PathsIfNonNil(a.headerJarFile),
-		ResourceJars:                        android.PathsIfNonNil(resourceJarFile),
-		TransitiveLibsHeaderJarsForR8:       a.transitiveLibsHeaderJarsForR8,
-		TransitiveStaticLibsHeaderJarsForR8: a.transitiveStaticLibsHeaderJarsForR8,
-		ImplementationAndResourcesJars:      android.PathsIfNonNil(a.implementationAndResourcesJarFile),
-		ImplementationJars:                  android.PathsIfNonNil(a.implementationJarFile),
-		StubsLinkType:                       Implementation,
+		HeaderJars:                             android.PathsIfNonNil(a.headerJarFile),
+		LocalHeaderJars:                        android.PathsIfNonNil(classpathFile),
+		TransitiveStaticLibsHeaderJars:         completeStaticLibsHeaderJars,
+		TransitiveStaticLibsImplementationJars: completeStaticLibsImplementationJars,
+		TransitiveStaticLibsResourceJars:       completeStaticLibsResourceJars,
+		ResourceJars:                           android.PathsIfNonNil(resourceJarFile),
+		TransitiveLibsHeaderJarsForR8:          a.transitiveLibsHeaderJarsForR8,
+		TransitiveStaticLibsHeaderJarsForR8:    a.transitiveStaticLibsHeaderJarsForR8,
+		ImplementationAndResourcesJars:         android.PathsIfNonNil(a.implementationAndResourcesJarFile),
+		ImplementationJars:                     android.PathsIfNonNil(a.implementationJarFile),
+		StubsLinkType:                          Implementation,
 		// TransitiveAconfigFiles: // TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
 	})
 
diff --git a/java/base.go b/java/base.go
index a31c848..a635331 100644
--- a/java/base.go
+++ b/java/base.go
@@ -464,15 +464,10 @@
 	// inserting into the bootclasspath/classpath of another compile
 	headerJarFile android.Path
 
-	repackagedHeaderJarFile android.Path
-
 	// jar file containing implementation classes including static library dependencies but no
 	// resources
 	implementationJarFile android.Path
 
-	// jar file containing only resources including from static library dependencies
-	resourceJar android.Path
-
 	// args and dependencies to package source files into a srcjar
 	srcJarArgs []string
 	srcJarDeps android.Paths
@@ -1256,7 +1251,6 @@
 	// Collect .java and .kt files for AIDEGen
 	j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, uniqueSrcFiles.Strings()...)
 
-	var kotlinJars android.Paths
 	var kotlinHeaderJars android.Paths
 
 	// Prepend extraClasspathJars to classpath so that the resource processor R.jar comes before
@@ -1266,6 +1260,8 @@
 
 	j.aconfigCacheFiles = append(deps.aconfigProtoFiles, j.properties.Aconfig_Cache_files...)
 
+	var localImplementationJars android.Paths
+
 	// If compiling headers then compile them and skip the rest
 	if proptools.Bool(j.properties.Headers_only) {
 		if srcFiles.HasExt(".kt") {
@@ -1275,20 +1271,41 @@
 			ctx.ModuleErrorf("headers_only is enabled but Turbine is disabled.")
 		}
 
-		_, combinedHeaderJarFile := j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
+		transitiveStaticLibsHeaderJars := deps.transitiveStaticLibsHeaderJars
+
+		localHeaderJars, combinedHeaderJarFile := j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
 			extraCombinedJars)
 
-		combinedHeaderJarFile = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
-		combinedHeaderJarFile = j.repackageFlagsIfNecessary(ctx, combinedHeaderJarFile, jarName, "repackage-turbine")
+		combinedHeaderJarFile, jarjared := j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		if jarjared {
+			localHeaderJars = android.Paths{combinedHeaderJarFile}
+			transitiveStaticLibsHeaderJars = nil
+		}
+		combinedHeaderJarFile, repackaged := j.repackageFlagsIfNecessary(ctx, combinedHeaderJarFile, jarName, "repackage-turbine")
+		if repackaged {
+			localHeaderJars = android.Paths{combinedHeaderJarFile}
+			transitiveStaticLibsHeaderJars = nil
+		}
 		if ctx.Failed() {
 			return
 		}
 		j.headerJarFile = combinedHeaderJarFile
 
-		ctx.CheckbuildFile(j.headerJarFile)
+		if ctx.Config().UseTransitiveJarsInClasspath() {
+			if len(localHeaderJars) > 0 {
+				ctx.CheckbuildFile(localHeaderJars...)
+			} else {
+				// There are no local sources or resources in this module, so there is nothing to checkbuild.
+				ctx.UncheckedModule()
+			}
+		} else {
+			ctx.CheckbuildFile(j.headerJarFile)
+		}
 
 		android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 			HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
+			LocalHeaderJars:                     localHeaderJars,
+			TransitiveStaticLibsHeaderJars:      android.NewDepSet(android.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
 			TransitiveLibsHeaderJarsForR8:       j.transitiveLibsHeaderJarsForR8,
 			TransitiveStaticLibsHeaderJarsForR8: j.transitiveStaticLibsHeaderJarsForR8,
 			AidlIncludeDirs:                     j.exportAidlIncludeDirs,
@@ -1347,7 +1364,7 @@
 			kaptResJar := android.PathForModuleOut(ctx, "kapt", "kapt-res.jar")
 			kotlinKapt(ctx, kaptSrcJar, kaptResJar, uniqueSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
 			srcJars = append(srcJars, kaptSrcJar)
-			kotlinJars = append(kotlinJars, kaptResJar)
+			localImplementationJars = append(localImplementationJars, kaptResJar)
 			// Disable annotation processing in javac, it's already been handled by kapt
 			flags.processorPath = nil
 			flags.processors = nil
@@ -1360,21 +1377,24 @@
 			return
 		}
 
-		kotlinJarPath := j.repackageFlagsIfNecessary(ctx, kotlinJar, jarName, "kotlinc")
+		kotlinJarPath, _ := j.repackageFlagsIfNecessary(ctx, kotlinJar, jarName, "kotlinc")
 
 		// Make javac rule depend on the kotlinc rule
 		flags.classpath = append(classpath{kotlinHeaderJar}, flags.classpath...)
 
-		kotlinJars = append(kotlinJars, kotlinJarPath)
+		localImplementationJars = append(localImplementationJars, kotlinJarPath)
+
 		kotlinHeaderJars = append(kotlinHeaderJars, kotlinHeaderJar)
 	}
 
-	jars := slices.Clone(kotlinJars)
-
 	j.compiledSrcJars = srcJars
 
+	transitiveStaticLibsHeaderJars := deps.transitiveStaticLibsHeaderJars
+
 	enableSharding := false
-	var headerJarFileWithoutDepsOrJarjar android.Path
+	var localHeaderJars android.Paths
+	var shardingHeaderJars android.Paths
+	var repackagedHeaderJarFile android.Path
 	if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !disableTurbine {
 		if j.properties.Javac_shard_size != nil && *(j.properties.Javac_shard_size) > 0 {
 			enableSharding = true
@@ -1387,11 +1407,26 @@
 		extraJars := slices.Clone(kotlinHeaderJars)
 		extraJars = append(extraJars, extraCombinedJars...)
 		var combinedHeaderJarFile android.Path
-		headerJarFileWithoutDepsOrJarjar, combinedHeaderJarFile =
-			j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName, extraJars)
+		localHeaderJars, combinedHeaderJarFile = j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName, extraJars)
+		shardingHeaderJars = localHeaderJars
 
-		j.headerJarFile = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
-		j.repackagedHeaderJarFile = j.repackageFlagsIfNecessary(ctx, j.headerJarFile, jarName, "turbine")
+		var jarjared bool
+		j.headerJarFile, jarjared = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		if jarjared {
+			// jarjar modifies transitive static dependencies, use the combined header jar and drop the transitive
+			// static libs header jars.
+			localHeaderJars = android.Paths{j.headerJarFile}
+			transitiveStaticLibsHeaderJars = nil
+		}
+		var repackaged bool
+		repackagedHeaderJarFile, repackaged = j.repackageFlagsIfNecessary(ctx, j.headerJarFile, jarName, "turbine")
+		if repackaged {
+			// repackage modifies transitive static dependencies, use the combined header jar and drop the transitive
+			// static libs header jars.
+			// TODO(b/356688296): this shouldn't export both the unmodified and repackaged header jars
+			localHeaderJars = android.Paths{j.headerJarFile, repackagedHeaderJarFile}
+			transitiveStaticLibsHeaderJars = nil
+		}
 	}
 	if len(uniqueJavaFiles) > 0 || len(srcJars) > 0 {
 		hasErrorproneableFiles := false
@@ -1426,8 +1461,8 @@
 		}
 
 		if enableSharding {
-			if headerJarFileWithoutDepsOrJarjar != nil {
-				flags.classpath = append(classpath{headerJarFileWithoutDepsOrJarjar}, flags.classpath...)
+			if len(shardingHeaderJars) > 0 {
+				flags.classpath = append(classpath(slices.Clone(shardingHeaderJars)), flags.classpath...)
 			}
 			shardSize := int(*(j.properties.Javac_shard_size))
 			var shardSrcs []android.Paths
@@ -1436,8 +1471,8 @@
 				for idx, shardSrc := range shardSrcs {
 					classes := j.compileJavaClasses(ctx, jarName, idx, shardSrc,
 						nil, flags, extraJarDeps)
-					classes = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac-"+strconv.Itoa(idx))
-					jars = append(jars, classes)
+					classes, _ = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac-"+strconv.Itoa(idx))
+					localImplementationJars = append(localImplementationJars, classes)
 				}
 			}
 			// Assume approximately 5 sources per srcjar.
@@ -1449,21 +1484,21 @@
 				for idx, shardSrcJars := range shardSrcJarsList {
 					classes := j.compileJavaClasses(ctx, jarName, startIdx+idx,
 						nil, shardSrcJars, flags, extraJarDeps)
-					classes = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac-"+strconv.Itoa(startIdx+idx))
-					jars = append(jars, classes)
+					classes, _ = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac-"+strconv.Itoa(startIdx+idx))
+					localImplementationJars = append(localImplementationJars, classes)
 				}
 			}
 		} else {
 			classes := j.compileJavaClasses(ctx, jarName, -1, uniqueJavaFiles, srcJars, flags, extraJarDeps)
-			classes = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac")
-			jars = append(jars, classes)
+			classes, _ = j.repackageFlagsIfNecessary(ctx, classes, jarName, "javac")
+			localImplementationJars = append(localImplementationJars, classes)
 		}
 		if ctx.Failed() {
 			return
 		}
 	}
 
-	jars = append(jars, extraCombinedJars...)
+	localImplementationJars = append(localImplementationJars, extraCombinedJars...)
 
 	j.srcJarArgs, j.srcJarDeps = resourcePathsToJarArgs(srcFiles), srcFiles
 
@@ -1490,42 +1525,18 @@
 	resArgs = append(resArgs, extraArgs...)
 	resDeps = append(resDeps, extraDeps...)
 
+	var localResourceJars android.Paths
 	if len(resArgs) > 0 {
 		resourceJar := android.PathForModuleOut(ctx, "res", jarName)
 		TransformResourcesToJar(ctx, resourceJar, resArgs, resDeps)
-		j.resourceJar = resourceJar
 		if ctx.Failed() {
 			return
 		}
+		localResourceJars = append(localResourceJars, resourceJar)
 	}
 
-	var resourceJars android.Paths
-	if j.resourceJar != nil {
-		resourceJars = append(resourceJars, j.resourceJar)
-	}
 	if Bool(j.properties.Include_srcs) {
-		resourceJars = append(resourceJars, includeSrcJar)
-	}
-	resourceJars = append(resourceJars, deps.staticResourceJars...)
-
-	if len(resourceJars) > 1 {
-		combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName)
-		TransformJarsToJar(ctx, combinedJar, "for resources", resourceJars, android.OptionalPath{},
-			false, nil, nil)
-		j.resourceJar = combinedJar
-	} else if len(resourceJars) == 1 {
-		j.resourceJar = resourceJars[0]
-	}
-
-	if len(deps.staticJars) > 0 {
-		jars = append(jars, deps.staticJars...)
-	}
-
-	jars = append(jars, extraDepCombinedJars...)
-
-	manifest := j.overrideManifest
-	if !manifest.Valid() && j.properties.Manifest != nil {
-		manifest = android.OptionalPathForPath(android.PathForModuleSrc(ctx, *j.properties.Manifest))
+		localResourceJars = append(localResourceJars, includeSrcJar)
 	}
 
 	services := android.PathsForModuleSrc(ctx, j.properties.Services)
@@ -1550,35 +1561,68 @@
 			Implicits: services,
 			Args:      args,
 		})
-		jars = append(jars, servicesJar)
+		localResourceJars = append(localResourceJars, servicesJar)
+	}
+
+	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, localResourceJars, deps.transitiveStaticLibsResourceJars)
+
+	var combinedResourceJar android.Path
+	var resourceJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		resourceJars = completeStaticLibsResourceJars.ToList()
+	} else {
+		resourceJars = append(slices.Clone(localResourceJars), deps.staticResourceJars...)
+	}
+	if len(resourceJars) == 1 {
+		combinedResourceJar = resourceJars[0]
+	} else if len(resourceJars) > 0 {
+		combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName)
+		TransformJarsToJar(ctx, combinedJar, "for resources", resourceJars, android.OptionalPath{},
+			false, nil, nil)
+		combinedResourceJar = combinedJar
+	}
+
+	manifest := j.overrideManifest
+	if !manifest.Valid() && j.properties.Manifest != nil {
+		manifest = android.OptionalPathForPath(android.PathForModuleSrc(ctx, *j.properties.Manifest))
 	}
 
 	// Combine the classes built from sources, any manifests, and any static libraries into
 	// classes.jar. If there is only one input jar this step will be skipped.
 	var outputFile android.Path
 
+	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, localImplementationJars, deps.transitiveStaticLibsImplementationJars)
+
+	var jars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		jars = completeStaticLibsImplementationJars.ToList()
+	} else {
+		jars = append(slices.Clone(localImplementationJars), deps.staticJars...)
+	}
+
+	jars = append(jars, extraDepCombinedJars...)
+
 	if len(jars) == 1 && !manifest.Valid() {
 		// Optimization: skip the combine step as there is nothing to do
 		// TODO(ccross): this leaves any module-info.class files, but those should only come from
 		// prebuilt dependencies until we support modules in the platform build, so there shouldn't be
-		// any if len(jars) == 1.
+		// any if len(extraJars) == 0.
 
 		// moduleStubLinkType determines if the module is the TopLevelStubLibrary generated
 		// from sdk_library. The TopLevelStubLibrary contains only one static lib,
 		// either with .from-source or .from-text suffix.
 		// outputFile should be agnostic to the build configuration,
-		// thus "combine" the single static lib in order to prevent the static lib from being exposed
+		// thus copy the single input static lib in order to prevent the static lib from being exposed
 		// to the copy rules.
-		stub, _ := moduleStubLinkType(j)
-
-		if stub {
-			combinedJar := android.PathForModuleOut(ctx, "combined", jarName)
+		if stub, _ := moduleStubLinkType(j); stub {
+			copiedJar := android.PathForModuleOut(ctx, "combined", jarName)
 			ctx.Build(pctx, android.BuildParams{
 				Rule:   android.Cp,
 				Input:  jars[0],
-				Output: combinedJar,
+				Output: copiedJar,
 			})
-			outputFile = combinedJar
+			completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, android.Paths{copiedJar}, nil)
+			outputFile = copiedJar
 		} else {
 			outputFile = jars[0]
 		}
@@ -1590,13 +1634,21 @@
 	}
 
 	// jarjar implementation jar if necessary
-	jarjarFile := j.jarjarIfNecessary(ctx, outputFile, jarName, "")
+	jarjarFile, jarjarred := j.jarjarIfNecessary(ctx, outputFile, jarName, "")
+	if jarjarred {
+		localImplementationJars = android.Paths{jarjarFile}
+		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+	}
 	outputFile = jarjarFile
 
 	// jarjar resource jar if necessary
-	if j.resourceJar != nil {
-		resourceJarJarFile := j.jarjarIfNecessary(ctx, j.resourceJar, jarName, "resource")
-		j.resourceJar = resourceJarJarFile
+	if combinedResourceJar != nil {
+		resourceJarJarFile, jarjarred := j.jarjarIfNecessary(ctx, combinedResourceJar, jarName, "resource")
+		combinedResourceJar = resourceJarJarFile
+		if jarjarred {
+			localResourceJars = android.Paths{resourceJarJarFile}
+			completeStaticLibsResourceJars = android.NewDepSet(android.PREORDER, localResourceJars, nil)
+		}
 	}
 
 	if ctx.Failed() {
@@ -1664,6 +1716,13 @@
 		headerJarFile := android.PathForModuleOut(ctx, "javac-header", jarName)
 		convertImplementationJarToHeaderJar(ctx, j.implementationJarFile, headerJarFile)
 		j.headerJarFile = headerJarFile
+		if len(localImplementationJars) == 1 && ctx.Config().UseTransitiveJarsInClasspath() {
+			localHeaderJarFile := android.PathForModuleOut(ctx, "local-javac-header", jarName)
+			convertImplementationJarToHeaderJar(ctx, localImplementationJars[0], localHeaderJarFile)
+			localHeaderJars = append(localHeaderJars, localHeaderJarFile)
+		} else {
+			localHeaderJars = append(localHeaderJars, headerJarFile)
+		}
 	}
 
 	// enforce syntax check to jacoco filters for any build (http://b/183622051)
@@ -1677,16 +1736,27 @@
 	}
 
 	// merge implementation jar with resources if necessary
-	implementationAndResourcesJar := outputFile
-	if j.resourceJar != nil {
-		jars := android.Paths{j.resourceJar, implementationAndResourcesJar}
-		combinedJar := android.PathForModuleOut(ctx, "withres", jarName)
-		TransformJarsToJar(ctx, combinedJar, "for resources", jars, manifest,
-			false, nil, nil)
-		implementationAndResourcesJar = combinedJar
+	var implementationAndResourcesJarsToCombine android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		resourceJars := completeStaticLibsResourceJars.ToList()
+		if len(resourceJars) > 0 {
+			implementationAndResourcesJarsToCombine = append(resourceJars, completeStaticLibsImplementationJars.ToList()...)
+			implementationAndResourcesJarsToCombine = append(implementationAndResourcesJarsToCombine, extraDepCombinedJars...)
+		}
+	} else {
+		if combinedResourceJar != nil {
+			implementationAndResourcesJarsToCombine = android.Paths{combinedResourceJar, outputFile}
+		}
 	}
 
-	j.implementationAndResourcesJar = implementationAndResourcesJar
+	if len(implementationAndResourcesJarsToCombine) > 0 {
+		combinedJar := android.PathForModuleOut(ctx, "withres", jarName)
+		TransformJarsToJar(ctx, combinedJar, "for resources", implementationAndResourcesJarsToCombine, manifest,
+			false, nil, nil)
+		outputFile = combinedJar
+	}
+
+	j.implementationAndResourcesJar = outputFile
 
 	// Enable dex compilation for the APEX variants, unless it is disabled explicitly
 	compileDex := j.dexProperties.Compile_dex
@@ -1712,7 +1782,7 @@
 				flags:         flags,
 				sdkVersion:    j.SdkVersion(ctx),
 				minSdkVersion: j.MinSdkVersion(ctx),
-				classesJar:    implementationAndResourcesJar,
+				classesJar:    outputFile,
 				jarName:       jarName,
 			}
 			if j.GetProfileGuided() && j.optimizeOrObfuscateEnabled() && !j.EnableProfileRewriting() {
@@ -1738,10 +1808,20 @@
 			}
 
 			// merge dex jar with resources if necessary
-			if j.resourceJar != nil {
-				jars := android.Paths{dexOutputFile, j.resourceJar}
+			var dexAndResourceJarsToCombine android.Paths
+			if ctx.Config().UseTransitiveJarsInClasspath() {
+				resourceJars := completeStaticLibsResourceJars.ToList()
+				if len(resourceJars) > 0 {
+					dexAndResourceJarsToCombine = append(android.Paths{dexOutputFile}, resourceJars...)
+				}
+			} else {
+				if combinedResourceJar != nil {
+					dexAndResourceJarsToCombine = android.Paths{dexOutputFile, combinedResourceJar}
+				}
+			}
+			if len(dexAndResourceJarsToCombine) > 0 {
 				combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName)
-				TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{},
+				TransformJarsToJar(ctx, combinedJar, "for dex resources", dexAndResourceJarsToCombine, android.OptionalPath{},
 					false, nil, nil)
 				if *j.dexProperties.Uncompress_dex {
 					combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName)
@@ -1774,15 +1854,12 @@
 		} else {
 			// There is no code to compile into a dex jar, make sure the resources are propagated
 			// to the APK if this is an app.
-			outputFile = implementationAndResourcesJar
-			j.dexJarFile = makeDexJarPathFromPath(j.resourceJar)
+			j.dexJarFile = makeDexJarPathFromPath(combinedResourceJar)
 		}
 
 		if ctx.Failed() {
 			return
 		}
-	} else {
-		outputFile = implementationAndResourcesJar
 	}
 
 	if ctx.Device() {
@@ -1814,17 +1891,34 @@
 
 	j.collectTransitiveSrcFiles(ctx, srcFiles)
 
-	ctx.CheckbuildFile(j.implementationJarFile)
-	ctx.CheckbuildFile(j.headerJarFile)
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		if len(localImplementationJars) > 0 || len(localResourceJars) > 0 || len(localHeaderJars) > 0 {
+			ctx.CheckbuildFile(localImplementationJars...)
+			ctx.CheckbuildFile(localResourceJars...)
+			ctx.CheckbuildFile(localHeaderJars...)
+		} else {
+			// There are no local sources or resources in this module, so there is nothing to checkbuild.
+			ctx.UncheckedModule()
+		}
+	} else {
+		ctx.CheckbuildFile(j.implementationJarFile)
+		ctx.CheckbuildFile(j.headerJarFile)
+	}
 
 	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
-		HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
-		RepackagedHeaderJars:                android.PathsIfNonNil(j.repackagedHeaderJarFile),
+		HeaderJars:           android.PathsIfNonNil(j.headerJarFile),
+		RepackagedHeaderJars: android.PathsIfNonNil(repackagedHeaderJarFile),
+
+		LocalHeaderJars:                        localHeaderJars,
+		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
+		TransitiveStaticLibsImplementationJars: completeStaticLibsImplementationJars,
+		TransitiveStaticLibsResourceJars:       completeStaticLibsResourceJars,
+
 		TransitiveLibsHeaderJarsForR8:       j.transitiveLibsHeaderJarsForR8,
 		TransitiveStaticLibsHeaderJarsForR8: j.transitiveStaticLibsHeaderJarsForR8,
 		ImplementationAndResourcesJars:      android.PathsIfNonNil(j.implementationAndResourcesJar),
 		ImplementationJars:                  android.PathsIfNonNil(j.implementationJarFile),
-		ResourceJars:                        android.PathsIfNonNil(j.resourceJar),
+		ResourceJars:                        android.PathsIfNonNil(combinedResourceJar),
 		AidlIncludeDirs:                     j.exportAidlIncludeDirs,
 		SrcJarArgs:                          j.srcJarArgs,
 		SrcJarDeps:                          j.srcJarDeps,
@@ -1960,22 +2054,26 @@
 
 func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars android.Paths,
 	deps deps, flags javaBuilderFlags, jarName string,
-	extraJars android.Paths) (headerJar android.Path, combinedHeaderJar android.Path) {
+	extraJars android.Paths) (localHeaderJars android.Paths, combinedHeaderJar android.Path) {
 
-	var jars android.Paths
 	if len(srcFiles) > 0 || len(srcJars) > 0 {
 		// Compile java sources into turbine.jar.
 		turbineJar := android.PathForModuleOut(ctx, "turbine", jarName)
 		TransformJavaToHeaderClasses(ctx, turbineJar, srcFiles, srcJars, flags)
-		jars = append(jars, turbineJar)
-		headerJar = turbineJar
+		localHeaderJars = append(localHeaderJars, turbineJar)
 	}
 
-	jars = append(jars, extraJars...)
+	localHeaderJars = append(localHeaderJars, extraJars...)
 
 	// Combine any static header libraries into classes-header.jar. If there is only
 	// one input jar this step will be skipped.
-	jars = append(jars, deps.staticHeaderJars...)
+	var jars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		depSet := android.NewDepSet(android.PREORDER, localHeaderJars, deps.transitiveStaticLibsHeaderJars)
+		jars = depSet.ToList()
+	} else {
+		jars = append(slices.Clone(localHeaderJars), deps.staticHeaderJars...)
+	}
 
 	// we cannot skip the combine step for now if there is only one jar
 	// since we have to strip META-INF/TRANSITIVE dir from turbine.jar
@@ -1983,9 +2081,7 @@
 	TransformJarsToJar(ctx, combinedHeaderJarOutputPath, "for turbine", jars, android.OptionalPath{},
 		false, nil, []string{"META-INF/TRANSITIVE"})
 
-	ctx.CheckbuildFile(combinedHeaderJarOutputPath)
-
-	return headerJar, combinedHeaderJarOutputPath
+	return localHeaderJars, combinedHeaderJarOutputPath
 }
 
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
@@ -2041,6 +2137,7 @@
 			if dep.TransitiveStaticLibsHeaderJarsForR8 != nil {
 				transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJarsForR8)
 			}
+
 		}
 	})
 	j.transitiveLibsHeaderJarsForR8 = android.NewDepSet(android.POSTORDER, directLibs, transitiveLibs)
@@ -2288,29 +2385,17 @@
 func (j *Module) collectDeps(ctx android.ModuleContext) deps {
 	var deps deps
 
-	if ctx.Device() {
-		sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
-		if sdkDep.invalidVersion {
-			ctx.AddMissingDependencies(sdkDep.bootclasspath)
-			ctx.AddMissingDependencies(sdkDep.java9Classpath)
-		} else if sdkDep.useFiles {
-			// sdkDep.jar is actually equivalent to turbine header.jar.
-			deps.classpath = append(deps.classpath, sdkDep.jars...)
-			deps.dexClasspath = append(deps.dexClasspath, sdkDep.jars...)
-			deps.aidlPreprocess = sdkDep.aidl
-			// Add the sdk module dependency to `compileDepNames`.
-			// This ensures that the dependency is reported in `module_bp_java_deps.json`
-			// TODO (b/358608607): Move this to decodeSdkDep
-			sdkSpec := android.SdkContext(j).SdkVersion(ctx)
-			j.compileDepNames = append(j.compileDepNames, fmt.Sprintf("sdk_%s_%s_android", sdkSpec.Kind.String(), sdkSpec.ApiLevel.String()))
-		} else {
-			deps.aidlPreprocess = sdkDep.aidl
-		}
-	}
-
 	sdkLinkType, _ := j.getSdkLinkType(ctx, ctx.ModuleName())
 
 	j.collectTransitiveHeaderJarsForR8(ctx)
+
+	var transitiveBootClasspathHeaderJars []*android.DepSet[android.Path]
+	var transitiveClasspathHeaderJars []*android.DepSet[android.Path]
+	var transitiveJava9ClasspathHeaderJars []*android.DepSet[android.Path]
+	var transitiveStaticJarsHeaderLibs []*android.DepSet[android.Path]
+	var transitiveStaticJarsImplementationLibs []*android.DepSet[android.Path]
+	var transitiveStaticJarsResourceLibs []*android.DepSet[android.Path]
+
 	ctx.VisitDirectDeps(func(module android.Module) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
@@ -2330,6 +2415,10 @@
 				depHeaderJars := dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))
 				deps.classpath = append(deps.classpath, depHeaderJars...)
 				deps.dexClasspath = append(deps.dexClasspath, depHeaderJars...)
+
+				// TODO: SDK libraries should export a provider with TransitiveClasspathHeaderJars
+				depHeaderJarsSet := android.NewDepSet(android.PREORDER, depHeaderJars, nil)
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, depHeaderJarsSet)
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
 			}
@@ -2345,6 +2434,9 @@
 			switch tag {
 			case bootClasspathTag:
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
 			case sdkLibTag, libTag, instrumentationForTag:
 				if _, ok := module.(*Plugin); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a libs dependency", otherName)
@@ -2358,8 +2450,15 @@
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
 				addPlugins(&deps, dep.ExportedPlugins, dep.ExportedPluginClasses...)
 				deps.disableTurbine = deps.disableTurbine || dep.ExportedPluginDisableTurbine
+
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
 			case java9LibTag:
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveJava9ClasspathHeaderJars = append(transitiveJava9ClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
 			case staticLibTag:
 				if _, ok := module.(*Plugin); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a static_libs dependency", otherName)
@@ -2375,6 +2474,17 @@
 				// optimization.
 				deps.disableTurbine = deps.disableTurbine || dep.ExportedPluginDisableTurbine
 				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
+
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+					transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, dep.TransitiveStaticLibsHeaderJars)
+				}
+				if dep.TransitiveStaticLibsImplementationJars != nil {
+					transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, dep.TransitiveStaticLibsImplementationJars)
+				}
+				if dep.TransitiveStaticLibsResourceJars != nil {
+					transitiveStaticJarsResourceLibs = append(transitiveStaticJarsResourceLibs, dep.TransitiveStaticLibsResourceJars)
+				}
 			case pluginTag:
 				if plugin, ok := module.(*Plugin); ok {
 					if plugin.pluginProperties.Processor_class != nil {
@@ -2423,11 +2533,18 @@
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
 				deps.dexClasspath = append(deps.classpath, dep.Srcs()...)
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars,
+					android.NewDepSet(android.PREORDER, dep.Srcs(), nil))
 			case staticLibTag:
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
 				deps.staticJars = append(deps.staticJars, dep.Srcs()...)
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs()...)
+
+				depHeaderJars := android.NewDepSet(android.PREORDER, dep.Srcs(), nil)
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, depHeaderJars)
+				transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, depHeaderJars)
+				transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, depHeaderJars)
 			}
 		} else if dep, ok := android.OtherModuleProvider(ctx, module, android.CodegenInfoProvider); ok {
 			switch tag {
@@ -2440,8 +2557,11 @@
 				// If a system modules dependency has been added to the bootclasspath
 				// then add its libs to the bootclasspath.
 				if sm, ok := android.OtherModuleProvider(ctx, module, SystemModulesProvider); ok {
-					depHeaderJars := sm.HeaderJars
-					deps.bootClasspath = append(deps.bootClasspath, depHeaderJars...)
+					deps.bootClasspath = append(deps.bootClasspath, sm.HeaderJars...)
+					if sm.TransitiveStaticLibsHeaderJars != nil {
+						transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars,
+							sm.TransitiveStaticLibsHeaderJars)
+					}
 				} else {
 					ctx.PropertyErrorf("boot classpath dependency %q does not provide SystemModulesProvider",
 						ctx.OtherModuleName(module))
@@ -2472,6 +2592,39 @@
 		addMissingOptionalUsesLibsFromDep(ctx, module, &j.usesLibrary)
 	})
 
+	deps.transitiveStaticLibsHeaderJars = transitiveStaticJarsHeaderLibs
+	deps.transitiveStaticLibsImplementationJars = transitiveStaticJarsImplementationLibs
+	deps.transitiveStaticLibsResourceJars = transitiveStaticJarsResourceLibs
+
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		depSet := android.NewDepSet(android.PREORDER, nil, transitiveClasspathHeaderJars)
+		deps.classpath = depSet.ToList()
+		depSet = android.NewDepSet(android.PREORDER, nil, transitiveBootClasspathHeaderJars)
+		deps.bootClasspath = depSet.ToList()
+		depSet = android.NewDepSet(android.PREORDER, nil, transitiveJava9ClasspathHeaderJars)
+		deps.java9Classpath = depSet.ToList()
+	}
+
+	if ctx.Device() {
+		sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
+		if sdkDep.invalidVersion {
+			ctx.AddMissingDependencies(sdkDep.bootclasspath)
+			ctx.AddMissingDependencies(sdkDep.java9Classpath)
+		} else if sdkDep.useFiles {
+			// sdkDep.jar is actually equivalent to turbine header.jar.
+			deps.classpath = append(slices.Clone(classpath(sdkDep.jars)), deps.classpath...)
+			deps.dexClasspath = append(slices.Clone(classpath(sdkDep.jars)), deps.dexClasspath...)
+			deps.aidlPreprocess = sdkDep.aidl
+			// Add the sdk module dependency to `compileDepNames`.
+			// This ensures that the dependency is reported in `module_bp_java_deps.json`
+			// TODO (b/358608607): Move this to decodeSdkDep
+			sdkSpec := android.SdkContext(j).SdkVersion(ctx)
+			j.compileDepNames = append(j.compileDepNames, fmt.Sprintf("sdk_%s_%s_android", sdkSpec.Kind.String(), sdkSpec.ApiLevel.String()))
+		} else {
+			deps.aidlPreprocess = sdkDep.aidl
+		}
+	}
+
 	return deps
 }
 
@@ -2783,22 +2936,22 @@
 }
 
 // Repackage the flags if the jarjar rule txt for the flags is generated
-func (j *Module) repackageFlagsIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) android.Path {
+func (j *Module) repackageFlagsIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) (android.Path, bool) {
 	if j.repackageJarjarRules == nil {
-		return infile
+		return infile, false
 	}
 	repackagedJarjarFile := android.PathForModuleOut(ctx, "repackaged-jarjar", info, jarName)
 	TransformJarJar(ctx, repackagedJarjarFile, infile, j.repackageJarjarRules)
-	return repackagedJarjarFile
+	return repackagedJarjarFile, true
 }
 
-func (j *Module) jarjarIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) android.Path {
+func (j *Module) jarjarIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) (android.Path, bool) {
 	if j.expandJarjarRules == nil {
-		return infile
+		return infile, false
 	}
 	jarjarFile := android.PathForModuleOut(ctx, "jarjar", info, jarName)
 	TransformJarJar(ctx, jarjarFile, infile, j.expandJarjarRules)
-	return jarjarFile
+	return jarjarFile, true
 
 }
 
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 7cc06fc..3f4e3cd 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -96,6 +96,10 @@
 		ctx.PropertyErrorf("libs", "at least one dependency is required")
 	}
 
+	var transitiveHeaderJars []*android.DepSet[android.Path]
+	var transitiveImplementationJars []*android.DepSet[android.Path]
+	var transitiveResourceJars []*android.DepSet[android.Path]
+
 	ctx.VisitDirectDepsWithTag(deviceHostConverterDepTag, func(m android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
 			d.headerJars = append(d.headerJars, dep.HeaderJars...)
@@ -105,6 +109,16 @@
 
 			d.srcJarArgs = append(d.srcJarArgs, dep.SrcJarArgs...)
 			d.srcJarDeps = append(d.srcJarDeps, dep.SrcJarDeps...)
+
+			if dep.TransitiveStaticLibsHeaderJars != nil {
+				transitiveHeaderJars = append(transitiveHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+			}
+			if dep.TransitiveStaticLibsImplementationJars != nil {
+				transitiveImplementationJars = append(transitiveImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+			}
+			if dep.TransitiveStaticLibsResourceJars != nil {
+				transitiveResourceJars = append(transitiveResourceJars, dep.TransitiveStaticLibsResourceJars)
+			}
 		} else {
 			ctx.PropertyErrorf("libs", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
 		}
@@ -131,13 +145,17 @@
 	}
 
 	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
-		HeaderJars:                     d.headerJars,
-		ImplementationAndResourcesJars: d.implementationAndResourceJars,
-		ImplementationJars:             d.implementationJars,
-		ResourceJars:                   d.resourceJars,
-		SrcJarArgs:                     d.srcJarArgs,
-		SrcJarDeps:                     d.srcJarDeps,
-		StubsLinkType:                  Implementation,
+		HeaderJars:                             d.headerJars,
+		LocalHeaderJars:                        d.headerJars,
+		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, nil, transitiveHeaderJars),
+		TransitiveStaticLibsImplementationJars: android.NewDepSet(android.PREORDER, nil, transitiveImplementationJars),
+		TransitiveStaticLibsResourceJars:       android.NewDepSet(android.PREORDER, nil, transitiveResourceJars),
+		ImplementationAndResourcesJars:         d.implementationAndResourceJars,
+		ImplementationJars:                     d.implementationJars,
+		ResourceJars:                           d.resourceJars,
+		SrcJarArgs:                             d.srcJarArgs,
+		SrcJarDeps:                             d.srcJarDeps,
+		StubsLinkType:                          Implementation,
 		// TODO: Not sure if aconfig flags that have been moved between device and host variants
 		// make sense.
 	})
diff --git a/java/java.go b/java/java.go
index f30d892..797a90d 100644
--- a/java/java.go
+++ b/java/java.go
@@ -254,6 +254,7 @@
 type JavaInfo struct {
 	// HeaderJars is a list of jars that can be passed as the javac classpath in order to link
 	// against this module.  If empty, ImplementationJars should be used instead.
+	// Unlike LocalHeaderJars, HeaderJars includes classes from static dependencies.
 	HeaderJars android.Paths
 
 	RepackagedHeaderJars android.Paths
@@ -264,6 +265,15 @@
 	// set of header jars for all transitive static libs deps
 	TransitiveStaticLibsHeaderJarsForR8 *android.DepSet[android.Path]
 
+	// depset of header jars for this module and all transitive static dependencies
+	TransitiveStaticLibsHeaderJars *android.DepSet[android.Path]
+
+	// depset of implementation jars for this module and all transitive static dependencies
+	TransitiveStaticLibsImplementationJars *android.DepSet[android.Path]
+
+	// depset of resource jars for this module and all transitive static dependencies
+	TransitiveStaticLibsResourceJars *android.DepSet[android.Path]
+
 	// ImplementationAndResourceJars is a list of jars that contain the implementations of classes
 	// in the module as well as any resources included in the module.
 	ImplementationAndResourcesJars android.Paths
@@ -275,6 +285,9 @@
 	// ResourceJars is a list of jars that contain the resources included in the module.
 	ResourceJars android.Paths
 
+	// LocalHeaderJars is a list of jars that contain classes from this module, but not from any static dependencies.
+	LocalHeaderJars android.Paths
+
 	// AidlIncludeDirs is a list of directories that should be passed to the aidl tool when
 	// depending on this module.
 	AidlIncludeDirs android.Paths
@@ -568,6 +581,10 @@
 	aconfigProtoFiles       android.Paths
 
 	disableTurbine bool
+
+	transitiveStaticLibsHeaderJars         []*android.DepSet[android.Path]
+	transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
+	transitiveStaticLibsResourceJars       []*android.DepSet[android.Path]
 }
 
 func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) {
@@ -1008,7 +1025,7 @@
 		}
 		hostDexNeeded := Bool(j.deviceProperties.Hostdex) && !ctx.Host()
 		if hostDexNeeded {
-			j.hostdexInstallFile = ctx.InstallFile(
+			j.hostdexInstallFile = ctx.InstallFileWithoutCheckbuild(
 				android.PathForHostDexInstall(ctx, "framework"),
 				j.Stem()+"-hostdex.jar", j.outputFile)
 		}
@@ -1022,7 +1039,7 @@
 		} else {
 			installDir = android.PathForModuleInstall(ctx, "framework")
 		}
-		j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
+		j.installFile = ctx.InstallFileWithoutCheckbuild(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
 }
 
@@ -2359,11 +2376,14 @@
 	ctx.Phony(ctx.ModuleName(), al.stubsJar)
 
 	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
-		HeaderJars:                     android.PathsIfNonNil(al.stubsJar),
-		ImplementationAndResourcesJars: android.PathsIfNonNil(al.stubsJar),
-		ImplementationJars:             android.PathsIfNonNil(al.stubsJar),
-		AidlIncludeDirs:                android.Paths{},
-		StubsLinkType:                  Stubs,
+		HeaderJars:                             android.PathsIfNonNil(al.stubsJar),
+		LocalHeaderJars:                        android.PathsIfNonNil(al.stubsJar),
+		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
+		TransitiveStaticLibsImplementationJars: android.NewDepSet(android.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
+		ImplementationAndResourcesJars:         android.PathsIfNonNil(al.stubsJar),
+		ImplementationJars:                     android.PathsIfNonNil(al.stubsJar),
+		AidlIncludeDirs:                        android.Paths{},
+		StubsLinkType:                          Stubs,
 		// No aconfig libraries on api libraries
 	})
 }
@@ -2634,6 +2654,12 @@
 
 	var flags javaBuilderFlags
 
+	var transitiveClasspathHeaderJars []*android.DepSet[android.Path]
+	var transitiveBootClasspathHeaderJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsResourceJars []*android.DepSet[android.Path]
+
 	j.collectTransitiveHeaderJarsForR8(ctx)
 	var staticJars android.Paths
 	var staticResourceJars android.Paths
@@ -2645,32 +2671,67 @@
 			case libTag, sdkLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars...)
 				flags.dexClasspath = append(flags.dexClasspath, dep.HeaderJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
 			case staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars...)
 				staticJars = append(staticJars, dep.ImplementationJars...)
 				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
 				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+					transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
+				if dep.TransitiveStaticLibsImplementationJars != nil {
+					transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+				}
+				if dep.TransitiveStaticLibsResourceJars != nil {
+					transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
+				}
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars...)
+				if dep.TransitiveStaticLibsHeaderJars != nil {
+					transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				}
 			}
 		} else if dep, ok := module.(SdkLibraryDependency); ok {
 			switch tag {
 			case libTag, sdkLibTag:
-				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))...)
+				depHeaderJars := dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))
+				flags.classpath = append(flags.classpath, depHeaderJars...)
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars,
+					android.NewDepSet(android.PREORDER, depHeaderJars, nil))
 			}
 		}
 
 		addCLCFromDep(ctx, module, j.classLoaderContexts)
 	})
 
-	jars := android.PathsForModuleSrc(ctx, j.properties.Jars)
+	localJars := android.PathsForModuleSrc(ctx, j.properties.Jars)
 	jarName := j.Stem() + ".jar"
 
+	// Combine only the local jars together for use in transitive classpaths.
+	// Always pass input jar through TransformJarsToJar to strip module-info.class from prebuilts.
+	localCombinedHeaderJar := android.PathForModuleOut(ctx, "local-combined", jarName)
+	TransformJarsToJar(ctx, localCombinedHeaderJar, "combine local prebuilt implementation jars", localJars, android.OptionalPath{},
+		false, j.properties.Exclude_files, j.properties.Exclude_dirs)
+	localStrippedJars := android.Paths{localCombinedHeaderJar}
+
+	completeStaticLibsHeaderJars := android.NewDepSet(android.PREORDER, localStrippedJars, transitiveStaticLibsHeaderJars)
+	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, localStrippedJars, transitiveStaticLibsImplementationJars)
+	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsResourceJars)
+
 	// Always pass the input jars to TransformJarsToJar, even if there is only a single jar, we need the output
 	// file of the module to be named jarName.
 	var outputFile android.Path
 	combinedImplementationJar := android.PathForModuleOut(ctx, "combined", jarName)
-	implementationJars := append(slices.Clone(jars), staticJars...)
+	var implementationJars android.Paths
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		implementationJars = completeStaticLibsImplementationJars.ToList()
+	} else {
+		implementationJars = append(slices.Clone(localJars), staticJars...)
+	}
 	TransformJarsToJar(ctx, combinedImplementationJar, "combine prebuilt implementation jars", implementationJars, android.OptionalPath{},
 		false, j.properties.Exclude_files, j.properties.Exclude_dirs)
 	outputFile = combinedImplementationJar
@@ -2693,7 +2754,12 @@
 	if reuseImplementationJarAsHeaderJar {
 		headerJar = outputFile
 	} else {
-		headerJars := append(slices.Clone(jars), staticHeaderJars...)
+		var headerJars android.Paths
+		if ctx.Config().UseTransitiveJarsInClasspath() {
+			headerJars = completeStaticLibsHeaderJars.ToList()
+		} else {
+			headerJars = append(slices.Clone(localJars), staticHeaderJars...)
+		}
 		headerOutputFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
 		TransformJarsToJar(ctx, headerOutputFile, "combine prebuilt header jars", headerJars, android.OptionalPath{},
 			false, j.properties.Exclude_files, j.properties.Exclude_dirs)
@@ -2712,6 +2778,11 @@
 		} else {
 			headerJar = outputFile
 		}
+
+		// Enabling jetifier requires modifying classes from transitive dependencies, disable transitive
+		// classpath and use the combined header jar instead.
+		completeStaticLibsHeaderJars = android.NewDepSet(android.PREORDER, android.Paths{headerJar}, nil)
+		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, android.Paths{outputFile}, nil)
 	}
 
 	implementationJarFile := outputFile
@@ -2735,7 +2806,11 @@
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
 
-	ctx.CheckbuildFile(outputFile)
+	if ctx.Config().UseTransitiveJarsInClasspath() {
+		ctx.CheckbuildFile(localJars...)
+	} else {
+		ctx.CheckbuildFile(outputFile)
+	}
 
 	if ctx.Device() {
 		// If this is a variant created for a prebuilt_apex then use the dex implementation jar
@@ -2817,14 +2892,18 @@
 	}
 
 	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
-		HeaderJars:                          android.PathsIfNonNil(j.combinedHeaderFile),
-		TransitiveLibsHeaderJarsForR8:       j.transitiveLibsHeaderJarsForR8,
-		TransitiveStaticLibsHeaderJarsForR8: j.transitiveStaticLibsHeaderJarsForR8,
-		ImplementationAndResourcesJars:      android.PathsIfNonNil(j.combinedImplementationFile),
-		ImplementationJars:                  android.PathsIfNonNil(implementationJarFile.WithoutRel()),
-		ResourceJars:                        android.PathsIfNonNil(resourceJarFile),
-		AidlIncludeDirs:                     j.exportAidlIncludeDirs,
-		StubsLinkType:                       j.stubsLinkType,
+		HeaderJars:                             android.PathsIfNonNil(j.combinedHeaderFile),
+		LocalHeaderJars:                        android.PathsIfNonNil(j.combinedHeaderFile),
+		TransitiveLibsHeaderJarsForR8:          j.transitiveLibsHeaderJarsForR8,
+		TransitiveStaticLibsHeaderJarsForR8:    j.transitiveStaticLibsHeaderJarsForR8,
+		TransitiveStaticLibsHeaderJars:         completeStaticLibsHeaderJars,
+		TransitiveStaticLibsImplementationJars: completeStaticLibsImplementationJars,
+		TransitiveStaticLibsResourceJars:       completeStaticLibsResourceJars,
+		ImplementationAndResourcesJars:         android.PathsIfNonNil(j.combinedImplementationFile),
+		ImplementationJars:                     android.PathsIfNonNil(implementationJarFile.WithoutRel()),
+		ResourceJars:                           android.PathsIfNonNil(resourceJarFile),
+		AidlIncludeDirs:                        j.exportAidlIncludeDirs,
+		StubsLinkType:                          j.stubsLinkType,
 		// TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
 	})
 
diff --git a/java/java_test.go b/java/java_test.go
index 9e39b51..e4e6bca 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -20,6 +20,7 @@
 	"path/filepath"
 	"reflect"
 	"runtime"
+	"slices"
 	"strconv"
 	"strings"
 	"testing"
@@ -211,7 +212,7 @@
 }
 
 func TestSimple(t *testing.T) {
-	ctx, _ := testJava(t, `
+	bp := `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -222,31 +223,157 @@
 		java_library {
 			name: "bar",
 			srcs: ["b.java"],
+			static_libs: ["quz"],
 		}
 
 		java_library {
 			name: "baz",
 			srcs: ["c.java"],
+			static_libs: ["quz"],
 		}
-	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
+		java_library {
+			name: "quz",
+			srcs: ["d.java"],
+		}`
 
-	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
-		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+	frameworkTurbineCombinedJars := []string{
+		"out/soong/.intermediates/default/java/ext/android_common/turbine-combined/ext.jar",
+		"out/soong/.intermediates/default/java/framework/android_common/turbine-combined/framework.jar",
 	}
 
-	baz := ctx.ModuleForTests("baz", "android_common").Rule("javac").Output.String()
-	barTurbine := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
-	bazTurbine := filepath.Join("out", "soong", ".intermediates", "baz", "android_common", "turbine-combined", "baz.jar")
+	frameworkTurbineJars := []string{
+		"out/soong/.intermediates/default/java/ext/android_common/turbine/ext.jar",
+		"out/soong/.intermediates/default/java/framework/android_common/turbine/framework.jar",
+	}
 
-	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], barTurbine)
+	testCases := []struct {
+		name string
 
-	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], bazTurbine)
+		preparer android.FixturePreparer
 
-	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
-		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
+		fooJavacInputs          []string
+		fooJavacClasspath       []string
+		fooCombinedInputs       []string
+		fooHeaderCombinedInputs []string
+
+		barJavacInputs          []string
+		barJavacClasspath       []string
+		barCombinedInputs       []string
+		barHeaderCombinedInputs []string
+	}{
+		{
+			name:           "normal",
+			preparer:       android.NullFixturePreparer,
+			fooJavacInputs: []string{"a.java"},
+			fooJavacClasspath: slices.Concat(
+				frameworkTurbineCombinedJars,
+				[]string{
+					"out/soong/.intermediates/bar/android_common/turbine-combined/bar.jar",
+					"out/soong/.intermediates/baz/android_common/turbine-combined/baz.jar",
+				},
+			),
+			fooCombinedInputs: []string{
+				"out/soong/.intermediates/foo/android_common/javac/foo.jar",
+				"out/soong/.intermediates/baz/android_common/combined/baz.jar",
+			},
+
+			fooHeaderCombinedInputs: []string{
+				"out/soong/.intermediates/foo/android_common/turbine/foo.jar",
+				"out/soong/.intermediates/baz/android_common/turbine-combined/baz.jar",
+			},
+
+			barJavacInputs: []string{"b.java"},
+			barJavacClasspath: slices.Concat(
+				frameworkTurbineCombinedJars,
+				[]string{
+					"out/soong/.intermediates/quz/android_common/turbine-combined/quz.jar",
+				},
+			),
+			barCombinedInputs: []string{
+				"out/soong/.intermediates/bar/android_common/javac/bar.jar",
+				"out/soong/.intermediates/quz/android_common/javac/quz.jar",
+			},
+			barHeaderCombinedInputs: []string{
+				"out/soong/.intermediates/bar/android_common/turbine/bar.jar",
+				"out/soong/.intermediates/quz/android_common/turbine-combined/quz.jar",
+			},
+		},
+		{
+			name:           "transitive classpath",
+			preparer:       PrepareForTestWithTransitiveClasspathEnabled,
+			fooJavacInputs: []string{"a.java"},
+			fooJavacClasspath: slices.Concat(
+				frameworkTurbineJars,
+				[]string{
+					"out/soong/.intermediates/bar/android_common/turbine/bar.jar",
+					"out/soong/.intermediates/quz/android_common/turbine/quz.jar",
+					"out/soong/.intermediates/baz/android_common/turbine/baz.jar",
+				},
+			),
+			fooCombinedInputs: []string{
+				"out/soong/.intermediates/foo/android_common/javac/foo.jar",
+				"out/soong/.intermediates/baz/android_common/javac/baz.jar",
+				"out/soong/.intermediates/quz/android_common/javac/quz.jar",
+			},
+
+			fooHeaderCombinedInputs: []string{
+				"out/soong/.intermediates/foo/android_common/turbine/foo.jar",
+				"out/soong/.intermediates/baz/android_common/turbine/baz.jar",
+				"out/soong/.intermediates/quz/android_common/turbine/quz.jar",
+			},
+
+			barJavacInputs: []string{"b.java"},
+			barJavacClasspath: slices.Concat(
+				frameworkTurbineJars,
+				[]string{"out/soong/.intermediates/quz/android_common/turbine/quz.jar"},
+			),
+			barCombinedInputs: []string{
+				"out/soong/.intermediates/bar/android_common/javac/bar.jar",
+				"out/soong/.intermediates/quz/android_common/javac/quz.jar",
+			},
+			barHeaderCombinedInputs: []string{
+				"out/soong/.intermediates/bar/android_common/turbine/bar.jar",
+				"out/soong/.intermediates/quz/android_common/turbine/quz.jar",
+			},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				tt.preparer,
+			).RunTestWithBp(t, bp)
+			foo := result.ModuleForTests("foo", "android_common")
+
+			fooJavac := foo.Rule("javac")
+			android.AssertPathsRelativeToTopEquals(t, "foo javac inputs", tt.fooJavacInputs, fooJavac.Inputs)
+
+			fooJavacClasspath := fooJavac.Args["classpath"]
+			android.AssertStringPathsRelativeToTopEquals(t, "foo javac classpath", result.Config, tt.fooJavacClasspath,
+				strings.Split(strings.TrimPrefix(fooJavacClasspath, "-classpath "), ":"))
+
+			fooCombinedJar := foo.Output("combined/foo.jar")
+			android.AssertPathsRelativeToTopEquals(t, "foo combined inputs", tt.fooCombinedInputs, fooCombinedJar.Inputs)
+
+			fooCombinedHeaderJar := foo.Output("turbine-combined/foo.jar")
+			android.AssertPathsRelativeToTopEquals(t, "foo header combined inputs", tt.fooHeaderCombinedInputs, fooCombinedHeaderJar.Inputs)
+
+			bar := result.ModuleForTests("bar", "android_common")
+			barJavac := bar.Rule("javac")
+			android.AssertPathsRelativeToTopEquals(t, "bar javac inputs", tt.barJavacInputs, barJavac.Inputs)
+
+			barJavacClasspath := barJavac.Args["classpath"]
+			android.AssertStringPathsRelativeToTopEquals(t, "bar javac classpath", result.Config, tt.barJavacClasspath,
+				strings.Split(strings.TrimPrefix(barJavacClasspath, "-classpath "), ":"))
+
+			barCombinedJar := bar.Output("combined/bar.jar")
+			android.AssertPathsRelativeToTopEquals(t, "bar combined inputs", tt.barCombinedInputs, barCombinedJar.Inputs)
+
+			barCombinedHeaderJar := bar.Output("turbine-combined/bar.jar")
+			android.AssertPathsRelativeToTopEquals(t, "bar header combined inputs", tt.barHeaderCombinedInputs, barCombinedHeaderJar.Inputs)
+		})
 	}
 }
 
@@ -590,7 +717,7 @@
 	barModule := ctx.ModuleForTests("bar", "android_common")
 	barJar := barModule.Output("combined/bar.jar").Output
 	bazModule := ctx.ModuleForTests("baz", "android_common")
-	bazJar := bazModule.Rule("combineJar").Output
+	bazJar := bazModule.Output("combined/baz.jar").Output
 	sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs", "android_common").
 		Output("combined/sdklib.stubs.jar").Output
 
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 844e974..f6e7fca 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -56,6 +56,13 @@
 		"out/soong/.intermediates/default/java/kotlin-annotations/android_common/turbine-combined/kotlin-annotations.jar",
 	}
 
+	kotlinStdlibTurbineJars := []string{
+		"out/soong/.intermediates/default/java/kotlin-stdlib/android_common/turbine/kotlin-stdlib.jar",
+		"out/soong/.intermediates/default/java/kotlin-stdlib-jdk7/android_common/turbine/kotlin-stdlib-jdk7.jar",
+		"out/soong/.intermediates/default/java/kotlin-stdlib-jdk8/android_common/turbine/kotlin-stdlib-jdk8.jar",
+		"out/soong/.intermediates/default/java/kotlin-annotations/android_common/turbine/kotlin-annotations.jar",
+	}
+
 	kotlinStdlibJavacJars := []string{
 		"out/soong/.intermediates/default/java/kotlin-stdlib/android_common/javac/kotlin-stdlib.jar",
 		"out/soong/.intermediates/default/java/kotlin-stdlib-jdk7/android_common/javac/kotlin-stdlib-jdk7.jar",
@@ -68,11 +75,21 @@
 		"out/soong/.intermediates/default/java/core-lambda-stubs/android_common/turbine-combined/core-lambda-stubs.jar",
 	}
 
+	bootclasspathTurbineJars := []string{
+		"out/soong/.intermediates/default/java/stable.core.platform.api.stubs/android_common/turbine/stable.core.platform.api.stubs.jar",
+		"out/soong/.intermediates/default/java/core-lambda-stubs/android_common/turbine/core-lambda-stubs.jar",
+	}
+
 	frameworkTurbineCombinedJars := []string{
 		"out/soong/.intermediates/default/java/ext/android_common/turbine-combined/ext.jar",
 		"out/soong/.intermediates/default/java/framework/android_common/turbine-combined/framework.jar",
 	}
 
+	frameworkTurbineJars := []string{
+		"out/soong/.intermediates/default/java/ext/android_common/turbine/ext.jar",
+		"out/soong/.intermediates/default/java/framework/android_common/turbine/framework.jar",
+	}
+
 	testCases := []struct {
 		name string
 
@@ -150,6 +167,69 @@
 				kotlinStdlibTurbineCombinedJars,
 			),
 		},
+		{
+			name:             "transitive classpath",
+			preparer:         PrepareForTestWithTransitiveClasspathEnabled,
+			fooKotlincInputs: []string{"a.java", "b.kt"},
+			fooJavacInputs:   []string{"a.java"},
+			fooKotlincClasspath: slices.Concat(
+				bootclasspathTurbineJars,
+				frameworkTurbineJars,
+				[]string{"out/soong/.intermediates/quz/android_common/kotlin_headers/quz.jar"},
+				kotlinStdlibTurbineJars,
+			),
+			fooJavacClasspath: slices.Concat(
+				[]string{"out/soong/.intermediates/foo/android_common/kotlin_headers/foo.jar"},
+				frameworkTurbineJars,
+				[]string{"out/soong/.intermediates/quz/android_common/kotlin_headers/quz.jar"},
+				kotlinStdlibTurbineJars,
+			),
+			fooCombinedInputs: slices.Concat(
+				[]string{
+					"out/soong/.intermediates/foo/android_common/kotlin/foo.jar",
+					"out/soong/.intermediates/foo/android_common/javac/foo.jar",
+					"out/soong/.intermediates/quz/android_common/kotlin/quz.jar",
+				},
+				kotlinStdlibJavacJars,
+			),
+			fooHeaderCombinedInputs: slices.Concat(
+				[]string{
+					"out/soong/.intermediates/foo/android_common/turbine/foo.jar",
+					"out/soong/.intermediates/foo/android_common/kotlin_headers/foo.jar",
+					"out/soong/.intermediates/quz/android_common/kotlin_headers/quz.jar",
+				},
+				kotlinStdlibTurbineJars,
+			),
+
+			barKotlincInputs: []string{"b.kt"},
+			barKotlincClasspath: slices.Concat(
+				bootclasspathTurbineJars,
+				frameworkTurbineJars,
+				[]string{
+					"out/soong/.intermediates/foo/android_common/turbine/foo.jar",
+					"out/soong/.intermediates/foo/android_common/kotlin_headers/foo.jar",
+					"out/soong/.intermediates/quz/android_common/kotlin_headers/quz.jar",
+				},
+				kotlinStdlibTurbineJars,
+				[]string{"out/soong/.intermediates/baz/android_common/turbine/baz.jar"},
+			),
+			barCombinedInputs: slices.Concat(
+				[]string{
+					"out/soong/.intermediates/bar/android_common/kotlin/bar.jar",
+					"out/soong/.intermediates/baz/android_common/javac/baz.jar",
+					"out/soong/.intermediates/quz/android_common/kotlin/quz.jar",
+				},
+				kotlinStdlibJavacJars,
+			),
+			barHeaderCombinedInputs: slices.Concat(
+				[]string{
+					"out/soong/.intermediates/bar/android_common/kotlin_headers/bar.jar",
+					"out/soong/.intermediates/baz/android_common/turbine/baz.jar",
+					"out/soong/.intermediates/quz/android_common/kotlin_headers/quz.jar",
+				},
+				kotlinStdlibTurbineJars,
+			),
+		},
 	}
 
 	for _, tt := range testCases {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 485776b..12d5877 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -907,7 +907,7 @@
 		fooModule := result.ModuleForTests("foo"+scope, "android_common")
 		javac := fooModule.Rule("javac")
 
-		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
+		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Output("combined/sdklib.stubs" + scope + ".jar").Output
 		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
 	}
 
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 2dac27a..9bfe6a2 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -391,15 +391,19 @@
 	t.Parallel()
 	t.Run("basic", func(t *testing.T) {
 		t.Parallel()
-		testClasspathTestCases(t, classpathTestcases, false)
+		testClasspathTestCases(t, classpathTestcases, false, false)
 	})
 
 	t.Run("Always_use_prebuilt_sdks=true", func(t *testing.T) {
-		testClasspathTestCases(t, classpathTestcases, true)
+		testClasspathTestCases(t, classpathTestcases, true, false)
+	})
+
+	t.Run("UseTransitiveJarsInClasspath", func(t *testing.T) {
+		testClasspathTestCases(t, classpathTestcases, false, true)
 	})
 }
 
-func testClasspathTestCases(t *testing.T, classpathTestcases []classpathTestCase, alwaysUsePrebuiltSdks bool) {
+func testClasspathTestCases(t *testing.T, classpathTestcases []classpathTestCase, alwaysUsePrebuiltSdks, useTransitiveJarsInClasspath bool) {
 	for _, testcase := range classpathTestcases {
 		if testcase.forAlwaysUsePrebuiltSdks != nil && *testcase.forAlwaysUsePrebuiltSdks != alwaysUsePrebuiltSdks {
 			continue
@@ -437,7 +441,14 @@
 			convertModulesToPaths := func(cp []string) []string {
 				ret := make([]string, len(cp))
 				for i, e := range cp {
-					ret[i] = defaultModuleToPath(e)
+					switch {
+					case e == `""`, strings.HasSuffix(e, ".jar"):
+						ret[i] = e
+					case useTransitiveJarsInClasspath:
+						ret[i] = filepath.Join("out", "soong", ".intermediates", defaultJavaDir, e, "android_common", "turbine", e+".jar")
+					default:
+						ret[i] = filepath.Join("out", "soong", ".intermediates", defaultJavaDir, e, "android_common", "turbine-combined", e+".jar")
+					}
 				}
 				return ret
 			}
@@ -531,6 +542,9 @@
 					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 				})
 			}
+			if useTransitiveJarsInClasspath {
+				preparer = PrepareForTestWithTransitiveClasspathEnabled
+			}
 
 			fixtureFactory := android.GroupFixturePreparers(
 				prepareForJavaTest,
diff --git a/java/system_modules.go b/java/system_modules.go
index f89bf9e..d9430b2 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -127,6 +127,9 @@
 
 	OutputDir     android.Path
 	OutputDirDeps android.Paths
+
+	// depset of header jars for this module and all transitive static dependencies
+	TransitiveStaticLibsHeaderJars *android.DepSet[android.Path]
 }
 
 var SystemModulesProvider = blueprint.NewProvider[*SystemModulesProviderInfo]()
@@ -149,18 +152,23 @@
 func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	var jars android.Paths
 
+	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
 	ctx.VisitDirectDepsWithTag(systemModulesLibsTag, func(module android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			jars = append(jars, dep.HeaderJars...)
+			if dep.TransitiveStaticLibsHeaderJars != nil {
+				transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+			}
 		}
 	})
 
 	system.outputDir, system.outputDeps = TransformJarsToSystemModules(ctx, jars)
 
 	android.SetProvider(ctx, SystemModulesProvider, &SystemModulesProviderInfo{
-		HeaderJars:    jars,
-		OutputDir:     system.outputDir,
-		OutputDirDeps: system.outputDeps,
+		HeaderJars:                     jars,
+		OutputDir:                      system.outputDir,
+		OutputDirDeps:                  system.outputDeps,
+		TransitiveStaticLibsHeaderJars: android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsHeaderJars),
 	})
 }
 
diff --git a/java/testing.go b/java/testing.go
index 03dcee6..e31e0d2 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -581,6 +581,7 @@
 				name: "%[1]s-lib",
 				sdk_version: "none",
 				system_modules: "none",
+				srcs: ["a.java"],
 			}
 		`, extra)
 	}
@@ -780,3 +781,5 @@
 		config.installDir = installDir
 	})
 }
+
+var PrepareForTestWithTransitiveClasspathEnabled = android.PrepareForTestWithBuildFlag("RELEASE_USE_TRANSITIVE_JARS_IN_CLASSPATH", "true")