blob: 6c021f6ae0d7f007cf074452b6e6ce02cc2dc7c6 [file] [edit]
// Copyright (c) 2023, 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 java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import javax.inject.Inject
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
plugins {
`java-library`
id("dependencies-plugin")
id("net.ltgt.errorprone")
}
// Properties that you can set in your ~/.gradle/gradle.properties:
// Use a separate sourceSet for files that have been modified when doing incremental builds.
// Speeds up compile times where the list of files isn't changed from 1-2 minutes -> 1-2 seconds.
//
// Modified files are determined using git, and the list of modified files never shrinks (since
// that would cause build errors). However, it is safe to fully reset the list of modified files,
// which you can do by deleting d8_r8/main/build/turbo-paths.txt.
//
// What's the catch?
// Unmodified sources that depend on modified ones will *not be rebuilt* when modified sources
// change. This is where the speed-up comes from, but can lead to runtime crashes if signatures
// change without references to them being updated.
// Be sure to fix problems reported by IntelliJ when using this mode.
val isGeminiCli = "1".equals(System.getenv("GEMINI_CLI"))
var enableTurboBuilds = project.hasProperty("enable_r8_turbo_builds") && !isGeminiCli
val MAIN_JAVA_PATH_PREFIX = "src/main/java/"
interface TurboPathsValueSourceParameters : ValueSourceParameters {
val pathPrefix: Property<String>
val turboPathsFile: Property<File>
val extraGlobs: ListProperty<String>
val mainOutputDir: Property<File>
}
enum class TurboReason {
FIRST_BUILD,
PATHS_CHANGED,
PATHS_UNCHANGED,
CORRUPT_FILE,
TOO_MANY_PATHS,
}
data class TurboState(val paths: List<String>, val reason: TurboReason)
abstract class TurboPathsValueSource : ValueSource<TurboState, TurboPathsValueSourceParameters> {
@get:Inject abstract val execOperations: ExecOperations
fun isDirectoryEmpty(path: File): Boolean {
if (!path.exists()) {
return true
}
val files = path.listFiles()
return files == null || files.isEmpty()
}
override fun obtain(): TurboState? {
val prefix = parameters.pathPrefix.get()
val turboPathsFile = parameters.turboPathsFile.get()
val extraGlobs = parameters.extraGlobs.get()
val mainOutputDir = parameters.mainOutputDir.get()
// Check for first build (since the turbo sourceSet requires the main one
// to have been built already).
if (isDirectoryEmpty(mainOutputDir)) {
return TurboState(listOf(), TurboReason.FIRST_BUILD)
}
var mergeBase = "origin/main"
val pathSet: MutableSet<String> = mutableSetOf()
if (turboPathsFile.exists()) {
val lines = turboPathsFile.readLines()
if (!lines.isEmpty() && lines[0].startsWith("mergebase=")) {
mergeBase = lines[0].removePrefix("mergebase=")
pathSet.addAll(lines.drop(1))
} else {
// Corrupt file.
turboPathsFile.delete()
return TurboState(listOf(), TurboReason.CORRUPT_FILE)
}
}
val prevNumSource = pathSet.size
val output = ByteArrayOutputStream()
execOperations.exec {
commandLine = listOf("git", "diff", "--name-only", "--merge-base", mergeBase)
standardOutput = output
}
val result = String(output.toByteArray(), Charset.defaultCharset())
val gitPaths =
result
.lines()
.filter { it.startsWith(prefix) && it.endsWith(".java") }
.map { it.trim().removePrefix(prefix) }
pathSet.addAll(gitPaths)
val ret = pathSet.toMutableList()
ret.sort()
// Allow users to specify extra globs.
ret += extraGlobs
if (mergeBase == "origin/main") {
output.reset()
execOperations.exec {
commandLine = listOf("git", "rev-parse", "origin/main")
standardOutput = output
}
mergeBase = String(output.toByteArray(), Charset.defaultCharset()).trim()
}
if (pathSet.size > 200 && gitPaths.size < 40) {
// File has gotten too big. Start fresh.
turboPathsFile.delete()
return TurboState(listOf(), TurboReason.TOO_MANY_PATHS)
}
turboPathsFile.writeText("mergebase=$mergeBase\n" + ret.joinToString("\n"))
val changed = prevNumSource != pathSet.size
val reason =
if (pathSet.isEmpty()) TurboReason.FIRST_BUILD
else if (changed) TurboReason.PATHS_CHANGED else TurboReason.PATHS_UNCHANGED
return TurboState(ret, reason)
}
}
val turboPathsProvider: Provider<TurboState> =
providers.of(TurboPathsValueSource::class.java) {
parameters.pathPrefix.set(MAIN_JAVA_PATH_PREFIX)
// Wipe this file to remove files from the active set.
parameters.turboPathsFile.set(layout.buildDirectory.file("turbo-paths.txt").get().asFile)
parameters.extraGlobs.set(
project.findProperty("turbo_build_globs")?.toString()?.split(',') ?: emptyList()
)
parameters.mainOutputDir.set(sourceSets["main"].java.destinationDirectory.get().getAsFile())
}
// Add all changed files to the "turbo" source set.
val turboState = if (enableTurboBuilds) turboPathsProvider.get() else null
if (turboState != null) {
val numFiles = turboState.paths.size
val msg =
when (turboState.reason) {
TurboReason.FIRST_BUILD -> "First build detected. Build will be slow."
TurboReason.PATHS_CHANGED -> "Paths in active set have changed. Build will be slow."
TurboReason.PATHS_UNCHANGED -> "Paths unchanged. Size=$numFiles. Build should be fast!"
TurboReason.CORRUPT_FILE -> "turbo-paths.txt was invalid. Build will be slow."
TurboReason.TOO_MANY_PATHS -> "Paths were compacted. Build will be slow."
}
logger.warn("Turbo: $msg")
} else if (isGeminiCli) {
logger.warn("Turbo: enable_r8_turbo_builds=false (gemini-cli detected)")
} else {
logger.warn("Turbo: enable_r8_turbo_builds=false")
}
java {
sourceSets {
val srcDir = getRoot().resolveAll("src", "main", "java")
main {
resources.srcDirs(getRoot().resolveAll("third_party", "api_database", "api_database"))
java {
srcDir(srcDir)
if (turboState != null && !turboState.paths.isEmpty()) {
exclude(turboState.paths)
}
}
}
// Must be created unconditionally so that other targets can depend on it.
create("turbo") {
java {
srcDir(srcDir)
if (turboState != null && !turboState.paths.isEmpty()) {
include(turboState.paths)
} else {
exclude("*")
}
}
}
}
sourceCompatibility = JvmCompatibility.sourceCompatibility
targetCompatibility = JvmCompatibility.targetCompatibility
toolchain { languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) }
withSourcesJar()
}
val sharedDepsScope by configurations.dependencyScope("sharedDepsScope")
val sharedDepsConfig by
configurations.resolvable("sharedDepsConfig") { extendsFrom(sharedDepsScope) }
dependencies { sharedDepsScope(project(":shared", "sharedDepsFiles")) }
fun mainJarDependencies(): FileCollection {
return sourceSets.main
.get()
.compileClasspath
.filter({
"$it".contains("third_party") &&
"$it".contains("dependencies") &&
!"$it".contains("errorprone")
})
}
val internalJarScope by configurations.dependencyScope("internalJarScope")
val internalJarResolvable by
configurations.resolvable("internalJarResolvable") { extendsFrom(internalJarScope) }
val internalClassesScope by configurations.dependencyScope("internalClassesScope")
val internalClassesResolvable by
configurations.resolvable("internalClassesResolvable") { extendsFrom(internalClassesScope) }
dependencies {
internalJarScope(project(":utils", "isolatedJar"))
internalClassesScope(project(":utils", "isolatedClasses"))
implementation(project(":assistant", "assistantJar"))
implementation(project(":keepradius", "keepradiusJar"))
implementation(project(":keepanno", "keepannoClasses"))
implementation(project(":resourceshrinker", "resourceshrinkerJar"))
implementation(project(":utils"))
Deps.compilerDeps.forEach { compileOnly(it) }
errorprone(Deps.errorprone)
}
if (enableTurboBuilds) {
tasks.named("compileJava") {
// Makes compileTurboJava run first, but does not cause compileJava to re-run if
// compileTurboJava changes.
dependsOn(tasks.named("compileTurboJava"))
}
// Does not include main's output directory, which must also be added when compilation avoidance
// causes only a subset of sources to be recompiled.
val mainClasspath = sourceSets["main"].compileClasspath.getAsPath()
tasks.named<JavaCompile>("compileTurboJava") {
// Add the main's classes to the classpath without letting gradle know about this dependency
// (as it's a circular one).
options.compilerArgs.add("-classpath")
options.compilerArgs.add(
"" +
sourceSets["turbo"].java.destinationDirectory.get() +
File.pathSeparator +
mainClasspath +
File.pathSeparator +
sourceSets["main"].java.destinationDirectory.get()
)
}
tasks.named<JavaCompile>("compileJava") {
// Add the turbo's classes to the classpath without letting gradle know about this dependency
// (or else it will cause it to rebuild whenever files in it change).
options.compilerArgs.add("-classpath")
options.compilerArgs.add(
"" +
sourceSets["main"].java.destinationDirectory.get() +
File.pathSeparator +
mainClasspath +
File.pathSeparator +
sourceSets["turbo"].java.destinationDirectory.get()
)
}
}
tasks {
jar {
from(sourceSets["turbo"].output)
doLast {
enforceUncompressedEntries(archiveFile.get().asFile, setOf("resources/new_api_database.ser"))
}
}
withType<Exec> { doFirst { println("Executing command: ${commandLine.joinToString(" ")}") } }
}
tasks.withType<JavaCompile> {
dependsOn(sharedDepsConfig)
logger.info("NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome)
}
tasks.named("sourcesJar") { dependsOn(sharedDepsConfig) }
tasks.withType<ProcessResources> { dependsOn(sharedDepsConfig) }
// Contains both :main jar and :utils jar but not third party dependencies.
val mainJar by configurations.consumable("mainJar") { extendsFrom(internalJarResolvable) }
// Contains partial class files of :main and all class files of :utils but not third party
// dependencies.
val mainClassesOutput by
configurations.consumable("mainClassesOutput") { extendsFrom(internalClassesResolvable) }
// Contains partial class files of :main but not :utils nor third party dependencies.
val turboClassesOutput by configurations.consumable("turboClassesOutput")
val mainResources by configurations.consumable("mainResources")
val mainSources by configurations.consumable("mainSources")
artifacts {
add(mainJar.name, tasks.named("jar"))
add(
mainClassesOutput.name,
tasks.named<JavaCompile>("compileJava").map { it.destinationDirectory },
)
add(
turboClassesOutput.name,
tasks.named<JavaCompile>("compileTurboJava").map { it.destinationDirectory },
)
add(
mainResources.name,
tasks.named<ProcessResources>("processResources").map { it.destinationDir },
)
add(mainSources.name, tasks.named("sourcesJar"))
}
configureErrorProneForJavaCompile()