blob: ee11c97ef2057ddde7216acf3b0ed5fa496d99cc [file]
// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import com.google.gson.Gson
import java.net.URI
import java.nio.file.Files.readString
import java.nio.file.Paths
import java.util.UUID
import java.util.concurrent.Callable
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.provideDelegate
import org.spdx.sbom.gradle.SpdxSbomTask
import org.spdx.sbom.gradle.extensions.DefaultSpdxSbomTaskExtension
plugins {
id("org.jetbrains.kotlin.jvm")
id("r8-conventions")
id("dependencies-plugin")
id("org.spdx.sbom")
}
if (project.hasProperty("spdxVersion")) {
project.version = project.property("spdxVersion")!!
}
// We need all the runtime deps for SPDX generation.
val r8Deps by
configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
compileOnly(libs.bundles.compilerDeps)
r8Deps(libs.bundles.compilerDeps)
}
spdxSbom {
targets {
create("r8") {
// Use of both compileClasspath and runtimeClasspath due to how the
// dependencies jar is built and dependencies above therefore use
// compileOnly for actual runtime dependencies.
configurations.set(listOf("compileClasspath", "runtimeClasspath"))
scm {
uri.set("https://r8.googlesource.com/r8/")
if (project.hasProperty("spdxRevision")) {
revision.set(project.property("spdxRevision").toString())
}
}
document {
name.set("R8 Compiler Suite")
// Generate version 5 UUID from fixed namespace UUID and name generated from revision
// (git hash) and artifact name.
if (project.hasProperty("spdxRevision")) {
namespace.set(
"https://spdx.google/" +
uuid5(
UUID.fromString("df17ea25-709b-4edc-8dc1-d3ca82c74e8e"),
project.property("spdxRevision").toString() + "-r8",
)
)
}
creator.set("Organization: Google LLC")
packageSupplier.set("Organization: Google LLC")
}
}
}
}
val keepRadiusProtoJarScope by configurations.dependencyScope("keepRadiusProtoJarScope")
val keepRadiusProtoJarConfig by
configurations.resolvable("keepRadiusProtoJarConfig") { extendsFrom(keepRadiusProtoJarScope) }
val keepAnnoDepsJarExceptAsmScope by configurations.dependencyScope("keepAnnoDepsJarExceptAsmScope")
val keepAnnoDepsJarExceptAsmConfig by
configurations.resolvable("keepAnnoDepsJarExceptAsmConfig") {
extendsFrom(keepAnnoDepsJarExceptAsmScope)
}
val keepAnnoToolsJarScope by configurations.dependencyScope("keepAnnoToolsJarScope")
val keepAnnoToolsJarConfig by
configurations.resolvable("keepAnnoToolsJarConfig") { extendsFrom(keepAnnoToolsJarScope) }
val libanalyzerProtoJarScope by configurations.dependencyScope("libanalyzerProtoJarScope")
val libanalyzerProtoJarConfig by
configurations.resolvable("libanalyzerProtoJarConfig") { extendsFrom(libanalyzerProtoJarScope) }
val mainJarScope by configurations.dependencyScope("mainJarScope")
val mainJarConfig by configurations.resolvable("mainJarConfig") { extendsFrom(mainJarScope) }
val mainResourcesScope by configurations.dependencyScope("mainResourcesScope")
val mainResourcesConfig by
configurations.resolvable("mainResourcesConfig") { extendsFrom(mainResourcesScope) }
val swissArmyKnifeScope by configurations.dependencyScope("swissArmyKnifeScope")
val swissArmyKnifeConfig by
configurations.resolvable("swissArmyKnifeConfig") {
extendsFrom(swissArmyKnifeScope)
isTransitive = false
}
dependencies {
keepRadiusProtoJarScope(project(":keepradius", "keepradiusProtoJar"))
keepAnnoDepsJarExceptAsmScope(project(":keepanno", "keepannoDepsJarExceptAsm"))
keepAnnoToolsJarScope(project(":keepanno", "keepannoToolsJar"))
libanalyzerProtoJarScope(project(":libanalyzer", "libanalyzer-proto-jar"))
mainJarScope(project(":main", "mainJar"))
mainResourcesScope(project(":main", "mainResources"))
swissArmyKnifeScope(project(":swissarmyknife"))
}
val sharedDepsScope by configurations.dependencyScope("sharedDepsScope")
val sharedDepsConfig by
configurations.resolvable("sharedDepsConfig") { extendsFrom(sharedDepsScope) }
val sharedTestDepsScope by configurations.dependencyScope("sharedTestDepsScope")
val sharedTestDepsConfig by
configurations.resolvable("sharedTestDepsConfig") { extendsFrom(sharedTestDepsScope) }
dependencies {
sharedDepsScope(project(":third_party", "sharedDepsFiles"))
sharedTestDepsScope(project(":third_party", "sharedTestDepsFiles"))
}
val depsJarFilesScope by configurations.dependencyScope("depsJarFilesScope")
val depsJarFiles by configurations.consumable("depsJarFiles") { extendsFrom(depsJarFilesScope) }
val depsFiles by configurations.consumable("depsFiles") { extendsFrom(depsJarFilesScope) }
fun relocateDepsExceptAsm(pkg: String): List<String> {
return listOf(
"--map",
"android.aapt.**->${pkg}.android.aapt",
"--map",
"androidx.annotation.**->${pkg}.androidx.annotation",
"--map",
"androidx.collection.**->${pkg}.androidx.collection",
"--map",
"androidx.tracing.**->${pkg}.androidx.tracing",
"--map",
"com.android.**->${pkg}.com.android",
"--map",
"com.android.zipflinger.**->${pkg}.com.android.zipflinger",
"--map",
"com.google.common.**->${pkg}.com.google.common",
"--map",
"com.google.gson.**->${pkg}.com.google.gson",
"--map",
"com.google.thirdparty.**->${pkg}.com.google.thirdparty",
"--map",
"com.squareup.wire.**->${pkg}.com.squareup.wire",
"--map",
"it.unimi.dsi.fastutil.**->${pkg}.it.unimi.dsi.fastutil",
"--map",
"kotlin.**->${pkg}.jetbrains.kotlin",
"--map",
"kotlinx.**->${pkg}.jetbrains.kotlinx",
"--map",
"okio.**->${pkg}.okio",
"--map",
"org.jetbrains.**->${pkg}.org.jetbrains",
"--map",
"org.intellij.**->${pkg}.org.intellij",
"--map",
"org.checkerframework.**->${pkg}.org.checkerframework",
"--map",
"com.google.j2objc.**->${pkg}.com.google.j2objc",
"--map",
"com.google.protobuf.**->${pkg}.com.google.protobuf",
"--map",
"perfetto.protos.**->${pkg}.perfetto.protos",
"--map",
"org.jspecify.annotations.**->${pkg}.org.jspecify.annotations",
"--map",
"_COROUTINE.**->${pkg}._COROUTINE",
)
}
interface InjectedArcOps {
@get:Inject val arcOps: ArchiveOperations
}
tasks {
val filteredDepsJar =
register<Jar>("filteredDepsJar") {
val injected = project.objects.newInstance<InjectedArcOps>()
from(
Callable {
r8Deps.incoming.files
.filter {
val path = it.absolutePath
path.contains("third_party") &&
path.contains("dependencies") &&
!path.contains("errorprone")
}
.map { injected.arcOps.zipTree(it) }
}
)
archiveFileName.set("r8-deps-merged.jar")
destinationDirectory.set(layout.buildDirectory.dir("libs"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
withType<SpdxSbomTask> {
taskExtension.set(
object : DefaultSpdxSbomTaskExtension() {
override fun mapRepoUri(input: URI?, moduleId: ModuleVersionIdentifier): URI? {
// Locate the file origin.json with URL for download location.
fun getOriginJson(): java.nio.file.Path {
var repositoryDir =
moduleId.group.replace('.', '/') + "/" + moduleId.name + "/" + moduleId.version
return Paths.get("third_party", "dependencies", repositoryDir, "origin.json")
}
// Simple data model of the content of origin.json generated by the tool to download
// and create a local repository. E.g.:
/*
{
"artifacts": [
{
"file": "org/ow2/asm/asm/9.5/asm-9.5.pom",
"repo": "https://repo1.maven.org/maven2/",
"artifact": "org.ow2.asm:asm:pom:9.5"
},
{
"file": "org/ow2/asm/asm/9.5/asm-9.5.jar",
"repo": "https://repo1.maven.org/maven2/",
"artifact": "org.ow2.asm:asm:jar:9.5"
}
]
}
*/
data class Artifact(val file: String, val repo: String, val artifact: String)
data class Artifacts(val artifacts: List<Artifact>)
// Read origin.json.
val json = readString(getOriginJson())
val artifacts = Gson().fromJson(json, Artifacts::class.java)
return URI.create(artifacts.artifacts.get(0).repo)
}
}
)
}
val consolidatedLicense =
register<ConsolidatedLicenseTask>("consolidatedLicense") {
r8License = File(getRootDir(), "LICENSE")
libraryLicenseMap = File(getRootDir(), "LIBRARY-LICENSE")
libraryLicenses = File(getRootDir(), "library-licensing")
dependencies =
configurations.named("runtimeClasspath").map { runtimeClasspath ->
runtimeClasspath.incoming.resolutionResult.allComponents.mapNotNull {
val compId = it.id
if (compId is ModuleComponentIdentifier) {
"${compId.group}:${compId.module}"
} else null
}
}
consolidatedOutputFile = File(getRootDir(), "build/generatedLicense/LICENSE")
}
val threadingModuleBlockingJar =
register<Zip>("threadingModuleBlockingJar") {
val injected = project.objects.newInstance<InjectedArcOps>()
dependsOn(mainJarConfig)
from(mainJarConfig.elements.map { it.map { injected.arcOps.zipTree(it) } })
include("com/android/tools/r8/threading/providers/blocking/**")
destinationDirectory.set(File(getRootDir(), "build/libs"))
archiveFileName.set("threading-module-blocking.jar")
}
val threadingModuleSingleThreadedJar =
register<Zip>("threadingModuleSingleThreadedJar") {
val injected = project.objects.newInstance<InjectedArcOps>()
dependsOn(mainJarConfig)
from(mainJarConfig.elements.map { it.map { injected.arcOps.zipTree(it) } })
include("com/android/tools/r8/threading/providers/singlethreaded/**")
destinationDirectory.set(File(getRootDir(), "build/libs"))
archiveFileName.set("threading-module-single-threaded.jar")
}
dependencies {
add(depsJarFilesScope.name, files(filteredDepsJar))
add(depsJarFilesScope.name, project(":resourceshrinker", "resourceshrinkerDepsJar"))
add(depsJarFilesScope.name, files(threadingModuleBlockingJar))
add(depsJarFilesScope.name, files(threadingModuleSingleThreadedJar))
}
val filteredDepsJarConfig by configurations.consumable("filteredDepsJarConfig")
artifacts {
add(depsFiles.name, consolidatedLicense)
add(filteredDepsJarConfig.name, filteredDepsJar)
}
val depsJarFilesConfig by
configurations.resolvable("depsJarFilesConfig") { extendsFrom(depsJarFilesScope) }
// Jar containing all 3p deps, plus R8 threading modules.
val depsJar =
register<Zip>("depsJar") {
dependsOn(depsJarFilesConfig)
from(Callable { depsJarFilesConfig.files.map(::zipTree) })
from(consolidatedLicense)
include("**/*.class")
include("META-INF/services/kotlin.metadata.internal.extensions.MetadataExtensions")
include("LICENSE")
exclude("**/module-info.class")
exclude("javax/annotation/**")
exclude("wireless/**")
exclude("META-INF/versions/**")
// Disabling compression makes this step go from 4s -> 2s as of Nov 2025,
// as measured by "gradle --profile".
entryCompression = ZipEntryCompression.STORED
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveFileName.set("deps.jar")
}
val protoJar =
register<Zip>("protoJar") {
val injected = project.objects.newInstance<InjectedArcOps>()
dependsOn(keepRadiusProtoJarConfig, libanalyzerProtoJarConfig)
from(keepRadiusProtoJarConfig.elements.map { it.map { injected.arcOps.zipTree(it) } })
from(libanalyzerProtoJarConfig.elements.map { it.map { injected.arcOps.zipTree(it) } })
exclude("META-INF/MANIFEST.MF")
archiveFileName.set("proto.jar")
destinationDirectory.set(File(getRootDir(), "build/libs"))
}
val r8WithRelocatedDepsManifest =
register<Jar>("r8WithRelocatedDepsManifest") {
manifest { attributes["Main-Class"] = "com.android.tools.r8.SwissArmyKnife" }
archiveFileName.set("r8-manifest.jar")
}
val r8WithRelocatedDeps =
register<SwissArmyKnifeTask>("r8WithRelocatedDeps") {
outputs.doNotCacheIf("Large standalone artifact (~50MB)") { true }
swissArmyKnifeClasspath.from(swissArmyKnifeConfig, depsJar)
compiler = "relocator"
inputFiles.from(depsJar)
inputFiles.from(protoJar)
inputFiles.from(mainResourcesConfig)
inputFiles.from(r8WithRelocatedDepsManifest)
inputNoResFiles.from(swissArmyKnifeConfig)
val pkg = "com.android.tools.r8"
extraArgs =
listOf(
"--map",
"com.android.tools.r8.**->${pkg}",
"--map",
"com.android.tools.r8.keepanno.annotations.**->${pkg}.keepanno.annotations",
"--map",
"com.android.tools.r8.keepanno.**->${pkg}.relocated.keepanno",
"--map",
"org.objectweb.asm.**->${pkg}.org.objectweb.asm",
) + relocateDepsExceptAsm(pkg) + listOf("--map-diagnostics", "warning", "error")
outputFile = File(getRootDir(), "build/libs/r8.jar")
}
register<SwissArmyKnifeTask>("keepAnnoToolsWithRelocatedDeps") {
outputs.doNotCacheIf("Large standalone artifact (~8MB)") { true }
swissArmyKnifeClasspath.from(swissArmyKnifeConfig, depsJar)
compiler = "relocator"
inputNoResFiles.from(keepAnnoDepsJarExceptAsmConfig)
inputNoResFiles.from(keepAnnoToolsJarConfig)
val pkg = "com.android.tools.r8.keepanno"
extraArgs =
listOf("--map", "com.android.tools.r8.keepanno.**->${pkg}") +
relocateDepsExceptAsm(pkg) +
listOf("--map-diagnostics", "warning", "error")
outputFile = File(getRootDir(), "build/libs/keepanno-tools.jar")
}
register<CreateR8LibraryTask>("processKeepRulesLibWithRelocatedDeps") {
r8compilerClasspath.from(r8WithRelocatedDeps.flatMap { it.outputFile })
inputJar = r8WithRelocatedDeps.flatMap { it.outputFile }
pgConfigs.from(File(rootDir, "src/main/keep_processkeeprules.txt"))
enableKeepAnnotations = false
setOutputJarFile(File(rootDir, "build/libs/processkeepruleslib.jar"))
}
}